Skip to content
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

Inconsistent String Literal Type Propagation #15271

Closed
mattnedrich opened this issue Apr 19, 2017 · 5 comments
Closed

Inconsistent String Literal Type Propagation #15271

mattnedrich opened this issue Apr 19, 2017 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@mattnedrich
Copy link

mattnedrich commented Apr 19, 2017

TypeScript Version: 2.3.0 RC

I'm observing some strange type behavior. Consider the following snippet.

type myType = "MY_TYPE";
const myType: myType = "MY_TYPE";

const foo = myType;
const bar = {
  baz: foo
};

The type of foo and baz are both "MY_TYPE" as expected.

However using this strEnum function as described here which, based on my understanding, should be doing the same thing as the above code, results in something different:

function strEnum<T extends string>(o: Array<T>): {[K in T]: K} {
  return o.reduce((res, key) => {
    res[key] = key;
    return res;
  }, Object.create(null));
}

const myTypeEnum = strEnum(["MY_TYPE"])

const foo = myTypeEnum.MY_TYPE;
const bar = {
  baz: foo
};

Here, foo is detected to be type "MY_TYPE", but baz is recognized as type string

Am I doing something wrong? Is this expected behavior?

@ahejlsberg
Copy link
Member

This is working as intended. In the call strEnum(["MY_TYPE"]), the "MY_TYPE" literal has a widening literal type (see #11126) because it was never given an explicit type through a type annotation or a type assertion. You get the desired result if you explicitly assert a type:

const myTypeEnum = strEnum(["MY_TYPE" as "MY_TYPE"])

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 19, 2017
@bilalq
Copy link

bilalq commented Apr 20, 2017

I'm left a bit surprised by the type widening here. In a project using react-redux, I'm having to declare types that I would expect the compiler to be able to infer.

In all of the following examples, I would expect the code to compile. However, there are many where it does not:

// Example 1
// DOES NOT compile
interface MyAction {
  type: 'MY_ACTION'
}
const action = {
  type: 'MY_ACTION'
}
const reassignment: MyAction = action;


// Example 2
// DOES NOT compile
const MY_ACTION = 'MY_ACTION';
interface MyAction {
  type: typeof MY_ACTION
}
const action = {
  type: MY_ACTION
}
const reassignment: MyAction = action;


// Example 3
// DOES compile
const MY_ACTION: 'MY_ACTION' = 'MY_ACTION';
interface MyAction {
  type: typeof MY_ACTION
}
const action = {
  type: MY_ACTION
}
const reassignment: MyAction = action;


// Example 4
// DOES compile
const MY_ACTION = 'MY_ACTION';
interface MyAction {
  type: typeof MY_ACTION
}
const action: MyAction = {
  type: MY_ACTION
}
const reassignment: MyAction = action;

Are all these situations truly as intended? Is this because the object is not guaranteed to be immutable? If that's the case, the widening difference between example 2 and 3 is throwing me off.

@RyanCavanaugh
Copy link
Member

Are all these situations truly as intended?

Consider a slight variant:

const DEFAULT_COLOR = 'RED';
const style = {
  background: DEFAULT_COLOR;
};
if (user.useNeatBackground) {
  // Error? Cannot convert "BLUE" to "RED" ?
  style.background = 'BLUE';
}

Without explicit intent, it's very tricky to infer literal types. You can quickly find yourself issuing errors on code that is extremely unsuspicious.

@mattnedrich
Copy link
Author

Thanks for the clarification! I'm going to close.

@bilalq
Copy link

bilalq commented Apr 20, 2017

Hmm... that case certainly makes sense. I don't know if it's prohibitively expensive to do this, but could the compiler avoid widening the type within a certain scope if it can guarantee that an object is never mutated? Though even in that case

Another possible alternative here that might be nice would be to introduce a keyword in the type layer called literal.

const SUBMIT_COMMENT_SUCCEEDED: literal = 'SUBMIT_COMMENT_SUCCEEDED';

It expresses intent clearly, and avoids the triple repetition that we currently have to do:

const SUBMIT_COMMENT_SUCCEEDED: 'SUBMIT_COMMENT_SUCCEEDED' = 'SUBMIT_COMMENT_SUCCEEDED';

Would this be a proposal that might be considered? Or are there downsides to this?

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants