Skip to content

Commit

Permalink
Merge pull request microsoft#26790 from Microsoft/fixWeakObjectRelati…
Browse files Browse the repository at this point in the history
…onCheck

Fix weak object relation check
  • Loading branch information
ahejlsberg authored Aug 31, 2018
2 parents b687caf + f597e2e commit d293b67
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 103 deletions.
68 changes: 34 additions & 34 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11190,13 +11190,10 @@ namespace ts {
}
}

if (relation !== comparableRelation &&
!(source.flags & TypeFlags.Union) &&
!(target.flags & TypeFlags.Union) &&
!isIntersectionConstituent &&
source !== globalObjectType &&
if (relation !== comparableRelation && !isIntersectionConstituent &&
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)) &&
isWeakType(target) &&
!hasCommonProperties(source, target)) {
if (reportErrors) {
const calls = getSignaturesOfType(source, SignatureKind.Call);
Expand Down Expand Up @@ -11841,6 +11838,9 @@ namespace ts {
errorInfo = saveErrorInfo;
}
}
else if (isTupleType(source) && (isArrayType(target) || isReadonlyArrayType(target)) || isArrayType(source) && isReadonlyArrayType(target)) {
return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors);
}
// Even if relationship doesn't hold for unions, intersections, or generic type references,
// it may hold in a structural comparison.
// In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
Expand Down Expand Up @@ -12020,34 +12020,6 @@ namespace ts {
return result;
}

/**
* A type is 'weak' if it is an object type with at least one optional property
* and no required properties, call/construct signatures or index signatures
*/
function isWeakType(type: Type): boolean {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 &&
!resolved.stringIndexInfo && !resolved.numberIndexInfo &&
resolved.properties.length > 0 &&
every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
}
if (type.flags & TypeFlags.Intersection) {
return every((<IntersectionType>type).types, isWeakType);
}
return false;
}

function hasCommonProperties(source: Type, target: Type) {
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
for (const prop of getPropertiesOfType(source)) {
if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
return true;
}
}
return false;
}

function propertiesIdenticalTo(source: Type, target: Type): Ternary {
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
return Ternary.False;
Expand Down Expand Up @@ -12292,6 +12264,34 @@ namespace ts {
}
}

/**
* A type is 'weak' if it is an object type with at least one optional property
* and no required properties, call/construct signatures or index signatures
*/
function isWeakType(type: Type): boolean {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 &&
!resolved.stringIndexInfo && !resolved.numberIndexInfo &&
resolved.properties.length > 0 &&
every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
}
if (type.flags & TypeFlags.Intersection) {
return every((<IntersectionType>type).types, isWeakType);
}
return false;
}

function hasCommonProperties(source: Type, target: Type) {
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
for (const prop of getPropertiesOfType(source)) {
if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
return true;
}
}
return false;
}

// Return a type reference where the source type parameter is replaced with the target marker
// type, and flag the result as a marker type reference.
function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(13,1): error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray<B>'.
Types of property 'concat' are incompatible.
Type '{ (...items: ConcatArray<A>[]): A[]; (...items: (A | ConcatArray<A>)[]): A[]; }' is not assignable to type '{ (...items: ConcatArray<B>[]): B[]; (...items: (B | ConcatArray<B>)[]): B[]; }'.
Type 'A[]' is not assignable to type 'B[]'.
Type 'A' is not assignable to type 'B'.
Property 'b' is missing in type 'A'.
Type 'A' is not assignable to type 'B'.
Property 'b' is missing in type 'A'.
tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error TS2322: Type 'C<A>' is not assignable to type 'ReadonlyArray<B>'.
Types of property 'concat' are incompatible.
Type '{ (...items: ConcatArray<A>[]): A[]; (...items: (A | ConcatArray<A>)[]): A[]; }' is not assignable to type '{ (...items: ConcatArray<B>[]): B[]; (...items: (B | ConcatArray<B>)[]): B[]; }'.
Type 'A[]' is not assignable to type 'B[]'.
Type 'A' is not assignable to type 'B'.


==== tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts (2 errors) ====
Expand All @@ -26,11 +24,8 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T
rrb = ara; // error: 'A' is not assignable to 'B'
~~~
!!! error TS2322: Type 'A[]' is not assignable to type 'ReadonlyArray<B>'.
!!! error TS2322: Types of property 'concat' are incompatible.
!!! error TS2322: Type '{ (...items: ConcatArray<A>[]): A[]; (...items: (A | ConcatArray<A>)[]): A[]; }' is not assignable to type '{ (...items: ConcatArray<B>[]): B[]; (...items: (B | ConcatArray<B>)[]): B[]; }'.
!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
!!! error TS2322: Property 'b' is missing in type 'A'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
!!! error TS2322: Property 'b' is missing in type 'A'.

