-
Notifications
You must be signed in to change notification settings - Fork 12.8k
type inference lost literal type #27704
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
Comments
When typing an object literal we don't know what type it's supposed to be without a hint. In |
I'm not familiar with PL, just a suggestion without confirming feasibility: Can we make TypeScript Compiler infer literal types by default, and allow compiler re-infer the type of a variable while detecting variables are assigned new values? For example: let a = 'hello'; // now infer the type of `a` is `'hello'`;
a = 'world'; // because of the type of `a` is inferred, and type `'hello'` and type `'world'` are both literal type of type `string`, so now we can re-infer the type of `a` is `'hello' | 'world'`
a = 1; // now assign a value which type is `1`, which is a literal type of `number` not `string`, so we may get an error here.
a = b as string; // assume we don't have a wrong line above, now compiler should re-infer the type of `a` is `string` And for those types are not inferred, we don't allow them can be re-inferred. let a: 'hello' = 'hello';
a = 'world'; // got an error here. because the type of `a` is not inferred, so compiler won't re-infer here. |
@foisonocean That actually is possible if the variable has a string union type. But not if it has a function f(_: "foo") {}
function g(_: "bar") {}
let s: "foo" | "bar" = "foo";
f(s);
s = "bar";
g(s); The problem is that without the explicit type annotation, the compiler can't time-travel and determine that it should have been |
I have a similar issue where the enum literal type is lost when going through a function with generic. export enum Status {
SAVING = 'SAVING',
ERROR = 'ERROR'
}
export type State = SavingState | {
_tag: Status.ERROR;
prop2: any;
};
export type SavingState = {
_tag: Status.SAVING;
prop1: any;
};
const thisWorks: SavingState = {
_tag: Status.SAVING,
prop1: 'whatever'
};
const passThrough = <T>(arg: T): T => arg;
// The following line fails with
// error TS2322: Type '{ _tag: Status; prop1: string; }' is not assignable to type 'SavingState'.
// Types of property '_tag' are incompatible.
// Type 'Status' is not assignable to type 'Status.SAVING'
const thisFailsWhenUsingEnum: SavingState = passThrough({
_tag: Status.SAVING,
prop1: 'whatever'
}); I found 2 possible workarounds in such situations: // Workaround 1 specify the type in the generic function
const workaround1 = passThrough<SavingState>({
_tag: Status.SAVING,
prop1: 'whatever'
});
// Workaround 2 re-specify the type of Status.SAVING
const workaround2 = passThrough({
_tag: Status.SAVING as Status.SAVING,
prop1: 'whatever'
}); |
However it feels non-intuitive that the compiler uses string as the type for enum values instead of using the corresponding string literal. I would expect the enum to behave similarly to the same code written using string literals: // Using literal constants the same code starts working
export const Status = {
SAVING: 'SAVING',
ERROR: 'ERROR'
}
export type State = SavingState | {
_tag: typeof Status.ERROR;
prop2: any;
};
export type SavingState = {
_tag: typeof Status.SAVING;
prop1: any;
};
const passThrough = <T>(arg: T): T => arg;
// This fails using enum but works fine here
const thisWorks: SavingState = passThrough({
_tag: Status.SAVING,
prop1: 'whatever'
}); |
Is this related? interface Options {
a: true
}
declare function test(options: Options): void
test({a: true})
// Success
const x = {
a: true
}
test(x)
/** [ts] Error
* Argument of type '{ a: boolean; }' is not assignable to parameter of type 'Options'.
* Types of property 'a' are incompatible.
* Type 'boolean' is not assignable to type 'true'.
*/
const y: Options = {
a: true
}
test(y)
// Success |
@LuminescentMoon Yes, that is an exact manifestation of the problem. The const x = {
a: true
} as const; |
@jack-williams |
@foisonocean Will be available in 3.4 (you can get it now to play with using |
Here's a MWE I just reduced: enum X {
A,
B
}
interface Y {
kind: X.A;
}
const foo = { kind: X.A };
// error: Type '{ kind: X; }' is not assignable to type 'Y'.
// Types of property 'kind' are incompatible.
// Type 'X' is not assignable to type 'X.A'.
const bar: Y = foo; Is there a particular reason that literals have their types widened by default? |
Here it is further reduced const fun = (a: 'one') => { };
fun('one'); // <- this is fine
let a = 'one';
fun(a); // <- error here using |
TypeScript Version: 3.1.2
Let assume we have a enum called
Type
. It seems type inference will treatconst a = Type.A
asconst a: Type
rather thanconst a: Type.A
. And this may causes some problems.Code
Expected behavior:
won't get any compile error in line
arr.push(a2);
.Actual behavior:
get a type error in line
arr.push(a2);
.Playground Link:
https://www.typescriptlang.org/play/index.html#src=enum%20Type%20%7B%0D%0A%20%20%20%20A%2C%0D%0A%20%20%20%20B%2C%0D%0A%7D%0D%0A%0D%0Ainterface%20BaseType%20%7B%0D%0A%20%20%20%20type%3A%20Type%3B%0D%0A%20%20%20%20name%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20T1%20extends%20BaseType%20%7B%0D%0A%20%20%20%20type%3A%20Type.A%3B%0D%0A%20%20%20%20foo%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20T2%20extends%20BaseType%20%7B%0D%0A%20%20%20%20type%3A%20Type.B%3B%0D%0A%20%20%20%20bar%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Atype%20T%20%3D%20T1%20%7C%20T2%3B%0D%0A%0D%0Aconst%20a1%3A%20T%20%3D%20%7B%0D%0A%20%20%20%20type%3A%20Type.A%2C%0D%0A%20%20%20%20name%3A%20'hello'%2C%0D%0A%20%20%20%20foo%3A%20100%2C%0D%0A%7D%3B%0D%0Aconst%20a2%20%3D%20%7B%0D%0A%20%20%20%20type%3A%20Type.A%2C%0D%0A%20%20%20%20name%3A%20'hello'%2C%0D%0A%20%20%20%20foo%3A%20100%2C%0D%0A%7D%3B%0D%0A%0D%0Aconst%20arr%3A%20T%5B%5D%20%3D%20%5B%5D%3B%0D%0A%0D%0Aarr.push(a1)%3B%0D%0Aarr.push(a2)%3B%0D%0A
Related Issues:
The text was updated successfully, but these errors were encountered: