mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-04-17 12:32:42 -05:00
Improve check of whether type query node possibly contains reference to type parameter (#50070)
* WIP * implement typequery contains reference check + tests * add unit test * fix unit test * use symbols in scope to check type query type parameter references * remove comment on unit test * remove comment * use isNodeDescendantOf implementation to check scoping * CR: small fixes * treat the different kinds of type parameter declarations * undo test change
This commit is contained in:
committed by
GitHub
parent
af9ced11f5
commit
48a8e8953a
@@ -728,6 +728,7 @@ namespace ts {
|
||||
isPropertyAccessible,
|
||||
getTypeOnlyAliasDeclaration,
|
||||
getMemberOverrideModifierStatus,
|
||||
isTypeParameterPossiblyReferenced,
|
||||
};
|
||||
|
||||
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
|
||||
@@ -17193,9 +17194,10 @@ 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.
|
||||
// type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter,
|
||||
// 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) {
|
||||
@@ -17214,6 +17216,28 @@ 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);
|
||||
const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called
|
||||
let tpScope: Node;
|
||||
if (tpDeclaration.kind === SyntaxKind.TypeParameter) { // Type parameter is a regular type parameter, e.g. foo<T>
|
||||
tpScope = tpDeclaration.parent;
|
||||
}
|
||||
else if (tp.isThisType) {
|
||||
// Type parameter is the this type, and its declaration is the class declaration.
|
||||
tpScope = tpDeclaration;
|
||||
}
|
||||
else {
|
||||
// Type parameter's declaration was unrecognized.
|
||||
// This could happen if the type parameter comes from e.g. a JSDoc annotation, so we default to returning true.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (firstIdentifierSymbol.declarations) {
|
||||
return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) ||
|
||||
some((node as TypeQueryNode).typeArguments, containsReference);
|
||||
}
|
||||
return true;
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
|
||||
@@ -4854,6 +4854,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 */
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"unittests/reuseProgramStructure.ts",
|
||||
"unittests/semver.ts",
|
||||
"unittests/transform.ts",
|
||||
"unittests/typeParameterIsPossiblyReferenced.ts",
|
||||
"unittests/config/commandLineParsing.ts",
|
||||
"unittests/config/configurationExtension.ts",
|
||||
"unittests/config/convertCompilerOptionsFromJson.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;
|
||||
`;
|
||||
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
tests/baselines/reference/typeofObjectInference.js
Normal file
51
tests/baselines/reference/typeofObjectInference.js
Normal file
@@ -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
tests/baselines/reference/typeofObjectInference.symbols
Normal file
85
tests/baselines/reference/typeofObjectInference.symbols
Normal file
@@ -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
tests/baselines/reference/typeofObjectInference.types
Normal file
96
tests/baselines/reference/typeofObjectInference.types
Normal file
@@ -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
tests/cases/compiler/typeofObjectInference.ts
Normal file
22
tests/cases/compiler/typeofObjectInference.ts
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user