rra = cra;
rra = crb; // OK, C<B> is assignable to ReadonlyArray<A>
Expand All @@ -41,4 +36,5 @@ tests/cases/compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts(18,1): error T
!!! error TS2322: Types of property 'concat' are incompatible.
!!! error TS2322: Type '{ (...items: ConcatArray<A>[]): A[]; (...items: (A | ConcatArray<A>)[]): A[]; }' is not assignable to type '{ (...items: ConcatArray<B>[]): B[]; (...items: (B | ConcatArray<B>)[]): B[]; }'.
!!! error TS2322: Type 'A[]' is not assignable to type 'B[]'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.

Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatBetweenTupleAndArray.ts(17,1): error TS2322: Type '[number, string]' is not assignable to type 'number[]'.
Types of property 'pop' are incompatible.
Type '() => string | number' is not assignable to type '() => number'.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatBetweenTupleAndArray.ts(18,1): error TS2322: Type '{}[]' is not assignable to type '[{}]'.
Property '0' is missing in type '{}[]'.

Expand All @@ -27,10 +25,8 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme
numArray = numStrTuple;
~~~~~~~~
!!! error TS2322: Type '[number, string]' is not assignable to type 'number[]'.
!!! error TS2322: Types of property 'pop' are incompatible.
!!! error TS2322: Type '() => string | number' is not assignable to type '() => number'.
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
emptyObjTuple = emptyObjArray;
~~~~~~~~~~~~~
!!! error TS2322: Type '{}[]' is not assignable to type '[{}]'.
Expand Down
20 changes: 6 additions & 14 deletions tests/baselines/reference/for-of39.errors.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
tests/cases/conformance/es6/for-ofStatements/for-of39.ts(1,19): error TS2345: Argument of type '([string, number] | [string, true])[]' is not assignable to parameter of type 'ReadonlyArray<[string, boolean]>'.
Types of property 'concat' are incompatible.
Type '{ (...items: ConcatArray<[string, number] | [string, true]>[]): ([string, number] | [string, true])[]; (...items: ([string, number] | [string, true] | ConcatArray<[string, number] | [string, true]>)[]): ([string, number] | [string, true])[]; }' is not assignable to type '{ (...items: ConcatArray<[string, boolean]>[]): [string, boolean][]; (...items: ([string, boolean] | ConcatArray<[string, boolean]>)[]): [string, boolean][]; }'.
Types of parameters 'items' and 'items' are incompatible.
Type 'ConcatArray<[string, boolean]>' is not assignable to type 'ConcatArray<[string, number] | [string, true]>'.
Type '[string, boolean]' is not assignable to type '[string, number] | [string, true]'.
Type '[string, boolean]' is not assignable to type '[string, number]'.
Type 'boolean' is not assignable to type 'number'.
Type '[string, number] | [string, true]' is not assignable to type '[string, boolean]'.
Type '[string, number]' is not assignable to type '[string, boolean]'.
Type 'number' is not assignable to type 'boolean'.


==== tests/cases/conformance/es6/for-ofStatements/for-of39.ts (1 errors) ====
var map = new Map([["", true], ["", 0]]);
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '([string, number] | [string, true])[]' is not assignable to parameter of type 'ReadonlyArray<[string, boolean]>'.
!!! error TS2345: Types of property 'concat' are incompatible.
!!! error TS2345: Type '{ (...items: ConcatArray<[string, number] | [string, true]>[]): ([string, number] | [string, true])[]; (...items: ([string, number] | [string, true] | ConcatArray<[string, number] | [string, true]>)[]): ([string, number] | [string, true])[]; }' is not assignable to type '{ (...items: ConcatArray<[string, boolean]>[]): [string, boolean][]; (...items: ([string, boolean] | ConcatArray<[string, boolean]>)[]): [string, boolean][]; }'.
!!! error TS2345: Types of parameters 'items' and 'items' are incompatible.
!!! error TS2345: Type 'ConcatArray<[string, boolean]>' is not assignable to type 'ConcatArray<[string, number] | [string, true]>'.
!!! error TS2345: Type '[string, boolean]' is not assignable to type '[string, number] | [string, true]'.
!!! error TS2345: Type '[string, boolean]' is not assignable to type '[string, number]'.
!!! error TS2345: Type 'boolean' is not assignable to type 'number'.
!!! error TS2345: Type '[string, number] | [string, true]' is not assignable to type '[string, boolean]'.
!!! error TS2345: Type '[string, number]' is not assignable to type '[string, boolean]'.
!!! error TS2345: Type 'number' is not assignable to type 'boolean'.
for (var [k, v] of map) {
k;
v;
Expand Down
16 changes: 15 additions & 1 deletion tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string | number | symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string | number | symbol>>], Record<"val", string>>["val"]'.
tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude<keyof B, K>]'.
tests/cases/compiler/infiniteConstraints.ts(27,37): error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(27,58): error TS2322: Type 'Record<"val", "test2">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(29,45): error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.


