Skip to content

Commit e089896

Browse files
Preserve parameter types for optional parameters /fields with undefined in type and for required params with default value (#57484)
1 parent 2d70b57 commit e089896

8 files changed

+241
-37
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48132,6 +48132,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4813248132
return false;
4813348133
}
4813448134

48135+
function declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration) {
48136+
if (!parameter.type) return false;
48137+
const type = getTypeFromTypeNode(parameter.type);
48138+
return containsUndefinedType(type);
48139+
}
48140+
function requiresAddingImplicitUndefined(parameter: ParameterDeclaration) {
48141+
return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter);
48142+
}
48143+
4813548144
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean {
4813648145
return !!strictNullChecks &&
4813748146
!isOptionalParameter(parameter) &&
@@ -48525,8 +48534,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4852548534
isTopLevelValueImportEqualsWithEntityName,
4852648535
isDeclarationVisible,
4852748536
isImplementationOfOverload,
48528-
isRequiredInitializedParameter,
48529-
isOptionalUninitializedParameterProperty,
48537+
requiresAddingImplicitUndefined,
4853048538
isExpandoFunctionDeclaration,
4853148539
getPropertiesOfContainerFunction,
4853248540
createTypeOfDeclaration,

src/compiler/emitter.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,8 +1091,7 @@ export const notImplementedResolver: EmitResolver = {
10911091
isLateBound: (_node): _node is LateBoundDeclaration => false,
10921092
collectLinkedAliases: notImplemented,
10931093
isImplementationOfOverload: notImplemented,
1094-
isRequiredInitializedParameter: notImplemented,
1095-
isOptionalUninitializedParameterProperty: notImplemented,
1094+
requiresAddingImplicitUndefined: notImplemented,
10961095
isExpandoFunctionDeclaration: notImplemented,
10971096
getPropertiesOfContainerFunction: notImplemented,
10981097
createTypeOfDeclaration: notImplemented,

src/compiler/transformers/declarations.ts

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ import {
127127
isModuleDeclaration,
128128
isOmittedExpression,
129129
isPrivateIdentifier,
130-
isPropertySignature,
131130
isSemicolonClassElement,
132131
isSetAccessorDeclaration,
133132
isSourceFile,
@@ -708,7 +707,6 @@ export function transformDeclarations(context: TransformationContext) {
708707
| FunctionDeclaration
709708
| MethodDeclaration
710709
| GetAccessorDeclaration
711-
| SetAccessorDeclaration
712710
| BindingElement
713711
| ConstructSignatureDeclaration
714712
| VariableDeclaration
@@ -727,46 +725,43 @@ export function transformDeclarations(context: TransformationContext) {
727725
// Literal const declarations will have an initializer ensured rather than a type
728726
return;
729727
}
730-
const shouldUseResolverType = node.kind === SyntaxKind.Parameter &&
731-
(resolver.isRequiredInitializedParameter(node) ||
732-
resolver.isOptionalUninitializedParameterProperty(node));
733-
if (type && !shouldUseResolverType) {
728+
const shouldAddImplicitUndefined = node.kind === SyntaxKind.Parameter && resolver.requiresAddingImplicitUndefined(node);
729+
if (type && !shouldAddImplicitUndefined) {
734730
return visitNode(type, visitDeclarationSubtree, isTypeNode);
735731
}
736-
if (!getParseTreeNode(node)) {
737-
return type ? visitNode(type, visitDeclarationSubtree, isTypeNode) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
738-
}
739-
if (node.kind === SyntaxKind.SetAccessor) {
740-
// 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
741-
// (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that)
742-
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
743-
}
732+
744733
errorNameNode = node.name;
745734
let oldDiag: typeof getSymbolAccessibilityDiagnostic;
746735
if (!suppressNewDiagnosticContexts) {
747736
oldDiag = getSymbolAccessibilityDiagnostic;
748737
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node);
749738
}
750-
if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
751-
return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker));
752-
}
753-
if (
754-
node.kind === SyntaxKind.Parameter
755-
|| node.kind === SyntaxKind.PropertyDeclaration
756-
|| node.kind === SyntaxKind.PropertySignature
757-
) {
758-
if (isPropertySignature(node) || !node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType));
759-
return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker));
739+
let typeNode;
740+
switch (node.kind) {
741+
case SyntaxKind.Parameter:
742+
case SyntaxKind.PropertySignature:
743+
case SyntaxKind.PropertyDeclaration:
744+
case SyntaxKind.BindingElement:
745+
case SyntaxKind.VariableDeclaration:
746+
typeNode = resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldAddImplicitUndefined);
747+
break;
748+
case SyntaxKind.FunctionDeclaration:
749+
case SyntaxKind.ConstructSignature:
750+
case SyntaxKind.MethodSignature:
751+
case SyntaxKind.MethodDeclaration:
752+
case SyntaxKind.GetAccessor:
753+
case SyntaxKind.CallSignature:
754+
typeNode = resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker);
755+
break;
756+
default:
757+
Debug.assertNever(node);
760758
}
761-
return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker));
762759

