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

Always use literal types #10676

Merged
merged 36 commits into from Sep 11, 2016

Conversation

Projects
None yet
@ahejlsberg
Member

ahejlsberg commented Sep 1, 2016

This PR switches our approach to literal types from being a concept we sometimes use to a concept we consistently use. Previously we'd only use literal types in "literal type locations", which was not particularly intuitive. With this PR we always use literal types for literals and then widen those literal types as appropriate when they are inferred as types for mutable locations.

First some terminology:

  • A literal type is a type that only has a single value, e.g. true, 1, "abc", undefined.
  • String and numeric literal types come in two flavors, widening and non-widening.
  • A literal union type is a union of only literal types.
  • The widened literal type of a type T is:
    • string, if T is a widening string literal type,
    • number, if T is a widening numeric literal type,
    • boolean, if T is true or false,
    • E, if T is a union enum member type E.X,
    • a union of the widened literal types of each constituent, if T is union type, or
    • T itself otherwise.

Note that the widened literal type of any non-primitive type is simply the type itself. For example, the widened literal type of { kind: true } is that type itself, even though the type contains a property with a literal type. Literal type widening is effectively a "shallow" operation.

The following changes are implemented by this PR:

  • The concept of "literal type locations" is gone.
  • The type of a literal in an expression is always a literal type (e.g. true, 1, "abc").
  • The type of a string or numeric literal occurring in an expression is a widening literal type.
  • The type of a string or numeric literal occurring in a type is a non-widening literal type.
  • The type inferred for a const variable or readonly property without a type annotation is the type of the initializer as-is.
  • The type inferred for a let variable, var variable, parameter, or non-readonly property with an initializer and no type annotation is the widened literal type of the initializer.
  • The type inferred for a property in an object literal is the widened literal type of the expression unless the property has a contextual type that includes literal types.
  • The type inferred for an element in an array literal is the widened literal type of the expression unless the element has a contextual type that includes literal types.
  • In a function with no return type annotation and multiple return statements, the inferred return type is a union of the return expression types. Previously the return expressions were required to have a best common supertype, but this is no longer the case.
  • In a function with no return type annotation, if the inferred return type is a literal type (but not a literal union type) and the function does not have a contextual type with a return type that includes literal types, the return type is widened to its widened literal type.
  • During type argument inference for a call expression, the type inferred for a type parameter T is widened to its widened literal type in certain situations as explained below.

The intuitive way to think of these rules is that immutable locations always have the most specific type inferred for them, whereas mutable locations have a widened type inferred. Some examples:

const c1 = 1;  // Type 1
const c2 = c1;  // Type 1
const c3 = "abc";  // Type "abc"
const c4 = true;  // Type true
const c5 = cond ? 1 : "abc";  // Type 1 | "abc"

let v1 = 1;  // Type number
let v2 = c2;  // Type number
let v3 = c3;  // Type string
let v4 = c4;  // Type boolean
let v5 = c5;  // Type number | string
const a = cond ? "foo" : "bar";  // Type "foo" | "bar"
let b = cond ? "foo" : "bar";  // Type string
let c: "foo" | "bar" = cond ? "foo" : "bar";  // Type "foo" | "bar"
const a1 = [1, 2, 3];   // Type number[], because elements are mutable
const a2: [1, 2, 3] = [1, 2, 3];  // Type [1, 2, 3]
const o1 = { kind: 0 };  // Type { kind: number }, because properties are mutable
const o2: { kind: 0 } = { kind: 0 };  // Type { kind: 0 }

Literal type widening can be controlled through explicit type annotations. Specifically, when an expression of a literal type is inferred for a const location without a type annotation, that const variable gets a widening literal type inferred. But when a const location has an explicit literal type annotation, the const variable gets a non-widening literal type.

const c1 = "hello";  // Widening type "hello"
let v1 = c1;  // Type string
const c2 = c1;  // Widening type "hello"
let v2 = c2;  // Type string
const c3: "hello" = "hello";  // Type "hello"
let v3 = c3;  // Type "hello"
const c4: "hello" = c1;  // Type "hello"
let v4 = c4;  // Type "hello"

For further details on widening vs. non-widening string and numeric literals, see #11126.

In a function with no return type annotation, if the inferred return type is a literal type (but not a literal union type) and the function does not have a contextual type with a return type that includes literal types, the return type is widened to its widened literal type:

function foo() {
    return "hello";
}

function bar() {
    return cond ? "foo" : "bar";
}

const c1 = foo();  // string
const c2 = bar();  // "foo" | "bar"

During type argument inference for a call expression the type inferred for a type parameter T is widened to its widened literal type if:

  • all inferences for T were made to top-level occurrences of T within the particular parameter type, and
  • T has no constraint or its constraint does not include primitive or literal types, and
  • T was fixed during inference or T does not occur at top-level in the return type.

An occurrence of a type X within a type S is said to be top-level if S is X or if S is a union or intersection type and X occurs at top-level within S.

In cases where literal types are preserved in inferences for a type parameter (i.e. when no widening takes place), if all inferences are literal types or literal union types with the same base primitive type, the resulting inferred type is a union of the inferences. Otherwise, the inferred type is the common supertype of the inferences, and an error occurs if there is no such common supertype.

Some examples of type inference involving literal types:

declare function f1<T>(x: T): T;
declare function f2<T>(x: T, y: T): T;
declare function f3<T, U>(x: T, y: U): T | U;
declare function f4<T>(x: T): T[];
declare function f5<T extends number>(x: T, y: T): T[];
declare function f6<T>(x: T[]): T;
declare function f7<T>(x: T[]): T[];

const a: (1 | 2)[] = [1, 2];

const x1 = f1(1);  // Type 1
const x2 = f2(1, 2);  // Type 1 | 2
const x3 = f2(1, "two");  // Error
const x4 = f3(1, "two");  // Type 1 | "two"
const x5 = f4(1);  // Type number[]
const x6 = f5(1, 2);  // Type (1 | 2)[]
const x7 = f6([1, 2]);  // Type number
const x8 = f6(a);  // Type 1 | 2
const x9 = f7(a);  // Type (1 | 2)[]

Note that this PR fixes the often complained about issue of requiring a type annotation on a const to preserve a literal type for the const (e.g. const foo: "foo" = "foo") because const now consistently preserves the most specific type for the expression. However, the PR does not provide a way to infer literal types for mutable locations. That is an orthogonal issue we can address in a seperate PR if need be.

NOTE: This description has been updated to reflect the changes introduced by #11126.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 1, 2016

Member

@mhegazy @RyanCavanaugh @DanielRosenwasser I'm sure you'll want to look at this!

Member

ahejlsberg commented Sep 1, 2016

@mhegazy @RyanCavanaugh @DanielRosenwasser I'm sure you'll want to look at this!

@ahejlsberg ahejlsberg referenced this pull request Sep 2, 2016

Closed

Union Types And Strings #9489

@Igorbek

This comment has been minimized.

Show comment
Hide comment
@Igorbek

Igorbek Sep 2, 2016

In your example

function foo() {
    return "hello";
}
const c1 = foo();  // string

Why c1 inferred as a string? What would be the type of foo then, () => string ? Why not literal type here? Would it be different if it'd be used as const c2: "hello" = foo(); ?

Igorbek commented Sep 2, 2016

In your example

function foo() {
    return "hello";
}
const c1 = foo();  // string

Why c1 inferred as a string? What would be the type of foo then, () => string ? Why not literal type here? Would it be different if it'd be used as const c2: "hello" = foo(); ?

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 2, 2016

Member

@Igorbek The issue is that you practically never want the literal type. After all, why write a function that promises to always return the same value? Also, if we infer a literal type this common pattern is broken:

class Base {
    getFoo() {
        return 0;  // Default result is 0
    }
}

class Derived extends Base {
    getFoo() {
        // Compute and return a number
    }
}

If we inferred the type 0 for the return type in Base.getFoo, it would be an error to override it with an implementation that actually computes a number. You can of course add a type annotation if you really want to return a literal type, i.e. getFoo(): 0 { return 0; }.

Of course, we could consider having different rules for functions vs. methods and only do the widening in methods, but I think that would be confusing.

Member

ahejlsberg commented Sep 2, 2016

@Igorbek The issue is that you practically never want the literal type. After all, why write a function that promises to always return the same value? Also, if we infer a literal type this common pattern is broken:

class Base {
    getFoo() {
        return 0;  // Default result is 0
    }
}

class Derived extends Base {
    getFoo() {
        // Compute and return a number
    }
}

If we inferred the type 0 for the return type in Base.getFoo, it would be an error to override it with an implementation that actually computes a number. You can of course add a type annotation if you really want to return a literal type, i.e. getFoo(): 0 { return 0; }.

Of course, we could consider having different rules for functions vs. methods and only do the widening in methods, but I think that would be confusing.

@RyanCavanaugh

This comment has been minimized.

Show comment
Hide comment
@RyanCavanaugh

RyanCavanaugh Sep 2, 2016

Member

Should the expression initializing a readonly class property also retain a literal type?

Member

RyanCavanaugh commented Sep 2, 2016

Should the expression initializing a readonly class property also retain a literal type?

@joewood

This comment has been minimized.

Show comment
Hide comment
@joewood

joewood Sep 2, 2016

This is great. I'm wondering if this will address the false positive scenario described by @AlexGalays in #6613 (comment).

This is related to using f-bound polymorphism as a solution to "partial types" (e.g. for Object.assign and React setState). I'm guessing not as parameter binding is treated as a mutable bind and widened.

joewood commented Sep 2, 2016

This is great. I'm wondering if this will address the false positive scenario described by @AlexGalays in #6613 (comment).

This is related to using f-bound polymorphism as a solution to "partial types" (e.g. for Object.assign and React setState). I'm guessing not as parameter binding is treated as a mutable bind and widened.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 2, 2016

Member

Should the expression initializing a readonly class property should also retain a literal type?

Yes, I think that would make sense.

I'm wondering if this will address the false positive scenario described by @AlexGalays in #6613 (comment).

No, the issue in #6613 is unrelated to this change.

Member

ahejlsberg commented Sep 2, 2016

Should the expression initializing a readonly class property should also retain a literal type?

Yes, I think that would make sense.

I'm wondering if this will address the false positive scenario described by @AlexGalays in #6613 (comment).

No, the issue in #6613 is unrelated to this change.

@Igorbek

This comment has been minimized.

Show comment
Hide comment
@Igorbek

Igorbek Sep 2, 2016

@ahejlsberg thank you, good point. However the same argument can be applied to conditional return as well:

class Base {
    startWithOne = true;
    getFoo() {
        return startWithOne ? 1 : 0; // now it will be typed as 1 | 0 ?
    }
}

class Derived extends Base {
    getFoo() {
        // Compute and return a number
    }
}

Technically speaking, class methods cannot be considered immutable locations. And even plain functions cannot be:

function foo() { return cond ? 1 : 2; }
foo = function() { return 3; } // error?
const foo2 = function() { return 1; } // now it's really immutable

Igorbek commented Sep 2, 2016

@ahejlsberg thank you, good point. However the same argument can be applied to conditional return as well:

class Base {
    startWithOne = true;
    getFoo() {
        return startWithOne ? 1 : 0; // now it will be typed as 1 | 0 ?
    }
}

class Derived extends Base {
    getFoo() {
        // Compute and return a number
    }
}

Technically speaking, class methods cannot be considered immutable locations. And even plain functions cannot be:

function foo() { return cond ? 1 : 2; }
foo = function() { return 3; } // error?
const foo2 = function() { return 1; } // now it's really immutable
@Igorbek

This comment has been minimized.

Show comment
Hide comment
@Igorbek

Igorbek Sep 2, 2016

I would say that function declaration

function foo() { return 1; }

is equivalent (with hoisting in mind) to

let foo = function () { return 1; };

where function () { return 1; } is an expression (immutable location) of type () => 1 and foo is a mutable location so that it must be of base type () => number.

function () { return 1; }; // () => 1
let foo = function () { return 1; }; // () => number
function foo() { return 1; }; // () => number
const boo = function () { return 1; }; // () => 1

Igorbek commented Sep 2, 2016

I would say that function declaration

function foo() { return 1; }

is equivalent (with hoisting in mind) to

let foo = function () { return 1; };

where function () { return 1; } is an expression (immutable location) of type () => 1 and foo is a mutable location so that it must be of base type () => number.

function () { return 1; }; // () => 1
let foo = function () { return 1; }; // () => number
function foo() { return 1; }; // () => number
const boo = function () { return 1; }; // () => 1
@alitaheri

This comment has been minimized.

Show comment
Hide comment
@alitaheri

alitaheri Sep 2, 2016

This is really great ❤️ ❤️

Just a small detail:

The issue is that you practically never want the literal type. After all, why write a function that promises to always return the same value?

How about a function from which we want to return a finite set of literal types:

function foo(n: 1 | 2 | 3) {
  switch (n) {
    case 1: return 'one';
    case 2: return 'two';
    case 3: return 'three';
    default: return 'none';
  }
}

Although it's a better practice to explicitly type the return value, but this might a good example of inferring the return type as a literal.

To avoid it, the developer can simply annotate the return type as string. But to make it return literal union another interface will need to be declared and maintained with the function body.

This is really great ❤️ ❤️

Just a small detail:

The issue is that you practically never want the literal type. After all, why write a function that promises to always return the same value?

How about a function from which we want to return a finite set of literal types:

function foo(n: 1 | 2 | 3) {
  switch (n) {
    case 1: return 'one';
    case 2: return 'two';
    case 3: return 'three';
    default: return 'none';
  }
}

Although it's a better practice to explicitly type the return value, but this might a good example of inferring the return type as a literal.

To avoid it, the developer can simply annotate the return type as string. But to make it return literal union another interface will need to be declared and maintained with the function body.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 2, 2016

Member

@alitaheri The function in your example has a literal union type inferred as its return type:

function foo(n: 1 | 2 | 3): "one" | "two" | "three" | "none";

Widening occurs only if the inferred return type is a singleton literal type.

Member

ahejlsberg commented Sep 2, 2016

@alitaheri The function in your example has a literal union type inferred as its return type:

function foo(n: 1 | 2 | 3): "one" | "two" | "three" | "none";

Widening occurs only if the inferred return type is a singleton literal type.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 2, 2016

Member

@Igorbek In the case of return cond ? 0 : 1 the inferred return type would be 0 | 1. I think in this case it is not at all clear we should widen to the base primitive type. After all, with a return type of 0 | 1 there is actually meaningful information being conveyed out of the function. It's like the difference between a function that always returns false vs. a function that returns true | false.

Member

ahejlsberg commented Sep 2, 2016

@Igorbek In the case of return cond ? 0 : 1 the inferred return type would be 0 | 1. I think in this case it is not at all clear we should widen to the base primitive type. After all, with a return type of 0 | 1 there is actually meaningful information being conveyed out of the function. It's like the difference between a function that always returns false vs. a function that returns true | false.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 3, 2016

Member

I have updated the introduction to include the changes in the latest commits, plus I have added some more details and examples.

Member

ahejlsberg commented Sep 3, 2016

I have updated the introduction to include the changes in the latest commits, plus I have added some more details and examples.

@trevorsg

This comment has been minimized.

Show comment
Hide comment
@trevorsg

trevorsg Sep 4, 2016

Member

This is great. If I understand everything correctly, the following scenario should now work.

namespace MyDomUtils {
  export function elem<T extends string>(type: T, ...classList: string[]) {
    const element = document.createElement(type);
    element.classList.add(...classList);
    return element;
  }
}

const ul = MyDomUtils.elem("ul"); // HTMLUListElement (before this was HTMLElement)
const li = MyDomUtils("li"); // HTMLLIElement (before this was HTMLElement)
Member

trevorsg commented Sep 4, 2016

This is great. If I understand everything correctly, the following scenario should now work.

namespace MyDomUtils {
  export function elem<T extends string>(type: T, ...classList: string[]) {
    const element = document.createElement(type);
    element.classList.add(...classList);
    return element;
  }
}

const ul = MyDomUtils.elem("ul"); // HTMLUListElement (before this was HTMLElement)
const li = MyDomUtils("li"); // HTMLLIElement (before this was HTMLElement)
@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 4, 2016

Member

@trevorsg No, that wouldn't be affected by this PR. You need to add a list of overloaded signatures to your elem function to get more specific return types.

Member

ahejlsberg commented Sep 4, 2016

@trevorsg No, that wouldn't be affected by this PR. You need to add a list of overloaded signatures to your elem function to get more specific return types.

case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
return isLiteralContextForType(node, booleanType) ? node.kind === SyntaxKind.TrueKeyword ? trueType : falseType : booleanType;
return node.kind === SyntaxKind.TrueKeyword ? trueType : falseType;

This comment has been minimized.

@DanielRosenwasser

DanielRosenwasser Sep 4, 2016

Member

Just return trueType in the TrueKeyword branch.

@DanielRosenwasser

DanielRosenwasser Sep 4, 2016

Member

Just return trueType in the TrueKeyword branch.

This comment has been minimized.

@ahejlsberg

ahejlsberg Sep 4, 2016

Member

Doh!

@thorn0

This comment has been minimized.

Show comment
Hide comment
@thorn0

thorn0 Sep 4, 2016

What about this case? Will it start working? (playground)

interface InputMaskOptions {
    // this API is a real-world example from the popular jquery.inputmask plugin
    digits: number | '*';
}

declare function defaultTo<T, U>(value: T, defaultValue: U): T | U;
declare var decimalDigits: number;

let options: InputMaskOptions = {
    digits: defaultTo(decimalDigits, '*')
    // this works:
    // digits: defaultTo(decimalDigits, '*' as '*')
};

Update. Okay. I understood that it won't work and found the explanation in #10685,

thorn0 commented Sep 4, 2016

What about this case? Will it start working? (playground)

interface InputMaskOptions {
    // this API is a real-world example from the popular jquery.inputmask plugin
    digits: number | '*';
}

declare function defaultTo<T, U>(value: T, defaultValue: U): T | U;
declare var decimalDigits: number;

let options: InputMaskOptions = {
    digits: defaultTo(decimalDigits, '*')
    // this works:
    // digits: defaultTo(decimalDigits, '*' as '*')
};

Update. Okay. I understood that it won't work and found the explanation in #10685,

@thorn0

This comment has been minimized.

Show comment
Hide comment
@thorn0

thorn0 Sep 4, 2016

But still... What exactly can break if we always infer type arguments to be literal types? From the explanation in #10685, I derived this example.

declare function f<T>(value: T): T;
var a1 = f('one');
var a2 = f('two');
a1 = a2; // ERROR

However, type inference for variables and type inference for type arguments are separate processes, aren't they? Can't we infer the type of f('one') to be 'one' and at the same time the type of var a1 to be string?

thorn0 commented Sep 4, 2016

But still... What exactly can break if we always infer type arguments to be literal types? From the explanation in #10685, I derived this example.

declare function f<T>(value: T): T;
var a1 = f('one');
var a2 = f('two');
a1 = a2; // ERROR

However, type inference for variables and type inference for type arguments are separate processes, aren't they? Can't we infer the type of f('one') to be 'one' and at the same time the type of var a1 to be string?

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 4, 2016

Member

@thorn0 The reason we need to widen is that a type parameter may be used as the type of a mutable location inside the function:

function makeArray<T>(x: T): T[] {
    return [x];
}

var a = makeArray("one");  // Type of a would be "one"[]
a.push("two");  // Would be an error
Member

ahejlsberg commented Sep 4, 2016

@thorn0 The reason we need to widen is that a type parameter may be used as the type of a mutable location inside the function:

function makeArray<T>(x: T): T[] {
    return [x];
}

var a = makeArray("one");  // Type of a would be "one"[]
a.push("two");  // Would be an error
@thorn0

This comment has been minimized.

Show comment
Hide comment
@thorn0

thorn0 Sep 4, 2016

But that's what I'm asking/proposing. Let makeArray("one") be 'one'[]. Why not? In this case it'll be possible to use it where either string[] or 'one'[] is needed. However, the type of var a should be inferred to be string[], not 'one'[]. It's similar to what you're doing in this PR when literal expressions are used to init a variable:

let v1 = 1;  // Type number

thorn0 commented Sep 4, 2016

But that's what I'm asking/proposing. Let makeArray("one") be 'one'[]. Why not? In this case it'll be possible to use it where either string[] or 'one'[] is needed. However, the type of var a should be inferred to be string[], not 'one'[]. It's similar to what you're doing in this PR when literal expressions are used to init a variable:

let v1 = 1;  // Type number
@thorn0

This comment has been minimized.

Show comment
Hide comment
@thorn0

thorn0 Sep 4, 2016

Okay, looks like converting 'one'[] to string[] isn't a good idea if we try to solve it in general case. But what about special-casing functions that return just T (not T[], { a: T }, etc)? Is the widening really needed for them?

thorn0 commented Sep 4, 2016

Okay, looks like converting 'one'[] to string[] isn't a good idea if we try to solve it in general case. But what about special-casing functions that return just T (not T[], { a: T }, etc)? Is the widening really needed for them?

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 6, 2016

Member

@thorn0 You have a point about not always widening during type inference. I'm thinking we need to track where inferences came from and then only widen when all inferences came from "unwidened" locations. For example:

function makeArray<T>(x: T): T[] {
    return [x];
}

function append<T>(a: T[], x: T): T[] {
    let result = a.slice();
    a.push(x);
    return result;
}

type Bit = 0 | 1;

let a = makeArray<Bit>(0);  // Bit[]
let b = append(a, 1);  // Should return Bit[]

Here, in the call to makeArray we only have inferences from "unwidened" locations, so we need to widen. But in the call to append the inference we make from the array itself is from a location that was already widened, so we should keep that type.

Now, to your point, in cases where a type parameter is only used in "unwidened" locations in the return type (effectively, when the return type is just T) there is never a need to widen. So, the simple f<T>(x: T): T should always infer the exact type of the argument.

Member

ahejlsberg commented Sep 6, 2016

@thorn0 You have a point about not always widening during type inference. I'm thinking we need to track where inferences came from and then only widen when all inferences came from "unwidened" locations. For example:

function makeArray<T>(x: T): T[] {
    return [x];
}

function append<T>(a: T[], x: T): T[] {
    let result = a.slice();
    a.push(x);
    return result;
}

type Bit = 0 | 1;

let a = makeArray<Bit>(0);  // Bit[]
let b = append(a, 1);  // Should return Bit[]

Here, in the call to makeArray we only have inferences from "unwidened" locations, so we need to widen. But in the call to append the inference we make from the array itself is from a location that was already widened, so we should keep that type.

Now, to your point, in cases where a type parameter is only used in "unwidened" locations in the return type (effectively, when the return type is just T) there is never a need to widen. So, the simple f<T>(x: T): T should always infer the exact type of the argument.

@aleksey-bykov

This comment has been minimized.

Show comment
Hide comment

yes please #10938

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 24, 2016

Member

I have updated the OP to reflect the changes made in #11126.

Member

ahejlsberg commented Sep 24, 2016

I have updated the OP to reflect the changes made in #11126.

@rictic rictic referenced this pull request Nov 9, 2016

Merged

Enable strictNullChecks #462

@masaeedu

This comment has been minimized.

Show comment
Hide comment
@masaeedu

masaeedu Jun 26, 2017

Contributor

Is there some kind of readonly array type I can use or write myself which will inform TypeScript that the array I am creating isn't allowed to be modified? Alternatively, is there some other trick I can use to get ["foo", "bar", ... to infer as ["foo", "bar", ...]?

Contributor

masaeedu commented Jun 26, 2017

Is there some kind of readonly array type I can use or write myself which will inform TypeScript that the array I am creating isn't allowed to be modified? Alternatively, is there some other trick I can use to get ["foo", "bar", ... to infer as ["foo", "bar", ...]?

@Vitucho

This comment has been minimized.

Show comment
Hide comment
@Vitucho

Vitucho Oct 26, 2017

i am trying to understand this matter here, but i got some problem with terminology:

quoting https://github.com/ahejlsberg

The widened literal type of a type T is:
string, if T is a widening string literal type,
number, if T is a widening numeric literal type,
boolean, if T is true or false,

But true or false are types itselfves? i don't get it... sorry if i'm annoying.

Vitucho commented Oct 26, 2017

i am trying to understand this matter here, but i got some problem with terminology:

quoting https://github.com/ahejlsberg

The widened literal type of a type T is:
string, if T is a widening string literal type,
number, if T is a widening numeric literal type,
boolean, if T is true or false,

But true or false are types itselfves? i don't get it... sorry if i'm annoying.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.