diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58ac3bc52c0bc..9224b6afd7c78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8258,11 +8258,11 @@ namespace ts { // The first time an anonymous type is instantiated we compute and store a list of the type // parameters that are in scope (and therefore potentially referenced). For type literals that // aren't the right hand side of a generic type alias declaration we optimize by reducing the - // set of type parameters to those that are actually referenced somewhere in the literal. + // set of type parameters to those that are possibly referenced in the literal. const declaration = symbol.declarations[0]; const outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true) || emptyArray; typeParameters = symbol.flags & SymbolFlags.TypeLiteral && !target.aliasTypeArguments ? - filter(outerTypeParameters, tp => isTypeParameterReferencedWithin(tp, declaration)) : + filter(outerTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : outerTypeParameters; links.typeParameters = typeParameters; if (typeParameters.length) { @@ -8288,13 +8288,27 @@ namespace ts { return type; } - function isTypeParameterReferencedWithin(tp: TypeParameter, node: Node) { - return tp.isThisType ? forEachChild(node, checkThis) : forEachChild(node, checkIdentifier); - function checkThis(node: Node): boolean { - return node.kind === SyntaxKind.ThisType || forEachChild(node, checkThis); + 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; + if (findAncestor(node, n => n.kind === SyntaxKind.Block ? "quit" : n === container)) { + return forEachChild(node, containsReference); + } } - function checkIdentifier(node: Node): boolean { - return node.kind === SyntaxKind.Identifier && isPartOfTypeNode(node) && getTypeFromTypeNode(node) === tp || forEachChild(node, checkIdentifier); + return true; + function containsReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisType: + return tp.isThisType; + case SyntaxKind.Identifier: + return !tp.isThisType && isPartOfTypeNode(node) && getTypeFromTypeNode(node) === tp; + case SyntaxKind.TypeQuery: + return true; + } + return forEachChild(node, containsReference); } } diff --git a/tests/baselines/reference/indirectTypeParameterReferences.js b/tests/baselines/reference/indirectTypeParameterReferences.js new file mode 100644 index 0000000000000..f947b8f7c1ffb --- /dev/null +++ b/tests/baselines/reference/indirectTypeParameterReferences.js @@ -0,0 +1,49 @@ +//// [indirectTypeParameterReferences.ts] +// Repro from #19043 + +type B = {b: string} + +const flowtypes = (b: B) => { + type Combined = A & B + + const combined = (fn: (combined: Combined) => void) => null + const literal = (fn: (aPlusB: A & B) => void) => null + + return {combined, literal} +} + +const {combined, literal} = flowtypes<{a: string}>({b: 'b-value'}) + +literal(aPlusB => { + aPlusB.b + aPlusB.a +}) + +combined(comb => { + comb.b + comb.a +}) + +// Repro from #19091 + +declare function f(a: T): { a: typeof a }; +let n: number = f(2).a; + + +//// [indirectTypeParameterReferences.js] +// Repro from #19043 +var flowtypes = function (b) { + var combined = function (fn) { return null; }; + var literal = function (fn) { return null; }; + return { combined: combined, literal: literal }; +}; +var _a = flowtypes({ b: 'b-value' }), combined = _a.combined, literal = _a.literal; +literal(function (aPlusB) { + aPlusB.b; + aPlusB.a; +}); +combined(function (comb) { + comb.b; + comb.a; +}); +var n = f(2).a; diff --git a/tests/baselines/reference/indirectTypeParameterReferences.symbols b/tests/baselines/reference/indirectTypeParameterReferences.symbols new file mode 100644 index 0000000000000..cf0f3e7bbc910 --- /dev/null +++ b/tests/baselines/reference/indirectTypeParameterReferences.symbols @@ -0,0 +1,91 @@ +=== tests/cases/compiler/indirectTypeParameterReferences.ts === +// Repro from #19043 + +type B = {b: string} +>B : Symbol(B, Decl(indirectTypeParameterReferences.ts, 0, 0)) +>b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 2, 10)) + +const flowtypes = (b: B) => { +>flowtypes : Symbol(flowtypes, Decl(indirectTypeParameterReferences.ts, 4, 5)) +>A : Symbol(A, Decl(indirectTypeParameterReferences.ts, 4, 19)) +>b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 4, 22)) +>B : Symbol(B, Decl(indirectTypeParameterReferences.ts, 0, 0)) + + type Combined = A & B +>Combined : Symbol(Combined, Decl(indirectTypeParameterReferences.ts, 4, 32)) +>A : Symbol(A, Decl(indirectTypeParameterReferences.ts, 4, 19)) +>B : Symbol(B, Decl(indirectTypeParameterReferences.ts, 0, 0)) + + const combined = (fn: (combined: Combined) => void) => null +>combined : Symbol(combined, Decl(indirectTypeParameterReferences.ts, 7, 7)) +>fn : Symbol(fn, Decl(indirectTypeParameterReferences.ts, 7, 20)) +>combined : Symbol(combined, Decl(indirectTypeParameterReferences.ts, 7, 25)) +>Combined : Symbol(Combined, Decl(indirectTypeParameterReferences.ts, 4, 32)) + + const literal = (fn: (aPlusB: A & B) => void) => null +>literal : Symbol(literal, Decl(indirectTypeParameterReferences.ts, 8, 7)) +>fn : Symbol(fn, Decl(indirectTypeParameterReferences.ts, 8, 19)) +>aPlusB : Symbol(aPlusB, Decl(indirectTypeParameterReferences.ts, 8, 24)) +>A : Symbol(A, Decl(indirectTypeParameterReferences.ts, 4, 19)) +>B : Symbol(B, Decl(indirectTypeParameterReferences.ts, 0, 0)) + + return {combined, literal} +>combined : Symbol(combined, Decl(indirectTypeParameterReferences.ts, 10, 10)) +>literal : Symbol(literal, Decl(indirectTypeParameterReferences.ts, 10, 19)) +} + +const {combined, literal} = flowtypes<{a: string}>({b: 'b-value'}) +>combined : Symbol(combined, Decl(indirectTypeParameterReferences.ts, 13, 7)) +>literal : Symbol(literal, Decl(indirectTypeParameterReferences.ts, 13, 16)) +>flowtypes : Symbol(flowtypes, Decl(indirectTypeParameterReferences.ts, 4, 5)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 13, 39)) +>b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 13, 52)) + +literal(aPlusB => { +>literal : Symbol(literal, Decl(indirectTypeParameterReferences.ts, 13, 16)) +>aPlusB : Symbol(aPlusB, Decl(indirectTypeParameterReferences.ts, 15, 8)) + + aPlusB.b +>aPlusB.b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 2, 10)) +>aPlusB : Symbol(aPlusB, Decl(indirectTypeParameterReferences.ts, 15, 8)) +>b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 2, 10)) + + aPlusB.a +>aPlusB.a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 13, 39)) +>aPlusB : Symbol(aPlusB, Decl(indirectTypeParameterReferences.ts, 15, 8)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 13, 39)) + +}) + +combined(comb => { +>combined : Symbol(combined, Decl(indirectTypeParameterReferences.ts, 13, 7)) +>comb : Symbol(comb, Decl(indirectTypeParameterReferences.ts, 20, 9)) + + comb.b +>comb.b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 2, 10)) +>comb : Symbol(comb, Decl(indirectTypeParameterReferences.ts, 20, 9)) +>b : Symbol(b, Decl(indirectTypeParameterReferences.ts, 2, 10)) + + comb.a +>comb.a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 13, 39)) +>comb : Symbol(comb, Decl(indirectTypeParameterReferences.ts, 20, 9)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 13, 39)) + +}) + +// Repro from #19091 + +declare function f(a: T): { a: typeof a }; +>f : Symbol(f, Decl(indirectTypeParameterReferences.ts, 23, 2)) +>T : Symbol(T, Decl(indirectTypeParameterReferences.ts, 27, 19)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 27, 22)) +>T : Symbol(T, Decl(indirectTypeParameterReferences.ts, 27, 19)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 27, 30)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 27, 22)) + +let n: number = f(2).a; +>n : Symbol(n, Decl(indirectTypeParameterReferences.ts, 28, 3)) +>f(2).a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 27, 30)) +>f : Symbol(f, Decl(indirectTypeParameterReferences.ts, 23, 2)) +>a : Symbol(a, Decl(indirectTypeParameterReferences.ts, 27, 30)) + diff --git a/tests/baselines/reference/indirectTypeParameterReferences.types b/tests/baselines/reference/indirectTypeParameterReferences.types new file mode 100644 index 0000000000000..e38f5dd2577aa --- /dev/null +++ b/tests/baselines/reference/indirectTypeParameterReferences.types @@ -0,0 +1,106 @@ +=== tests/cases/compiler/indirectTypeParameterReferences.ts === +// Repro from #19043 + +type B = {b: string} +>B : B +>b : string + +const flowtypes = (b: B) => { +>flowtypes : (b: B) => { combined: (fn: (combined: A & B) => void) => any; literal: (fn: (aPlusB: A & B) => void) => any; } +>(b: B) => { type Combined = A & B const combined = (fn: (combined: Combined) => void) => null const literal = (fn: (aPlusB: A & B) => void) => null return {combined, literal}} : (b: B) => { combined: (fn: (combined: A & B) => void) => any; literal: (fn: (aPlusB: A & B) => void) => any; } +>A : A +>b : B +>B : B + + type Combined = A & B +>Combined : A & B +>A : A +>B : B + + const combined = (fn: (combined: Combined) => void) => null +>combined : (fn: (combined: A & B) => void) => any +>(fn: (combined: Combined) => void) => null : (fn: (combined: A & B) => void) => any +>fn : (combined: A & B) => void +>combined : A & B +>Combined : A & B +>null : null + + const literal = (fn: (aPlusB: A & B) => void) => null +>literal : (fn: (aPlusB: A & B) => void) => any +>(fn: (aPlusB: A & B) => void) => null : (fn: (aPlusB: A & B) => void) => any +>fn : (aPlusB: A & B) => void +>aPlusB : A & B +>A : A +>B : B +>null : null + + return {combined, literal} +>{combined, literal} : { combined: (fn: (combined: A & B) => void) => any; literal: (fn: (aPlusB: A & B) => void) => any; } +>combined : (fn: (combined: A & B) => void) => any +>literal : (fn: (aPlusB: A & B) => void) => any +} + +const {combined, literal} = flowtypes<{a: string}>({b: 'b-value'}) +>combined : (fn: (combined: { a: string; } & B) => void) => any +>literal : (fn: (aPlusB: { a: string; } & B) => void) => any +>flowtypes<{a: string}>({b: 'b-value'}) : { combined: (fn: (combined: { a: string; } & B) => void) => any; literal: (fn: (aPlusB: { a: string; } & B) => void) => any; } +>flowtypes : (b: B) => { combined: (fn: (combined: A & B) => void) => any; literal: (fn: (aPlusB: A & B) => void) => any; } +>a : string +>{b: 'b-value'} : { b: string; } +>b : string +>'b-value' : "b-value" + +literal(aPlusB => { +>literal(aPlusB => { aPlusB.b aPlusB.a}) : any +>literal : (fn: (aPlusB: { a: string; } & B) => void) => any +>aPlusB => { aPlusB.b aPlusB.a} : (aPlusB: { a: string; } & B) => void +>aPlusB : { a: string; } & B + + aPlusB.b +>aPlusB.b : string +>aPlusB : { a: string; } & B +>b : string + + aPlusB.a +>aPlusB.a : string +>aPlusB : { a: string; } & B +>a : string + +}) + +combined(comb => { +>combined(comb => { comb.b comb.a}) : any +>combined : (fn: (combined: { a: string; } & B) => void) => any +>comb => { comb.b comb.a} : (comb: { a: string; } & B) => void +>comb : { a: string; } & B + + comb.b +>comb.b : string +>comb : { a: string; } & B +>b : string + + comb.a +>comb.a : string +>comb : { a: string; } & B +>a : string + +}) + +// Repro from #19091 + +declare function f(a: T): { a: typeof a }; +>f : (a: T) => { a: T; } +>T : T +>a : T +>T : T +>a : T +>a : T + +let n: number = f(2).a; +>n : number +>f(2).a : number +>f(2) : { a: number; } +>f : (a: T) => { a: T; } +>2 : 2 +>a : number + diff --git a/tests/cases/compiler/indirectTypeParameterReferences.ts b/tests/cases/compiler/indirectTypeParameterReferences.ts new file mode 100644 index 0000000000000..c8cb56ad5e714 --- /dev/null +++ b/tests/cases/compiler/indirectTypeParameterReferences.ts @@ -0,0 +1,29 @@ +// Repro from #19043 + +type B = {b: string} + +const flowtypes = (b: B) => { + type Combined = A & B + + const combined = (fn: (combined: Combined) => void) => null + const literal = (fn: (aPlusB: A & B) => void) => null + + return {combined, literal} +} + +const {combined, literal} = flowtypes<{a: string}>({b: 'b-value'}) + +literal(aPlusB => { + aPlusB.b + aPlusB.a +}) + +combined(comb => { + comb.b + comb.a +}) + +// Repro from #19091 + +declare function f(a: T): { a: typeof a }; +let n: number = f(2).a;