From fc15aa2bf017f0ed91fb044fc3fdf2c2e28aa3c4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 1 Mar 2022 14:54:51 -0800 Subject: [PATCH 1/7] Only permanently record non-nested getVariancesWorker results --- src/compiler/checker.ts | 72 +++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cb52a13176aca..8dee6e1084cc6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -346,6 +346,8 @@ namespace ts { let instantiationCount = 0; let instantiationDepth = 0; let inlineLevel = 0; + let varianceLevel = 0; + let nestedVarianceSymbols: Symbol[] | undefined; let currentNode: Node | undefined; const emptySymbols = createSymbolTable(); @@ -17947,7 +17949,7 @@ namespace ts { } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); - if (related !== undefined) { + if (related !== undefined && !(related & RelationComparisonResult.ReportsMask)) { return !!(related & RelationComparisonResult.Succeeded); } } @@ -20366,21 +20368,31 @@ namespace ts { return false; } + function getVariances(type: GenericType): VarianceFlags[] { + // Arrays and tuples are known to be covariant, no need to spend time computing this. + if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { + return arrayVariances; + } + return getVariancesWorker(type.symbol, type.typeParameters, getMarkerTypeReference); + } + // 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) { + function getMarkerTypeReference(symbol: Symbol, source: TypeParameter, target: Type) { + const type = getDeclaredTypeOfSymbol(symbol) as GenericType; const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); result.objectFlags |= ObjectFlags.MarkerType; return result; } function getAliasVariances(symbol: Symbol) { - const links = getSymbolLinks(symbol); - return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { - const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); - type.aliasTypeArgumentsContainsMarker = true; - return type; - }); + return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters, getMarkerTypeAliasReference); + } + + function getMarkerTypeAliasReference(symbol: Symbol, source: TypeParameter, target: Type) { + const result = getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, makeUnaryTypeMapper(source, target))); + result.aliasTypeArgumentsContainsMarker = true; + return result; } // Return an array containing the variance of each type parameter. The variance is effectively @@ -20388,13 +20400,17 @@ namespace ts { // generic type are structurally compared. We infer the variance information by comparing // instantiations of the generic type for type arguments with known relations. The function // returns the emptyArray singleton when invoked recursively for the given generic type. - function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { - let variances = cache.variances; - if (!variances) { - tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: (cache as any).id ?? (cache as any).declaredType?.id ?? -1 }); + function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray, createMarkerType: (symbol: Symbol, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { + const links = getSymbolLinks(symbol); + if (!links.variances) { + tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getSymbolId(symbol) }); + if (varianceLevel > 0) { + nestedVarianceSymbols = append(nestedVarianceSymbols, symbol); + } + varianceLevel++; // The emptyArray singleton is used to signal a recursive invocation. - cache.variances = emptyArray; - variances = []; + links.variances = emptyArray; + const variances = []; for (const tp of typeParameters) { let unmeasurable = false; let unreliable = false; @@ -20403,15 +20419,15 @@ namespace ts { // We first compare instantiations where the type parameter is replaced with // marker types that have a known subtype relationship. From this we can infer // invariance, covariance, contravariance or bivariance. - const typeWithSuper = createMarkerType(cache, tp, markerSuperType); - const typeWithSub = createMarkerType(cache, tp, markerSubType); + const typeWithSuper = createMarkerType(symbol, tp, markerSuperType); + const typeWithSub = createMarkerType(symbol, tp, markerSubType); let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); // If the instantiations appear to be related bivariantly it may be because the // type parameter is independent (i.e. it isn't witnessed anywhere in the generic // type). To determine this we compare instantiations where the type parameter is // replaced with marker types that are known to be unrelated. - if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { + if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(symbol, tp, markerOtherType), typeWithSuper)) { variance = VarianceFlags.Independent; } outofbandVarianceMarkerHandler = oldHandler; @@ -20425,18 +20441,20 @@ namespace ts { } variances.push(variance); } - cache.variances = variances; + links.variances = variances; + varianceLevel--; + // Recursive invocations of getVariancesWorker occur when two or more types circularly reference each + // other. In such cases, the nested invocations might observe in-process variance computations, i.e. + // cases where getVariancesWorker returns emptyArray, and thus might compute incomplete variances. For + // this reason we clear (and thus re-compute) the results of nested variance computations and only + // permanently record the outermost result. See #44572. + if (varianceLevel === 0 && nestedVarianceSymbols) { + for (const sym of nestedVarianceSymbols) getSymbolLinks(sym).variances = undefined; + nestedVarianceSymbols = undefined; + } tracing?.pop(); } - return variances; - } - - function getVariances(type: GenericType): VarianceFlags[] { - // Arrays and tuples are known to be covariant, no need to spend time computing this. - if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { - return arrayVariances; - } - return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); + return links.variances; } // Return true if the given type reference has a 'void' type argument for a covariant type parameter. From de4a1669057f3da5462b6204243397d4889d13af Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 1 Mar 2022 14:55:42 -0800 Subject: [PATCH 2/7] Add tests --- ...rDependenceGenericAssignability.errors.txt | 92 +++++++++ ...heckOrderDependenceGenericAssignability.js | 88 ++++++++ ...rderDependenceGenericAssignability.symbols | 193 ++++++++++++++++++ ...kOrderDependenceGenericAssignability.types | 144 +++++++++++++ ...heckOrderDependenceGenericAssignability.ts | 70 +++++++ 5 files changed, 587 insertions(+) create mode 100644 tests/baselines/reference/checkOrderDependenceGenericAssignability.errors.txt create mode 100644 tests/baselines/reference/checkOrderDependenceGenericAssignability.js create mode 100644 tests/baselines/reference/checkOrderDependenceGenericAssignability.symbols create mode 100644 tests/baselines/reference/checkOrderDependenceGenericAssignability.types create mode 100644 tests/cases/compiler/checkOrderDependenceGenericAssignability.ts diff --git a/tests/baselines/reference/checkOrderDependenceGenericAssignability.errors.txt b/tests/baselines/reference/checkOrderDependenceGenericAssignability.errors.txt new file mode 100644 index 0000000000000..448081e8daf19 --- /dev/null +++ b/tests/baselines/reference/checkOrderDependenceGenericAssignability.errors.txt @@ -0,0 +1,92 @@ +tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(21,1): error TS2322: Type 'Parent1' is not assignable to type 'Parent1'. + Type 'unknown' is not assignable to type 'string'. +tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(45,1): error TS2322: Type 'Parent2' is not assignable to type 'Parent2'. + Type 'unknown' is not assignable to type 'string'. +tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(62,1): error TS2322: Type 'Child3' is not assignable to type 'Child3'. + Type 'unknown' is not assignable to type 'string'. +tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(68,1): error TS2322: Type 'Parent3' is not assignable to type 'Parent3'. + Type 'unknown' is not assignable to type 'string'. + + +==== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts (4 errors) ==== + // Repro from #44572 with interface types + + interface Parent1 { + child: Child1; + parent: Parent1; + } + + interface Child1 extends Parent1 { + readonly a: A; + readonly b: B; + } + + function fn1(inp: Child1) { + const a: Child1 = inp; + } + + declare let pu1: Parent1; + declare let ps1: Parent1; + + pu1 = ps1; // Ok + ps1 = pu1; // Error expected + ~~~ +!!! error TS2322: Type 'Parent1' is not assignable to type 'Parent1'. +!!! error TS2322: Type 'unknown' is not assignable to type 'string'. + + // Repro from #44572 with aliased object types + + type Parent2 = { + child: Child2; + parent: Parent2; + } + + type Child2 = { + child: Child2; + parent: Parent2; + readonly a: A; + readonly b: B; + } + + function fn2(inp: Child2) { + const a: Child2 = inp; + } + + declare let pu2: Parent2; + declare let ps2: Parent2; + + pu2 = ps2; // Ok + ps2 = pu2; // Error expected + ~~~ +!!! error TS2322: Type 'Parent2' is not assignable to type 'Parent2'. +!!! error TS2322: Type 'unknown' is not assignable to type 'string'. + + // Simpler repro for same issue + + interface Parent3 { + child: Child3; + parent: Parent3; + } + + interface Child3 extends Parent3 { + readonly a: A; + } + + declare let cu3: Child3; + declare let cs3: Child3; + + cu3 = cs3; // Ok + cs3 = cu3; // Error expected + ~~~ +!!! error TS2322: Type 'Child3' is not assignable to type 'Child3'. +!!! error TS2322: Type 'unknown' is not assignable to type 'string'. + + declare let pu3: Parent3; + declare let ps3: Parent3; + + pu3 = ps3; // Ok + ps3 = pu3; // Error expected + ~~~ +!!! error TS2322: Type 'Parent3' is not assignable to type 'Parent3'. +!!! error TS2322: Type 'unknown' is not assignable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/checkOrderDependenceGenericAssignability.js b/tests/baselines/reference/checkOrderDependenceGenericAssignability.js new file mode 100644 index 0000000000000..c88526e85bf94 --- /dev/null +++ b/tests/baselines/reference/checkOrderDependenceGenericAssignability.js @@ -0,0 +1,88 @@ +//// [checkOrderDependenceGenericAssignability.ts] +// Repro from #44572 with interface types + +interface Parent1 { + child: Child1; + parent: Parent1; +} + +interface Child1 extends Parent1 { + readonly a: A; + readonly b: B; +} + +function fn1(inp: Child1) { + const a: Child1 = inp; +} + +declare let pu1: Parent1; +declare let ps1: Parent1; + +pu1 = ps1; // Ok +ps1 = pu1; // Error expected + +// Repro from #44572 with aliased object types + +type Parent2 = { + child: Child2; + parent: Parent2; +} + +type Child2 = { + child: Child2; + parent: Parent2; + readonly a: A; + readonly b: B; +} + +function fn2(inp: Child2) { + const a: Child2 = inp; +} + +declare let pu2: Parent2; +declare let ps2: Parent2; + +pu2 = ps2; // Ok +ps2 = pu2; // Error expected + +// Simpler repro for same issue + +interface Parent3 { + child: Child3; + parent: Parent3; +} + +interface Child3 extends Parent3 { + readonly a: A; +} + +declare let cu3: Child3; +declare let cs3: Child3; + +cu3 = cs3; // Ok +cs3 = cu3; // Error expected + +declare let pu3: Parent3; +declare let ps3: Parent3; + +pu3 = ps3; // Ok +ps3 = pu3; // Error expected + + +//// [checkOrderDependenceGenericAssignability.js] +"use strict"; +// Repro from #44572 with interface types +function fn1(inp) { + var a = inp; +} +pu1 = ps1; // Ok +ps1 = pu1; // Error expected +function fn2(inp) { + var a = inp; +} +pu2 = ps2; // Ok +ps2 = pu2; // Error expected +cu3 = cs3; // Ok +cs3 = cu3; // Error expected +pu3 = ps3; // Ok +ps3 = pu3; // Error expected diff --git a/tests/baselines/reference/checkOrderDependenceGenericAssignability.symbols b/tests/baselines/reference/checkOrderDependenceGenericAssignability.symbols new file mode 100644 index 0000000000000..8bbda87191438 --- /dev/null +++ b/tests/baselines/reference/checkOrderDependenceGenericAssignability.symbols @@ -0,0 +1,193 @@ +=== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts === +// Repro from #44572 with interface types + +interface Parent1 { +>Parent1 : Symbol(Parent1, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 18)) + + child: Child1; +>child : Symbol(Parent1.child, Decl(checkOrderDependenceGenericAssignability.ts, 2, 22)) +>Child1 : Symbol(Child1, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 18)) + + parent: Parent1; +>parent : Symbol(Parent1.parent, Decl(checkOrderDependenceGenericAssignability.ts, 3, 21)) +>Parent1 : Symbol(Parent1, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 18)) +} + +interface Child1 extends Parent1 { +>Child1 : Symbol(Child1, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 17)) +>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 7, 19)) +>Parent1 : Symbol(Parent1, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 17)) + + readonly a: A; +>a : Symbol(Child1.a, Decl(checkOrderDependenceGenericAssignability.ts, 7, 53)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 17)) + + readonly b: B; +>b : Symbol(Child1.b, Decl(checkOrderDependenceGenericAssignability.ts, 8, 18)) +>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 7, 19)) +} + +function fn1(inp: Child1) { +>fn1 : Symbol(fn1, Decl(checkOrderDependenceGenericAssignability.ts, 10, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 12, 13)) +>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 12, 16)) +>Child1 : Symbol(Child1, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 12, 13)) + + const a: Child1 = inp; +>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 13, 9)) +>Child1 : Symbol(Child1, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1)) +>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 12, 16)) +} + +declare let pu1: Parent1; +>pu1 : Symbol(pu1, Decl(checkOrderDependenceGenericAssignability.ts, 16, 11)) +>Parent1 : Symbol(Parent1, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0)) + +declare let ps1: Parent1; +>ps1 : Symbol(ps1, Decl(checkOrderDependenceGenericAssignability.ts, 17, 11)) +>Parent1 : Symbol(Parent1, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0)) + +pu1 = ps1; // Ok +>pu1 : Symbol(pu1, Decl(checkOrderDependenceGenericAssignability.ts, 16, 11)) +>ps1 : Symbol(ps1, Decl(checkOrderDependenceGenericAssignability.ts, 17, 11)) + +ps1 = pu1; // Error expected +>ps1 : Symbol(ps1, Decl(checkOrderDependenceGenericAssignability.ts, 17, 11)) +>pu1 : Symbol(pu1, Decl(checkOrderDependenceGenericAssignability.ts, 16, 11)) + +// Repro from #44572 with aliased object types + +type Parent2 = { +>Parent2 : Symbol(Parent2, Decl(checkOrderDependenceGenericAssignability.ts, 20, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 13)) + + child: Child2; +>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 24, 19)) +>Child2 : Symbol(Child2, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 13)) + + parent: Parent2; +>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 25, 21)) +>Parent2 : Symbol(Parent2, Decl(checkOrderDependenceGenericAssignability.ts, 20, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 24, 13)) +} + +type Child2 = { +>Child2 : Symbol(Child2, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 12)) +>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 29, 14)) + + child: Child2; +>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 29, 31)) +>Child2 : Symbol(Child2, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 12)) + + parent: Parent2; +>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 30, 21)) +>Parent2 : Symbol(Parent2, Decl(checkOrderDependenceGenericAssignability.ts, 20, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 12)) + + readonly a: A; +>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 31, 23)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 29, 12)) + + readonly b: B; +>b : Symbol(b, Decl(checkOrderDependenceGenericAssignability.ts, 32, 18)) +>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 29, 14)) +} + +function fn2(inp: Child2) { +>fn2 : Symbol(fn2, Decl(checkOrderDependenceGenericAssignability.ts, 34, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 36, 13)) +>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 36, 16)) +>Child2 : Symbol(Child2, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 36, 13)) + + const a: Child2 = inp; +>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 37, 9)) +>Child2 : Symbol(Child2, Decl(checkOrderDependenceGenericAssignability.ts, 27, 1)) +>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 36, 16)) +} + +declare let pu2: Parent2; +>pu2 : Symbol(pu2, Decl(checkOrderDependenceGenericAssignability.ts, 40, 11)) +>Parent2 : Symbol(Parent2, Decl(checkOrderDependenceGenericAssignability.ts, 20, 10)) + +declare let ps2: Parent2; +>ps2 : Symbol(ps2, Decl(checkOrderDependenceGenericAssignability.ts, 41, 11)) +>Parent2 : Symbol(Parent2, Decl(checkOrderDependenceGenericAssignability.ts, 20, 10)) + +pu2 = ps2; // Ok +>pu2 : Symbol(pu2, Decl(checkOrderDependenceGenericAssignability.ts, 40, 11)) +>ps2 : Symbol(ps2, Decl(checkOrderDependenceGenericAssignability.ts, 41, 11)) + +ps2 = pu2; // Error expected +>ps2 : Symbol(ps2, Decl(checkOrderDependenceGenericAssignability.ts, 41, 11)) +>pu2 : Symbol(pu2, Decl(checkOrderDependenceGenericAssignability.ts, 40, 11)) + +// Simpler repro for same issue + +interface Parent3 { +>Parent3 : Symbol(Parent3, Decl(checkOrderDependenceGenericAssignability.ts, 44, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 48, 18)) + + child: Child3; +>child : Symbol(Parent3.child, Decl(checkOrderDependenceGenericAssignability.ts, 48, 22)) +>Child3 : Symbol(Child3, Decl(checkOrderDependenceGenericAssignability.ts, 51, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 48, 18)) + + parent: Parent3; +>parent : Symbol(Parent3.parent, Decl(checkOrderDependenceGenericAssignability.ts, 49, 23)) +>Parent3 : Symbol(Parent3, Decl(checkOrderDependenceGenericAssignability.ts, 44, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 48, 18)) +} + +interface Child3 extends Parent3 { +>Child3 : Symbol(Child3, Decl(checkOrderDependenceGenericAssignability.ts, 51, 1)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 53, 17)) +>Parent3 : Symbol(Parent3, Decl(checkOrderDependenceGenericAssignability.ts, 44, 10)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 53, 17)) + + readonly a: A; +>a : Symbol(Child3.a, Decl(checkOrderDependenceGenericAssignability.ts, 53, 40)) +>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 53, 17)) +} + +declare let cu3: Child3; +>cu3 : Symbol(cu3, Decl(checkOrderDependenceGenericAssignability.ts, 57, 11)) +>Child3 : Symbol(Child3, Decl(checkOrderDependenceGenericAssignability.ts, 51, 1)) + +declare let cs3: Child3; +>cs3 : Symbol(cs3, Decl(checkOrderDependenceGenericAssignability.ts, 58, 11)) +>Child3 : Symbol(Child3, Decl(checkOrderDependenceGenericAssignability.ts, 51, 1)) + +cu3 = cs3; // Ok +>cu3 : Symbol(cu3, Decl(checkOrderDependenceGenericAssignability.ts, 57, 11)) +>cs3 : Symbol(cs3, Decl(checkOrderDependenceGenericAssignability.ts, 58, 11)) + +cs3 = cu3; // Error expected +>cs3 : Symbol(cs3, Decl(checkOrderDependenceGenericAssignability.ts, 58, 11)) +>cu3 : Symbol(cu3, Decl(checkOrderDependenceGenericAssignability.ts, 57, 11)) + +declare let pu3: Parent3; +>pu3 : Symbol(pu3, Decl(checkOrderDependenceGenericAssignability.ts, 63, 11)) +>Parent3 : Symbol(Parent3, Decl(checkOrderDependenceGenericAssignability.ts, 44, 10)) + +declare let ps3: Parent3; +>ps3 : Symbol(ps3, Decl(checkOrderDependenceGenericAssignability.ts, 64, 11)) +>Parent3 : Symbol(Parent3, Decl(checkOrderDependenceGenericAssignability.ts, 44, 10)) + +pu3 = ps3; // Ok +>pu3 : Symbol(pu3, Decl(checkOrderDependenceGenericAssignability.ts, 63, 11)) +>ps3 : Symbol(ps3, Decl(checkOrderDependenceGenericAssignability.ts, 64, 11)) + +ps3 = pu3; // Error expected +>ps3 : Symbol(ps3, Decl(checkOrderDependenceGenericAssignability.ts, 64, 11)) +>pu3 : Symbol(pu3, Decl(checkOrderDependenceGenericAssignability.ts, 63, 11)) + diff --git a/tests/baselines/reference/checkOrderDependenceGenericAssignability.types b/tests/baselines/reference/checkOrderDependenceGenericAssignability.types new file mode 100644 index 0000000000000..afe81326fbcd3 --- /dev/null +++ b/tests/baselines/reference/checkOrderDependenceGenericAssignability.types @@ -0,0 +1,144 @@ +=== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts === +// Repro from #44572 with interface types + +interface Parent1 { + child: Child1; +>child : Child1 + + parent: Parent1; +>parent : Parent1 +} + +interface Child1 extends Parent1 { + readonly a: A; +>a : A + + readonly b: B; +>b : B +} + +function fn1(inp: Child1) { +>fn1 : (inp: Child1) => void +>inp : Child1 + + const a: Child1 = inp; +>a : Child1 +>inp : Child1 +} + +declare let pu1: Parent1; +>pu1 : Parent1 + +declare let ps1: Parent1; +>ps1 : Parent1 + +pu1 = ps1; // Ok +>pu1 = ps1 : Parent1 +>pu1 : Parent1 +>ps1 : Parent1 + +ps1 = pu1; // Error expected +>ps1 = pu1 : Parent1 +>ps1 : Parent1 +>pu1 : Parent1 + +// Repro from #44572 with aliased object types + +type Parent2 = { +>Parent2 : Parent2 + + child: Child2; +>child : Child2 + + parent: Parent2; +>parent : Parent2 +} + +type Child2 = { +>Child2 : Child2 + + child: Child2; +>child : Child2 + + parent: Parent2; +>parent : Parent2 + + readonly a: A; +>a : A + + readonly b: B; +>b : B +} + +function fn2(inp: Child2) { +>fn2 : (inp: Child2) => void +>inp : Child2 + + const a: Child2 = inp; +>a : Child2 +>inp : Child2 +} + +declare let pu2: Parent2; +>pu2 : Parent2 + +declare let ps2: Parent2; +>ps2 : Parent2 + +pu2 = ps2; // Ok +>pu2 = ps2 : Parent2 +>pu2 : Parent2 +>ps2 : Parent2 + +ps2 = pu2; // Error expected +>ps2 = pu2 : Parent2 +>ps2 : Parent2 +>pu2 : Parent2 + +// Simpler repro for same issue + +interface Parent3 { + child: Child3; +>child : Child3 + + parent: Parent3; +>parent : Parent3 +} + +interface Child3 extends Parent3 { + readonly a: A; +>a : A +} + +declare let cu3: Child3; +>cu3 : Child3 + +declare let cs3: Child3; +>cs3 : Child3 + +cu3 = cs3; // Ok +>cu3 = cs3 : Child3 +>cu3 : Child3 +>cs3 : Child3 + +cs3 = cu3; // Error expected +>cs3 = cu3 : Child3 +>cs3 : Child3 +>cu3 : Child3 + +declare let pu3: Parent3; +>pu3 : Parent3 + +declare let ps3: Parent3; +>ps3 : Parent3 + +pu3 = ps3; // Ok +>pu3 = ps3 : Parent3 +>pu3 : Parent3 +>ps3 : Parent3 + +ps3 = pu3; // Error expected +>ps3 = pu3 : Parent3 +>ps3 : Parent3 +>pu3 : Parent3 + diff --git a/tests/cases/compiler/checkOrderDependenceGenericAssignability.ts b/tests/cases/compiler/checkOrderDependenceGenericAssignability.ts new file mode 100644 index 0000000000000..b4701ed36dc3b --- /dev/null +++ b/tests/cases/compiler/checkOrderDependenceGenericAssignability.ts @@ -0,0 +1,70 @@ +// @strict: true + +// Repro from #44572 with interface types + +interface Parent1 { + child: Child1; + parent: Parent1; +} + +interface Child1 extends Parent1 { + readonly a: A; + readonly b: B; +} + +function fn1(inp: Child1) { + const a: Child1 = inp; +} + +declare let pu1: Parent1; +declare let ps1: Parent1; + +pu1 = ps1; // Ok +ps1 = pu1; // Error expected + +// Repro from #44572 with aliased object types + +type Parent2 = { + child: Child2; + parent: Parent2; +} + +type Child2 = { + child: Child2; + parent: Parent2; + readonly a: A; + readonly b: B; +} + +function fn2(inp: Child2) { + const a: Child2 = inp; +} + +declare let pu2: Parent2; +declare let ps2: Parent2; + +pu2 = ps2; // Ok +ps2 = pu2; // Error expected + +// Simpler repro for same issue + +interface Parent3 { + child: Child3; + parent: Parent3; +} + +interface Child3 extends Parent3 { + readonly a: A; +} + +declare let cu3: Child3; +declare let cs3: Child3; + +cu3 = cs3; // Ok +cs3 = cu3; // Error expected + +declare let pu3: Parent3; +declare let ps3: Parent3; + +pu3 = ps3; // Ok +ps3 = pu3; // Error expected From d05979172859c0968efa0bd5c99cfe0fffcae65a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 1 Mar 2022 17:02:58 -0800 Subject: [PATCH 3/7] Track if incomplete variances have been observed --- src/compiler/checker.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8dee6e1084cc6..3fd944d53b585 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -348,6 +348,7 @@ namespace ts { let inlineLevel = 0; let varianceLevel = 0; let nestedVarianceSymbols: Symbol[] | undefined; + let incompleteVariancesObserved = false; let currentNode: Node | undefined; const emptySymbols = createSymbolTable(); @@ -20445,15 +20446,22 @@ namespace ts { varianceLevel--; // Recursive invocations of getVariancesWorker occur when two or more types circularly reference each // other. In such cases, the nested invocations might observe in-process variance computations, i.e. - // cases where getVariancesWorker returns emptyArray, and thus might compute incomplete variances. For - // this reason we clear (and thus re-compute) the results of nested variance computations and only - // permanently record the outermost result. See #44572. - if (varianceLevel === 0 && nestedVarianceSymbols) { - for (const sym of nestedVarianceSymbols) getSymbolLinks(sym).variances = undefined; + // cases where getVariancesWorker returns emptyArray. If that happens we clear (and thus re-compute) the + // results of nested variance computations and only permanently record the outermost result. See #44572. + if (varianceLevel === 0) { + if (nestedVarianceSymbols && incompleteVariancesObserved) { + for (const sym of nestedVarianceSymbols) { + getSymbolLinks(sym).variances = undefined; + } + } nestedVarianceSymbols = undefined; + incompleteVariancesObserved = false; } tracing?.pop(); } + else { + incompleteVariancesObserved ||= links.variances === emptyArray; + } return links.variances; } From 466bcb2b93836c6a6821a7abce772241e6c32901 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 6 Mar 2022 12:12:38 -0800 Subject: [PATCH 4/7] Always fully compute variances structurally --- src/compiler/checker.ts | 112 +++++++++++++--------------------------- src/compiler/types.ts | 44 ++++++++-------- 2 files changed, 58 insertions(+), 98 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fd944d53b585..4569368f44091 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -346,9 +346,6 @@ namespace ts { let instantiationCount = 0; let instantiationDepth = 0; let inlineLevel = 0; - let varianceLevel = 0; - let nestedVarianceSymbols: Symbol[] | undefined; - let incompleteVariancesObserved = false; let currentNode: Node | undefined; const emptySymbols = createSymbolTable(); @@ -16629,6 +16626,7 @@ namespace ts { return result; } + function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node : @@ -16686,15 +16684,16 @@ namespace ts { } function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { - // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks - // between the node and the type parameter declaration, if the node contains actual references to the - // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. - if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { - const container = tp.symbol.declarations[0].parent; - for (let n = node; n !== container; n = n.parent) { + // If there are intervening statement blocks between the node and the type parameter declaration, if the node + // contains actual references to the type parameter, or if the node contains type queries, we consider the + // type parameter possibly referenced. + if (tp.symbol && tp.symbol.declarations) { + let n = node; + while (!some(tp.symbol.declarations, d => n === d.parent)) { if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { return true; } + n = n.parent; } return containsReference(node); } @@ -19052,16 +19051,13 @@ namespace ts { // We limit alias variance probing to only object and conditional types since their alias behavior // is more predictable than other, interned types, which may or may not have an alias depending on // the order in which things were checked. - if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && - source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && - !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { + if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { const variances = getAliasVariances(source.aliasSymbol); - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + if (variances !== emptyArray) { + const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; + } } } @@ -19432,26 +19428,21 @@ namespace ts { else if (isGenericMappedType(source)) { return Ternary.False; } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && - !isTupleType(source) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && !isTupleType(source)) { // When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType, // and an empty array literal wouldn't be assignable to a `never[]` without this check. if (isEmptyArrayLiteralType(source)) { return Ternary.True; } - // We have type references to the same generic type, and the type references are not marker - // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. + // We have type references to the same generic type. Obtain the variance information for the type + // parameters and relate the type arguments accordingly. If variance information for the type is in + // the process of being computed, fall through and relate the type references structurally. const variances = getVariances((source as TypeReference).target); - // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This - // effectively means we measure variance only from type parameter occurrences that aren't nested in - // recursive instantiations of the generic type. - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + if (variances !== emptyArray) { + const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; + } } } else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { @@ -20371,29 +20362,13 @@ namespace ts { function getVariances(type: GenericType): VarianceFlags[] { // Arrays and tuples are known to be covariant, no need to spend time computing this. - if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { - return arrayVariances; - } - return getVariancesWorker(type.symbol, type.typeParameters, getMarkerTypeReference); - } - - // 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(symbol: Symbol, source: TypeParameter, target: Type) { - const type = getDeclaredTypeOfSymbol(symbol) as GenericType; - const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); - result.objectFlags |= ObjectFlags.MarkerType; - return result; + return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ? + arrayVariances : + getVariancesWorker(type.symbol, type.typeParameters); } function getAliasVariances(symbol: Symbol) { - return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters, getMarkerTypeAliasReference); - } - - function getMarkerTypeAliasReference(symbol: Symbol, source: TypeParameter, target: Type) { - const result = getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, makeUnaryTypeMapper(source, target))); - result.aliasTypeArgumentsContainsMarker = true; - return result; + return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters); } // Return an array containing the variance of each type parameter. The variance is effectively @@ -20401,15 +20376,10 @@ namespace ts { // generic type are structurally compared. We infer the variance information by comparing // instantiations of the generic type for type arguments with known relations. The function // returns the emptyArray singleton when invoked recursively for the given generic type. - function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray, createMarkerType: (symbol: Symbol, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { + function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] { const links = getSymbolLinks(symbol); if (!links.variances) { tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getSymbolId(symbol) }); - if (varianceLevel > 0) { - nestedVarianceSymbols = append(nestedVarianceSymbols, symbol); - } - varianceLevel++; - // The emptyArray singleton is used to signal a recursive invocation. links.variances = emptyArray; const variances = []; for (const tp of typeParameters) { @@ -20443,28 +20413,20 @@ namespace ts { variances.push(variance); } links.variances = variances; - varianceLevel--; - // Recursive invocations of getVariancesWorker occur when two or more types circularly reference each - // other. In such cases, the nested invocations might observe in-process variance computations, i.e. - // cases where getVariancesWorker returns emptyArray. If that happens we clear (and thus re-compute) the - // results of nested variance computations and only permanently record the outermost result. See #44572. - if (varianceLevel === 0) { - if (nestedVarianceSymbols && incompleteVariancesObserved) { - for (const sym of nestedVarianceSymbols) { - getSymbolLinks(sym).variances = undefined; - } - } - nestedVarianceSymbols = undefined; - incompleteVariancesObserved = false; - } tracing?.pop(); } - else { - incompleteVariancesObserved ||= links.variances === emptyArray; - } return links.variances; } + function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) { + const mapper = makeUnaryTypeMapper(source, target); + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper)); + } + const type = getDeclaredTypeOfSymbol(symbol) as GenericType; + return createTypeReference(type, instantiateTypes(type.typeParameters, mapper)); + } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. // See comment at call in recursiveTypeRelatedTo for when this case matters. function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 54936e787bc7a..991cb088b163f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5227,7 +5227,6 @@ namespace ts { pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) aliasSymbol?: Symbol; // Alias associated with type aliasTypeArguments?: readonly Type[]; // Alias type arguments (if any) - /* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any) /* @internal */ permissiveInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type /* @internal */ @@ -5306,22 +5305,21 @@ namespace ts { ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties ReverseMapped = 1 << 10, // Object contains a property from a reverse-mapped type JsxAttributes = 1 << 11, // Jsx attributes type - MarkerType = 1 << 12, // Marker type used for variance probing - JSLiteral = 1 << 13, // Object type declared in JS - disables errors on read/write of nonexisting members - FreshLiteral = 1 << 14, // Fresh object literal - ArrayLiteral = 1 << 15, // Originates in an array literal + JSLiteral = 1 << 12, // Object type declared in JS - disables errors on read/write of nonexisting members + FreshLiteral = 1 << 13, // Fresh object literal + ArrayLiteral = 1 << 14, // Originates in an array literal /* @internal */ - PrimitiveUnion = 1 << 16, // Union of only primitive types + PrimitiveUnion = 1 << 15, // Union of only primitive types /* @internal */ - ContainsWideningType = 1 << 17, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 16, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectOrArrayLiteral = 1 << 18, // Type is or contains object literal type + ContainsObjectOrArrayLiteral = 1 << 17, // Type is or contains object literal type /* @internal */ - NonInferrableType = 1 << 19, // Type is or contains anyFunctionType or silentNeverType + NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType /* @internal */ - CouldContainTypeVariablesComputed = 1 << 20, // CouldContainTypeVariables flag has been computed + CouldContainTypeVariablesComputed = 1 << 19, // CouldContainTypeVariables flag has been computed /* @internal */ - CouldContainTypeVariables = 1 << 21, // Type could contain a type variable + CouldContainTypeVariables = 1 << 20, // Type could contain a type variable ClassOrInterface = Class | Interface, /* @internal */ @@ -5333,36 +5331,36 @@ namespace ts { ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray, // Flags that require TypeFlags.Object - ContainsSpread = 1 << 22, // Object literal contains spread operation - ObjectRestType = 1 << 23, // Originates in object rest declaration - InstantiationExpressionType = 1 << 24, // Originates in instantiation expression + ContainsSpread = 1 << 21, // Object literal contains spread operation + ObjectRestType = 1 << 22, // Originates in object rest declaration + InstantiationExpressionType = 1 << 23, // Originates in instantiation expression /* @internal */ - IsClassInstanceClone = 1 << 25, // Type is a clone of a class instance type + IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type // Flags that require TypeFlags.Object and ObjectFlags.Reference /* @internal */ - IdenticalBaseTypeCalculated = 1 << 26, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already + IdenticalBaseTypeCalculated = 1 << 25, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already /* @internal */ - IdenticalBaseTypeExists = 1 << 27, // has a defined cachedEquivalentBaseType member + IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member // Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution /* @internal */ - IsGenericTypeComputed = 1 << 22, // IsGenericObjectType flag has been computed + IsGenericTypeComputed = 1 << 21, // IsGenericObjectType flag has been computed /* @internal */ - IsGenericObjectType = 1 << 23, // Union or intersection contains generic object type + IsGenericObjectType = 1 << 22, // Union or intersection contains generic object type /* @internal */ - IsGenericIndexType = 1 << 24, // Union or intersection contains generic index type + IsGenericIndexType = 1 << 23, // Union or intersection contains generic index type /* @internal */ IsGenericType = IsGenericObjectType | IsGenericIndexType, // Flags that require TypeFlags.Union /* @internal */ - ContainsIntersections = 1 << 25, // Union contains intersections + ContainsIntersections = 1 << 24, // Union contains intersections // Flags that require TypeFlags.Intersection /* @internal */ - IsNeverIntersectionComputed = 1 << 25, // IsNeverLike flag has been computed + IsNeverIntersectionComputed = 1 << 24, // IsNeverLike flag has been computed /* @internal */ - IsNeverIntersection = 1 << 26, // Intersection reduces to never + IsNeverIntersection = 1 << 25, // Intersection reduces to never } /* @internal */ From a3230370ddc87206e2756aeac95ed3abbabc9c8c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 6 Mar 2022 12:12:58 -0800 Subject: [PATCH 5/7] Accept new baselines --- .../reference/api/tsserverlibrary.d.ts | 13 ++++----- tests/baselines/reference/api/typescript.d.ts | 13 ++++----- .../reference/bivariantInferences.symbols | 4 +-- .../reference/bivariantInferences.types | 4 +-- .../completionEntryForUnionMethod.baseline | 6 ++-- .../reference/sliceResultCast.symbols | 4 +-- .../baselines/reference/sliceResultCast.types | 4 +-- .../reference/tsxUnionElementType3.errors.txt | 2 +- .../reference/tsxUnionElementType4.errors.txt | 2 +- .../reference/varianceMeasurement.errors.txt | 28 +++++++++++++++---- 10 files changed, 48 insertions(+), 32 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 255fb6a1305c7..21c4593a9886f 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2654,14 +2654,13 @@ declare namespace ts { ObjectLiteralPatternWithComputedProperties = 512, ReverseMapped = 1024, JsxAttributes = 2048, - MarkerType = 4096, - JSLiteral = 8192, - FreshLiteral = 16384, - ArrayLiteral = 32768, + JSLiteral = 4096, + FreshLiteral = 8192, + ArrayLiteral = 16384, ClassOrInterface = 3, - ContainsSpread = 4194304, - ObjectRestType = 8388608, - InstantiationExpressionType = 16777216, + ContainsSpread = 2097152, + ObjectRestType = 4194304, + InstantiationExpressionType = 8388608, } export interface ObjectType extends Type { objectFlags: ObjectFlags; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 63a6a9bc49be3..4637193338298 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2654,14 +2654,13 @@ declare namespace ts { ObjectLiteralPatternWithComputedProperties = 512, ReverseMapped = 1024, JsxAttributes = 2048, - MarkerType = 4096, - JSLiteral = 8192, - FreshLiteral = 16384, - ArrayLiteral = 32768, + JSLiteral = 4096, + FreshLiteral = 8192, + ArrayLiteral = 16384, ClassOrInterface = 3, - ContainsSpread = 4194304, - ObjectRestType = 8388608, - InstantiationExpressionType = 16777216, + ContainsSpread = 2097152, + ObjectRestType = 4194304, + InstantiationExpressionType = 8388608, } export interface ObjectType extends Type { objectFlags: ObjectFlags; diff --git a/tests/baselines/reference/bivariantInferences.symbols b/tests/baselines/reference/bivariantInferences.symbols index 0697c171c54e8..f7b25569d8cd3 100644 --- a/tests/baselines/reference/bivariantInferences.symbols +++ b/tests/baselines/reference/bivariantInferences.symbols @@ -24,8 +24,8 @@ declare const b: (string | number)[] | null[] | undefined[] | {}[]; let x = a.equalsShallow(b); >x : Symbol(x, Decl(bivariantInferences.ts, 9, 3)) ->a.equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20)) +>a.equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20)) >a : Symbol(a, Decl(bivariantInferences.ts, 6, 13)) ->equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20)) +>equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20)) >b : Symbol(b, Decl(bivariantInferences.ts, 7, 13)) diff --git a/tests/baselines/reference/bivariantInferences.types b/tests/baselines/reference/bivariantInferences.types index ac987cbddaca4..7ad35660c556f 100644 --- a/tests/baselines/reference/bivariantInferences.types +++ b/tests/baselines/reference/bivariantInferences.types @@ -19,8 +19,8 @@ declare const b: (string | number)[] | null[] | undefined[] | {}[]; let x = a.equalsShallow(b); >x : boolean >a.equalsShallow(b) : boolean ->a.equalsShallow : ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) +>a.equalsShallow : (this: readonly T[], other: readonly T[]) => boolean >a : (string | number)[] | null[] | undefined[] | {}[] ->equalsShallow : ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) +>equalsShallow : (this: readonly T[], other: readonly T[]) => boolean >b : (string | number)[] | null[] | undefined[] | {}[] diff --git a/tests/baselines/reference/completionEntryForUnionMethod.baseline b/tests/baselines/reference/completionEntryForUnionMethod.baseline index f03ee666dc0d8..888c5b3c2787d 100644 --- a/tests/baselines/reference/completionEntryForUnionMethod.baseline +++ b/tests/baselines/reference/completionEntryForUnionMethod.baseline @@ -2244,7 +2244,7 @@ }, { "text": "join", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", @@ -5906,7 +5906,7 @@ }, { "text": "toLocaleString", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", @@ -5980,7 +5980,7 @@ }, { "text": "toString", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", diff --git a/tests/baselines/reference/sliceResultCast.symbols b/tests/baselines/reference/sliceResultCast.symbols index 5fed27ca3fd5c..0f8a4761baaa4 100644 --- a/tests/baselines/reference/sliceResultCast.symbols +++ b/tests/baselines/reference/sliceResultCast.symbols @@ -3,7 +3,7 @@ declare var x: [number, string] | [number, string, string]; >x : Symbol(x, Decl(sliceResultCast.ts, 0, 11)) x.slice(1) as readonly string[]; ->x.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>x.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) >x : Symbol(x, Decl(sliceResultCast.ts, 0, 11)) ->slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) diff --git a/tests/baselines/reference/sliceResultCast.types b/tests/baselines/reference/sliceResultCast.types index 99f799f27ff38..221046e037d96 100644 --- a/tests/baselines/reference/sliceResultCast.types +++ b/tests/baselines/reference/sliceResultCast.types @@ -5,8 +5,8 @@ declare var x: [number, string] | [number, string, string]; x.slice(1) as readonly string[]; >x.slice(1) as readonly string[] : readonly string[] >x.slice(1) : (string | number)[] ->x.slice : ((start?: number, end?: number) => (string | number)[]) | ((start?: number, end?: number) => (string | number)[]) +>x.slice : (start?: number, end?: number) => (string | number)[] >x : [number, string] | [number, string, string] ->slice : ((start?: number, end?: number) => (string | number)[]) | ((start?: number, end?: number) => (string | number)[]) +>slice : (start?: number, end?: number) => (string | number)[] >1 : 1 diff --git a/tests/baselines/reference/tsxUnionElementType3.errors.txt b/tests/baselines/reference/tsxUnionElementType3.errors.txt index 73d8ccb0ce2b2..1474b9fb77490 100644 --- a/tests/baselines/reference/tsxUnionElementType3.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType3.errors.txt @@ -36,7 +36,7 @@ tests/cases/conformance/jsx/file.tsx(32,17): error TS2322: Type 'string' is not let a = ; ~ !!! error TS2322: Type 'string' is not assignable to type 'never'. -!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; } & { children?: ReactNode; }' +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; }' let a1 = ; let a2 = ; let b = diff --git a/tests/baselines/reference/tsxUnionElementType4.errors.txt b/tests/baselines/reference/tsxUnionElementType4.errors.txt index 03cb6c62a526e..7f2b4bca54211 100644 --- a/tests/baselines/reference/tsxUnionElementType4.errors.txt +++ b/tests/baselines/reference/tsxUnionElementType4.errors.txt @@ -40,7 +40,7 @@ tests/cases/conformance/jsx/file.tsx(34,22): error TS2322: Type '{ prop: true; } let a = ; ~ !!! error TS2322: Type 'boolean' is not assignable to type 'never'. -!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; } & { children?: ReactNode; }' +!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:36: The expected type comes from property 'x' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes & { x: number; } & { children?: ReactNode; } & { x: string; }' let b = ~ !!! error TS2322: Type '{ x: number; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & { children?: ReactNode; }'. diff --git a/tests/baselines/reference/varianceMeasurement.errors.txt b/tests/baselines/reference/varianceMeasurement.errors.txt index 4deb2d81e0b64..e65e8c1936881 100644 --- a/tests/baselines/reference/varianceMeasurement.errors.txt +++ b/tests/baselines/reference/varianceMeasurement.errors.txt @@ -1,5 +1,9 @@ tests/cases/compiler/varianceMeasurement.ts(10,7): error TS2322: Type 'Foo1' is not assignable to type 'Foo1<"a">'. - Type 'string' is not assignable to type '"a"'. + Types of property 'x' are incompatible. + Type 'string' is not assignable to type '"a"'. +tests/cases/compiler/varianceMeasurement.ts(11,7): error TS2322: Type 'Foo1' is not assignable to type 'Foo1'. + The types of 'y.x' are incompatible between these types. + Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'. tests/cases/compiler/varianceMeasurement.ts(21,7): error TS2322: Type 'Foo2' is not assignable to type 'Foo2<"a">'. Types of property 'x' are incompatible. Type 'string' is not assignable to type '"a"'. @@ -9,7 +13,11 @@ tests/cases/compiler/varianceMeasurement.ts(22,7): error TS2322: Type 'Foo2' is not assignable to type 'Foo3<"a">'. - Type 'string' is not assignable to type '"a"'. + Types of property 'x' are incompatible. + Type 'string' is not assignable to type '"a"'. +tests/cases/compiler/varianceMeasurement.ts(34,7): error TS2322: Type 'Foo3' is not assignable to type 'Foo3'. + The types of 'y.x' are incompatible between these types. + Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'. tests/cases/compiler/varianceMeasurement.ts(44,7): error TS2322: Type 'Foo4' is not assignable to type 'Foo4<"a">'. Types of property 'x' are incompatible. Type 'string' is not assignable to type '"a"'. @@ -26,7 +34,7 @@ tests/cases/compiler/varianceMeasurement.ts(75,7): error TS2322: Type 'C = f10; ~~~ !!! error TS2322: Type 'Foo1' is not assignable to type 'Foo1<"a">'. -!!! error TS2322: Type 'string' is not assignable to type '"a"'. +!!! error TS2322: Types of property 'x' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type '"a"'. const f12: Foo1 = f10; + ~~~ +!!! error TS2322: Type 'Foo1' is not assignable to type 'Foo1'. +!!! error TS2322: The types of 'y.x' are incompatible between these types. +!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'. // The type below is invariant in T and is measured as such. @@ -75,8 +88,13 @@ tests/cases/compiler/varianceMeasurement.ts(75,7): error TS2322: Type 'C = f30; ~~~ !!! error TS2322: Type 'Foo3' is not assignable to type 'Foo3<"a">'. -!!! error TS2322: Type 'string' is not assignable to type '"a"'. +!!! error TS2322: Types of property 'x' are incompatible. +!!! error TS2322: Type 'string' is not assignable to type '"a"'. const f32: Foo3 = f30; + ~~~ +!!! error TS2322: Type 'Foo3' is not assignable to type 'Foo3'. +!!! error TS2322: The types of 'y.x' are incompatible between these types. +!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'. // The type below is invariant in T and is measured as such. From 08c0fa9acd6ef212820cca5beebcb6dea9ff8e8a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 7 Mar 2022 09:40:32 -0800 Subject: [PATCH 6/7] Limit structural traversal to only types containing marker types --- src/compiler/checker.ts | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4569368f44091..261136551b86f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19059,6 +19059,9 @@ namespace ts { return varianceResult; } } + else if (!someContainsMarkerType(source.aliasTypeArguments) && !someContainsMarkerType(target.aliasTypeArguments)) { + return Ternary.Unknown; + } } // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], @@ -19436,7 +19439,8 @@ namespace ts { } // We have type references to the same generic type. Obtain the variance information for the type // parameters and relate the type arguments accordingly. If variance information for the type is in - // the process of being computed, fall through and relate the type references structurally. + // the process of being computed, structurally relate references that contain marker types in their + // type arguments, but otherwise return Ternary.Unknown (the non-cached version of Ternary.Maybe). const variances = getVariances((source as TypeReference).target); if (variances !== emptyArray) { const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); @@ -19444,6 +19448,9 @@ namespace ts { return varianceResult; } } + else if (!someContainsMarkerType(getTypeArguments(source as TypeReference)) && !someContainsMarkerType(getTypeArguments(target as TypeReference))) { + return Ternary.Unknown; + } } else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { @@ -20427,6 +20434,49 @@ namespace ts { return createTypeReference(type, instantiateTypes(type.typeParameters, mapper)); } + // Check if any of the given types contain marker types. We visit type arguments of generic classes, interfaces, and + // type aliases, members of anonymous type literals and mapped types, constituents of union, intersection, and + // template literal types, and targets of index types and string mapping types. This is by design a limited set of + // types to avoid excessive fan out. In deferred (and thus possibly circular) locations we limit the traversal to + // four levels of depth. + function someContainsMarkerType(types: readonly Type[] | undefined, maxDepth = 4) { + return some(types, t => containsMarkerType(t, maxDepth)); + } + + function containsMarkerType(type: Type, maxDepth: number): boolean { + return maxDepth > 0 && ( + type.flags & TypeFlags.TypeParameter ? type === markerSubType || type === markerSubType || type === markerOtherType : + type.aliasSymbol ? someContainsMarkerType(type.aliasTypeArguments, maxDepth) : + type.flags & TypeFlags.Object ? objectTypeContainsMarkerType(type as ObjectType, maxDepth) : + type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && !(type.flags & TypeFlags.EnumLiteral) ? someContainsMarkerType((type as UnionOrIntersectionType | TemplateLiteralType).types, maxDepth) : + type.flags & (TypeFlags.Index | TypeFlags.StringMapping) ? containsMarkerType((type as IndexType | StringMappingType).type, maxDepth) : + false); + } + + function objectTypeContainsMarkerType(type: ObjectType, maxDepth: number): boolean { + const objectFlags = getObjectFlags(type); + if (objectFlags & ObjectFlags.Reference) { + return someContainsMarkerType(getTypeArguments(type as TypeReference), maxDepth - ((type as TypeReference).node ? 1 : 0)); + } + if (objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { + const resolved = resolveStructuredTypeMembers(type); + return some(resolved.properties, prop => containsMarkerType(getTypeOfSymbol(prop), maxDepth - 1)) || + some(resolved.callSignatures, sig => signatureContainsMarkerType(sig, maxDepth)) || + some(resolved.constructSignatures, sig => signatureContainsMarkerType(sig, maxDepth)) || + some(resolved.indexInfos, info => containsMarkerType(info.type, maxDepth - 1)); + } + if (objectFlags & ObjectFlags.Mapped) { + return containsMarkerType(getConstraintTypeFromMappedType(type as MappedType), maxDepth - 1) || + containsMarkerType(getTemplateTypeFromMappedType(type as MappedType), maxDepth - 1); + } + return false; + } + + function signatureContainsMarkerType(sig: Signature, maxDepth: number): boolean { + return some(sig.parameters, param => containsMarkerType(getTypeOfSymbol(param), maxDepth - 1)) || + containsMarkerType(getReturnTypeOfSignature(sig), maxDepth - 1); + } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. // See comment at call in recursiveTypeRelatedTo for when this case matters. function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { From 367ef6d3c47121fc720ca4b9143c712596b3cfed Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 7 Mar 2022 10:23:28 -0800 Subject: [PATCH 7/7] Fix tracing id --- 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 261136551b86f..25f3f41bc4dc5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20386,7 +20386,7 @@ namespace ts { function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] { const links = getSymbolLinks(symbol); if (!links.variances) { - tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getSymbolId(symbol) }); + tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) }); links.variances = emptyArray; const variances = []; for (const tp of typeParameters) {