From 3e041023b9c4e50f43234c00b0c9f20cf65b50e0 Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Thu, 22 Feb 2024 17:02:11 +0000 Subject: [PATCH 1/3] Added new test --- .../verbatim-declarations-parameters.js | 81 +++++++++++++++++++ .../verbatim-declarations-parameters.symbols | 56 +++++++++++++ .../verbatim-declarations-parameters.types | 49 +++++++++++ .../verbatim-declarations-parameters.ts | 24 ++++++ 4 files changed, 210 insertions(+) create mode 100644 tests/baselines/reference/verbatim-declarations-parameters.js create mode 100644 tests/baselines/reference/verbatim-declarations-parameters.symbols create mode 100644 tests/baselines/reference/verbatim-declarations-parameters.types create mode 100644 tests/cases/compiler/verbatim-declarations-parameters.ts diff --git a/tests/baselines/reference/verbatim-declarations-parameters.js b/tests/baselines/reference/verbatim-declarations-parameters.js new file mode 100644 index 0000000000000..f221c5cdd4f03 --- /dev/null +++ b/tests/baselines/reference/verbatim-declarations-parameters.js @@ -0,0 +1,81 @@ +//// [tests/cases/compiler/verbatim-declarations-parameters.ts] //// + +//// [verbatim-declarations-parameters.ts] +type Map = {} & { [P in string]: any } +type MapOrUndefined = Map | undefined | "dummy" +export class Foo { + constructor( + // Type node is accurate, preserve + public reuseTypeNode?: Map | undefined, + public reuseTypeNode2?: Exclude, + // Resolve type node, requires adding | undefined + public resolveType?: Map, + ) { } +} + +export function foo1( + // Type node is accurate, preserve + reuseTypeNode: Map | undefined = {}, + reuseTypeNode2: Exclude = {}, + // Resolve type node, requires adding | undefined + resolveType: Map = {}, + requiredParam: number) { + +} + + +//// [verbatim-declarations-parameters.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.foo1 = exports.Foo = void 0; +var Foo = /** @class */ (function () { + function Foo( + // Type node is accurate, preserve + reuseTypeNode, reuseTypeNode2, + // Resolve type node, requires adding | undefined + resolveType) { + this.reuseTypeNode = reuseTypeNode; + this.reuseTypeNode2 = reuseTypeNode2; + this.resolveType = resolveType; + } + return Foo; +}()); +exports.Foo = Foo; +function foo1( +// Type node is accurate, preserve +reuseTypeNode, reuseTypeNode2, +// Resolve type node, requires adding | undefined +resolveType, requiredParam) { + if (reuseTypeNode === void 0) { reuseTypeNode = {}; } + if (reuseTypeNode2 === void 0) { reuseTypeNode2 = {}; } + if (resolveType === void 0) { resolveType = {}; } +} +exports.foo1 = foo1; + + +//// [verbatim-declarations-parameters.d.ts] +export declare class Foo { + reuseTypeNode?: { + [x: string]: any; + } | undefined; + reuseTypeNode2?: { + [x: string]: any; + } | undefined; + resolveType?: { + [x: string]: any; + } | undefined; + constructor(reuseTypeNode?: { + [x: string]: any; + } | undefined, reuseTypeNode2?: { + [x: string]: any; + } | undefined, resolveType?: { + [x: string]: any; + } | undefined); +} +export declare function foo1(reuseTypeNode: { + [x: string]: any; +} | undefined, reuseTypeNode2: { + [x: string]: any; +} | undefined, resolveType: { + [x: string]: any; +} | undefined, requiredParam: number): void; diff --git a/tests/baselines/reference/verbatim-declarations-parameters.symbols b/tests/baselines/reference/verbatim-declarations-parameters.symbols new file mode 100644 index 0000000000000..f84ec29175c03 --- /dev/null +++ b/tests/baselines/reference/verbatim-declarations-parameters.symbols @@ -0,0 +1,56 @@ +//// [tests/cases/compiler/verbatim-declarations-parameters.ts] //// + +=== verbatim-declarations-parameters.ts === +type Map = {} & { [P in string]: any } +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) +>P : Symbol(P, Decl(verbatim-declarations-parameters.ts, 0, 19)) + +type MapOrUndefined = Map | undefined | "dummy" +>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38)) +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) + +export class Foo { +>Foo : Symbol(Foo, Decl(verbatim-declarations-parameters.ts, 1, 47)) + + constructor( + // Type node is accurate, preserve + public reuseTypeNode?: Map | undefined, +>reuseTypeNode : Symbol(Foo.reuseTypeNode, Decl(verbatim-declarations-parameters.ts, 3, 14)) +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) + + public reuseTypeNode2?: Exclude, +>reuseTypeNode2 : Symbol(Foo.reuseTypeNode2, Decl(verbatim-declarations-parameters.ts, 5, 43)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38)) + + // Resolve type node, requires adding | undefined + public resolveType?: Map, +>resolveType : Symbol(Foo.resolveType, Decl(verbatim-declarations-parameters.ts, 6, 61)) +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) + + ) { } +} + +export function foo1( +>foo1 : Symbol(foo1, Decl(verbatim-declarations-parameters.ts, 10, 1)) + + // Type node is accurate, preserve + reuseTypeNode: Map | undefined = {}, +>reuseTypeNode : Symbol(reuseTypeNode, Decl(verbatim-declarations-parameters.ts, 12, 21)) +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) + + reuseTypeNode2: Exclude = {}, +>reuseTypeNode2 : Symbol(reuseTypeNode2, Decl(verbatim-declarations-parameters.ts, 14, 40)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38)) + + // Resolve type node, requires adding | undefined + resolveType: Map = {}, +>resolveType : Symbol(resolveType, Decl(verbatim-declarations-parameters.ts, 15, 59)) +>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0)) + + requiredParam: number) { +>requiredParam : Symbol(requiredParam, Decl(verbatim-declarations-parameters.ts, 17, 26)) + +} + diff --git a/tests/baselines/reference/verbatim-declarations-parameters.types b/tests/baselines/reference/verbatim-declarations-parameters.types new file mode 100644 index 0000000000000..c22a7281300e3 --- /dev/null +++ b/tests/baselines/reference/verbatim-declarations-parameters.types @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/verbatim-declarations-parameters.ts] //// + +=== verbatim-declarations-parameters.ts === +type Map = {} & { [P in string]: any } +>Map : { [x: string]: any; } + +type MapOrUndefined = Map | undefined | "dummy" +>MapOrUndefined : { [x: string]: any; } | "dummy" | undefined + +export class Foo { +>Foo : Foo + + constructor( + // Type node is accurate, preserve + public reuseTypeNode?: Map | undefined, +>reuseTypeNode : { [x: string]: any; } | undefined + + public reuseTypeNode2?: Exclude, +>reuseTypeNode2 : { [x: string]: any; } | undefined + + // Resolve type node, requires adding | undefined + public resolveType?: Map, +>resolveType : { [x: string]: any; } | undefined + + ) { } +} + +export function foo1( +>foo1 : (reuseTypeNode: Map | undefined, reuseTypeNode2: Exclude, resolveType: { [x: string]: any; } | undefined, requiredParam: number) => void + + // Type node is accurate, preserve + reuseTypeNode: Map | undefined = {}, +>reuseTypeNode : { [x: string]: any; } | undefined +>{} : {} + + reuseTypeNode2: Exclude = {}, +>reuseTypeNode2 : { [x: string]: any; } | undefined +>{} : {} + + // Resolve type node, requires adding | undefined + resolveType: Map = {}, +>resolveType : { [x: string]: any; } +>{} : {} + + requiredParam: number) { +>requiredParam : number + +} + diff --git a/tests/cases/compiler/verbatim-declarations-parameters.ts b/tests/cases/compiler/verbatim-declarations-parameters.ts new file mode 100644 index 0000000000000..da1a6616356ed --- /dev/null +++ b/tests/cases/compiler/verbatim-declarations-parameters.ts @@ -0,0 +1,24 @@ +// @strictNullChecks: true +// @declaration: true + +type Map = {} & { [P in string]: any } +type MapOrUndefined = Map | undefined | "dummy" +export class Foo { + constructor( + // Type node is accurate, preserve + public reuseTypeNode?: Map | undefined, + public reuseTypeNode2?: Exclude, + // Resolve type node, requires adding | undefined + public resolveType?: Map, + ) { } +} + +export function foo1( + // Type node is accurate, preserve + reuseTypeNode: Map | undefined = {}, + reuseTypeNode2: Exclude = {}, + // Resolve type node, requires adding | undefined + resolveType: Map = {}, + requiredParam: number) { + +} From 85f60d9aa50934f35cea3876f7a8f4bbe166f208 Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Thu, 22 Feb 2024 17:14:51 +0000 Subject: [PATCH 2/3] Preserve parameter types unless we absolutely must resolve them. --- src/compiler/checker.ts | 12 +++- src/compiler/emitter.ts | 3 +- src/compiler/transformers/declarations.ts | 57 +++++++++---------- src/compiler/types.ts | 3 +- .../verbatim-declarations-parameters.js | 25 +++----- 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..cb20108c00297 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -48219,6 +48219,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } + function declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration) { + if (!parameter.type) return false; + const type = getTypeFromTypeNode(parameter.type); + return type === undefinedType || !!(type.flags & TypeFlags.Union) && !!((type as UnionType).types[0].flags & TypeFlags.Undefined); + } + function requiresAddingImplicitUndefined(parameter: ParameterDeclaration) { + return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter); + } + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { return !!strictNullChecks && !isOptionalParameter(parameter) && @@ -48612,8 +48621,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, isImplementationOfOverload, - isRequiredInitializedParameter, - isOptionalUninitializedParameterProperty, + requiresAddingImplicitUndefined, isExpandoFunctionDeclaration, getPropertiesOfContainerFunction, createTypeOfDeclaration, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f27f9e7247e74..28e74f0d7eafa 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1161,8 +1161,7 @@ export const notImplementedResolver: EmitResolver = { isLateBound: (_node): _node is LateBoundDeclaration => false, collectLinkedAliases: notImplemented, isImplementationOfOverload: notImplemented, - isRequiredInitializedParameter: notImplemented, - isOptionalUninitializedParameterProperty: notImplemented, + requiresAddingImplicitUndefined: notImplemented, isExpandoFunctionDeclaration: notImplemented, getPropertiesOfContainerFunction: notImplemented, createTypeOfDeclaration: notImplemented, diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 0a9e58c6de3b8..6ca0d12885697 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -128,7 +128,6 @@ import { isModuleDeclaration, isOmittedExpression, isPrivateIdentifier, - isPropertySignature, isSemicolonClassElement, isSetAccessorDeclaration, isSourceFile, @@ -722,7 +721,6 @@ export function transformDeclarations(context: TransformationContext) { | FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration - | SetAccessorDeclaration | BindingElement | ConstructSignatureDeclaration | VariableDeclaration @@ -741,46 +739,43 @@ export function transformDeclarations(context: TransformationContext) { // Literal const declarations will have an initializer ensured rather than a type return; } - const shouldUseResolverType = node.kind === SyntaxKind.Parameter && - (resolver.isRequiredInitializedParameter(node) || - resolver.isOptionalUninitializedParameterProperty(node)); - if (type && !shouldUseResolverType) { + const shouldAddImplicitUndefined = node.kind === SyntaxKind.Parameter && resolver.requiresAddingImplicitUndefined(node); + if (type && !shouldAddImplicitUndefined) { return visitNode(type, visitDeclarationSubtree, isTypeNode); } - if (!getParseTreeNode(node)) { - return type ? visitNode(type, visitDeclarationSubtree, isTypeNode) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (node.kind === SyntaxKind.SetAccessor) { - // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now - // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + errorNameNode = node.name; let oldDiag: typeof getSymbolAccessibilityDiagnostic; if (!suppressNewDiagnosticContexts) { oldDiag = getSymbolAccessibilityDiagnostic; getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); } - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - if ( - node.kind === SyntaxKind.Parameter - || node.kind === SyntaxKind.PropertyDeclaration - || node.kind === SyntaxKind.PropertySignature - ) { - if (isPropertySignature(node) || !node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + let typeNode; + switch (node.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + typeNode = resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldAddImplicitUndefined); + break; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.CallSignature: + typeNode = resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker); + break; + default: + Debug.assertNever(node); } - return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - function cleanup(returnValue: TypeNode | undefined) { - errorNameNode = undefined; - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; - } - return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + errorNameNode = undefined; + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; } + return typeNode ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } function isDeclarationAndNotVisible(node: NamedDeclaration) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28061c88e94bb..6ddbaf04a7bf3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5699,8 +5699,7 @@ export interface EmitResolver { isLateBound(node: Declaration): node is LateBoundDeclaration; collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined; isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined; - isRequiredInitializedParameter(node: ParameterDeclaration): boolean; - isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean; + requiresAddingImplicitUndefined(node: ParameterDeclaration): boolean; isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean; getPropertiesOfContainerFunction(node: Declaration): Symbol[]; createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined; diff --git a/tests/baselines/reference/verbatim-declarations-parameters.js b/tests/baselines/reference/verbatim-declarations-parameters.js index f221c5cdd4f03..d0d54ba7e5e36 100644 --- a/tests/baselines/reference/verbatim-declarations-parameters.js +++ b/tests/baselines/reference/verbatim-declarations-parameters.js @@ -54,28 +54,21 @@ exports.foo1 = foo1; //// [verbatim-declarations-parameters.d.ts] +type Map = {} & { + [P in string]: any; +}; +type MapOrUndefined = Map | undefined | "dummy"; export declare class Foo { - reuseTypeNode?: { - [x: string]: any; - } | undefined; - reuseTypeNode2?: { - [x: string]: any; - } | undefined; + reuseTypeNode?: Map | undefined; + reuseTypeNode2?: Exclude; resolveType?: { [x: string]: any; } | undefined; - constructor(reuseTypeNode?: { - [x: string]: any; - } | undefined, reuseTypeNode2?: { - [x: string]: any; - } | undefined, resolveType?: { + constructor(reuseTypeNode?: Map | undefined, reuseTypeNode2?: Exclude, resolveType?: { [x: string]: any; } | undefined); } -export declare function foo1(reuseTypeNode: { - [x: string]: any; -} | undefined, reuseTypeNode2: { - [x: string]: any; -} | undefined, resolveType: { +export declare function foo1(reuseTypeNode: Map | undefined, reuseTypeNode2: Exclude, resolveType: { [x: string]: any; } | undefined, requiredParam: number): void; +export {}; From a05d427e5e1a8f554daf41375865fd1bba15c550 Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Tue, 27 Feb 2024 13:39:00 +0000 Subject: [PATCH 3/3] Used containsUndefinedType in place of inline test. --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cb20108c00297..a7015a3cc9ad1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -48222,7 +48222,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration) { if (!parameter.type) return false; const type = getTypeFromTypeNode(parameter.type); - return type === undefinedType || !!(type.flags & TypeFlags.Union) && !!((type as UnionType).types[0].flags & TypeFlags.Undefined); + return containsUndefinedType(type); } function requiresAddingImplicitUndefined(parameter: ParameterDeclaration) { return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter);