microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.08k stars 12.49k forks source link

`any` appears to shortcircut structural type checking #31295

Open webstrand opened 5 years ago

webstrand commented 5 years ago

TypeScript Version: 3.5.0-dev.20190507

Search Terms: any, unsound, extends, never

Code

// I detect if F is `any` by checking if F extends `never`.
// (F extends never ? true : false) produces `true|false` for `F = any`
// and `false` or `never` for everything else.
type Decider<F> = {
    prop: (F extends never ? true : false) extends false ? "isNotAny" : "isAny";
};

let foo!: Decider<string>;
let bar: Decider<any> = foo;

let fooProp: "isNotAny" = foo.prop;
let barProp: "isAny" = bar.prop;

Expected behavior: Either bar.prop should have the type "isAny"|"isNotAny" or foo should not be assignable to bar.

Actual behavior: foo is assignable to bar and bar.prop has the type "isAny" which is incompatible with foo.prop's "isNotAny".

Playground Link: link

jack-williams commented 5 years ago

This is an issue with variance markers and conditional types I think. The issue is that when assigning foo to bar the checker is just relating the type arguments (string and any) which are related. The markers are missing the fact that the type arguments appear as check types in a conditional type.

cc @weswigham

Related: #31251

jcalz commented 5 years ago

This is not directly relevant to the issue but in case you want to be able to distinguish any from never you can use the following instead:

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type Decider<F> = {
    prop: IfAny<F, "isAny", "isNotAny">
};

Which, again, doesn't address the actual assignability problem you're talking about.