GeezerVison is showing github.com — Strict function types by ahejlsberg · Pull Request #18654 · microsoft/TypeScript
Conversation
With this PR we introduce a --strictFunctionTypes mode in which function type parameter positions are checked contravariantly instead of bivariantly. The stricter checking applies to all function types, except those originating in method or construcor declarations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array<T>) continue to mostly relate covariantly. The impact of strictly checking methods would be a much bigger breaking change as a large number of generic types would become invariant (even so, we may continue to explore this stricter mode).
The --strictFunctionTypes switch is part of the --strict family of switches, meaning that it defaults to on in --strict mode. This PR is therefore a breaking change only in --strict mode.
Consider the following example in which Animal is the supertype of Dog and Cat:
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2; // Error with --strictFunctionTypes
f2 = f1; // Ok
f2 = f3; // Error
The first assignment is permitted in default type checking mode, but flagged as an error in strict function types mode. Intuitively, the default mode permits the assignment because it is possibly sound, whereas strict function types mode makes it an error because it isn't provably sound. In either mode the third assignment is an error because it is never sound.
Another way to describe the example is that the type (x: T) => void is bivariant (i.e. covariant or contravariant) for T in default type checking mode, but contravariant for T in strict function types mode.
Another example:
interface Comparer<T> {
compare(a: T, b: T): number;
}
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;
animalComparer = dogComparer; // Ok because of bivariance
dogComparer = animalComparer; // Ok
In --strictFunctionTypes mode the first assignment is still permitted because compare is declared as a method. Effectively, T is bivariant in Comparer<T> because it is used only in method parameter positions. However, changing compare to be a property with a function type causes stricter checking to take effect:
interface Comparer<T> {
compare: (a: T, b: T) => number;
}
declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;
animalComparer = dogComparer; // Error
dogComparer = animalComparer; // Ok
The first assignment is now an error. Effectively, T is contravariant in Comparer<T> because it is used only in function type parameter positions.
By the way, note that whereas some languages (e.g. C# and Scala) require variance annotations (out/in or +/-), variance emerges naturally from the actual use of a type parameter within a generic type due to TypeScript's structural type system.
We also improve type inference involving contravariant positions in this PR:
function combine<T>(...funcs: ((x: T) => void)[]): (x: T) => void {
return x => {
for (const f of funcs) f(x);
}
}
function animalFunc(x: Animal) {}
function dogFunc(x: Dog) {}
let combined = combine(animalFunc, dogFunc); // (x: Dog) => void
Above, all inferences for T originate in contravariant positions, and we therefore infer the best common subtype for T. This contrasts with inferences from covariant positions, where we infer the best common supertype. Note that inferences from covariant positions have precedence over inferences from contravariant positions.
Fixes #6102.
Fixes #7472.
Fixes #9514.
Fixes #9765.
Fixes #12498.
Fixes #13248.
Fixes #15579.
Fixes #18337.
Fixes #18466.
Also related are #10717 and #14973.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
aluanhaddad, Brooooooklyn, trotyl, HerringtonDarkholme, FabianLauer, plantain-00, dmikis, MrKomish, federicoce-moravia, sledorze, and 34 more reacted with thumbs up emoji
RyanCavanaugh, aluanhaddad, trotyl, HerringtonDarkholme, plantain-00, federicoce-moravia, sebald, brendanzab, sean-vieira, hccampos, and 18 more reacted with hooray emoji
Strate reacted with confused emoji
aluanhaddad, alexrock, trotyl, HerringtonDarkholme, plantain-00, federicoce-moravia, sledorze, sean-vieira, hccampos, leoasis, and 10 more reacted with heart emoji
All reactions
-
👍
44 reactions
-
🎉
28 reactions
-
😕
1 reaction
-
❤️
20 reactions
|
I ran RWC on this and saw changes only in elaborations and order of printing union types. No errors were added or removed, and no types changed.
The changes appear to result from better type caching leading to less elaboration. The only interesting result was from the Azure Framework tests, where one incorrectly duplicated error disappeared entirely, maybe because the whole error was already issued just prior.
I'll run DefinitelyTyped next and look for diffs.
|
aluanhaddad reacted with thumbs up emoji
aluanhaddad reacted with hooray emoji
All reactions
-
👍
1 reaction
-
🎉
1 reaction
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Ah, good point. I forgot --strict the first time around. First I compiled everything using 0ac8406, the commit before this PR. Then I compiled again with this PR's commits and diffed the changes. Interestingly, a few packages on DefinitelyTyped still have more errors even without --strictFunctionTypes. The errors look like they come from tighter variance checking too, so it's likely that some changes are escaping from behind the flag.
Take a look at the test files for recompose, rx-lite and rx.wamp (which depends on rx-lite). Recompose is clearer to see (the rx* errors are obscured by overloads in rx-lite):
recompose-tests.tsx(248,7): error TS2345: Argument of type 'InferableComponentEnhancerWithProps<{ count: number; } & { update: (state: number) => number; }, {}>' is not assignable to parameter of type 'ComponentEnhancer<any, any> | InferableComponentEnhancerWithProps<{}, {}>'.
Type 'InferableComponentEnhancerWithProps<{ count: number; } & { update: (state: number) => number; }, {}>' is not assignable to type 'InferableComponentEnhancerWithProps<{}, {}>'.
Type '{}' is not assignable to type '{ count: number; } & { update: (state: number) => number; }'.
Type '{}' is not assignable to type '{ count: number; }'.
Property 'count' is missing in type '{}'.
Notice that the direction of assignability switches, I think because of contravariance.
In total, I noticed changes with
- recompose
- rx-lite
- rx.wamp (for nearly the same reason as rx-lite)
- jasmine
- a few other projects had different line numbers for errors in lib.d.ts, which doesn't make sense to me.
I'll take a look at the results with --strictFunctionChecks turned on next. It's only halfway done, but there are already lots of failures, so I'll only provide a summary.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Nathan Shively-Sanders (@sandersn) With the latest commits we revert to always performing a structural comparison when the relationships indicated by getVariances don't hold. In effect, the variance information computed by getVariances is now an indication of which relationships to try first as an optimization, but it doesn't preclude other relationships. In the default checking mode we now behave exactly the same way as before, so I would expect no changes to RWC baselines or DefinitelyTyped. I would expect to see new errors only in --strictFunctionTypes mode.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
}
|
|
|
if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) {
|
|
|
const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false);
|
|
|
const name = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false) : createIdentifier("?");
|
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.
When does this case occur?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
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 happens when we have a synthetic type parameter, such as the type parameters we create for tuple types and the markerXXXType type parameters we use for variance determination.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
LGTM but I'd like to see what happens if we run RWC with this forced on
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
With the latest commits, both RWC and DT crash much more due to running out of memory. I only get about 2/3 of the RWC tests to succeed, and DT crashes at 'bookshelf'.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
With --strictFunctionTypes
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Nathan Shively-Sanders (@sandersn) It is likely because we do more structural comparisons of types. There are enough edge cases (e.g. any or void type arguments) where types don't act exactly according to the gathered variance information, so if the optimized first check of the type arguments doesn't succeed we need to do a structural comparison. We'll have to look at the specific cases, but it's not clear what else we could do.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Latest commits address code review feedback. Also, we improve error elaboration for generic types where one or more type parameters are invariant. In some cases generic types that are covariant in regular type checking mode become invariant in --strictFunctionTypes mode because one or more type parameters are used in both co- and contravariant positions with the generic type. In order to make it easier to diagnose why such types are invariant, if any of the type parameters are invariant we reset the reported errors and instead force a structural comparison (which will include elaborations that reveal the reason).
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Anders Hejlsberg (@ahejlsberg) Even though you mention only 3% of type packages are affected, I think the real negative impact is in much higher considering that major packages like angular, express, hapi, cucumber, selenium-webdriver etc. are affected. Hopefully the undocummented "skipLibCheck" option can rescue some projects.
|
styfle and zpdDG4gta8XKpMCd reacted with confused emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
xnnkmd The list above doesn't show it, but in practically all cases (including all of the ones you mention) the errors are in the tests associated with the type package, not in the package itself. So, there should be no need to use --skipLibCheck.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
the errors are in the tests associated with the type package, not in the package itself
Anders Hejlsberg (@ahejlsberg) Can you post a list of packages where only the package itself is affected? (not the tests)
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
gentlemen, pardon me, but i don't understand your attitude towards these mighty new features... even if we break 97% of all code out there, even then:
-
it's under the flag --strictFunctionTypes, so if you are scared of it, stay away from it! so if you did stay away from it, you are in the old safe TypeScript and nothing brakes
-
secondly, the code that breaks is ill and it's a good thing that you see it breaks, it means you know where you weak parts are, so please be grateful, will you?
|
gcnew, nicolashenry, adidahiya, SlurpTheo, and jesseschalken reacted with thumbs up emoji
gcnew reacted with laugh emoji
All reactions
-
👍
5 reactions
-
😄
1 reaction
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
DefinitelyTyped/DefinitelyTyped#20219 fixes the packages on DefinitelyTyped that break with --strictFunctionTypes. Plus it updates the tests for many "popular" packages whose tests will break so you can take a look to see how you might have to update your code.
Packages fixed by DefinitelyTyped/DefinitelyTyped#20219:
- backbone
- bootbox
- cordova-plugin-appbrowser
- ibm-mobilefirst
- jointjs
- jquery
- knockout
- lodash
- ramda
- react
I also fixed tests in
- node
- log4js
- q
- react-native
- react-redux
- sharepoint
- webpack
Notes:
- Here, "popular" just means a random selection of packages I have heard of. I didn't fix hapi's tests, for example, even though I think it's fairly popular.
- I didn't submit PRs to projects that bundles types with their packages. I don't have a good way to scan all Typescript that eventually ends up on npm.
|
styfle, xnnkmd, SlurpTheo, and StefanRein reacted with hooray emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Nov 6, 2017
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|