Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve check of whether type query node possibly contains reference to type parameter #50070

Merged
merged 13 commits into from Sep 19, 2022
10 changes: 9 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -726,6 +726,7 @@ namespace ts {
isPropertyAccessible,
getTypeOnlyAliasDeclaration,
getMemberOverrideModifierStatus,
isTypeParameterPossiblyReferenced,
};

function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
Expand Down Expand Up @@ -17047,7 +17048,7 @@ namespace ts {
}

function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) {
// If the type parameter doesn't have exactly one declaration, if there are invening statement blocks
// If the type parameter doesn't have exactly one declaration, 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 && tp.symbol.declarations.length === 1) {
Expand All @@ -17068,6 +17069,13 @@ namespace ts {
return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) &&
getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality
case SyntaxKind.TypeQuery:
const entityName = (node as TypeQueryNode).exprName;
const firstIdentifier = getFirstIdentifier(entityName);
const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier);
gabritto marked this conversation as resolved.
Show resolved Hide resolved
if (firstIdentifierSymbol.declarations) {
gabritto marked this conversation as resolved.
Show resolved Hide resolved
return !!forEach(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tp.symbol.declarations![0].parent)) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the some function instead of forEach. Also, to get the declaration of tp use getDeclarationOfKind<TypeParameterDeclaration>(tp.symbol, SyntaxKind.TypeParameter)!. Although very unlikely, it is possible for there to be other declarations in the array, for example if the same name is used for a type parameter and a regular parameter.

!!forEach((node as TypeQueryNode).typeArguments, containsReference);
}
return true;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -4624,6 +4624,7 @@ namespace ts {
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
/* @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined;
/* @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus;
/* @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean;
}

/* @internal */
Expand Down
1 change: 1 addition & 0 deletions src/testRunner/tsconfig.json
Expand Up @@ -79,6 +79,7 @@
"unittests/createMapShim.ts",
"unittests/createSetShim.ts",
"unittests/transform.ts",
"unittests/typeParameterIsPossiblyReferenced.ts",
"unittests/config/commandLineParsing.ts",
"unittests/config/configurationExtension.ts",
"unittests/config/convertCompilerOptionsFromJson.ts",
Expand Down
33 changes: 33 additions & 0 deletions src/testRunner/unittests/typeParameterIsPossiblyReferenced.ts
@@ -0,0 +1,33 @@
describe("unittests :: internalApi :: typeParameterIsPossiblyReferenced", () => {
it("with type parameter aliasing", () => {
const content = `
declare function foo<T>(b: T, f: <T>(a: typeof b) => typeof a): typeof f;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't come up with a test program such that type checking it observed the consequences of an incorrect containsReference/typeParameterIsPossiblyReferenced result in the presence of type parameter aliasing, so I wrote a unit test for it.

`;
const host = new fakes.CompilerHost(vfs.createFromFileSystem(
Harness.IO,
/*ignoreCases*/ true,
{
documents: [
new documents.TextDocument("/file.ts", content)
],
cwd: "/",
}
));
const program = ts.createProgram({
host,
rootNames: ["/file.ts"],
options: { strict: true },
});
const checker = program.getTypeChecker();
const file = program.getSourceFile("/file.ts")!;
const typeQueryNode = (((file.statements[0] as ts.FunctionDeclaration) // function f<T>
.parameters[1] // f
.type! as ts.FunctionTypeNode) // <T>(a: typeof b) => typeof a
.type as ts.TypeQueryNode) // typeof a
;
const typeParameterDecl = (file.statements[0] as ts.FunctionDeclaration).typeParameters![0]; // T in f<T>
const typeParameter = checker.getTypeAtLocation(typeParameterDecl)! as ts.TypeParameter;
const isReferenced = checker.isTypeParameterPossiblyReferenced(typeParameter, typeQueryNode);
assert.ok(isReferenced, "Type parameter is referenced in type query node");
});
});
51 changes: 51 additions & 0 deletions tests/baselines/reference/typeofObjectInference.js
@@ -0,0 +1,51 @@
//// [typeofObjectInference.ts]
let val = 1

function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
return (): O => fn({value: val})
}
let a = decorateA(({value}) => 5)

