-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Improve check of whether type query node possibly contains reference to type parameter #50070
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
Changes from 8 commits
4a6a005
b501b47
6d2c0f9
028b30c
b17a5dd
5853796
03bc5fe
f05889e
4269875
fbf3cad
8fa0061
6edce8c
8ff3987
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -726,6 +726,7 @@ namespace ts { | |
isPropertyAccessible, | ||
getTypeOnlyAliasDeclaration, | ||
getMemberOverrideModifierStatus, | ||
isTypeParameterPossiblyReferenced, | ||
}; | ||
|
||
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T { | ||
|
@@ -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) { | ||
|
@@ -17068,6 +17069,15 @@ 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 = getSymbolAtLocation(getFirstIdentifier(entityName)); | ||
if (firstIdentifier?.declarations && firstIdentifier.declarations.length === 1 && tp.symbol) { | ||
const symbols = getSymbolsInScope(firstIdentifier.declarations[0], SymbolFlags.All, /*allowDuplicates*/ true); | ||
gabritto marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!symbols.find(sym => sym === tp.symbol)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the |
||
const typeArguments = (node as TypeQueryNode).typeArguments; | ||
return typeArguments? !!forEach(typeArguments, containsReference) : false; | ||
} | ||
} | ||
return true; | ||
case SyntaxKind.MethodDeclaration: | ||
case SyntaxKind.MethodSignature: | ||
|
@@ -42074,19 +42084,26 @@ namespace ts { | |
|
||
// Language service support | ||
|
||
function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { | ||
function getSymbolsInScope(location: Node, meaning: SymbolFlags, allowDuplicates = false): Symbol[] { | ||
if (location.flags & NodeFlags.InWithStatement) { | ||
// We cannot answer semantic questions within a with block, do not proceed any further | ||
return []; | ||
} | ||
|
||
const symbols = createSymbolTable(); | ||
const symbols = new Map<__String, Symbol[]>(); | ||
let isStaticSymbol = false; | ||
|
||
populateSymbols(); | ||
|
||
symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword | ||
return symbolsToArray(symbols); | ||
|
||
const result: Symbol[] = []; | ||
symbols.forEach((symbolArray, id) => { | ||
if (!isReservedMemberName(id)) { | ||
result.push(...symbolArray); | ||
} | ||
}); | ||
return result; | ||
|
||
function populateSymbols() { | ||
while (location) { | ||
|
@@ -42155,8 +42172,14 @@ namespace ts { | |
// We will copy all symbol regardless of its reserved name because | ||
// symbolsToArray will check whether the key is a reserved name and | ||
// it will not copy symbol with reserved name to the array | ||
if (!symbols.has(id)) { | ||
symbols.set(id, symbol); | ||
if (allowDuplicates) { | ||
if (!symbols.has(id)) { | ||
symbols.set(id, []); | ||
} | ||
symbols.get(id)!.push(symbol); | ||
} | ||
else if (!symbols.has(id)) { | ||
symbols.set(id, [symbol]); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
`; | ||
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"); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Uh oh!
There was an error while loading. Please reload this page.