763-
function cleanup(returnValue: TypeNode | undefined) {
764-
errorNameNode = undefined;
765-
if (!suppressNewDiagnosticContexts) {
766-
getSymbolAccessibilityDiagnostic = oldDiag;
767-
}
768-
return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
760+
errorNameNode = undefined;
761+
if (!suppressNewDiagnosticContexts) {
762+
getSymbolAccessibilityDiagnostic = oldDiag!;
769763
}
764+
return typeNode ?? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
770765
}
771766

772767
function isDeclarationAndNotVisible(node: NamedDeclaration) {

src/compiler/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5603,8 +5603,7 @@ export interface EmitResolver {
56035603
isLateBound(node: Declaration): node is LateBoundDeclaration;
56045604
collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined;
56055605
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
5606-
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
5607-
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
5606+
requiresAddingImplicitUndefined(node: ParameterDeclaration): boolean;
56085607
isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean;
56095608
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
56105609
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//// [tests/cases/compiler/verbatim-declarations-parameters.ts] ////
2+
3+
//// [verbatim-declarations-parameters.ts]
4+
type Map = {} & { [P in string]: any }
5+
type MapOrUndefined = Map | undefined | "dummy"
6+
export class Foo {
7+
constructor(
8+
// Type node is accurate, preserve
9+
public reuseTypeNode?: Map | undefined,
10+
public reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">,
11+
// Resolve type node, requires adding | undefined
12+
public resolveType?: Map,
13+
) { }
14+
}
15+
16+
export function foo1(
17+
// Type node is accurate, preserve
18+
reuseTypeNode: Map | undefined = {},
19+
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
20+
// Resolve type node, requires adding | undefined
21+
resolveType: Map = {},
22+
requiredParam: number) {
23+
24+
}
25+
26+
27+
//// [verbatim-declarations-parameters.js]
28+
"use strict";
29+
Object.defineProperty(exports, "__esModule", { value: true });
30+
exports.foo1 = exports.Foo = void 0;
31+
var Foo = /** @class */ (function () {
32+
function Foo(
33+
// Type node is accurate, preserve
34+
reuseTypeNode, reuseTypeNode2,
35+
// Resolve type node, requires adding | undefined
36+
resolveType) {
37+
this.reuseTypeNode = reuseTypeNode;
38+
this.reuseTypeNode2 = reuseTypeNode2;
39+
this.resolveType = resolveType;
40+
}
41+
return Foo;
42+
}());
43+
exports.Foo = Foo;
44+
function foo1(
45+
// Type node is accurate, preserve
46+
reuseTypeNode, reuseTypeNode2,
47+
// Resolve type node, requires adding | undefined
48+
resolveType, requiredParam) {
49+
if (reuseTypeNode === void 0) { reuseTypeNode = {}; }
50+
if (reuseTypeNode2 === void 0) { reuseTypeNode2 = {}; }
51+
if (resolveType === void 0) { resolveType = {}; }
52+
}
53+
exports.foo1 = foo1;
54+
55+
56+
//// [verbatim-declarations-parameters.d.ts]
57+
type Map = {} & {
58+
[P in string]: any;
59+
};
60+
type MapOrUndefined = Map | undefined | "dummy";
61+
export declare class Foo {
62+
reuseTypeNode?: Map | undefined;
63+
reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">;
64+
resolveType?: {
65+
[x: string]: any;
66+
} | undefined;
67+
constructor(reuseTypeNode?: Map | undefined, reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">, resolveType?: {
68+
[x: string]: any;
69+
} | undefined);
70+
}
71+
export declare function foo1(reuseTypeNode: Map | undefined, reuseTypeNode2: Exclude<MapOrUndefined, "dummy">, resolveType: {
72+
[x: string]: any;
73+
} | undefined, requiredParam: number): void;
74+
export {};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [tests/cases/compiler/verbatim-declarations-parameters.ts] ////
2+
3+
=== verbatim-declarations-parameters.ts ===
4+
type Map = {} & { [P in string]: any }
5+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
6+
>P : Symbol(P, Decl(verbatim-declarations-parameters.ts, 0, 19))
7+
8+
type MapOrUndefined = Map | undefined | "dummy"
9+
>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38))
10+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
11+
12+
export class Foo {
13+
>Foo : Symbol(Foo, Decl(verbatim-declarations-parameters.ts, 1, 47))
14+
15+
constructor(
16+
// Type node is accurate, preserve
17+
public reuseTypeNode?: Map | undefined,
18+
>reuseTypeNode : Symbol(Foo.reuseTypeNode, Decl(verbatim-declarations-parameters.ts, 3, 14))
19+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
20+
21+
public reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">,
22+
>reuseTypeNode2 : Symbol(Foo.reuseTypeNode2, Decl(verbatim-declarations-parameters.ts, 5, 43))
23+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
24+
>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38))
25+
26+
// Resolve type node, requires adding | undefined
27+
public resolveType?: Map,
28+
>resolveType : Symbol(Foo.resolveType, Decl(verbatim-declarations-parameters.ts, 6, 61))
29+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
30+
31+
) { }
32+
}
33+
34+
export function foo1(
35+
>foo1 : Symbol(foo1, Decl(verbatim-declarations-parameters.ts, 10, 1))
36+
37+
// Type node is accurate, preserve
38+
reuseTypeNode: Map | undefined = {},
39+
>reuseTypeNode : Symbol(reuseTypeNode, Decl(verbatim-declarations-parameters.ts, 12, 21))
40+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
41+
42+
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
43+
>reuseTypeNode2 : Symbol(reuseTypeNode2, Decl(verbatim-declarations-parameters.ts, 14, 40))
44+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
45+
>MapOrUndefined : Symbol(MapOrUndefined, Decl(verbatim-declarations-parameters.ts, 0, 38))
46+
47+
// Resolve type node, requires adding | undefined
48+
resolveType: Map = {},
49+
>resolveType : Symbol(resolveType, Decl(verbatim-declarations-parameters.ts, 15, 59))
50+
>Map : Symbol(Map, Decl(verbatim-declarations-parameters.ts, 0, 0))
51+
52+
requiredParam: number) {
53+
>requiredParam : Symbol(requiredParam, Decl(verbatim-declarations-parameters.ts, 17, 26))
54+
55+
}
56+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/compiler/verbatim-declarations-parameters.ts] ////
2+
3+
=== verbatim-declarations-parameters.ts ===
4+
type Map = {} & { [P in string]: any }
5+
>Map : { [x: string]: any; }
6+
7+
type MapOrUndefined = Map | undefined | "dummy"
8+
>MapOrUndefined : { [x: string]: any; } | "dummy" | undefined
9+
10+
export class Foo {
11+
>Foo : Foo
12+
13+
constructor(
14+
// Type node is accurate, preserve
15+
public reuseTypeNode?: Map | undefined,
16+
>reuseTypeNode : { [x: string]: any; } | undefined
17+
18+
public reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">,
19+
>reuseTypeNode2 : { [x: string]: any; } | undefined
20+
21+
// Resolve type node, requires adding | undefined
22+
public resolveType?: Map,
23+
>resolveType : { [x: string]: any; } | undefined
24+
25+
) { }
26+
}
27+
28+
export function foo1(
29+
>foo1 : (reuseTypeNode: Map | undefined, reuseTypeNode2: Exclude<MapOrUndefined, "dummy">, resolveType: { [x: string]: any; } | undefined, requiredParam: number) => void
30+
31+
// Type node is accurate, preserve
32+
reuseTypeNode: Map | undefined = {},
33+
>reuseTypeNode : { [x: string]: any; } | undefined
34+
>{} : {}
35+
36+
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
37+
>reuseTypeNode2 : { [x: string]: any; } | undefined
38+
>{} : {}
39+
40+
// Resolve type node, requires adding | undefined
41+
resolveType: Map = {},
42+
>resolveType : { [x: string]: any; }
43+
>{} : {}
44+
45+
requiredParam: number) {
46+
>requiredParam : number
47+
48+
}
49+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// @strictNullChecks: true
2+
// @declaration: true
3+
4+
type Map = {} & { [P in string]: any }
5+
type MapOrUndefined = Map | undefined | "dummy"
6+
export class Foo {
7+
constructor(
8+
// Type node is accurate, preserve
9+
public reuseTypeNode?: Map | undefined,
10+
public reuseTypeNode2?: Exclude<MapOrUndefined, "dummy">,
11+
// Resolve type node, requires adding | undefined
12+
public resolveType?: Map,
13+
) { }
14+
}
15+
16+
export function foo1(
17+
// Type node is accurate, preserve
18+
reuseTypeNode: Map | undefined = {},
19+
reuseTypeNode2: Exclude<MapOrUndefined, "dummy"> = {},
20+
// Resolve type node, requires adding | undefined
21+
resolveType: Map = {},
22+
requiredParam: number) {
23+
24+
}

0 commit comments

Comments
 (0)