Fix spurious circularity caused by removeOptionalityFromDeclaredType, cache result (#52696)

This commit is contained in:
Jake Bailey
2023-02-13 17:55:12 -05:00
committed by GitHub
parent 6aba9b8a24
commit 5b71f59450
4 changed files with 289 additions and 14 deletions

View File

@@ -1233,6 +1233,7 @@ const enum TypeSystemPropertyName {
ResolvedTypeArguments,
ResolvedBaseTypes,
WriteType,
ParameterInitializerContainsUndefined,
}
/** @internal */
@@ -9983,6 +9984,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(target as InterfaceType).baseTypesResolved;
case TypeSystemPropertyName.WriteType:
return !!getSymbolLinks(target as Symbol).writeType;
case TypeSystemPropertyName.ParameterInitializerContainsUndefined:
return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined;
}
return Debug.assertNever(propertyName);
}
@@ -27295,22 +27298,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0;
}
function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean {
const links = getNodeLinks(declaration);
if (links.parameterInitializerContainsUndefined === undefined) {
if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) {
reportCircularityError(declaration.symbol);
return true;
}
const containsUndefined = !!(getTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFacts.IsUndefined);
if (!popTypeResolution()) {
reportCircularityError(declaration.symbol);
return true;
}
links.parameterInitializerContainsUndefined = containsUndefined;
}
return links.parameterInitializerContainsUndefined;
}
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type {
if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) {
const annotationIncludesUndefined = strictNullChecks &&
declaration.kind === SyntaxKind.Parameter &&
declaration.initializer &&
getTypeFacts(declaredType) & TypeFacts.IsUndefined &&
!(getTypeFacts(checkExpression(declaration.initializer)) & TypeFacts.IsUndefined);
popTypeResolution();
const removeUndefined = strictNullChecks &&
declaration.kind === SyntaxKind.Parameter &&
declaration.initializer &&
getTypeFacts(declaredType) & TypeFacts.IsUndefined &&
!parameterInitializerContainsUndefined(declaration);
return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
}
else {
reportCircularityError(declaration.symbol);
return declaredType;
}
return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
}
function isConstraintPosition(type: Type, node: Node) {

View File

@@ -5997,6 +5997,7 @@ export interface NodeLinks {
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime.
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
}
/** @internal */

View File

@@ -38,4 +38,36 @@ describe("unittests:: tsserver:: inconsistentErrorInEditor", () => {
verifyGetErrRequest({ session, host, files: ["^/untitled/ts-nul-authority/Untitled-1"] });
baselineTsserverLogs("inconsistentErrorInEditor", "should not error", session);
});
});
});
describe("unittests:: tsserver:: inconsistentErrorInEditor2", () => {
it("should not error", () => {
const host = createServerHost([]);
const session = createSession(host, { canUseEvents: true, noGetErrOnBackgroundUpdate: true, logger: createLoggerWithInMemoryLogs(host) });
session.executeCommandSeq<ts.server.protocol.UpdateOpenRequest>({
command: ts.server.protocol.CommandTypes.UpdateOpen,
arguments: {
changedFiles: [],
closedFiles: [],
openFiles: [
{
file: "^/untitled/ts-nul-authority/Untitled-1",
fileContent: "function fn(Foo: number) {\r\n type Foo = typeof Foo;\r\n return 0 as any as {x: Foo};\r\n}",
scriptKindName: "TS"
}
]
}
});
session.executeCommandSeq<ts.server.protocol.EncodedSemanticClassificationsRequest>({
command: ts.server.protocol.CommandTypes.EncodedSemanticClassificationsFull,
arguments: {
file: "^/untitled/ts-nul-authority/Untitled-1",
start: 0,
length: 128,
format: "2020"
}
});
verifyGetErrRequest({ session, host, files: ["^/untitled/ts-nul-authority/Untitled-1"] });
baselineTsserverLogs("inconsistentErrorInEditor2", "should not error", session);
});
});

View File

@@ -0,0 +1,224 @@
Info 0 [00:00:02.000] Provided types map file "/a/lib/typesMap.json" doesn't exist
Info 1 [00:00:03.000] request:
{
"command": "updateOpen",
"arguments": {
"changedFiles": [],
"closedFiles": [],
"openFiles": [
{
"file": "^/untitled/ts-nul-authority/Untitled-1",
"fileContent": "function fn(Foo: number) {\r\n type Foo = typeof Foo;\r\n return 0 as any as {x: Foo};\r\n}",
"scriptKindName": "TS"
}
]
},
"seq": 1,
"type": "request"
}
Before request
PolledWatches::
FsWatches::
FsWatchesRecursive::
Info 2 [00:00:04.000] Search path: ^/untitled/ts-nul-authority
Info 3 [00:00:05.000] For info: ^/untitled/ts-nul-authority/Untitled-1 :: No config files found.
Info 4 [00:00:06.000] Starting updateGraphWorker: Project: /dev/null/inferredProject1*
Info 5 [00:00:07.000] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /dev/null/inferredProject1* WatchType: Missing file
Info 6 [00:00:08.000] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms
Info 7 [00:00:09.000] Project '/dev/null/inferredProject1*' (Inferred)
Info 8 [00:00:10.000] Files (1)
^/untitled/ts-nul-authority/Untitled-1
^/untitled/ts-nul-authority/Untitled-1
Root file specified for compilation
Info 9 [00:00:11.000] -----------------------------------------------
Info 10 [00:00:12.000] Project '/dev/null/inferredProject1*' (Inferred)
Info 10 [00:00:13.000] Files (1)
Info 10 [00:00:14.000] -----------------------------------------------
Info 10 [00:00:15.000] Open files:
Info 10 [00:00:16.000] FileName: ^/untitled/ts-nul-authority/Untitled-1 ProjectRootPath: undefined
Info 10 [00:00:17.000] Projects: /dev/null/inferredProject1*
After request
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 10 [00:00:18.000] response:
{
"response": true,
"responseRequired": true
}
Info 11 [00:00:19.000] request:
{
"command": "encodedSemanticClassifications-full",
"arguments": {
"file": "^/untitled/ts-nul-authority/Untitled-1",
"start": 0,
"length": 128,
"format": "2020"
},
"seq": 2,
"type": "request"
}
Before request
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
After request
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 12 [00:00:20.000] response:
{
"response": {
"spans": [
9,
2,
2817,
12,
3,
1536,
38,
3,
1537,
51,
3,
1536,
81,
1,
2561,
84,
3,
1536
],
"endOfLineState": 0
},
"responseRequired": true
}
Info 13 [00:00:21.000] request:
{
"command": "geterr",
"arguments": {
"delay": 0,
"files": [
"^/untitled/ts-nul-authority/Untitled-1"
]
},
"seq": 3,
"type": "request"
}
Before request
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
After request
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 14 [00:00:22.000] response:
{
"responseRequired": false
}
Before checking timeout queue length (1) and running
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 15 [00:00:23.000] event:
{"seq":0,"type":"event","event":"syntaxDiag","body":{"file":"^/untitled/ts-nul-authority/Untitled-1","diagnostics":[]}}
After checking timeout queue length (1) and running
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Before running immediate callbacks and checking length (1)
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 16 [00:00:24.000] event:
{"seq":0,"type":"event","event":"semanticDiag","body":{"file":"^/untitled/ts-nul-authority/Untitled-1","diagnostics":[]}}
Before running immediate callbacks and checking length (1)
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Before running immediate callbacks and checking length (1)
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::
Info 17 [00:00:25.000] event:
{"seq":0,"type":"event","event":"suggestionDiag","body":{"file":"^/untitled/ts-nul-authority/Untitled-1","diagnostics":[]}}
Info 18 [00:00:26.000] event:
{"seq":0,"type":"event","event":"requestCompleted","body":{"request_seq":3}}
Before running immediate callbacks and checking length (1)
PolledWatches::
/a/lib/lib.d.ts:
{"pollingInterval":500}
FsWatches::
FsWatchesRecursive::