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);
});
});