microsoft / TypeScript Public
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
Literal String Union Autocomplete #29729
Comments
From the compiler's point of view, this is just a very fancy way of writing You could write something like this: Naturally this doesn't stop you from writing |
Why not improve the compiler to keep more metadata around? |
It may accomplish the same behavior, but that's not intuitive to me at all. I doubt I could have gotten there on my own, and I know I would have trouble explaining it to someone newly coming to TS from JS. |
|
It would be great to have this built in, although understand it may be difficult to implement on the compiler. In the meantime, a generic workaround based on @RyanCavanaugh's solution might help: type LiteralUnion<T extends U, U = string> = T | (U & { zz_IGNORE_ME?: never })
type Color = LiteralUnion<'red' | 'black'>
var c: Color = 'red' // Has intellisense
var d: Color = 'any-string' // Any string is OK
var d: Color = { zz_IGNORE_ME: '' } // { zz_IGNORE_ME } placeholder is at the bottom of intellisense list and errors because of never
type N = LiteralUnion<1 | 2, number> // Works with numbers too |
|
Would be great if this could be implemented. It has the potential to improve even core Node.js APIs. The hash.digest |
|
Hey Guys type LiteralUnion<T extends U, U = string> = T | (U & {});
let x: LiteralUnion<"hello" | "world">;
x = "hey";While this code is perfectly valid, "hello", and "world" are still showing up in the autocompletion. |
|
I noticed a problem with type guards using this hack: type LiteralUnion<T extends U, U = string> = T | (U & {});
function something(arg: LiteralUnion<'a' | 'b'>): 'a' {
if (arg === 'a') {
return arg; // Type '(string & {}) | "a"' is not assignable to type '"a"'
}
}Is there a way around this? |
|
I think I might have found a solution: Use a type like my type LiteralUnion<T extends U, U = string> = T | (U & {});
type UnpackedLiteralUnion<T> = T extends LiteralUnion<any, infer U> ? U : never
function something(arg: LiteralUnion<'a' | 'b'>): 'a' {
let unpackedArg = arg as UnpackedLiteralUnion<typeof arg>;
if (unpackedArg === "a") {
return unpackedArg;
}
else {
return "a";
}
} |
|
@manuth Your solution not work for me can you know why ? |
|
@AhmedElywa it's because you're using Here's the longer explanation: let x: (string & never); // x has type `never`
let y: number | (string & never); // y has type `number`
let z: ("Hello" | "world") | (string & never); // z has type `"Hello" | "world"`In order to get the solution to work you have to use How the solution workslet a: ("hello" | "world") | string;In this snippet let a: ("hello" | "world") | (string & {});In this code-snippet Hope this helped you understanding. |
|
For those interested, there is a workaround for this called |
|
Is there a way to use this hack with the object key? |
|
Surprised this hasn't been mentioned yet, but to comply with the export type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>); |
@a2br This is very cool, never thought of using |
Changes the XFrameOptionsOptions to let typescript suggest "DENY" and "SAMEORIGIN" in autocomplete with the trick in microsoft/TypeScript#29729
Changes the XFrameOptionsOptions to let typescript suggest "DENY" and "SAMEORIGIN" in autocomplete with the trick in microsoft/TypeScript#29729
Doesn't work with type guard situation function something(arg: LiteralUnion<'a'| 'c', string>): 'a' {
if (arg === 'a') {
return arg; // Type '"a" | (string & { _?: undefined; })' is not assignable to type '"a"'.
}
return 'a'
} |
|
This is similar to @aquaductape's observation that the current work around doesn't work with type guards, but I was hoping to build an API that leveraged discriminated unions to offer autocompletion for known types but also supported unknown types. Something like this: interface Circle {
kind: 'circle';
radius: number;
};
interface Square {
kind: 'square';
width: number;
};
interface UnknownShape {
kind: string & {zzIngore: any};
getArea(): number;
}
type Shape = Circle | Square | UnknownShape;
function area(shape: Shape): number {
if (shape.kind === 'square') {
// Property 'width' does not exist on type 'Square | GenericShape'.
// Property 'width' does not exist on type 'GenericShape'.(2339)
return shape.width * shape.width;
}
if (shape.kind === 'circle') {
return Math.PI * shape.radius * shape.radius;
}
return shape.getArea();
} |
|
Any updates on this from the typescript team ? |
|
AFAIK, none of the proposed workarounds work in javascript (with jsdoc). The closest I got from making this work was with template literal types, but that forces me to use a pattern // Note the ! in the template string
/** @type {'default' | `!${string}`} */
let value = 'default' // Autocompleted in vscode as exected
value = '!allowed' // TS allows
value = 'nop' // TS errors here
// Doesn't work without
/** @type {'default' | `${string}`} */
let value = '|' // No autocomplete here |
Seems to work (TS 4.6.2). // @ts-check
/** @type {'default' | string & {}} */
let value = "default"; // autocompletesMust have just been getting hung up on it being wrapped in a template literal. |
|
The easiest way that I have learned is to simply omit the constant string values from string itself like so: interface Options {
borderColor:
| 'black'
| 'red'
| 'green'
| 'yellow'
| 'blue'
| Omit<string, 'black' | 'red' | 'green' | 'yellow' | 'blue'>
}
const opts: Options = { borderColor: 'red' }Now you get autocomplete but can still set borderColor to arbitrary strings :) |
Based on yours type LiteralUnion<T extends string | number> = T | Omit<T, T>;
let str: LiteralUnion<'abc' | 'cba'>
str = 'abc'
str = 'abcc'
console.log(str.valueOf())
let num: LiteralUnion<123 | 321>
num = 123
num = 1.23
console.log(num.valueOf())
let mix: LiteralUnion<123 | 'abc'>
mix = 'abc'
mix = 123
console.log(mix.valueOf()) |
|
@chavyleung Unfortunately doesn't work for type guards #29729 (comment) type LiteralUnion<T extends string | number> = T | Omit<T, T>;
function something(arg: LiteralUnion<'a'| 'c'>): 'a' {
if (arg === 'a') {
return arg; // Type '"a" | Omit<"a" | "c", "a" | "c">' is not assignable to type '"a"'. Type 'Omit<"a" | "c", "a" | "c">' is not assignable to type '"a"'.ts(2322)
}
return 'a'
} |



Autocomplete works for literal string unions, but adding a union of
stringnegates autocomplete entirely. This has been brought up before but I believe there is enough value in this feature to be reconsidered.My use case is to have a union of string literals for several colors, but also allow hex codes without having to add 16.7 million string literals.
TypeScript Version: 3.4.0-dev.20190202
Search Terms: Literal string union autocomplete
Code
Expected behavior:
Actual behavior:
Playground Link: https://stackblitz.com/edit/typescript-bwyyab
Related Issues: #12687 #13614
The text was updated successfully, but these errors were encountered: