-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Add undefined to default-initialised parameters #12033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
995732f
286845c
00ff0e5
bb9b681
96c14de
d62dd29
caad486
bb6f3ad
f097eaf
8b29e82
6046318
739c083
cee708d
61c742a
6543048
3b1309d
e0bf73f
156d5a9
8a9ee1a
e96b17b
01a9e4f
3c243db
bb40819
a235d54
7cf595a
6f7c984
6328e6c
c2cd4f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2694,7 +2694,11 @@ namespace ts { | |
| writePunctuation(writer, SyntaxKind.ColonToken); | ||
| writeSpace(writer); | ||
|
|
||
| buildTypeDisplay(getTypeOfSymbol(p), writer, enclosingDeclaration, flags, symbolStack); | ||
| let type = getTypeOfSymbol(p); | ||
| if (strictNullChecks && parameterNode.initializer && !(getModifierFlags(parameterNode) & ModifierFlags.ParameterPropertyModifier)) { | ||
| type = includeFalsyTypes(type, TypeFlags.Undefined); | ||
| } | ||
| buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); | ||
| } | ||
|
|
||
| function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { | ||
|
|
@@ -3163,7 +3167,8 @@ namespace ts { | |
| /** Return the inferred type for a binding element */ | ||
| function getTypeForBindingElement(declaration: BindingElement): Type { | ||
| const pattern = <BindingPattern>declaration.parent; | ||
| const parentType = getTypeForBindingElementParent(<VariableLikeDeclaration>pattern.parent); | ||
| let parentType = getTypeForBindingElementParent(<VariableLikeDeclaration>pattern.parent); | ||
|
||
|
|
||
| // If parent has the unknown (error) type, then so does this binding element | ||
| if (parentType === unknownType) { | ||
| return unknownType; | ||
|
|
@@ -3177,6 +3182,12 @@ namespace ts { | |
| } | ||
| return parentType; | ||
| } | ||
| // In strict null checking mode, a default value of a binding pattern adds undefined, | ||
| // which should be removed to get the type of the elements | ||
| const func = getContainingFunction(declaration); | ||
| if (strictNullChecks && func && !func.body && getFalsyFlags(parentType) & TypeFlags.Undefined) { | ||
|
||
| parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); | ||
| } | ||
|
|
||
| let type: Type; | ||
| if (pattern.kind === SyntaxKind.ObjectBindingPattern) { | ||
|
|
@@ -3275,6 +3286,16 @@ namespace ts { | |
| return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type; | ||
| } | ||
|
|
||
| /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ | ||
| function removeOptionalityFromAnnotation(annotatedType: Type, declaration: VariableLikeDeclaration): Type { | ||
| const annotationIncludesUndefined = strictNullChecks && | ||
| declaration.kind === SyntaxKind.Parameter && | ||
| declaration.initializer && | ||
| getFalsyFlags(annotatedType) & TypeFlags.Undefined && | ||
| !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); | ||
| return annotationIncludesUndefined ? getNonNullableType(annotatedType) : annotatedType; | ||
| } | ||
|
|
||
| // Return the inferred type for a variable, parameter, or property declaration | ||
| function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type { | ||
| if (declaration.flags & NodeFlags.JavaScriptFile) { | ||
|
|
@@ -3308,7 +3329,8 @@ namespace ts { | |
|
|
||
| // Use type from type annotation if one is present | ||
| if (declaration.type) { | ||
| return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); | ||
| const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration); | ||
| return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); | ||
| } | ||
|
|
||
| if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && | ||
|
|
@@ -5133,6 +5155,12 @@ namespace ts { | |
| Debug.assert(parameterIndex >= 0); | ||
| return parameterIndex >= signature.minArgumentCount; | ||
| } | ||
| const iife = getImmediatelyInvokedFunctionExpression(node.parent); | ||
| if (iife) { | ||
| return !node.type && | ||
| !node.dotDotDotToken && | ||
| indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
@@ -20617,9 +20645,15 @@ namespace ts { | |
| function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { | ||
| // Get type of the symbol if this is the valid symbol otherwise get type at location | ||
| const symbol = getSymbolOfNode(declaration); | ||
| const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) | ||
| let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) | ||
| ? getWidenedLiteralType(getTypeOfSymbol(symbol)) | ||
| : unknownType; | ||
| if (strictNullChecks && | ||
| declaration.kind === SyntaxKind.Parameter && | ||
| (declaration as ParameterDeclaration).initializer && | ||
| !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)) { | ||
| type = includeFalsyTypes(type, TypeFlags.Undefined); | ||
| } | ||
|
|
||
| getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1594,7 +1594,12 @@ namespace ts { | |
| emitTypeOfVariableDeclarationFromTypeLiteral(node); | ||
| } | ||
| else if (!hasModifier(node.parent, ModifierFlags.Private)) { | ||
| writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError); | ||
| // use the checker's type, not the declared type, | ||
| // for optional parameters and initialized ones that aren't a parameter property | ||
| const typeShouldAddUndefined = resolver.isOptionalParameter(node) || | ||
|
||
| node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); | ||
| const typeNode = typeShouldAddUndefined ? undefined : node.type; | ||
| writeTypeOfDeclaration(node, typeNode, getParameterDeclarationTypeVisibilityError); | ||
| } | ||
|
|
||
| function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| //// [defaultParameterAddsUndefinedWithStrictNullChecks.ts] | ||
| function f(addUndefined1 = "J", addUndefined2?: number) { | ||
| return addUndefined1.length + (addUndefined2 || 0); | ||
| } | ||
| function g(addUndefined = "J", addDefined: number) { | ||
| return addUndefined.length + addDefined; | ||
| } | ||
| let total = f() + f('a', 1) + f('b') + f(undefined, 2); | ||
| total = g('c', 3) + g(undefined, 4); | ||
|
|
||
| function foo1(x: string = "string", b: number) { | ||
| x.length; | ||
| } | ||
|
|
||
| function foo2(x = "string", b: number) { | ||
| x.length; // ok, should be string | ||
| } | ||
|
|
||
| function foo3(x: string | undefined = "string", b: number) { | ||
| x.length; // ok, should be string | ||
| } | ||
|
|
||
| function foo4(x: string | undefined = undefined, b: number) { | ||
| x; // should be string | undefined | ||
| } | ||
|
|
||
|
|
||
|
|
||
| // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 | ||
| foo1(undefined, 1); | ||
| foo2(undefined, 1); | ||
| foo3(undefined, 1); | ||
| foo4(undefined, 1); | ||
|
|
||
|
|
||
| function removeUndefinedButNotFalse(x = true) { | ||
| if (x === false) { | ||
| return x; | ||
| } | ||
| } | ||
|
|
||
| declare const cond: boolean; | ||
| function removeNothing(y = cond ? true : undefined) { | ||
| if (y !== undefined) { | ||
| if (y === false) { | ||
| return y; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
|
|
||
| //// [defaultParameterAddsUndefinedWithStrictNullChecks.js] | ||
| function f(addUndefined1, addUndefined2) { | ||
| if (addUndefined1 === void 0) { addUndefined1 = "J"; } | ||
| return addUndefined1.length + (addUndefined2 || 0); | ||
| } | ||
| function g(addUndefined, addDefined) { | ||
| if (addUndefined === void 0) { addUndefined = "J"; } | ||
| return addUndefined.length + addDefined; | ||
| } | ||
| var total = f() + f('a', 1) + f('b') + f(undefined, 2); | ||
| total = g('c', 3) + g(undefined, 4); | ||
| function foo1(x, b) { | ||
| if (x === void 0) { x = "string"; } | ||
| x.length; | ||
| } | ||
| function foo2(x, b) { | ||
| if (x === void 0) { x = "string"; } | ||
| x.length; // ok, should be string | ||
| } | ||
| function foo3(x, b) { | ||
| if (x === void 0) { x = "string"; } | ||
| x.length; // ok, should be string | ||
| } | ||
| function foo4(x, b) { | ||
| if (x === void 0) { x = undefined; } | ||
| x; // should be string | undefined | ||
| } | ||
| // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 | ||
| foo1(undefined, 1); | ||
| foo2(undefined, 1); | ||
| foo3(undefined, 1); | ||
| foo4(undefined, 1); | ||
| function removeUndefinedButNotFalse(x) { | ||
| if (x === void 0) { x = true; } | ||
| if (x === false) { | ||
| return x; | ||
| } | ||
| } | ||
| function removeNothing(y) { | ||
| if (y === void 0) { y = cond ? true : undefined; } | ||
| if (y !== undefined) { | ||
| if (y === false) { | ||
| return y; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
|
|
||
| //// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] | ||
| declare function f(addUndefined1?: string | undefined, addUndefined2?: number | undefined): number; | ||
| declare function g(addUndefined: string | undefined, addDefined: number): number; | ||
| declare let total: number; | ||
| declare function foo1(x: string | undefined, b: number): void; | ||
| declare function foo2(x: string | undefined, b: number): void; | ||
| declare function foo3(x: string | undefined, b: number): void; | ||
| declare function foo4(x: string | undefined, b: number): void; | ||
| declare function removeUndefinedButNotFalse(x?: boolean | undefined): false | undefined; | ||
| declare const cond: boolean; | ||
| declare function removeNothing(y?: boolean | undefined): boolean; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| === tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === | ||
| function f(addUndefined1 = "J", addUndefined2?: number) { | ||
| >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) | ||
| >addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) | ||
| >addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) | ||
|
|
||
| return addUndefined1.length + (addUndefined2 || 0); | ||
| >addUndefined1.length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) | ||
| >length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) | ||
| } | ||
| function g(addUndefined = "J", addDefined: number) { | ||
| >g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) | ||
| >addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) | ||
| >addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) | ||
|
|
||
| return addUndefined.length + addDefined; | ||
| >addUndefined.length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) | ||
| >length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) | ||
| } | ||
| let total = f() + f('a', 1) + f('b') + f(undefined, 2); | ||
| >total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) | ||
| >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) | ||
| >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) | ||
| >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) | ||
| >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| total = g('c', 3) + g(undefined, 4); | ||
| >total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) | ||
| >g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) | ||
| >g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| function foo1(x: string = "string", b: number) { | ||
| >foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) | ||
| >b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 35)) | ||
|
|
||
| x.length; | ||
| >x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) | ||
| >length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| } | ||
|
|
||
| function foo2(x = "string", b: number) { | ||
| >foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) | ||
| >b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 27)) | ||
|
|
||
| x.length; // ok, should be string | ||
| >x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) | ||
| >length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| } | ||
|
|
||
| function foo3(x: string | undefined = "string", b: number) { | ||
| >foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) | ||
| >b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 47)) | ||
|
|
||
| x.length; // ok, should be string | ||
| >x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) | ||
| >length : Symbol(String.length, Decl(lib.d.ts, --, --)) | ||
| } | ||
|
|
||
| function foo4(x: string | undefined = undefined, b: number) { | ||
| >foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) | ||
| >undefined : Symbol(undefined) | ||
| >b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 48)) | ||
|
|
||
| x; // should be string | undefined | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) | ||
| } | ||
|
|
||
|
|
||
|
|
||
| // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 | ||
| foo1(undefined, 1); | ||
| >foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| foo2(undefined, 1); | ||
| >foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| foo3(undefined, 1); | ||
| >foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| foo4(undefined, 1); | ||
| >foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
|
|
||
| function removeUndefinedButNotFalse(x = true) { | ||
| >removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 31, 19)) | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) | ||
|
|
||
| if (x === false) { | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) | ||
|
|
||
| return x; | ||
| >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) | ||
| } | ||
| } | ||
|
|
||
| declare const cond: boolean; | ||
| >cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) | ||
|
|
||
| function removeNothing(y = cond ? true : undefined) { | ||
| >removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 28)) | ||
| >y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) | ||
| >cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| if (y !== undefined) { | ||
| >y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| if (y === false) { | ||
| >y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) | ||
|
|
||
| return y; | ||
| >y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe turn this into a helper function. You have the same logic duplicated in
writeTypeOfDeclaration.