From 3f8f173e6ea4948e922e78bf8c0cd67a40f4ea84 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Nov 2019 08:32:23 -0800 Subject: [PATCH 1/6] Check combined intersection properties against target index signatures --- src/compiler/checker.ts | 67 +++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b718c72c009b9..1973d1f6a1a95 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14694,6 +14694,14 @@ namespace ts { } } } + // When relating an intersection type to an object type with one or more index signatures, we need an + // extra check to ensure the combined properties and index signatures in the target relate to the source. + // For example, { a: string } & { b: number } should not be related to { [key: string]: string }, but + // would be without this additional check. + if (result && source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Object && + (getIndexInfoOfType(target, IndexKind.String) || getIndexInfoOfType(target, IndexKind.Number))) { + result &= recursiveTypeRelatedTo(source, target, reportErrors, /*isIntersectionConstituent*/ false); + } if (!result && reportErrors) { let maybeSuppress = overrideNextErrorInfo > 0; @@ -15995,12 +16003,10 @@ namespace ts { function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { let result = Ternary.True; - for (const prop of getPropertiesOfObjectType(source)) { - if (isIgnoredJsxProperty(source, prop)) { - continue; - } - // Skip over symbol-named members - if (prop.nameType && prop.nameType.flags & TypeFlags.UniqueESSymbol) { + const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source) : getPropertiesOfObjectType(source); + for (const prop of props) { + // Skip over ignored JSX and symbol-named members + if (isIgnoredJsxProperty(source, prop) || prop.nameType && prop.nameType.flags & TypeFlags.UniqueESSymbol) { continue; } if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { @@ -16017,50 +16023,51 @@ namespace ts { return result; } - function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors); + function indexTypeRelatedTo(sourceType: Type, targetType: Type, reportErrors: boolean) { + const related = isRelatedTo(sourceType, targetType, reportErrors); if (!related && reportErrors) { reportError(Diagnostics.Index_signatures_are_incompatible); } return related; } + function hasIndexInfo(type: Type, kind: IndexKind): boolean { + return type.flags & TypeFlags.Intersection ? every((type).types, t => hasIndexInfo(t, kind)) : + !!(getIndexInfoOfType(type, kind) || kind === IndexKind.Number && getIndexInfoOfType(type, IndexKind.String) || isObjectTypeWithInferableIndex(type)); + } + function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(source, target, kind); } - const targetInfo = getIndexInfoOfType(target, kind); - if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) { + const targetType = getIndexTypeOfType(target, kind); + if (!targetType || targetType.flags & TypeFlags.Any && !sourceIsPrimitive) { // Index signature of type any permits assignment from everything but primitives return Ternary.True; } - const sourceInfo = getIndexInfoOfType(source, kind) || - kind === IndexKind.Number && getIndexInfoOfType(source, IndexKind.String); - if (sourceInfo) { - return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); - } if (isGenericMappedType(source)) { // A generic mapped type { [P in K]: T } is related to an index signature { [x: string]: U } // if T is related to U. - return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, reportErrors) : Ternary.False; + return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetType, reportErrors) : Ternary.False; } - if (isObjectTypeWithInferableIndex(source)) { - let related = Ternary.True; - if (kind === IndexKind.String) { - const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number); - if (sourceNumberInfo) { - related = indexInfoRelatedTo(sourceNumberInfo, targetInfo, reportErrors); - } - } - if (related) { - related &= eachPropertyRelatedTo(source, targetInfo.type, kind, reportErrors); + if (!hasIndexInfo(source, kind)) { + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } - return related; + return Ternary.False; } - if (reportErrors) { - reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); + const indexType = getIndexTypeOfType(source, kind) || kind === IndexKind.Number && getIndexTypeOfType(source, IndexKind.String); + if (indexType) { + return indexTypeRelatedTo(indexType, targetType, reportErrors); } - return Ternary.False; + let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); + if (related && kind === IndexKind.String) { + const numberIndexType = getIndexTypeOfType(source, IndexKind.Number); + if (numberIndexType) { + related &= indexTypeRelatedTo(numberIndexType, targetType, reportErrors); + } + } + return related; } function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary { From f3b60073da3ad0d42292365541bc73862b274230 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Nov 2019 08:32:34 -0800 Subject: [PATCH 2/6] Add tests --- .../intersectionWithIndexSignatures.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts diff --git a/tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts b/tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts new file mode 100644 index 0000000000000..aa570cbd7ff81 --- /dev/null +++ b/tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts @@ -0,0 +1,37 @@ +// @strict: true + +type A = { a: string }; +type B = { b: string }; + +declare let sa1: { x: A & B }; +declare let sa2: { x: A } & { x: B }; +declare let ta1: { [key: string]: A & B }; +declare let ta2: { [key: string]: A } & { [key: string]: B }; + +ta1 = sa1; +ta1 = sa2; +ta2 = sa1; +ta2 = sa2; + +declare let sb1: { x: A } & { y: B }; +declare let tb1: { [key: string]: A }; + +tb1 = sb1; // Error + +// Repro from #32484 + +type constr = { [K in keyof Source]: string } & Pick>; + +type s = constr<{}, { [key: string]: { a: string } }>; + +declare const q: s; +q["asd"].a.substr(1); +q["asd"].b; // Error + +const d: { [key: string]: {a: string, b: string} } = q; // Error + +// Repro from #32484 + +declare let ss: { a: string } & { b: number }; +declare let tt: { [key: string]: string }; +tt = ss; // Error From b1f746bb2aec16a15aad2161bf7c1a07f3f2164e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Nov 2019 08:34:24 -0800 Subject: [PATCH 3/6] Accept new baselines --- ...intersectionWithIndexSignatures.errors.txt | 65 +++++++++ .../intersectionWithIndexSignatures.js | 49 +++++++ .../intersectionWithIndexSignatures.symbols | 123 ++++++++++++++++++ .../intersectionWithIndexSignatures.types | 115 ++++++++++++++++ 4 files changed, 352 insertions(+) create mode 100644 tests/baselines/reference/intersectionWithIndexSignatures.errors.txt create mode 100644 tests/baselines/reference/intersectionWithIndexSignatures.js create mode 100644 tests/baselines/reference/intersectionWithIndexSignatures.symbols create mode 100644 tests/baselines/reference/intersectionWithIndexSignatures.types diff --git a/tests/baselines/reference/intersectionWithIndexSignatures.errors.txt b/tests/baselines/reference/intersectionWithIndexSignatures.errors.txt new file mode 100644 index 0000000000000..596757aef8bcc --- /dev/null +++ b/tests/baselines/reference/intersectionWithIndexSignatures.errors.txt @@ -0,0 +1,65 @@ +tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts(17,1): error TS2322: Type '{ x: A; } & { y: B; }' is not assignable to type '{ [key: string]: A; }'. + Property 'y' is incompatible with index signature. + Property 'a' is missing in type 'B' but required in type 'A'. +tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts(27,10): error TS2339: Property 'b' does not exist on type '{ a: string; }'. +tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts(29,7): error TS2322: Type 'constr<{}, { [key: string]: { a: string; }; }>' is not assignable to type '{ [key: string]: { a: string; b: string; }; }'. + Index signatures are incompatible. + Property 'b' is missing in type '{ a: string; }' but required in type '{ a: string; b: string; }'. +tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts(35,1): error TS2322: Type '{ a: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'. + Property 'b' is incompatible with index signature. + Type 'number' is not assignable to type 'string'. + + +==== tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts (4 errors) ==== + type A = { a: string }; + type B = { b: string }; + + declare let sa1: { x: A & B }; + declare let sa2: { x: A } & { x: B }; + declare let ta1: { [key: string]: A & B }; + declare let ta2: { [key: string]: A } & { [key: string]: B }; + + ta1 = sa1; + ta1 = sa2; + ta2 = sa1; + ta2 = sa2; + + declare let sb1: { x: A } & { y: B }; + declare let tb1: { [key: string]: A }; + + tb1 = sb1; // Error + ~~~ +!!! error TS2322: Type '{ x: A; } & { y: B; }' is not assignable to type '{ [key: string]: A; }'. +!!! error TS2322: Property 'y' is incompatible with index signature. +!!! error TS2322: Property 'a' is missing in type 'B' but required in type 'A'. +!!! related TS2728 tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts:1:12: 'a' is declared here. + + // Repro from #32484 + + type constr = { [K in keyof Source]: string } & Pick>; + + type s = constr<{}, { [key: string]: { a: string } }>; + + declare const q: s; + q["asd"].a.substr(1); + q["asd"].b; // Error + ~ +!!! error TS2339: Property 'b' does not exist on type '{ a: string; }'. + + const d: { [key: string]: {a: string, b: string} } = q; // Error + ~ +!!! error TS2322: Type 'constr<{}, { [key: string]: { a: string; }; }>' is not assignable to type '{ [key: string]: { a: string; b: string; }; }'. +!!! error TS2322: Index signatures are incompatible. +!!! error TS2322: Property 'b' is missing in type '{ a: string; }' but required in type '{ a: string; b: string; }'. +!!! related TS2728 tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts:29:39: 'b' is declared here. + + // Repro from #32484 + + declare let ss: { a: string } & { b: number }; + declare let tt: { [key: string]: string }; + tt = ss; // Error + ~~ +!!! error TS2322: Type '{ a: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'. +!!! error TS2322: Property 'b' is incompatible with index signature. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/intersectionWithIndexSignatures.js b/tests/baselines/reference/intersectionWithIndexSignatures.js new file mode 100644 index 0000000000000..c70b7086b407a --- /dev/null +++ b/tests/baselines/reference/intersectionWithIndexSignatures.js @@ -0,0 +1,49 @@ +//// [intersectionWithIndexSignatures.ts] +type A = { a: string }; +type B = { b: string }; + +declare let sa1: { x: A & B }; +declare let sa2: { x: A } & { x: B }; +declare let ta1: { [key: string]: A & B }; +declare let ta2: { [key: string]: A } & { [key: string]: B }; + +ta1 = sa1; +ta1 = sa2; +ta2 = sa1; +ta2 = sa2; + +declare let sb1: { x: A } & { y: B }; +declare let tb1: { [key: string]: A }; + +tb1 = sb1; // Error + +// Repro from #32484 + +type constr = { [K in keyof Source]: string } & Pick>; + +type s = constr<{}, { [key: string]: { a: string } }>; + +declare const q: s; +q["asd"].a.substr(1); +q["asd"].b; // Error + +const d: { [key: string]: {a: string, b: string} } = q; // Error + +// Repro from #32484 + +declare let ss: { a: string } & { b: number }; +declare let tt: { [key: string]: string }; +tt = ss; // Error + + +//// [intersectionWithIndexSignatures.js] +"use strict"; +ta1 = sa1; +ta1 = sa2; +ta2 = sa1; +ta2 = sa2; +tb1 = sb1; // Error +q["asd"].a.substr(1); +q["asd"].b; // Error +var d = q; // Error +tt = ss; // Error diff --git a/tests/baselines/reference/intersectionWithIndexSignatures.symbols b/tests/baselines/reference/intersectionWithIndexSignatures.symbols new file mode 100644 index 0000000000000..7a247833af68b --- /dev/null +++ b/tests/baselines/reference/intersectionWithIndexSignatures.symbols @@ -0,0 +1,123 @@ +=== tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts === +type A = { a: string }; +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 0, 10)) + +type B = { b: string }; +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) +>b : Symbol(b, Decl(intersectionWithIndexSignatures.ts, 1, 10)) + +declare let sa1: { x: A & B }; +>sa1 : Symbol(sa1, Decl(intersectionWithIndexSignatures.ts, 3, 11)) +>x : Symbol(x, Decl(intersectionWithIndexSignatures.ts, 3, 18)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) + +declare let sa2: { x: A } & { x: B }; +>sa2 : Symbol(sa2, Decl(intersectionWithIndexSignatures.ts, 4, 11)) +>x : Symbol(x, Decl(intersectionWithIndexSignatures.ts, 4, 18)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>x : Symbol(x, Decl(intersectionWithIndexSignatures.ts, 4, 29)) +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) + +declare let ta1: { [key: string]: A & B }; +>ta1 : Symbol(ta1, Decl(intersectionWithIndexSignatures.ts, 5, 11)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 5, 20)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) + +declare let ta2: { [key: string]: A } & { [key: string]: B }; +>ta2 : Symbol(ta2, Decl(intersectionWithIndexSignatures.ts, 6, 11)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 6, 20)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 6, 43)) +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) + +ta1 = sa1; +>ta1 : Symbol(ta1, Decl(intersectionWithIndexSignatures.ts, 5, 11)) +>sa1 : Symbol(sa1, Decl(intersectionWithIndexSignatures.ts, 3, 11)) + +ta1 = sa2; +>ta1 : Symbol(ta1, Decl(intersectionWithIndexSignatures.ts, 5, 11)) +>sa2 : Symbol(sa2, Decl(intersectionWithIndexSignatures.ts, 4, 11)) + +ta2 = sa1; +>ta2 : Symbol(ta2, Decl(intersectionWithIndexSignatures.ts, 6, 11)) +>sa1 : Symbol(sa1, Decl(intersectionWithIndexSignatures.ts, 3, 11)) + +ta2 = sa2; +>ta2 : Symbol(ta2, Decl(intersectionWithIndexSignatures.ts, 6, 11)) +>sa2 : Symbol(sa2, Decl(intersectionWithIndexSignatures.ts, 4, 11)) + +declare let sb1: { x: A } & { y: B }; +>sb1 : Symbol(sb1, Decl(intersectionWithIndexSignatures.ts, 13, 11)) +>x : Symbol(x, Decl(intersectionWithIndexSignatures.ts, 13, 18)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) +>y : Symbol(y, Decl(intersectionWithIndexSignatures.ts, 13, 29)) +>B : Symbol(B, Decl(intersectionWithIndexSignatures.ts, 0, 23)) + +declare let tb1: { [key: string]: A }; +>tb1 : Symbol(tb1, Decl(intersectionWithIndexSignatures.ts, 14, 11)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 14, 20)) +>A : Symbol(A, Decl(intersectionWithIndexSignatures.ts, 0, 0)) + +tb1 = sb1; // Error +>tb1 : Symbol(tb1, Decl(intersectionWithIndexSignatures.ts, 14, 11)) +>sb1 : Symbol(sb1, Decl(intersectionWithIndexSignatures.ts, 13, 11)) + +// Repro from #32484 + +type constr = { [K in keyof Source]: string } & Pick>; +>constr : Symbol(constr, Decl(intersectionWithIndexSignatures.ts, 16, 10)) +>Source : Symbol(Source, Decl(intersectionWithIndexSignatures.ts, 20, 12)) +>Tgt : Symbol(Tgt, Decl(intersectionWithIndexSignatures.ts, 20, 19)) +>K : Symbol(K, Decl(intersectionWithIndexSignatures.ts, 20, 30)) +>Source : Symbol(Source, Decl(intersectionWithIndexSignatures.ts, 20, 12)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>Tgt : Symbol(Tgt, Decl(intersectionWithIndexSignatures.ts, 20, 19)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>Tgt : Symbol(Tgt, Decl(intersectionWithIndexSignatures.ts, 20, 19)) +>Source : Symbol(Source, Decl(intersectionWithIndexSignatures.ts, 20, 12)) + +type s = constr<{}, { [key: string]: { a: string } }>; +>s : Symbol(s, Decl(intersectionWithIndexSignatures.ts, 20, 105)) +>constr : Symbol(constr, Decl(intersectionWithIndexSignatures.ts, 16, 10)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 22, 23)) +>a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 22, 38)) + +declare const q: s; +>q : Symbol(q, Decl(intersectionWithIndexSignatures.ts, 24, 13)) +>s : Symbol(s, Decl(intersectionWithIndexSignatures.ts, 20, 105)) + +q["asd"].a.substr(1); +>q["asd"].a.substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --)) +>q["asd"].a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 22, 38)) +>q : Symbol(q, Decl(intersectionWithIndexSignatures.ts, 24, 13)) +>a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 22, 38)) +>substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --)) + +q["asd"].b; // Error +>q : Symbol(q, Decl(intersectionWithIndexSignatures.ts, 24, 13)) + +const d: { [key: string]: {a: string, b: string} } = q; // Error +>d : Symbol(d, Decl(intersectionWithIndexSignatures.ts, 28, 5)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 28, 12)) +>a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 28, 27)) +>b : Symbol(b, Decl(intersectionWithIndexSignatures.ts, 28, 37)) +>q : Symbol(q, Decl(intersectionWithIndexSignatures.ts, 24, 13)) + +// Repro from #32484 + +declare let ss: { a: string } & { b: number }; +>ss : Symbol(ss, Decl(intersectionWithIndexSignatures.ts, 32, 11)) +>a : Symbol(a, Decl(intersectionWithIndexSignatures.ts, 32, 17)) +>b : Symbol(b, Decl(intersectionWithIndexSignatures.ts, 32, 33)) + +declare let tt: { [key: string]: string }; +>tt : Symbol(tt, Decl(intersectionWithIndexSignatures.ts, 33, 11)) +>key : Symbol(key, Decl(intersectionWithIndexSignatures.ts, 33, 19)) + +tt = ss; // Error +>tt : Symbol(tt, Decl(intersectionWithIndexSignatures.ts, 33, 11)) +>ss : Symbol(ss, Decl(intersectionWithIndexSignatures.ts, 32, 11)) + diff --git a/tests/baselines/reference/intersectionWithIndexSignatures.types b/tests/baselines/reference/intersectionWithIndexSignatures.types new file mode 100644 index 0000000000000..632a0da8d5e66 --- /dev/null +++ b/tests/baselines/reference/intersectionWithIndexSignatures.types @@ -0,0 +1,115 @@ +=== tests/cases/conformance/types/intersection/intersectionWithIndexSignatures.ts === +type A = { a: string }; +>A : A +>a : string + +type B = { b: string }; +>B : B +>b : string + +declare let sa1: { x: A & B }; +>sa1 : { x: A & B; } +>x : A & B + +declare let sa2: { x: A } & { x: B }; +>sa2 : { x: A; } & { x: B; } +>x : A +>x : B + +declare let ta1: { [key: string]: A & B }; +>ta1 : { [key: string]: A & B; } +>key : string + +declare let ta2: { [key: string]: A } & { [key: string]: B }; +>ta2 : { [key: string]: A; } & { [key: string]: B; } +>key : string +>key : string + +ta1 = sa1; +>ta1 = sa1 : { x: A & B; } +>ta1 : { [key: string]: A & B; } +>sa1 : { x: A & B; } + +ta1 = sa2; +>ta1 = sa2 : { x: A; } & { x: B; } +>ta1 : { [key: string]: A & B; } +>sa2 : { x: A; } & { x: B; } + +ta2 = sa1; +>ta2 = sa1 : { x: A & B; } +>ta2 : { [key: string]: A; } & { [key: string]: B; } +>sa1 : { x: A & B; } + +ta2 = sa2; +>ta2 = sa2 : { x: A; } & { x: B; } +>ta2 : { [key: string]: A; } & { [key: string]: B; } +>sa2 : { x: A; } & { x: B; } + +declare let sb1: { x: A } & { y: B }; +>sb1 : { x: A; } & { y: B; } +>x : A +>y : B + +declare let tb1: { [key: string]: A }; +>tb1 : { [key: string]: A; } +>key : string + +tb1 = sb1; // Error +>tb1 = sb1 : { x: A; } & { y: B; } +>tb1 : { [key: string]: A; } +>sb1 : { x: A; } & { y: B; } + +// Repro from #32484 + +type constr = { [K in keyof Source]: string } & Pick>; +>constr : constr + +type s = constr<{}, { [key: string]: { a: string } }>; +>s : constr<{}, { [key: string]: { a: string; }; }> +>key : string +>a : string + +declare const q: s; +>q : constr<{}, { [key: string]: { a: string; }; }> + +q["asd"].a.substr(1); +>q["asd"].a.substr(1) : string +>q["asd"].a.substr : (from: number, length?: number | undefined) => string +>q["asd"].a : string +>q["asd"] : { a: string; } +>q : constr<{}, { [key: string]: { a: string; }; }> +>"asd" : "asd" +>a : string +>substr : (from: number, length?: number | undefined) => string +>1 : 1 + +q["asd"].b; // Error +>q["asd"].b : any +>q["asd"] : { a: string; } +>q : constr<{}, { [key: string]: { a: string; }; }> +>"asd" : "asd" +>b : any + +const d: { [key: string]: {a: string, b: string} } = q; // Error +>d : { [key: string]: { a: string; b: string; }; } +>key : string +>a : string +>b : string +>q : constr<{}, { [key: string]: { a: string; }; }> + +// Repro from #32484 + +declare let ss: { a: string } & { b: number }; +>ss : { a: string; } & { b: number; } +>a : string +>b : number + +declare let tt: { [key: string]: string }; +>tt : { [key: string]: string; } +>key : string + +tt = ss; // Error +>tt = ss : { a: string; } & { b: number; } +>tt : { [key: string]: string; } +>ss : { a: string; } & { b: number; } + From 3d8a9c8b28b461fbc7d887583a622aed3e9e0988 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Nov 2019 17:11:56 -0800 Subject: [PATCH 4/6] Less aggressive check for index signatures --- src/compiler/checker.ts | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1973d1f6a1a95..931fefd4dc919 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16031,11 +16031,6 @@ namespace ts { return related; } - function hasIndexInfo(type: Type, kind: IndexKind): boolean { - return type.flags & TypeFlags.Intersection ? every((type).types, t => hasIndexInfo(t, kind)) : - !!(getIndexInfoOfType(type, kind) || kind === IndexKind.Number && getIndexInfoOfType(type, IndexKind.String) || isObjectTypeWithInferableIndex(type)); - } - function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(source, target, kind); @@ -16050,24 +16045,24 @@ namespace ts { // if T is related to U. return kind === IndexKind.String ? isRelatedTo(getTemplateTypeFromMappedType(source), targetType, reportErrors) : Ternary.False; } - if (!hasIndexInfo(source, kind)) { - if (reportErrors) { - reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); - } - return Ternary.False; - } const indexType = getIndexTypeOfType(source, kind) || kind === IndexKind.Number && getIndexTypeOfType(source, IndexKind.String); if (indexType) { return indexTypeRelatedTo(indexType, targetType, reportErrors); } - let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); - if (related && kind === IndexKind.String) { - const numberIndexType = getIndexTypeOfType(source, IndexKind.Number); - if (numberIndexType) { - related &= indexTypeRelatedTo(numberIndexType, targetType, reportErrors); + if (isObjectTypeWithInferableIndex(source)) { + let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); + if (related && kind === IndexKind.String) { + const numberIndexType = getIndexTypeOfType(source, IndexKind.Number); + if (numberIndexType) { + related &= indexTypeRelatedTo(numberIndexType, targetType, reportErrors); + } } + return related; } - return related; + if (reportErrors) { + reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); + } + return Ternary.False; } function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary { @@ -16820,7 +16815,8 @@ namespace ts { * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type): boolean { - return !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && + return type.flags & TypeFlags.Intersection ? every((type).types, isObjectTypeWithInferableIndex) : + !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); } From f87aea31c9c0cc6069f13f3ea1ab89d8b194913a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 9 Jan 2020 14:09:20 -0800 Subject: [PATCH 5/6] Track intersection membership state for both source and target --- src/compiler/checker.ts | 119 ++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 435511d3f235b..ce1bce107c6dc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -192,6 +192,13 @@ namespace ts { Callback = BivariantCallback | StrictCallback, } + const enum IntersectionState { + None = 0, + Source = 1 << 0, + Target = 1 << 1, + SourceOrTarget = Source | Target, + } + const enum MappedTypeModifiers { IncludeReadonly = 1 << 0, ExcludeReadonly = 1 << 1, @@ -14519,7 +14526,7 @@ namespace ts { return true; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation)); + const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } @@ -14847,7 +14854,7 @@ namespace ts { * * Ternary.Maybe if they are related with assumptions of other relationships, or * * Ternary.False if they are not related. */ - function isRelatedTo(originalSource: Type, originalTarget: Type, reportErrors = false, headMessage?: DiagnosticMessage, isApparentIntersectionConstituent?: boolean): Ternary { + function isRelatedTo(originalSource: Type, originalTarget: Type, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { // Normalize the source and target types: Turn fresh literal types into regular literal types, // turn deferred type references into regular type references, simplify indexed access and // conditional types, and resolve substitution types to either the substitution (on the source @@ -14883,7 +14890,7 @@ namespace ts { isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - const isPerformingExcessPropertyChecks = !isApparentIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); + const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); if (isPerformingExcessPropertyChecks) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { @@ -14893,7 +14900,7 @@ namespace ts { } } - const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent && + const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); @@ -14914,14 +14921,13 @@ namespace ts { let result = Ternary.False; const saveErrorInfo = captureErrorCalculationState(); - let isIntersectionConstituent = !!isApparentIntersectionConstituent; // Note that these checks are specifically ordered to produce correct results. In particular, // we need to deconstruct unions before intersections (because unions are always at the top), // and we need to handle "each" relations before "some" relations for the same kind of type. if (source.flags & TypeFlags.Union) { result = relation === comparableRelation ? - someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)) : + someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive)); } else { @@ -14929,11 +14935,10 @@ namespace ts { result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); } else if (target.flags & TypeFlags.Intersection) { - isIntersectionConstituent = true; // set here to affect the following trio of checks - result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors); + result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target); if (result && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks)) { // Validate against excess props using the original `source` - if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*isIntersectionConstituent*/ false)) { + if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None)) { return Ternary.False; } } @@ -14952,10 +14957,10 @@ namespace ts { // // - For a primitive type or type parameter (such as 'number = A & B') there is no point in // breaking the intersection apart. - result = someTypeRelatedToType(source, target, /*reportErrors*/ false); + result = someTypeRelatedToType(source, target, /*reportErrors*/ false, IntersectionState.Source); } if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) { - if (result = recursiveTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent)) { + if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) { resetErrorInfo(saveErrorInfo); } } @@ -14978,20 +14983,12 @@ namespace ts { if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this - if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { + if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); } } } } - // When relating an intersection type to an object type with one or more index signatures, we need an - // extra check to ensure the combined properties and index signatures in the target relate to the source. - // For example, { a: string } & { b: number } should not be related to { [key: string]: string }, but - // would be without this additional check. - if (result && source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Object && - (getIndexInfoOfType(target, IndexKind.String) || getIndexInfoOfType(target, IndexKind.Number))) { - result &= recursiveTypeRelatedTo(source, target, reportErrors, /*isIntersectionConstituent*/ false); - } if (!result && reportErrors) { source = originalSource.aliasSymbol ? originalSource : source; @@ -15037,7 +15034,7 @@ namespace ts { let result: Ternary; const flags = source.flags & target.flags; if (flags & TypeFlags.Object || flags & TypeFlags.IndexedAccess || flags & TypeFlags.Conditional || flags & TypeFlags.Index || flags & TypeFlags.Substitution) { - return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, /*isIntersectionConstituent*/ false); + return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None); } if (flags & (TypeFlags.Union | TypeFlags.Intersection)) { if (result = eachTypeRelatedToSomeType(source, target)) { @@ -15169,11 +15166,11 @@ namespace ts { return Ternary.False; } - function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary { + function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { let result = Ternary.True; const targetTypes = target.types; for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, /*isIntersectionConstituent*/ true); + const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } @@ -15182,14 +15179,14 @@ namespace ts { return result; } - function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { + function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourceTypes = source.types; if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { return Ternary.True; } const len = sourceTypes.length; for (let i = 0; i < len; i++) { - const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1); + const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); if (related) { return related; } @@ -15210,7 +15207,7 @@ namespace ts { return result; } - function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (sources.length !== targets.length && relation === identityRelation) { return Ternary.False; } @@ -15234,10 +15231,10 @@ namespace ts { related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t); } else if (variance === VarianceFlags.Covariant) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); } else if (variance === VarianceFlags.Contravariant) { - related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); } else if (variance === VarianceFlags.Bivariant) { // In the bivariant case we first compare contravariantly without reporting @@ -15246,16 +15243,16 @@ namespace ts { // which is generally easier to reason about. related = isRelatedTo(t, s, /*reportErrors*/ false); if (!related) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); } } else { // In the invariant case we first compare covariantly, and only when that // succeeds do we proceed to compare contravariantly. Thus, error elaboration // will typically be based on the covariant check. - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); if (related) { - related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); } } if (!related) { @@ -15272,11 +15269,11 @@ namespace ts { // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. - function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (overflow) { return Ternary.False; } - const id = getRelationKey(source, target, isIntersectionConstituent, relation); + const id = getRelationKey(source, target, intersectionState, relation); const entry = relation.get(id); if (entry !== undefined) { if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { @@ -15332,7 +15329,7 @@ namespace ts { return originalHandler!(onlyUnreliable); }; } - const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent) : Ternary.Maybe; + const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe; if (outofbandVarianceMarkerHandler) { outofbandVarianceMarkerHandler = originalHandler; } @@ -15356,7 +15353,7 @@ namespace ts { return result; } - function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const flags = source.flags & target.flags; if (relation === identityRelation && !(flags & TypeFlags.Object)) { if (flags & TypeFlags.Index) { @@ -15401,7 +15398,7 @@ namespace ts { source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { const variances = getAliasVariances(source.aliasSymbol); - const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, isIntersectionConstituent); + const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); if (varianceResult !== undefined) { return varianceResult; } @@ -15510,12 +15507,12 @@ namespace ts { } } // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed - else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) { + else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); return result; } // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example - else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) { + else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); return result; } @@ -15597,7 +15594,7 @@ namespace ts { // type references (which are intended by be compared structurally). Obtain the variance // information for the type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); - const varianceResult = relateVariances(getTypeArguments(source), getTypeArguments(target), variances, isIntersectionConstituent); + const varianceResult = relateVariances(getTypeArguments(source), getTypeArguments(target), variances, intersectionState); if (varianceResult !== undefined) { return varianceResult; } @@ -15625,15 +15622,15 @@ namespace ts { if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent); + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); if (result) { result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); if (result) { result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors); + result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors, intersectionState); if (result) { - result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors); + result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors, intersectionState); } } } @@ -15661,8 +15658,8 @@ namespace ts { } return Ternary.False; - function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], isIntersectionConstituent: boolean) { - if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, isIntersectionConstituent)) { + function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { + if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { return result; } if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { @@ -15790,7 +15787,7 @@ namespace ts { if (!targetProperty) continue outer; if (sourceProperty === targetProperty) continue; // We compare the source property to the target in the context of a single discriminant type. - const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, /*isIntersectionConstituent*/ false); + const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None); // If the target property could not be found, or if the properties were not related, // then this constituent is not a match. if (!related) { @@ -15809,15 +15806,15 @@ namespace ts { // Compare the remaining non-discriminant properties of each match. let result = Ternary.True; for (const type of matchingTypes) { - result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, /*isIntersectionConstituent*/ false); + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); if (result) { - result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); if (result) { - result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false); + result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); } } } @@ -15845,7 +15842,7 @@ namespace ts { return result || properties; } - function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); const source = getTypeOfSourceProperty(sourceProp); if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) { @@ -15857,7 +15854,7 @@ namespace ts { let result = unionParent ? Ternary.False : Ternary.True; const targetTypes = links.deferralConstituents!; for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent); + const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, unionParent ? 0 : IntersectionState.Target); if (!unionParent) { if (!related) { // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization) @@ -15884,11 +15881,11 @@ namespace ts { return result; } else { - return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, isIntersectionConstituent); + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, intersectionState); } } - function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary { + function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { @@ -15930,7 +15927,7 @@ namespace ts { return Ternary.False; } // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, isIntersectionConstituent); + const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); if (!related) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); @@ -16007,7 +16004,7 @@ namespace ts { // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined, isIntersectionConstituent: boolean): Ternary { + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); @@ -16071,7 +16068,7 @@ namespace ts { if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { const sourceProp = getPropertyOfType(source, name); if (sourceProp && sourceProp !== targetProp) { - const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, isIntersectionConstituent); + const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, intersectionState); if (!related) { return Ternary.False; } @@ -16258,7 +16255,7 @@ namespace ts { return related; } - function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean): Ternary { + function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(source, target, kind); } @@ -16276,7 +16273,7 @@ namespace ts { if (indexType) { return indexTypeRelatedTo(indexType, targetType, reportErrors); } - if (isObjectTypeWithInferableIndex(source)) { + if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); if (related && kind === IndexKind.String) { const numberIndexType = getIndexTypeOfType(source, IndexKind.Number); @@ -16518,18 +16515,18 @@ namespace ts { * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ - function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map) { + function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: Map) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } - const delimiter = isIntersectionConstituent ? ";" : ","; + const postFix = intersectionState ? ":" + intersectionState : ""; if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { const typeParameters: Type[] = []; - return getTypeReferenceId(source, typeParameters) + delimiter + getTypeReferenceId(target, typeParameters); + return getTypeReferenceId(source, typeParameters) + "," + getTypeReferenceId(target, typeParameters) + postFix; } - return source.id + delimiter + target.id; + return source.id + "," + target.id + postFix; } // Invoke the callback for each underlying property symbol of the given symbol and return the first From 099ff31d58d29ceafedd84b68284203ab487690e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 9 Jan 2020 15:44:45 -0800 Subject: [PATCH 6/6] Minor fixes --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce1bce107c6dc..68dd6cf23aeaf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -196,7 +196,6 @@ namespace ts { None = 0, Source = 1 << 0, Target = 1 << 1, - SourceOrTarget = Source | Target, } const enum MappedTypeModifiers { @@ -16274,6 +16273,7 @@ namespace ts { return indexTypeRelatedTo(indexType, targetType, reportErrors); } if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { + // Intersection constituents are never considered to have an inferred index signature let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); if (related && kind === IndexKind.String) { const numberIndexType = getIndexTypeOfType(source, IndexKind.Number);