Skip to content

Commit 46d9f37

Browse files
authored
Merge pull request #12033 from Microsoft/add-undefined-to-default-valued-parameters
Add undefined to default-initialised parameters
2 parents 0652298 + c2cd4f6 commit 46d9f37

9 files changed

+534
-11
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,7 +2690,11 @@ namespace ts {
26902690
writePunctuation(writer, SyntaxKind.ColonToken);
26912691
writeSpace(writer);
26922692

2693-
buildTypeDisplay(getTypeOfSymbol(p), writer, enclosingDeclaration, flags, symbolStack);
2693+
let type = getTypeOfSymbol(p);
2694+
if (isRequiredInitializedParameter(parameterNode)) {
2695+
type = includeFalsyTypes(type, TypeFlags.Undefined);
2696+
}
2697+
buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack);
26942698
}
26952699

26962700
function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) {
@@ -3271,6 +3275,16 @@ namespace ts {
32713275
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
32723276
}
32733277

3278+
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
3279+
function removeOptionalityFromAnnotation(annotatedType: Type, declaration: VariableLikeDeclaration): Type {
3280+
const annotationIncludesUndefined = strictNullChecks &&
3281+
declaration.kind === SyntaxKind.Parameter &&
3282+
declaration.initializer &&
3283+
getFalsyFlags(annotatedType) & TypeFlags.Undefined &&
3284+
!(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined);
3285+
return annotationIncludesUndefined ? getNonNullableType(annotatedType) : annotatedType;
3286+
}
3287+
32743288
// Return the inferred type for a variable, parameter, or property declaration
32753289
function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type {
32763290
if (declaration.flags & NodeFlags.JavaScriptFile) {
@@ -3304,7 +3318,8 @@ namespace ts {
33043318

33053319
// Use type from type annotation if one is present
33063320
if (declaration.type) {
3307-
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
3321+
const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration);
3322+
return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality);
33083323
}
33093324

33103325
if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) &&
@@ -5198,6 +5213,12 @@ namespace ts {
51985213
Debug.assert(parameterIndex >= 0);
51995214
return parameterIndex >= signature.minArgumentCount;
52005215
}
5216+
const iife = getImmediatelyInvokedFunctionExpression(node.parent);
5217+
if (iife) {
5218+
return !node.type &&
5219+
!node.dotDotDotToken &&
5220+
indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length;
5221+
}
52015222

52025223
return false;
52035224
}
@@ -20702,6 +20723,13 @@ namespace ts {
2070220723
return false;
2070320724
}
2070420725

20726+
function isRequiredInitializedParameter(parameter: ParameterDeclaration) {
20727+
return strictNullChecks &&
20728+
!isOptionalParameter(parameter) &&
20729+
parameter.initializer &&
20730+
!(getModifierFlags(parameter) & ModifierFlags.ParameterPropertyModifier);
20731+
}
20732+
2070520733
function getNodeCheckFlags(node: Node): NodeCheckFlags {
2070620734
node = getParseTreeNode(node);
2070720735
return node ? getNodeLinks(node).flags : undefined;
@@ -20793,10 +20821,12 @@ namespace ts {
2079320821
function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) {
2079420822
// Get type of the symbol if this is the valid symbol otherwise get type at location
2079520823
const symbol = getSymbolOfNode(declaration);
20796-
const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
20824+
let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature))
2079720825
? getWidenedLiteralType(getTypeOfSymbol(symbol))
2079820826
: unknownType;
20799-
20827+
if (flags & TypeFormatFlags.AddUndefined) {
20828+
type = includeFalsyTypes(type, TypeFlags.Undefined);
20829+
}
2080020830
getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags);
2080120831
}
2080220832

@@ -20895,6 +20925,7 @@ namespace ts {
2089520925
isTopLevelValueImportEqualsWithEntityName,
2089620926
isDeclarationVisible,
2089720927
isImplementationOfOverload,
20928+
isRequiredInitializedParameter,
2089820929
writeTypeOfDeclaration,
2089920930
writeReturnTypeOfSignatureDeclaration,
2090020931
writeTypeOfExpression,

src/compiler/declarationEmitter.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,20 @@ namespace ts {
324324
function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) {
325325
writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic;
326326
write(": ");
327-
if (type) {
327+
328+
// use the checker's type, not the declared type,
329+
// for non-optional initialized parameters that aren't a parameter property
330+
const shouldUseResolverType = declaration.kind === SyntaxKind.Parameter &&
331+
resolver.isRequiredInitializedParameter(declaration as ParameterDeclaration);
332+
if (type && !shouldUseResolverType) {
328333
// Write the type
329334
emitType(type);
330335
}
331336
else {
332337
errorNameNode = declaration.name;
333-
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
338+
const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue |
339+
(shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0);
340+
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer);
334341
errorNameNode = undefined;
335342
}
336343
}

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2474,7 +2474,8 @@
24742474
InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type
24752475
InTypeAlias = 0x00000200, // Writing type in type alias declaration
24762476
UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file.
2477-
SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type.
2477+
SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type.
2478+
AddUndefined = 0x00001000, // Add undefined to types of initialized, non-optional parameters
24782479
}
24792480

