-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
When --strictNullChecks is on
When --strictNullChecks is on Also update a baseline that changes now.
@@ -128,11 +128,11 @@ interface Foo { | |||
declare function test1(x: Foo): void; | |||
declare class Bar { | |||
d: number; | |||
e: number; | |||
e: number | undefined; |
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.
This is incorrect -- because there was a non-undefined
initializer, the property should not include undefined
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.
Fixed.
They are actually properties, so don't get undefined added when they have an initialiser. Also update baselines
As @blakeembrey points out in #12726, default parameters should only add undefined to the type for the signature. In the body of the function, the parameters should not add undefined. |
This is not the right fix. The behaviour is correct today -- you can pass |
Actually, this is the right fix. It just needs to improve control flow to remove the added |
Also a little code to emit them in declarations, but this doesn't work yet.
They don't narrow the parameter type, except to remove undefined, and only if the initialiser type doesn't include undefined.
src/compiler/declarationEmitter.ts
Outdated
writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError); | ||
// use the checker's type, not the declared type, for an initialized parameter (that isn't a parameter property) | ||
const isInitializedParameter = node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); | ||
const typeNode = isInitializedParameter ? undefined : node.type; |
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.
so we treat foo(x?: string = "")
different from foo(x?:string)
?
i think with this the first will be foo(x?: string | undefined)
and for the second foo(x?: string)
. i would say both should be the same.
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.
That seems like a good idea. Two notes though:
- We must not have very much declaration coverage because only two other tests broke.
- This change makes it clearer than ever that duplicate marking of optionality is the way to go with our current semantics (which ignores the missing-undefined vs provided-undefined distinction).
@mhegazy I think this is ready to go now if you want to take one more look. |
Related to adding undefined, though not strictly the same, this change adds '?' to unused IIFE parameters in quick info.
I don't think this PR is the right fix. The type of a parameter that has an initializer should only include |
@ahejlsberg The simpler fix doesn't take care of the narrowing an explicit function foo1(x: string = "string", b: number) {
x.length;
}
function foo2(x: string | undefined = "string", b: number) {
x.length; // ok, should be narrowed to string
~
!!! error TS2532: Object is possibly 'undefined'.
}
function foo3(x = "string", b: number) {
x.length; // ok, should be narrowed to string
} However, the code is so much simpler that I don't mind missing this case. I'll commit the simplification so you can see what it looks like. |
Just add undefined when displaying the type. Don't actually add it to the type.
OK, the simplified version is up now. |
Regarding this error case function foo2(x: string | undefined = "string", b: number) {
x.length; // ok, should be narrowed to string
~
!!! error TS2532: Object is possibly 'undefined'.
} Similarly to how we add |
OK, |
src/compiler/checker.ts
Outdated
// 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) { |
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.
Not sure I understand the func && !func.body
check. Also, where do you check if the binding pattern has a default value?
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.
Removal of undefined only applies to calls from declarationEmitter. But your other suggestion below (get rid of undefined for optional parameters) removes the need for this code entirely, so I'll just delete it.
src/compiler/checker.ts
Outdated
@@ -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)) { |
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
.
src/compiler/declarationEmitter.ts
Outdated
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) || |
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.
Could this be moved into writeTypeOfDeclaration
. Seems like you're having to do this twice.
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.
In declarationEmitter, writeTypeOfDeclaration
decides whether to call its own emitType
or the checker's writeTypeOfDeclaration
. I poked around and tried a few things, and I ended up creating a new TypeFormatFlags entry called AddUndefined
.
@@ -36,11 +36,11 @@ function f2(_a) { | |||
declare function f1({a, b}?: { | |||
a: number; | |||
b: string; | |||
}): void; | |||
} | undefined): void; |
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.
Seems sort of redundant to include undefined
when we already have a ?
on the parameter. Same in a number of cases below.
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.
Done.
1. Add undefined only when an initialized parameter is required (not optional). 2. Create isRequiredInitializedParameter helper function 3. Call this function only once from declarationEmitter
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.
Approved with the noted changes.
src/compiler/checker.ts
Outdated
@@ -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); |
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.
Change back to const
.
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.
done
src/compiler/declarationEmitter.ts
Outdated
if (shouldUseResolverType) { | ||
format |= TypeFormatFlags.AddUndefined; | ||
} | ||
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); |
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.
Use functional style:
const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue |
(shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0);
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.
done
Fixes #11900
Fixes #11424
Fixes #12726