==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
!!! error TS2321: Excessive stack depth comparing types 'Extract<T[Exclude<keyof T, string | number | symbol>], Record<"val", string>>["val"]' and 'Extract<T[Exclude<keyof T, Exclude<keyof T, string | number | symbol>>], Record<"val", string>>["val"]'.
==== tests/cases/compiler/infiniteConstraints.ts (7 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
Expand Down Expand Up @@ -34,8 +39,17 @@ tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' c
>(vals: T): void;

const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});
~~~~
!!! error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'.
!!! related TS6500 tests/cases/compiler/infiniteConstraints.ts:27:37: The expected type comes from property 'main' which is declared here on type '{ main: never; alternate: never; }'
~~~~~~~~~
!!! error TS2322: Type 'Record<"val", "test2">' is not assignable to type 'never'.
!!! related TS6500 tests/cases/compiler/infiniteConstraints.ts:27:58: The expected type comes from property 'alternate' which is declared here on type '{ main: never; alternate: never; }'

const shouldBeNoError = ensureNoDuplicates({main: value("test")});
~~~~
!!! error TS2322: Type 'Record<"val", "test">' is not assignable to type 'never'.
!!! related TS6500 tests/cases/compiler/infiniteConstraints.ts:29:45: The expected type comes from property 'main' which is declared here on type '{ main: never; }'

const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});
~~~~
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/infiniteConstraints.types
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ declare function value<V extends string>(val: V): Value<V>;
>val : V

declare function ensureNoDuplicates<
>ensureNoDuplicates : <T extends { [K in keyof T]: Extract<T[K], Record<"val", string>>["val"] extends Extract<T[Exclude<keyof T, K>], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void
>ensureNoDuplicates : <T extends { [K in keyof T]: never; }>(vals: T) => void

T extends {
[K in keyof T]: Extract<T[K], Value>["val"] extends Extract<T[Exclude<keyof T, K>], Value>["val"]
Expand All @@ -50,9 +50,9 @@ declare function ensureNoDuplicates<
>vals : T

const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")});
>noError : void
>ensureNoDuplicates({main: value("test"), alternate: value("test2")}) : void
>ensureNoDuplicates : <T extends { [K in keyof T]: Extract<T[K], Record<"val", string>>["val"] extends Extract<T[Exclude<keyof T, K>], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void
>noError : any
>ensureNoDuplicates({main: value("test"), alternate: value("test2")}) : any
>ensureNoDuplicates : <T extends { [K in keyof T]: never; }>(vals: T) => void
>{main: value("test"), alternate: value("test2")} : { main: Record<"val", "test">; alternate: Record<"val", "test2">; }
>main : Record<"val", "test">
>value("test") : Record<"val", "test">
Expand All @@ -64,9 +64,9 @@ const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2
>"test2" : "test2"

const shouldBeNoError = ensureNoDuplicates({main: value("test")});
>shouldBeNoError : void
>ensureNoDuplicates({main: value("test")}) : void
>ensureNoDuplicates : <T extends { [K in keyof T]: Extract<T[K], Record<"val", string>>["val"] extends Extract<T[Exclude<keyof T, K>], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void
>shouldBeNoError : any
>ensureNoDuplicates({main: value("test")}) : any
>ensureNoDuplicates : <T extends { [K in keyof T]: never; }>(vals: T) => void
>{main: value("test")} : { main: Record<"val", "test">; }
>main : Record<"val", "test">
>value("test") : Record<"val", "test">
Expand All @@ -76,7 +76,7 @@ const shouldBeNoError = ensureNoDuplicates({main: value("test")});
const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")});
>shouldBeError : any
>ensureNoDuplicates({main: value("dup"), alternate: value("dup")}) : any
>ensureNoDuplicates : <T extends { [K in keyof T]: Extract<T[K], Record<"val", string>>["val"] extends Extract<T[Exclude<keyof T, K>], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void
>ensureNoDuplicates : <T extends { [K in keyof T]: never; }>(vals: T) => void
>{main: value("dup"), alternate: value("dup")} : { main: Record<"val", "dup">; alternate: Record<"val", "dup">; }
>main : Record<"val", "dup">
>value("dup") : Record<"val", "dup">
Expand Down
Loading

0 comments on commit d293b67

Please sign in to comment.