24802481
export const enum SymbolFormatFlags {
@@ -2579,6 +2580,7 @@
25792580
isDeclarationVisible(node: Declaration): boolean;
25802581
collectLinkedAliases(node: Identifier): Node[];
25812582
isImplementationOfOverload(node: FunctionLikeDeclaration): boolean;
2583+
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
25822584
writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
25832585
writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
25842586
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;

tests/baselines/reference/contextuallyTypedIife.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,17 +253,17 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } });
253253
// missing arguments
254254
(function(x, undefined) { return x; })(42);
255255
>(function(x, undefined) { return x; })(42) : number
256-
>(function(x, undefined) { return x; }) : (x: number, undefined: any) => number
257-
>function(x, undefined) { return x; } : (x: number, undefined: any) => number
256+
>(function(x, undefined) { return x; }) : (x: number, undefined?: any) => number
257+
>function(x, undefined) { return x; } : (x: number, undefined?: any) => number
258258
>x : number
259259
>undefined : any
260260
>x : number
261261
>42 : 42
262262

263263
((x, y, z) => 42)();
264264
>((x, y, z) => 42)() : number
265-
>((x, y, z) => 42) : (x: any, y: any, z: any) => number
266-
>(x, y, z) => 42 : (x: any, y: any, z: any) => number
265+
>((x, y, z) => 42) : (x?: any, y?: any, z?: any) => number
266+
>(x, y, z) => 42 : (x?: any, y?: any, z?: any) => number
267267
>x : any
268268
>y : any
269269
>z : any
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//// [defaultParameterAddsUndefinedWithStrictNullChecks.ts]
2+
function f(addUndefined1 = "J", addUndefined2?: number) {
3+
return addUndefined1.length + (addUndefined2 || 0);
4+
}
5+
function g(addUndefined = "J", addDefined: number) {
6+
return addUndefined.length + addDefined;
7+
}
8+
let total = f() + f('a', 1) + f('b') + f(undefined, 2);
9+
total = g('c', 3) + g(undefined, 4);
10+
11+
function foo1(x: string = "string", b: number) {
12+
x.length;
13+
}
14+
15+
function foo2(x = "string", b: number) {
16+
x.length; // ok, should be string
17+
}
18+
19+
function foo3(x: string | undefined = "string", b: number) {
20+
x.length; // ok, should be string
21+
}
22+
23+
function foo4(x: string | undefined = undefined, b: number) {
24+
x; // should be string | undefined
25+
}
26+
27+
28+
29+
// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4
30+
foo1(undefined, 1);
31+
foo2(undefined, 1);
32+
foo3(undefined, 1);
33+
foo4(undefined, 1);
34+
35+
36+
function removeUndefinedButNotFalse(x = true) {
37+
if (x === false) {
38+
return x;
39+
}
40+
}
41+
42+
declare const cond: boolean;
43+
function removeNothing(y = cond ? true : undefined) {
44+
if (y !== undefined) {
45+
if (y === false) {
46+
return y;
47+
}
48+
}
49+
return true;
50+
}
51+
52+
53+
//// [defaultParameterAddsUndefinedWithStrictNullChecks.js]
54+
function f(addUndefined1, addUndefined2) {
55+
if (addUndefined1 === void 0) { addUndefined1 = "J"; }
56+
return addUndefined1.length + (addUndefined2 || 0);
57+
}
58+
function g(addUndefined, addDefined) {
59+
if (addUndefined === void 0) { addUndefined = "J"; }
60+
return addUndefined.length + addDefined;
61+
}
62+
var total = f() + f('a', 1) + f('b') + f(undefined, 2);
63+
total = g('c', 3) + g(undefined, 4);
64+
function foo1(x, b) {
65+
if (x === void 0) { x = "string"; }
66+
x.length;
67+
}
68+
function foo2(x, b) {
69+
if (x === void 0) { x = "string"; }
70+
x.length; // ok, should be string
71+
}
72+
function foo3(x, b) {
73+
if (x === void 0) { x = "string"; }
74+
x.length; // ok, should be string
75+
}
76+
function foo4(x, b) {
77+
if (x === void 0) { x = undefined; }
78+
x; // should be string | undefined
79+
}
80+
// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4
81+
foo1(undefined, 1);
82+
foo2(undefined, 1);
83+
foo3(undefined, 1);
84+
foo4(undefined, 1);
85+
function removeUndefinedButNotFalse(x) {
86+
if (x === void 0) { x = true; }
87+
if (x === false) {
88+
return x;
89+
}
90+
}
91+
function removeNothing(y) {
92+
if (y === void 0) { y = cond ? true : undefined; }
93+
if (y !== undefined) {
94+
if (y === false) {
95+
return y;
96+
}
97+
}
98+
return true;
99+
}
100+
101+
102+
//// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts]
103+
declare function f(addUndefined1?: string, addUndefined2?: number): number;
104+
declare function g(addUndefined: string | undefined, addDefined: number): number;
105+
declare let total: number;
106+
declare function foo1(x: string | undefined, b: number): void;
107+
declare function foo2(x: string | undefined, b: number): void;
108+
declare function foo3(x: string | undefined, b: number): void;
109+
declare function foo4(x: string | undefined, b: number): void;
110+
declare function removeUndefinedButNotFalse(x?: boolean): false | undefined;
111+
declare const cond: boolean;
112+
declare function removeNothing(y?: boolean | undefined): boolean;

0 commit comments

Comments
 (0)