-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
allow a flag that turns off covariant parameters when checking function assignability #6102
Comments
I checked out your branch and it has hundreds of warnings in lib.d.ts. It would be good to see a demo branch where this isn't the case. I cannot see how this would be useful in practice. Consider some normal code: function scan(x: { indexOf(element: {}): number; }) {
// ....
}
scan([1, 2]); This errors in your branch:
So the user is supposed to write... Have you run this flag on your own code? |
Sorry for confusion. A blooper was fixed. More working code: https://github.com/aleksey-bykov/TypeScript/commit/b5ce252342c26ebef2bde66036e58d6c05b0200b Lib.d.ts compiles no problem. Your example compiles too. Here is a real project that is taking advantage of it as described in #4895 Instructions:
|
I don't see what's being accomplished now function onEvent(x: (n: HTMLElement) => void) { }
onEvent((x: HTMLDivElement) => undefined);
Same behavior with the initial example you posted |
you are right, surprisingly that example does not work, it must be a interface A { x: number }
interface B { x: number, y: number }
let foo: (value: A) => void;
let bar: (value: B) => void;
bar = foo; // works in original, breaks in mine
does it mean that there is more than one place in the checker that does any insights would be welcome |
oh crap, you are right, just realized I don't make sense, let me get my shit together |
@Aleksey-Bykov: the check you actually want is the contravariant one: I tried checking some code with covariant parameters disallowed, and most errors found were *EDIT: None of the errors were exactly on two array types, one of them was always an interface extending an array. It looks like generics are always assumed to be covariant themselves, but structurally identical types are not. |
We chatted about this for a while in the team room. I was hoping to have a sort of Socratic "But what about ..." inspection of this since I know we've argued about it for a long time, but let me relay some points @ahejlsberg raised that might inform your next direction here. Your premise here is that it should be invalid to assign First off, the compiler assumes that If we keep that assumption, then there is a gaping unsoundness hole in the interface EventHandler<T> {
handle(data: T): void;
}
let x: EventHandler<Base>;
let y: EventHandler<Derived>;
// Relationship between x and y is different from the relationship
// between a and b, even though they are identical (!)
let a: { handle(data: Base): void };
let b: { handle(data: Derived): void }; If we remove that assumption, then you get a) a massive perf hit, because all generic types have to be structurally checked and b) terrible semantics: let x: Base[] = [];
let derived1: Derived;
// Error, cannot convert Derived[] to Base[] because types of 'push' do not match
x = [derived1]; Second, it's unclear why one should take such umbrage with functions when this problem is omnipresent in the entire type system: var x: Derived[] = [d1, d2, d3];
var y: Base[] = x;
y.push(base);
x.some(e => !e.isDerived); // true, wat You could argue "this is why I think it's instructive to think about the signatures It's easy to convince yourself that you can just think about signatures without thinking about the implications on the types they are embedded in, but this is a fallacy. In a language without extensive const and in/out annotations, this is an unsoundness you have to accept. It should be possible to write a TSLint rule that can detect most of the cases that humans would consider suspicious. But it's not going to be possible to wire this rule through the entire type system and get sane results. |
To me the most important point you made is that But to nourish this idea a bit more. I see no reason why
How come it is? Granted, for arrays it will work because they uses reference equality, but in general case, where equality is structural and hardcoded, it is not safe. To make it safe a comparison function should be passed too.
As long as |
there was a comment earlier, now it's gone.. anyway, i am glad someone brought it back to life what i seen from my little exercise is that THE ONLY USE CASE for covariant function parameters are the event handlers @RyanCavanaugh, am i wrong? where? if so, it looks like such an overkill to allow the covariant parameters just to make stupid event handlers work Instead, did you think of making event handlers discriminated by the event name WHOLE DIFFERENT METHODS merely making the event type argument value a CONTINUATION of the METHOD NAME? so instead of
we get (not in syntax, but semantically) something like :
where |
it looks like the method overloading was a bad idea for allowing specific event handlers on different event types what i mean is: if we had specific names for the method that register different event handlers the problem wound have not existed at all
as apposed to
|
Event handlers, and every single generic class that uses its type parameter in a method parameter signature (which is to say, nearly all of them). |
Lack of variance control is more problematic than export function Component<State>(options: {
connect: (dom: DomApi) => Property<State>;
render: (state: State) => Vnode;
}): Vnode; Here, I will have very poor guarantees that the developer is properly returning a One way to trick it is to force the type Param every time at declaration time with By the way, Flow added covariance/contravariance support a few versions ago. PS: I assume lack of variance control is the reason why everything is bivariant by default? |
I tracked it down : https://github.com/facebook/flow/blob/master/Changelog.md#v0210 Quote:
I suspect its not working based on the |
The still pending comment was only about transpiler (Babel) support (though I believe this has been since added to Babel). You can try this out at flowtype.org/try though. Would love to see something like this land in TypeScript! |
Currently the following flawed code is permitted:
Thank to the following line: https://github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts#L5654
I suggest to give developers a flag that would disable such unsound behavior allowing function subtyping the way it is supposed to be: https://en.wikipedia.org/wiki/Subtyping#Function_types.
An outline of a pull request of how this might look like: https://github.com/aleksey-bykov/TypeScript/commit/34b8f9b5a937cb7bb471771d5c9b9c4d4f629fc8
Related issues: #5961, #3523, #4895, #5741, #3067, #222, #5673
The text was updated successfully, but these errors were encountered: