- Notifications
You must be signed in to change notification settings - Fork13.2k
Adds support for type parameter defaults#13487
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
rbuckton commentedJan 14, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@RyanCavanaugh: this is my take on refreshing the genericDefaults PR. It should resolve the open items you had on#6354 regarding checking defaults against constraints. I also added missing support for typeToString and the declaration emitter. Please let me know if you can suggest any additional tests. @mhegazy: when we discussed this, I suggested that interface declarations that have defaults must have identical defaults, allowing you to mix declarations with and without defaults: interfaceX<A,B>{}interfaceX<A,B=number>{} However, this PR does not reflect that suggestion and is more strict, requiring all declarations to have identical defaults as well. It seems more consistent and we can loosen this restriction at any point if we so choose. @ahejlsberg: The |
src/compiler/checker.ts Outdated
| const types = (<UnionOrIntersectionType>type).types; | ||
| const defaultTypes = filter(map(types, getResolvedDefaultWorker), x => x !== circularConstraintOrDefaultType); | ||
| return type.flags & TypeFlags.Union && defaultTypes.length === types.length ? getUnionType(defaultTypes) : | ||
| type.flags & TypeFlags.Intersection && defaultTypes.length ? getIntersectionType(defaultTypes) : |
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 a little confusing - so if there were any circular types in a union, you returnundefined. But you disregard all circularities in an intersection 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.
I reused this fromline 4827, above. It does seem like it should also be performing a comparison against types.length as well, however I am unsure as to whether there was a specific reason for 4827 to ignore circularity in an intersection 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.
src/compiler/checker.ts Outdated
| typeArguments = []; | ||
| } | ||
| const mapper: TypeMapper = t => { | ||
| for (let i = 0; i < numTypeParameters; i++) { |
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.
You technically don't need to run through the initial type arguments that were already supplied, do you? Could you just start atnumTypeArguments as you did 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.
No, if a type parameter has a default type that is a reference to another type parameter, then we need to map it to the other, possibly earlier, type parameter.
Otherwise you end up with this:
declarefunctionf<T,U=T>();f<number>() // becomes f<number, T>()
TheT is invalid at the call site, so we replace it with the typenumber for the supplied type argument.
src/compiler/checker.ts Outdated
| } | ||
| const mapper: TypeMapper = t => { | ||
| for (let i = 0; i < numTypeParameters; i++) { | ||
| if (t === typeParameters[i]) { |
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.
if(t!==typeParameters[i]){continue;}
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.
I have restructured and simplified this in25cb02e
rbuckton commentedJan 19, 2017
The latest commit makes the following changes (also noted above):
For example: interfaceX{}// no type parametersinterfaceX<T=never>{}// ok, add a new default type parameterinterfaceY<T>{}// one required type parameterinterfaceY<T=never>{}// ok, make an existing type parameter optionalinterfaceZ{}// no type parametersinterfaceZ<T>{}// error, type parameter lists aren't identical//// uncomment the following to remove the error as the declarations will merge:// interface Z<T = never> { } |
rbuckton commentedJan 20, 2017
@ahejlsberg Per our conversation in the design meeting, I've further modified |
src/compiler/checker.ts Outdated
| function getInferredType(context: InferenceContext, index: number): Type { | ||
| let inferredType = context.inferredTypes[index]; | ||
| let inferredType = context.inferredTypes[index] || getSuppliedType(context, index); |
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.
Weird newline thing here
src/compiler/checker.ts Outdated
| minTypeArgumentCount = maxTypeArgumentCount; | ||
| } | ||
| if (typeParameters && typeParameters.length > maxTypeArgumentCount) { | ||
| // Trim the type parameters to the maximum length |
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.
What's an example of how this could happen?
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 to preserve the current behavior with respect to where we report errors for type parameter lists:
interfaceX<T>{}// no error on first declinterfaceX<T,U>{}// no defaults so we trim to `<T>` and report error for this decl
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.
Makes sense, thanks
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.
I don't think it works to trim like this. You might have members that reference the trimmed type parameter and those members would never be properly instantiated. In other words, the trimmed type parameters could "leak".
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.
The intent is that excess type parameters would result in an error, consistent with our behavior today as in the example above. Today we only get the type parameters of the first declaration, so this shouldn't be any different than how we would instantiateX above today.
src/compiler/diagnosticMessages.json Outdated
| "category":"Error", | ||
| "code":2704 | ||
| }, | ||
| "Required type parameters may not follow optional type parameters": { |
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.
Missing trailing period in this and in line 2046
rbuckton commentedJan 21, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Following the discussion during the design meeting, I am making the following changes:
For example: declarefunctionf<T,U=T>(a:T,b?:U):void;f(1);// ok. Using inference: T is number, U is number.f(1,"a");// ok. Using inference: T is number, U is string.f<number>(1,"a");// error. Not using inference: T is number, U is number. While this is generally stricter, we can loosen this restriction at a later date if necessary. |
rbuckton commentedJan 21, 2017
I have added the following restriction:
This is to provide an error for the following case: interfaceA{}interface B{x:A;// error}interfaceB<A=number>{} |
rbuckton commentedFeb 11, 2017
I have looked into including#13609 in this change, but it results in significant change in behavior that may be too much to take on if we want to take this for 2.2. @ahejlsberg Do you have any other feedback for this change? |
mramos-dev commentedFeb 15, 2017
@rbuckton You mentioned taking this for 2.2 in an earlier comment but I see that this issue still has the 2.3 milestone attached. Will this be in 2.2? |
mhegazy commentedFeb 15, 2017
jeffijoe commentedFeb 19, 2017
Can't wait for this, writing a library in TS and this will save the consumers from a lot of confusion. Great work! |
Uh oh!
There was an error while loading.Please reload this page.
This PR adds support for defaults for generic type parameters and replaces#6354 as it is fairly out of date.
Default types for generic type parameters have the following syntax:
Default types have the following rules:
{}will be used.{}is inferred.This is the existing behvaior.This PR also updates the signature for
Promise<T>andPromiseLike<T>to take full advantage of the new support for type parameter defaults.Fixes#2175
Fixes#10977
Fixes#12725
Fixes#12886
Fixes#12910
Fixes#13008
Fixes#13015
Fixes#13117