Preserve parameter types for optional parameters /fields with undefined in type and for required params with default value (#57484)

This commit is contained in:
Titian Cernicova-Dragomir
2024-02-29 17:42:26 +00:00
committed by GitHub
parent 2d70b57df4
commit e089896be4
8 changed files with 241 additions and 37 deletions

View File

@@ -48132,6 +48132,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return false;
}
function declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration) {
if (!parameter.type) return false;
const type = getTypeFromTypeNode(parameter.type);
return containsUndefinedType(type);
}
function requiresAddingImplicitUndefined(parameter: ParameterDeclaration) {
return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter);
}
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean {
return !!strictNullChecks &&
!isOptionalParameter(parameter) &&
@@ -48525,8 +48534,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
isTopLevelValueImportEqualsWithEntityName,
isDeclarationVisible,
isImplementationOfOverload,
isRequiredInitializedParameter,
isOptionalUninitializedParameterProperty,
requiresAddingImplicitUndefined,
isExpandoFunctionDeclaration,
getPropertiesOfContainerFunction,
createTypeOfDeclaration,

View File

@@ -1091,8 +1091,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,

View File

@@ -127,7 +127,6 @@ import {
isModuleDeclaration,
isOmittedExpression,
isPrivateIdentifier,
isPropertySignature,
isSemicolonClassElement,
isSetAccessorDeclaration,
isSourceFile,
@@ -708,7 +707,6 @@ export function transformDeclarations(context: TransformationContext) {
| FunctionDeclaration
| MethodDeclaration
| GetAccessorDeclaration
| SetAccessorDeclaration
| BindingElement
| ConstructSignatureDeclaration
| VariableDeclaration
@@ -727,46 +725,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));
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);
}
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));
}
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) {

View File

@@ -5603,8 +5603,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;

View File

@@ -0,0 +1,74 @@
//// [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<MapOrUndefined, "dummy">,
// Resolve type node, requires adding | undefined
public resolveType?: Map,
) { }
}
export function foo1(
// Type node is accurate, preserve
reuseTypeNode: Map | undefined = {},
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
// 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]
type Map = {} & {
[P in string]: any;
};
type MapOrUndefined = Map | undefined | "dummy";
export declare class Foo {
reuseTypeNode?: Map | undefined;
reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">;
resolveType?: {
[x: string]: any;
} | undefined;
constructor(reuseTypeNode?: Map | undefined, reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">, resolveType?: {
[x: string]: any;
} | undefined);
}
export declare function foo1(reuseTypeNode: Map | undefined, reuseTypeNode2: Exclude<MapOrUndefined, "dummy">, resolveType: {
[x: string]: any;
} | undefined, requiredParam: number): void;
export {};

View File

@@ -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<MapOrUndefined, "dummy">,
>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<MapOrUndefined, "dummy"> = {},
>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))
}

View File

@@ -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<MapOrUndefined, "dummy">,
>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<MapOrUndefined, "dummy">, resolveType: { [x: string]: any; } | undefined, requiredParam: number) => void
// Type node is accurate, preserve
reuseTypeNode: Map | undefined = {},
>reuseTypeNode : { [x: string]: any; } | undefined
>{} : {}
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
>reuseTypeNode2 : { [x: string]: any; } | undefined
>{} : {}
// Resolve type node, requires adding | undefined
resolveType: Map = {},
>resolveType : { [x: string]: any; }
>{} : {}
requiredParam: number) {
>requiredParam : number
}

View File

@@ -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<MapOrUndefined, "dummy">,
// Resolve type node, requires adding | undefined
public resolveType?: Map,
) { }
}
export function foo1(
// Type node is accurate, preserve
reuseTypeNode: Map | undefined = {},
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
// Resolve type node, requires adding | undefined
resolveType: Map = {},
requiredParam: number) {
}