-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Type inference for object literals with string types and Object.assign #17943
Comments
There is a way to work around this in some cases. You can replace |
Or this: const x = Object.assign({ type: 'abc' }, {}) as Tagged; This is expressly what type coercion is designed for, for dealing with situations where narrower types cannot be reliably contextually inferred. |
Yes, that will also work in this case. In my case, I had some existing objects that weren't tagged, and I had some new types where I was creating objects which have tags in them. So the arguments were I'm wondering if it would be possible to have TypeScript figure out these kinds of expressions automatically. It certainly wouldn't be easy. It might be impossible. |
Yeah it would be great if TypeScript allowed you to annotate an object literal in a way that allowed it to choose the narrowest type possible so you don't have to repeat yourself. Here's something I sometimes use (as a library) when repeating myself feels worse than jumping through hoops: class LiterallyTypedObjectBuilder<T> {
obj = {} as T;
private constructor() {
}
and<K extends string, V extends string | number | boolean | {}>(k: K, v: V): LiterallyTypedObjectBuilder<T & Record<K, V>> {
var that = this as any as LiterallyTypedObjectBuilder<T & Record<K,V>>
that.obj[k] = v;
return that;
}
build(): T {
return this.obj;
}
static of<K extends string, V extends string | number | boolean | {}>(k: K, v: V): LiterallyTypedObjectBuilder<Record<K,V>> {
return new LiterallyTypedObjectBuilder<{}>().and(k,v);
}
} You'd use it like this: interface Tagged {
type: 'abc'
}
const x: Tagged = LiterallyTypedObjectBuilder.of('type', 'abc').build();
const x2: Tagged = Object.assign(LiterallyTypedObjectBuilder.of('type', 'abc').build(), {});
interface Another {
type: 'def'
size: 'S' | 'M' | 'L'
age: number
happy: boolean
}
const y = LiterallyTypedObjectBuilder.
of('type', 'def').
and('size', 'M').
and('age', 42).
and('happy', false).
build();
const yAsAnother: Another = y; Ugly and obnoxious, but it does infer the literals. |
it becomes a chicken-and-egg problem, since the compiler needs to know the types of the inputs to find the return type, but the return type is what provides the contextual type to inform the inference. |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
TypeScript Version: 2.4.0
I didn't know if there was an issue open for this already, but this is a tricky one. Consider the following code.
This code will not compile, because the type inference for the literal
{type: 'abc'}
ends when being passed to a function call and the type is inferred as{type: string}
, which is of course not assignable to{type: 'abc'}
, which is a good thing.Expected behavior:
I expect the literal to instead be inferred as
{type: 'abc'}
, so the object can be assigned.Actual behavior:
The literal is inferred as
{type: string}
, so assignment cannot be done.Rationale:
Literal types like
'foo'
and3
are narrower types thanstring
andnumber
, so better type checking can be done with those types. If TypeScript can infer narrower types where possible, then assignments like the above will work.There is a problem with assignments like the following, however.
If TypeScript were to start inferring
{type: 'abc'}
in these cases instead of{type: string}
, then this could break existing code. So this isn't an easy issue to handle.The text was updated successfully, but these errors were encountered: