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

Allow dynamic names in types #15473

Merged
merged 65 commits into from Nov 16, 2017

Conversation

Projects
None yet
@rbuckton
Member

rbuckton commented Apr 29, 2017

This changes allows the use of an Identifier or PropertyAccessExpression as part of a computed property name in an interface, class, or type literal as long as the type of the expression is a string or numeric literal type, or is a unique symbol type.

Unique Symbol Types

A unique symbol type is created in specific cases when calling the global Symbol function or the global Symbol.for function, or when you use the unique symbol type.

Unique symbol types have several rules and restrictions:

  • The unique symbol type may only be used on a const variable or readonly property declaration.
  • The unique symbol type widens to symbol.
  • Two unique symbol types are not assignable to each other.
  • A unique symbol type is bound to the symbol of the declaration where it was defined.
    • This prevents assignability errors when merging declarations of the same variable/property that both define their type as unique symbol, as the names for these declarations merge into the same symbol.
  • NOTE: Even though Symbol.for(x) would return the same ES Symbol at runtime if called multiple times for the same x, we currently treat separate calls to Symbol.for(x) as unique symbols. We may choose to revisit this in the future.

Examples

// main.ts (input)
const x = Symbol();
const y = x;
const z: typeof x = x;
interface SymbolConstructor {
  readonly iterator: unique symbol; 
}
interface SymbolConstructor {
  readonly iterator: unique symbol; // ok
}

// main.d.ts (declaration output)
const x: unique symbol;
const y: symbol;
const z: typeof x;
interface SymbolConstructor {
  readonly iterator: unique symbol; 
}
interface SymbolConstructor {
  readonly iterator: unique symbol;
}

Late Binding

Dynamic member names are resolved and "bound" in the checker on-demand whenever the members of a symbol are requested, allowing members with dynamic names to participate in type relationships.

Since dynamic members are bound later than syntactically recognizable member names, we disallow defining a member both syntactically and via a dynamic name so as not to introduce inconsistencies with overload resolution as the declarations might end up in the wrong order.

Examples

// main.ts (input)
export const x = "literal name";
const y = 1;
export interface A {
  [x]: string;
  [y]: string; // error: Interface 'A' has or is using private name '[y]' (when using --declaration)
}
type B {
  [x]: string;
  [y]: boolean;
}
let a: A;
let b: B;
a = b; // error: Type 'B' is not assignable to type 'A'. Types of property '[y]' are incompatible.

// main.d.ts (declaration output)
export declare const x = "literal name";
export interface A { 
  [x]: string; 
}

Fixes #2012
Fixes #5579
Fixes #7436 (via typeof SAYHELLO)
Fixes #11736 (via typeof opAdd)
Partially Fixes #13031 (via unique symbol type, though there are other issues that still block this)

@rbuckton rbuckton requested review from sandersn, ahejlsberg and mhegazy Apr 29, 2017

@sandersn

This comment has been minimized.

Show comment
Hide comment
@sandersn

sandersn May 2, 2017

Member

Can you give an example of the symbol literal type scenario?

Member

sandersn commented May 2, 2017

Can you give an example of the symbol literal type scenario?

@sandersn

The code looks solid enough, but I don't really get why this change is needed right now. It seems like a lot of change without much payoff. Maybe this should wait until we have symbol literal types?

Also a few nitpicks in the comments.

Show outdated Hide outdated tests/cases/compiler/dynamicNames.ts
Show outdated Hide outdated src/compiler/checker.ts
Show outdated Hide outdated src/compiler/checker.ts
@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton May 2, 2017

Member

@sandersn Symbol literal types are a whole other complicated issue. This is basically a rough-in for where symbol literal types would be used, but at least gives us the ability to use string and numeric literal types for dynamic property names now. The branch I am working on that has symbol literal types builds on this, and will help keep the scope of a future PR to just symbol literal specific functionality.

The PR has been updated to include unique symbol types.

Member

rbuckton commented May 2, 2017

@sandersn Symbol literal types are a whole other complicated issue. This is basically a rough-in for where symbol literal types would be used, but at least gives us the ability to use string and numeric literal types for dynamic property names now. The branch I am working on that has symbol literal types builds on this, and will help keep the scope of a future PR to just symbol literal specific functionality.

The PR has been updated to include unique symbol types.

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton May 6, 2017

Member

Updated with support for unique symbol types. Also updated the description, above.

Member

rbuckton commented May 6, 2017

Updated with support for unique symbol types. Also updated the description, above.

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton May 6, 2017

Member

At some point we may choose to simplify the binder with respect to well-known symbols and let late-binding take care of it.

Member

rbuckton commented May 6, 2017

At some point we may choose to simplify the binder with respect to well-known symbols and let late-binding take care of it.

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton Jun 7, 2017

Member

@sandersn any other comments?

Member

rbuckton commented Jun 7, 2017

@sandersn any other comments?

@sandersn

This comment has been minimized.

Show comment
Hide comment
@sandersn

sandersn Jun 7, 2017

Member

I haven't looked at the unique symbol types code yet, but I will. Did we ever discuss this in a design meeting?

Member

sandersn commented Jun 7, 2017

I haven't looked at the unique symbol types code yet, but I will. Did we ever discuss this in a design meeting?

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton Nov 13, 2017

Member

@ahejlsberg, @mhegazy: In ae11ae5 I've made some changes to how we handle widening in getReturnTypeFromBody to address widening of unique symbol types. This changes how we resolve the return types for async functions, async generators, and generators where it seems like we weren't sufficiently widening literal types in these cases like we were for normal functions. This could be considered a breaking change, so I'd like to know if this is acceptable. If not I can revert to the old behavior, but this seems more consistent.

Member

rbuckton commented Nov 13, 2017

@ahejlsberg, @mhegazy: In ae11ae5 I've made some changes to how we handle widening in getReturnTypeFromBody to address widening of unique symbol types. This changes how we resolve the return types for async functions, async generators, and generators where it seems like we weren't sufficiently widening literal types in these cases like we were for normal functions. This could be considered a breaking change, so I'd like to know if this is acceptable. If not I can revert to the old behavior, but this seems more consistent.

rbuckton added some commits Nov 15, 2017

rbuckton added some commits Nov 16, 2017

@rbuckton rbuckton merged commit b4ea700 into master Nov 16, 2017

4 of 5 checks passed

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details
TypeScript Test Run typescript_node.6 Build finished.
Details
TypeScript Test Run typescript_node.8 Build finished.
Details
TypeScript Test Run typescript_node.stable Build finished.
Details
license/cla All CLA requirements met.
Details

@rbuckton rbuckton deleted the dynamicNames branch Nov 16, 2017

@nisimjoseph

This comment has been minimized.

Show comment
Hide comment
@nisimjoseph

nisimjoseph Dec 6, 2017

can I use without use "// @ts-ignore" instruction to make the compiler ignore that "mistake"?

for example:

interface HandlerHash
{
    [eventType:string | symbol]:Function[];
}

nisimjoseph commented Dec 6, 2017

can I use without use "// @ts-ignore" instruction to make the compiler ignore that "mistake"?

for example:

interface HandlerHash
{
    [eventType:string | symbol]:Function[];
}

@HerringtonDarkholme

This comment has been minimized.

Show comment
Hide comment
@HerringtonDarkholme

HerringtonDarkholme Jan 22, 2018

Contributor

@DanielRosenwasser unique symbol is a notable feature in TS2.7 and it can benefit terminal users a lot, e.g., Angular's NgOnInit interface can use this to avoid method name conflict.

Can we add it to What's new in TypeScript wiki? Indeed sometimes TS' new awesome features are more than one can follow. 😜

Contributor

HerringtonDarkholme commented Jan 22, 2018

@DanielRosenwasser unique symbol is a notable feature in TS2.7 and it can benefit terminal users a lot, e.g., Angular's NgOnInit interface can use this to avoid method name conflict.

Can we add it to What's new in TypeScript wiki? Indeed sometimes TS' new awesome features are more than one can follow. 😜

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Jan 24, 2018

Contributor

@HerringtonDarkholme we are updating the docs currently. we should have them all up-to-date before the final 2.7 goes out next week.

Contributor

mhegazy commented Jan 24, 2018

@HerringtonDarkholme we are updating the docs currently. we should have them all up-to-date before the final 2.7 goes out next week.

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