Skip to content

Commit ba5a612

Browse files
committed
Merge pull request #1795 from Microsoft/objectLiteralWidening
Improved subtype compatibility rules for types of object literals
2 parents 0dcc168 + 5c3da7c commit ba5a612

28 files changed

+139
-144
lines changed

src/compiler/checker.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ module ts {
6666
var numberType = createIntrinsicType(TypeFlags.Number, "number");
6767
var booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean");
6868
var voidType = createIntrinsicType(TypeFlags.Void, "void");
69-
var undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.Unwidened, "undefined");
70-
var nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.Unwidened, "null");
69+
var undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined");
70+
var nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null");
7171
var unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
7272
var resolvingType = createIntrinsicType(TypeFlags.Any, "__resolving__");
7373

@@ -2826,15 +2826,22 @@ module ts {
28262826
}
28272827
}
28282828

2829-
function getUnwidenedFlagOfTypes(types: Type[]): TypeFlags {
2830-
return forEach(types, t => t.flags & TypeFlags.Unwidened) || 0;
2829+
// This function is used to propagate widening flags when creating new object types references and union types.
2830+
// It is only necessary to do so if a constituent type might be the undefined type, the null type, or the type
2831+
// of an object literal (since those types have widening related information we need to track).
2832+
function getWideningFlagsOfTypes(types: Type[]): TypeFlags {
2833+
var result: TypeFlags = 0;
2834+
for (var i = 0; i < types.length; i++) {
2835+
result |= types[i].flags;
2836+
}
2837+
return result & TypeFlags.RequiresWidening;
28312838
}
28322839

28332840
function createTypeReference(target: GenericType, typeArguments: Type[]): TypeReference {
28342841
var id = getTypeListId(typeArguments);
28352842
var type = target.instantiations[id];
28362843
if (!type) {
2837-
var flags = TypeFlags.Reference | getUnwidenedFlagOfTypes(typeArguments);
2844+
var flags = TypeFlags.Reference | getWideningFlagsOfTypes(typeArguments);
28382845
type = target.instantiations[id] = <TypeReference>createObjectType(flags, target.symbol);
28392846
type.target = target;
28402847
type.typeArguments = typeArguments;
@@ -3095,7 +3102,7 @@ module ts {
30953102
var id = getTypeListId(sortedTypes);
30963103
var type = unionTypes[id];
30973104
if (!type) {
3098-
type = unionTypes[id] = <UnionType>createObjectType(TypeFlags.Union | getUnwidenedFlagOfTypes(sortedTypes));
3105+
type = unionTypes[id] = <UnionType>createObjectType(TypeFlags.Union | getWideningFlagsOfTypes(sortedTypes));
30993106
type.types = sortedTypes;
31003107
}
31013108
return type;
@@ -3711,12 +3718,13 @@ module ts {
37113718
}
37123719
var result = Ternary.True;
37133720
var properties = getPropertiesOfObjectType(target);
3721+
var requireOptionalProperties = relation === subtypeRelation && !(source.flags & TypeFlags.ObjectLiteral);
37143722
for (var i = 0; i < properties.length; i++) {
37153723
var targetProp = properties[i];
37163724
var sourceProp = getPropertyOfType(source, targetProp.name);
37173725
if (sourceProp !== targetProp) {
37183726
if (!sourceProp) {
3719-
if (relation === subtypeRelation || !(targetProp.flags & SymbolFlags.Optional)) {
3727+
if (!(targetProp.flags & SymbolFlags.Optional) || requireOptionalProperties) {
37203728
if (reportErrors) {
37213729
reportError(Diagnostics.Property_0_is_missing_in_type_1, symbolToString(targetProp), typeToString(source));
37223730
}
@@ -4107,10 +4115,6 @@ module ts {
41074115
errorMessageChainHead);
41084116
}
41094117

4110-
function isTypeOfObjectLiteral(type: Type): boolean {
4111-
return (type.flags & TypeFlags.Anonymous) && type.symbol && (type.symbol.flags & SymbolFlags.ObjectLiteral) ? true : false;
4112-
}
4113-
41144118
function isArrayType(type: Type): boolean {
41154119
return type.flags & TypeFlags.Reference && (<TypeReference>type).target === globalArrayType;
41164120
}
@@ -4123,13 +4127,18 @@ module ts {
41234127
var properties = getPropertiesOfObjectType(type);
41244128
var members: SymbolTable = {};
41254129
forEach(properties, p => {
4126-
var symbol = <TransientSymbol>createSymbol(p.flags | SymbolFlags.Transient, p.name);
4127-
symbol.declarations = p.declarations;
4128-
symbol.parent = p.parent;
4129-
symbol.type = getWidenedType(getTypeOfSymbol(p));
4130-
symbol.target = p;
4131-
if (p.valueDeclaration) symbol.valueDeclaration = p.valueDeclaration;
4132-
members[symbol.name] = symbol;
4130+
var propType = getTypeOfSymbol(p);
4131+
var widenedType = getWidenedType(propType);
4132+
if (propType !== widenedType) {
4133+
var symbol = <TransientSymbol>createSymbol(p.flags | SymbolFlags.Transient, p.name);
4134+
symbol.declarations = p.declarations;
4135+
symbol.parent = p.parent;
4136+
symbol.type = widenedType;
4137+
symbol.target = p;
4138+
if (p.valueDeclaration) symbol.valueDeclaration = p.valueDeclaration;
4139+
p = symbol;
4140+
}
4141+
members[p.name] = p;
41334142
});
41344143
var stringIndexType = getIndexTypeOfType(type, IndexKind.String);
41354144
var numberIndexType = getIndexTypeOfType(type, IndexKind.Number);
@@ -4139,16 +4148,16 @@ module ts {
41394148
}
41404149

41414150
function getWidenedType(type: Type): Type {
4142-
if (type.flags & TypeFlags.Unwidened) {
4151+
if (type.flags & TypeFlags.RequiresWidening) {
41434152
if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) {
41444153
return anyType;
41454154
}
4155+
if (type.flags & TypeFlags.ObjectLiteral) {
4156+
return getWidenedTypeOfObjectLiteral(type);
4157+
}
41464158
if (type.flags & TypeFlags.Union) {
41474159
return getUnionType(map((<UnionType>type).types, getWidenedType));
41484160
}
4149-
if (isTypeOfObjectLiteral(type)) {
4150-
return getWidenedTypeOfObjectLiteral(type);
4151-
}
41524161
if (isArrayType(type)) {
41534162
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
41544163
}
@@ -4169,11 +4178,11 @@ module ts {
41694178
if (isArrayType(type)) {
41704179
return reportWideningErrorsInType((<TypeReference>type).typeArguments[0]);
41714180
}
4172-
if (isTypeOfObjectLiteral(type)) {
4181+
if (type.flags & TypeFlags.ObjectLiteral) {
41734182
var errorReported = false;
41744183
forEach(getPropertiesOfObjectType(type), p => {
41754184
var t = getTypeOfSymbol(p);
4176-
if (t.flags & TypeFlags.Unwidened) {
4185+
if (t.flags & TypeFlags.ContainsUndefinedOrNull) {
41774186
if (!reportWideningErrorsInType(t)) {
41784187
error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, p.name, typeToString(getWidenedType(t)));
41794188
}
@@ -4217,7 +4226,7 @@ module ts {
42174226
}
42184227

42194228
function reportErrorsFromWidening(declaration: Declaration, type: Type) {
4220-
if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.Unwidened) {
4229+
if (produceDiagnostics && compilerOptions.noImplicitAny && type.flags & TypeFlags.ContainsUndefinedOrNull) {
42214230
// Report implicit any error within type if possible, otherwise report error on declaration
42224231
if (!reportWideningErrorsInType(type)) {
42234232
reportImplicitAnyError(declaration, type);
@@ -5492,21 +5501,10 @@ module ts {
54925501
propertiesArray.push(member);
54935502
}
54945503

5495-
// If object literal is contextually (but not inferentially) typed, copy missing optional properties from
5496-
// the contextual type such that the resulting type becomes a subtype in cases where only optional properties
5497-
// were omitted. There is no need to create new property objects as nothing in them needs to change.
5498-
if (contextualType && !isInferentialContext(contextualMapper)) {
5499-
forEach(getPropertiesOfObjectType(contextualType), p => {
5500-
if (p.flags & SymbolFlags.Optional && !hasProperty(propertiesTable, p.name)) {
5501-
propertiesTable[p.name] = p;
5502-
}
5503-
});
5504-
}
5505-
55065504
var stringIndexType = getIndexType(IndexKind.String);
55075505
var numberIndexType = getIndexType(IndexKind.Number);
55085506
var result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType);
5509-
result.flags |= (typeFlags & TypeFlags.Unwidened);
5507+
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | (typeFlags & TypeFlags.ContainsUndefinedOrNull);
55105508
return result;
55115509

55125510
function getIndexType(kind: IndexKind) {
@@ -5525,7 +5523,9 @@ module ts {
55255523
}
55265524
}
55275525
}
5528-
return propTypes.length ? getUnionType(propTypes) : undefinedType;
5526+
var result = propTypes.length ? getUnionType(propTypes) : undefinedType;
5527+
typeFlags |= result.flags;
5528+
return result;
55295529
}
55305530
return undefined;
55315531
}

src/compiler/types.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,29 +1266,32 @@ module ts {
12661266
}
12671267

12681268
export const enum TypeFlags {
1269-
Any = 0x00000001,
1270-
String = 0x00000002,
1271-
Number = 0x00000004,
1272-
Boolean = 0x00000008,
1273-
Void = 0x00000010,
1274-
Undefined = 0x00000020,
1275-
Null = 0x00000040,
1276-
Enum = 0x00000080, // Enum type
1277-
StringLiteral = 0x00000100, // String literal type
1278-
TypeParameter = 0x00000200, // Type parameter
1279-
Class = 0x00000400, // Class
1280-
Interface = 0x00000800, // Interface
1281-
Reference = 0x00001000, // Generic type reference
1282-
Tuple = 0x00002000, // Tuple
1283-
Union = 0x00004000, // Union
1284-
Anonymous = 0x00008000, // Anonymous
1285-
FromSignature = 0x00010000, // Created for signature assignment check
1286-
Unwidened = 0x00020000, // Unwidened type (is or contains Undefined or Null type)
1269+
Any = 0x00000001,
1270+
String = 0x00000002,
1271+
Number = 0x00000004,
1272+
Boolean = 0x00000008,
1273+
Void = 0x00000010,
1274+
Undefined = 0x00000020,
1275+
Null = 0x00000040,
1276+
Enum = 0x00000080, // Enum type
1277+
StringLiteral = 0x00000100, // String literal type
1278+
TypeParameter = 0x00000200, // Type parameter
1279+
Class = 0x00000400, // Class
1280+
Interface = 0x00000800, // Interface
1281+
Reference = 0x00001000, // Generic type reference
1282+
Tuple = 0x00002000, // Tuple
1283+
Union = 0x00004000, // Union
1284+
Anonymous = 0x00008000, // Anonymous
1285+
FromSignature = 0x00010000, // Created for signature assignment check
1286+
ObjectLiteral = 0x00020000, // Originates in an object literal
1287+
ContainsUndefinedOrNull = 0x00040000, // Type is or contains Undefined or Null type
1288+
ContainsObjectLiteral = 0x00080000, // Type is or contains object literal type
12871289

12881290
Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
12891291
StringLike = String | StringLiteral,
12901292
NumberLike = Number | Enum,
12911293
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
1294+
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral
12921295
}
12931296

12941297
// Properties common to all types

tests/baselines/reference/arrayCast.errors.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
tests/cases/compiler/arrayCast.ts(3,1): error TS2352: Neither type '{ foo: string; }[]' nor type '{ id: number; }[]' is assignable to the other.
22
Type '{ foo: string; }' is not assignable to type '{ id: number; }'.
3+
Property 'id' is missing in type '{ foo: string; }'.
34

45

56
==== tests/cases/compiler/arrayCast.ts (1 errors) ====
@@ -9,6 +10,7 @@ tests/cases/compiler/arrayCast.ts(3,1): error TS2352: Neither type '{ foo: strin
910
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1011
!!! error TS2352: Neither type '{ foo: string; }[]' nor type '{ id: number; }[]' is assignable to the other.
1112
!!! error TS2352: Type '{ foo: string; }' is not assignable to type '{ id: number; }'.
13+
!!! error TS2352: Property 'id' is missing in type '{ foo: string; }'.
1214

1315
// Should succeed, as the {} element causes the type of the array to be {}[]
1416
<{ id: number; }[]>[{ foo: "s" }, {}];

tests/baselines/reference/assignmentCompatBug2.errors.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ tests/cases/compiler/assignmentCompatBug2.ts(1,5): error TS2322: Type '{ a: numb
22
Property 'b' is missing in type '{ a: number; }'.
33
tests/cases/compiler/assignmentCompatBug2.ts(3,1): error TS2322: Type '{ a: number; }' is not assignable to type '{ b: number; }'.
44
Property 'b' is missing in type '{ a: number; }'.
5-
tests/cases/compiler/assignmentCompatBug2.ts(15,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
6-
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }'.
7-
tests/cases/compiler/assignmentCompatBug2.ts(20,1): error TS2322: Type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
8-
Property 'g' is missing in type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }'.
5+
tests/cases/compiler/assignmentCompatBug2.ts(15,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
6+
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; }'.
7+
tests/cases/compiler/assignmentCompatBug2.ts(20,1): error TS2322: Type '{ f: (n: number) => number; m: number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
8+
Property 'g' is missing in type '{ f: (n: number) => number; m: number; }'.
99
tests/cases/compiler/assignmentCompatBug2.ts(33,1): error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n: number; k: (a: any) => any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
1010
Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n: number; k: (a: any) => any; }'.
1111

@@ -33,16 +33,16 @@ tests/cases/compiler/assignmentCompatBug2.ts(33,1): error TS2322: Type '{ f: (n:
3333

3434
b3 = {
3535
~~
36-
!!! error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
37-
!!! error TS2322: Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; n?: number; k?(a: any): any; }'.
36+
!!! error TS2322: Type '{ f: (n: number) => number; g: (s: string) => number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
37+
!!! error TS2322: Property 'm' is missing in type '{ f: (n: number) => number; g: (s: string) => number; }'.
3838
f: (n) => { return 0; },
3939
g: (s) => { return 0; },
4040
}; // error
4141

4242
b3 = {
4343
~~
44-
!!! error TS2322: Type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
45-
!!! error TS2322: Property 'g' is missing in type '{ f: (n: number) => number; m: number; n?: number; k?(a: any): any; }'.
44+
!!! error TS2322: Type '{ f: (n: number) => number; m: number; }' is not assignable to type '{ f(n: number): number; g(s: string): number; m: number; n?: number; k?(a: any): any; }'.
45+
!!! error TS2322: Property 'g' is missing in type '{ f: (n: number) => number; m: number; }'.
4646
f: (n) => { return 0; },
4747
m: 0,
4848
}; // error

tests/baselines/reference/assignmentCompatWithObjectMembers3.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ var b: { foo: string; baz?: string }
5151
var a2: S2 = { foo: '' };
5252
>a2 : S2
5353
>S2 : S2
54-
>{ foo: '' } : { foo: string; bar?: string; }
54+
>{ foo: '' } : { foo: string; }
5555
>foo : string
5656

5757
var b2: T2 = { foo: '' };
5858
>b2 : T2
5959
>T2 : T2
60-
>{ foo: '' } : { foo: string; baz?: string; }
60+
>{ foo: '' } : { foo: string; }
6161
>foo : string
6262

6363
s = t;

tests/baselines/reference/assignmentCompatability1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability2.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability3.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability4.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability5.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;

tests/baselines/reference/assignmentCompatability6.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module __test1__ {
1212
>U : U
1313
>obj4 : interfaceWithPublicAndOptional<number, string>
1414
>interfaceWithPublicAndOptional : interfaceWithPublicAndOptional<T, U>
15-
>{ one: 1 } : { one: number; two?: string; }
15+
>{ one: 1 } : { one: number; }
1616
>one : number
1717

1818
export var __val__obj4 = obj4;
@@ -29,7 +29,7 @@ module __test2__ {
2929
>T : T
3030
>obj3 : interfaceWithOptional<number>
3131
>interfaceWithOptional : interfaceWithOptional<T>
32-
>{ } : { one?: number; }
32+
>{ } : {}
3333

3434
export var __val__obj3 = obj3;
3535
>__val__obj3 : interfaceWithOptional<number>

0 commit comments

Comments
 (0)