function decorateB<O extends any>(fn: (first: typeof val) => O) {
return (): O => fn(val)
}
let b = decorateB((value) => 5)

function decorateC<O extends any>(fn: (first: {value: number}) => O) {
return (): O => fn({value: val})
}
let c = decorateC(({value}) => 5)

type First = {value: typeof val}
function decorateD<O extends any>(fn: (first: First) => O) {
return (): O => fn({value: val})
}
let d = decorateD(({value}) => 5)

//// [typeofObjectInference.js]
var val = 1;
function decorateA(fn) {
return function () { return fn({ value: val }); };
}
var a = decorateA(function (_a) {
var value = _a.value;
return 5;
});
function decorateB(fn) {
return function () { return fn(val); };
}
var b = decorateB(function (value) { return 5; });
function decorateC(fn) {
return function () { return fn({ value: val }); };
}
var c = decorateC(function (_a) {
var value = _a.value;
return 5;
});
function decorateD(fn) {
return function () { return fn({ value: val }); };
}
var d = decorateD(function (_a) {
var value = _a.value;
return 5;
});
85 changes: 85 additions & 0 deletions tests/baselines/reference/typeofObjectInference.symbols
@@ -0,0 +1,85 @@
=== tests/cases/compiler/typeofObjectInference.ts ===
let val = 1
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))

function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
>decorateA : Symbol(decorateA, Decl(typeofObjectInference.ts, 0, 11))
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 2, 34))
>first : Symbol(first, Decl(typeofObjectInference.ts, 2, 39))
>value : Symbol(value, Decl(typeofObjectInference.ts, 2, 47))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))

return (): O => fn({value: val})
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 2, 34))
>value : Symbol(value, Decl(typeofObjectInference.ts, 3, 24))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
}
let a = decorateA(({value}) => 5)
>a : Symbol(a, Decl(typeofObjectInference.ts, 5, 3))
>decorateA : Symbol(decorateA, Decl(typeofObjectInference.ts, 0, 11))
>value : Symbol(value, Decl(typeofObjectInference.ts, 5, 20))

function decorateB<O extends any>(fn: (first: typeof val) => O) {
>decorateB : Symbol(decorateB, Decl(typeofObjectInference.ts, 5, 33))
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 7, 34))
>first : Symbol(first, Decl(typeofObjectInference.ts, 7, 39))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))

return (): O => fn(val)
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 7, 34))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
}
let b = decorateB((value) => 5)
>b : Symbol(b, Decl(typeofObjectInference.ts, 10, 3))
>decorateB : Symbol(decorateB, Decl(typeofObjectInference.ts, 5, 33))
>value : Symbol(value, Decl(typeofObjectInference.ts, 10, 19))

function decorateC<O extends any>(fn: (first: {value: number}) => O) {
>decorateC : Symbol(decorateC, Decl(typeofObjectInference.ts, 10, 31))
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 12, 34))
>first : Symbol(first, Decl(typeofObjectInference.ts, 12, 39))
>value : Symbol(value, Decl(typeofObjectInference.ts, 12, 47))
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))

return (): O => fn({value: val})
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 12, 34))
>value : Symbol(value, Decl(typeofObjectInference.ts, 13, 24))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
}
let c = decorateC(({value}) => 5)
>c : Symbol(c, Decl(typeofObjectInference.ts, 15, 3))
>decorateC : Symbol(decorateC, Decl(typeofObjectInference.ts, 10, 31))
>value : Symbol(value, Decl(typeofObjectInference.ts, 15, 20))

type First = {value: typeof val}
>First : Symbol(First, Decl(typeofObjectInference.ts, 15, 33))
>value : Symbol(value, Decl(typeofObjectInference.ts, 17, 14))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))

function decorateD<O extends any>(fn: (first: First) => O) {
>decorateD : Symbol(decorateD, Decl(typeofObjectInference.ts, 17, 32))
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 18, 34))
>first : Symbol(first, Decl(typeofObjectInference.ts, 18, 39))
>First : Symbol(First, Decl(typeofObjectInference.ts, 15, 33))
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))

return (): O => fn({value: val})
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 18, 34))
>value : Symbol(value, Decl(typeofObjectInference.ts, 19, 24))
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
}
let d = decorateD(({value}) => 5)
>d : Symbol(d, Decl(typeofObjectInference.ts, 21, 3))
>decorateD : Symbol(decorateD, Decl(typeofObjectInference.ts, 17, 32))
>value : Symbol(value, Decl(typeofObjectInference.ts, 21, 20))

96 changes: 96 additions & 0 deletions tests/baselines/reference/typeofObjectInference.types
@@ -0,0 +1,96 @@
=== tests/cases/compiler/typeofObjectInference.ts ===
let val = 1
>val : number
>1 : 1

function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
>decorateA : <O extends unknown>(fn: (first: { value: typeof val;}) => O) => () => O
>fn : (first: { value: typeof val;}) => O
>first : { value: typeof val; }
>value : number
>val : number

return (): O => fn({value: val})
>(): O => fn({value: val}) : () => O
>fn({value: val}) : O
>fn : (first: { value: number; }) => O
>{value: val} : { value: number; }
>value : number
>val : number
}
let a = decorateA(({value}) => 5)
>a : () => number
>decorateA(({value}) => 5) : () => number
>decorateA : <O extends unknown>(fn: (first: { value: number; }) => O) => () => O
>({value}) => 5 : ({ value }: { value: number; }) => number
>value : number
>5 : 5

function decorateB<O extends any>(fn: (first: typeof val) => O) {
>decorateB : <O extends unknown>(fn: (first: typeof val) => O) => () => O
>fn : (first: typeof val) => O
>first : number
>val : number

return (): O => fn(val)
>(): O => fn(val) : () => O
>fn(val) : O
>fn : (first: number) => O
>val : number
}
let b = decorateB((value) => 5)
>b : () => number
>decorateB((value) => 5) : () => number
>decorateB : <O extends unknown>(fn: (first: number) => O) => () => O
>(value) => 5 : (value: number) => number
>value : number
>5 : 5

function decorateC<O extends any>(fn: (first: {value: number}) => O) {
>decorateC : <O extends unknown>(fn: (first: { value: number;}) => O) => () => O
>fn : (first: { value: number;}) => O
>first : { value: number; }
>value : number

return (): O => fn({value: val})
>(): O => fn({value: val}) : () => O
>fn({value: val}) : O
>fn : (first: { value: number; }) => O
>{value: val} : { value: number; }
>value : number
>val : number
}
let c = decorateC(({value}) => 5)
>c : () => number
>decorateC(({value}) => 5) : () => number
>decorateC : <O extends unknown>(fn: (first: { value: number; }) => O) => () => O
>({value}) => 5 : ({ value }: { value: number; }) => number
>value : number
>5 : 5

type First = {value: typeof val}
>First : { value: typeof val; }
>value : number
>val : number

function decorateD<O extends any>(fn: (first: First) => O) {
>decorateD : <O extends unknown>(fn: (first: First) => O) => () => O
>fn : (first: First) => O
>first : First

return (): O => fn({value: val})
>(): O => fn({value: val}) : () => O
>fn({value: val}) : O
>fn : (first: First) => O
>{value: val} : { value: number; }
>value : number
>val : number
}
let d = decorateD(({value}) => 5)
>d : () => number
>decorateD(({value}) => 5) : () => number
>decorateD : <O extends unknown>(fn: (first: First) => O) => () => O
>({value}) => 5 : ({ value }: First) => number
>value : number
>5 : 5

22 changes: 22 additions & 0 deletions tests/cases/compiler/typeofObjectInference.ts
@@ -0,0 +1,22 @@
let val = 1

function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
return (): O => fn({value: val})
}
let a = decorateA(({value}) => 5)

function decorateB<O extends any>(fn: (first: typeof val) => O) {
return (): O => fn(val)
}
let b = decorateB((value) => 5)

function decorateC<O extends any>(fn: (first: {value: number}) => O) {
return (): O => fn({value: val})
}
let c = decorateC(({value}) => 5)

type First = {value: typeof val}
function decorateD<O extends any>(fn: (first: First) => O) {
return (): O => fn({value: val})
}
let d = decorateD(({value}) => 5)