Literal String Union Autocomplete
MrTarantula opened this issue Β· 69 comments
Autocomplete works for literal string unions, but adding a union of string
negates 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
interface Options {
borderColor: 'black' | 'red' | 'green' | 'yellow' | 'blue' | string
};
const opts: Options = {borderColor: 'red'};
Expected behavior:
Actual behavior:
Playground Link: https://stackblitz.com/edit/typescript-bwyyab
'black' | 'red' | 'green' | 'yellow' | 'blue' | string
From the compiler's point of view, this is just a very fancy way of writing string
. By the time we're looking for suggestions at borderColor
, all the literals are lost.
You could write something like this:
Naturally this doesn't stop you from writing "bluck"
. You might want to track #6579
By the time we're looking for suggestions at borderColor, all the literals are lost.
Why not improve the compiler to keep more metadata around?
You could write something like this:
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 encondig
param, for example should accept plain strings but there are values available on all platforms that could be enumerated for ease of use.
Hey Guys
Though the issue still isn't solved but I found out that in TS 3.5.1 you don't even have to create some weird property in order to get the workaround done:
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.
Thank you for this great fix, @RyanCavanaugh and @spcfran!
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 UnpackedLiteralUnion
type to unpack the actual type of the LiteralUnion
:
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 (U & never)
.
Edit: After a year or something I finally noticed that my very own example was incorrect... sorry, pal π
π
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 U & {}
.
Following might give you an idea why this solution works.
How the solution works
let a: ("hello" | "world") | string;
In this snippet a
will have type string
because both "hello"
and "world"
inherit string
.
let a: ("hello" | "world") | (string & {});
In this code-snippet a
will be of type "hello" | "world" | (string & {})
because though both "hello"
and "world"
inherit string
, they do not inherit string & {}
, which means "hello" | "world"
and string & {}
are treated as distinguishable types.
Hope this helped you understanding.
For those interested, there is a workaround for this called LiteralUnion
in type-fest.
I have questions
type SuggestingString = "foo" | "bar" | string;
type Hmm<T> = T extends "foo" ? true : false;
// x: false (today)
// x: true | false or false (if this feature exists) ?
type X = Hmm<SuggestingString>;
Should we just do something like this?
/**
* @suggest "foo", "bar", "baz"
*/
type SuggestingString = string;
Humh... especially when pursuing the goal to have non-contractual property key completion (for example keyof Employee | string
) this solution would cause one to end up with something like this:
/**
* @suggest "PostalCode", "FullName", "FirstName", "LastName"
*/
Unless it's possible to do something like this:
class Employee
{
// [...]
}
/**
* @suggest keyof Employee
*/
type SuggestingString = string;
Would that be an option? Is it possible for typescript language-server to treat the content of a tsdoc-tag (in this case suggest
) as actual typescript-code?
Should we just do something like this?
For all use cases I've faced so far I wanted to have helpful completion without restricting user from passing an arbitrary value, so your second suggestion would work just fine if it is easier to implement than the original proposal.
Similar to @manuth I would like to not limit this to the fixed list of values, but rather allow to specify an arbitrary subtype of the variable's type and generate completion from this type instead of the main type.
E.g.
type CoreIconName = "user" | "customer";
interface CustomIcons {
worker: unknown;
}
/**
* @suggest CoreIconName | keyof CustomIcons
**/
type IconName = string;
But if this is much harder to implement or has some tricky corner cases, the list of fixed values will be a huge improvement over the current state of things anyways.
One case which comes to mind is how to merge these types:
/**
* @suggest CoreIconName | keyof CustomIcons
**/
type IconName = string;
// What should X have in the completion list?
// (CoreIconName | keyof CustomIcons) | string -> nothing?
type X = IconName | string;
But even for the fixed list this may be tricky:
/**
* @suggest "user", "customer"
**/
type CoreIconName = string;
/**
* @suggest "worker"
**/
type CustomIconName = string;
// What should X have in the completion list?
// Nothing or all of the options?
type X = CoreIconName & CustomIconName;
Maybe @suggest
is never merged/inherited is the answer...
Should we just do something like this?
@RyanCavanaugh I would rather this be fixed in the type system. Alternatively, add a built-in type like LiteralUnion
. Adding it to doc comments would be my last choice, as it limits a lot possible use-cases, like generating from other types, merging, excluding, etc.
In case there's any interest, a pretty clean-cut use-case for this feature can be found here #38886
@sindresorhus if you want it to be in the type system, it'd be good to provide feedback on how you think it should interact with the type system. That's the conceptual blocker here
I have noticed that that workaround type Values = "foo" | (string & {})
only gives autocompletion when you use Values
explicitly, it does not work when you extend Values
:
// Gives autocompletion
function test(arg: Values) {}
// Does not give autocompletion
function test<T extends Values>(arg: T) {}
Anyone knows how to get around this?
EDIT
I discovered that you can do this:
// Gives autocompletion
function test<T extends Values>(arg: T | Values) {}
Now you get inferred types and autocompletion
Hi @christianalfoni, can you give an example of autocomplete you're referring to?
For example, if we consider type Values = "foo" | "bar" | (string & {})
and you're given a type T
that extends Values
, you cannot have autocomplete anymore, because T
could be "baz" | "qux"
, so not getting autocomplete is actually correct in this case.
Maybe you're considering a different situation?
Hi @papb! Maybe autocomplete
is the wrong term here. I mean when you hit quotes and VSCode starts suggesting what to enter. Is it autosuggestions
? :)
Anyways, in the example:
type Values = 'foo' | 'bar' | (string & {})
function test<T extends Values>(arg: T | Values) {}
When you write:
test('')
VSCode now shows you "foo" and "bar" as suggestions, but it still allows any string π
My specific use case is tokens. The user can configure a set of tokens that represents specific values. So a better example would be:
type Color = 'primary' | 'secondary' | (string & {})
function draw<T extends Color>(arg: T | Color) {}
The user has configured a value for "primary" and "secondary", which we want to suggest to the user when they call draw
. But they can pass "red", "blue", hex color or whatever they want.
@christianalfoni That's what I meant too... But what is the difference between
function draw<T extends Color>(arg: T | Color) {}
and
function draw(arg: Color) {}
? Aren't they equivalent?
@christianalfoni Hmm, I think something is wrong with your VSCode. I just tested and both methods give the exact same autocompletion, as they should:
type Color = 'primary' | 'secondary' | (string & {});
function draw1<T extends Color>(arg: T | Color) {}
function draw2(arg: Color) {}
draw1('');
draw2('');
The given LiteralUnion
is great for nearly any use case, except one: the union between a list of possible strings, a string, and an object.
For example, I want people to be able to declare a color, or an object describing the color:
type LiteralUnion<T extends U, U = string> = T | (U & {});
type COLOR = LiteralUnion<'red' | 'blue'> | { red: number, green: number, blue: number }
Here, trying to assign an object will autocomplete all string methods instead of just red/green/blue.
As you can see, it's giving me the autocompletion on all possible string methods, and also on green/red/blue.
That clutters the autocompletion, and makes such an hack very annoying. Any workaround regarding this problem?
Edit:
I found a solution. First, the problem using U & {}
is that any object will be accepted, aka:
type LiteralUnion<T extends U, U = string> = T | (U & {});
type MyObject = LiteralUnion<'hi'> | { test: string }
const myObj: MyObject = { test: 0 }
This snippet will compile, despite test
being a number and not a string. Therefore, you should use U & {_?: never}
.
Then, to solve the cluttered autocompletion, you can use this:
type LiteralUnion<T extends U, U = string> = T | (Pick<U, never> & {_?: never});
Here, the Pick<U, never>
will remove all keys from the string
type, but any string will still be accepted. Finally, to prevent people from wondering what this _
property is, the best is a little comment:
export type LiteralUnion<T extends U, U = string> = T | (Pick<U, never> & {
/**
* Ignore this property.
* @deprecated
*/
_?: never
});
Now, such an example will compile:
type Color = LiteralUnion<'blue'> | {red: number}
//@ts-expect-error
const wrong: Color = { red: 'randomstring' }
const right: Color[] = [{
red: 255,
}, 'blue', 'randomcolor']
Second edit
Well it's working-ish, but it's not convenient because you have to explicitly cast your types to strings. Example:
- const myString: LiteralUnion<'hey'> = 'hey'
- const myStringBis: string = myString
+ const myString: LiteralUnion<'hey'> = 'hey'
+ const myStringBis: string = myString as string
Needless to say, it's annoying. I tried other stuff but it's not working. An official feature would be neat.
Here's a list of all the known problems with the LiteralUnion
workaround suggested here.
-
Type guards don't narrow the variable (source)
https://www.typescriptlang.org/play?#code/C4TwDgpgBAMglsCAnAhgGwKoDs4HssA8AKlBAB6JYAmAzlBgDT1QC8UNwScWA5gHysoJAD5QAFBigAyKAG8AvgEoAUMoBmAVywBjYHixREHMSiQ8AXLATJ02fQQDka3Lgd9Fc5VG9Q4a8aY8rCxsTi4OHrJePjHa+BxQAG7olmGugoHR3vLK8kA -
No property auto-completion when an object type is in the union (source)
https://www.typescriptlang.org/play?#code/C4TwDgpgBAMglsCAnAhgGwKoDs4HssA8AKlBAB6JYAmAzlBgDT1QC8UNwScWA5gHysoJAD5QAFBigAyKAG8AvgEoAUMoDG+DlABu6AFywEydNjyEA5ADNcucwNGyo13AYBGNtBBRYo8wbOUoIKgAegAqISQQKBQAV2BcDQBbME9EKG4oAAtkaDCQ5XkgA -
No string auto-completion when extended by generic type (source)
https://www.typescriptlang.org/play?#code/C4TwDgpgBAMglsCAnAhgGwKoDs4HssA8AKlBAB6JYAmAzlBgDT1QC8UNwScWA5gHysoJAD5QAFBigAyKAG8AvgEoAUMtCQoAMVy5B8RKkw58BAOQAzHab6rzAVywBjYHixREHYqQoRqdbbh8YihIPABcQopy8qoewGLKUElQAPQAVEJIIFAodsC4jrgAtmBoEIhQ3O4AFtAcXLxQaSmJyaamyopAA -
Interferes with conditional types that expect
string & {}
to not be a subtype ofobject
https://www.typescriptlang.org/play?#code/C4TwDgpgBAMglsCAnAhgGwKoDs4HssA8AKlBAB6JYAmAzlBgDT1QC8UNwScWA5gHysoJAD5QAFBigAyKAG8AvgEoAUMtCQhEDsQFsS5SrSi4ARgCsIAY2BQA-HIDaABSjcoAawghcAMyEBdAC4oLABXAFsTZHkoYKJVdWgAMVxcQXhEVEwcfAIAch9UvL5lKis0FCRoS3wOKELcOK1gAhTcEuUAek7SMkhrCCooAAMG4ahgNKiRgCIGmahRMQ4uXmk5JWGmLFwbYbnUhdEFYeUarDqAN3RgtsEGoA
I think that the compiler should keep this metadata, possible separately, to support IntelliSense, similar to the fact that it supports IntelliSense with the following type:
interface T {
method(): void;
property: string;
[p: string]: any;
}
declare let v: T;
v.method(); // Has IntelliSense
console.log(v.property); // Has IntelliSense
console.log(v.anotherProperty); // Still supported
I wish it could be solved with TS4.1, but sadly it doesn't support the complexity: https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gGzgJwM5QLxQHIEMC2EAJvEmlAD5QASEAHqSqgNwBQrokg0AQ4HGNouAAq4YeXAOQAjBLgDGAawnCKIicmLKhqyQHMNEAHZadEkBARIA7ibUyArhGVcuzLu07RaDRE0xQAAwBiABIAb1RgZABLQ10oADIaegAhEGAIAF9wyJi4xOS6NIzsiKjY+KTvYqyAtg5wL1T06CwAnPL8qvoAEWjdaOBS3IqC7z6BobqPRsKJwf8JAAYJSigJAEZVqgkAJm31gGYDiQAWE4BWE4A2E4B2E4AOE4BOE4BBE5STmBOek4AoicAGISepcORwQyRYRyDYALlgvjQIhcYjUGiIYKAA
I know TypeScript doesn't have opaque types yet, but consider this case:
type HexColor = string;
type AppThemeColors = 'my-app-blue' | 'my-app-red';
type Color = LiteralUnion<HexColor, AppThemeColors>;
There's a way in which I kinda don't want to allow the following:
const myFunc = (color: Color) = { ... }
myFunc('ziltoid') // bad
const oneTimeUseRed: HexColor = '#FF0000'
myFunc(oneTimeUseRed) // good
not sure if --strictFunctionTypes
will cover this case, but I hope it would/could.
@dimitropoulos Then you don't need the LiteralUnion
hack at all:
type HexColor = `#${string}`;
type AppThemeColors = 'my-app-blue' | 'my-app-red';
type Color = AppThemeColors | HexColor;
const myFunc = (color: Color) => {}
myFunc('ziltoid') // bad
myFunc('my-app-blue') // good
myFunc('#FF0000') // good
That's a great point @frenic. I think I picked a bad example (trying to think through one beyond #29729 (comment)), but consider this augmented example:
type A = string;
type B = 'b1' | 'b2';
type AorB = LiteralUnion<A, B>;
const func = (aorb: AorB) = { ... }
func('ziltoid') // bad
const someA: A = 'some-random-A'
func(someA) // good
I think it is a bit further than just autocomplete. I have this use-case here:
const a = 'a';
const b = 'b';
type Union = typeof a | typeof b | string;
interface SettingA {
foo: 'bar';
}
interface SettingB {
bar: 'foo';
}
interface Setting {
dam: 'bam';
}
type Settings<T = string> = T extends typeof a
? SettingA
: T extends typeof b
? SettingB
: T extends string
? Setting
: never;
type Ty = { [T in Union]: Settings<T> };
type ValueOf<T> = T[keyof T];
type Ty2 = ValueOf<Ty>;
const array: Array<Ty2> = [];
If Union
contains string
then array type will be Setting[]
but if I remove string
from the Union
, then array type is Array<SettingA | SettingB>
My use-case would be that I need the array to be of type Array<SettingA | SettingB | Setting>
@andrei9669 I'm trying to follow your example but I have to be honest I'm getting a little lost. Is it intentional that Setting
and SettingB
are structurally equivalent? Would it be possible to provide a less abstract use-case?
@andrei9669 I'm trying to follow your example but I have to be honest I'm getting a little lost. Is it intentional that
Setting
andSettingB
are structurally equivalent? Would it be possible to provide a less abstract use-case?
@dimitropoulos oh, sorry, this wasn't my intent, lemme correct that quickly.
If you need better clarification, I can make something up, just let me know.
I found a new way to achieve this, however it results unnecessary boilerplate. It uses generic functions to trick TypeScript and provide autocompletion while accepting any string.
Disclaimer: It only works for functions arguments.
Here is the code:
type AnyString<T, U> = T | U
function recipe<T extends string = ''>(ingredient: AnyString<T, 'carrot' | 'tomato' | 'potato' | 'potatoes'>) {
/** */
}
As you can see, we defined a T
generic on the recipe
function, and basically define ingredient
to be T | 'carrot' | 'tomato'...
.
A few remarks:
-
Why using an
AnyString
type, where using a basic union would work? Because it is displayed when people look at the quick documentation, and it explicitely tells the user he can use any string. If we directly used the union, the user would see'' | 'carrot' | 'tomato' | 'potato' | 'potatoes'
because T defaults to''
, and could think only those 5 strings are allowed. See the difference below.
-
Why does
T
defaults to''
? We could make it default tostring
, or remove the default: the documentation would then show thatingredient
can be astring
, and that's all. This isn't necessarily a bad thing, it depends if you want your users to see the possible autocompleted strings (or a name likeINGREDIENTS
) before actually defining the parameter. See the result of defaultingT
tostring
below.
We can define an intermediate type for our possible ingredients:
type AnyString<T, U> = T | U
type INGREDIENTS = 'carrot' | 'tomato' | 'potato' | 'potatoes'
function recipe<T extends string = ''>(ingredient: AnyString<T, INGREDIENTS>) {
/** */
}
So the main argument against having auto-complete on literal unions with a string
"just work" was that TypeScript normalizes types early for performance and it is impossible to preserve original type information until later to produce a useful auto-complete for this use case.
From https://devblogs.microsoft.com/typescript/announcing-typescript-4-2-rc/#smarter-type-alias-preservation it sounds like TypeScript is actually fine with preserving some original type information to provide a better developer experience. Was there a change of heart? Is it reasonable to ask TypeScript team to re-consider this use case and implement something similar for literal string unions?
Surprised this hasn't been mentioned yet, but to comply with the eslint(@typescript-eslint/ban-types)
rule, you can use Record<never, never>
instead of {}
(which may have unexpected behaviors) when using LiteralUnion
:
export type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>);
Surprised this hasn't been mentioned yet, but to comply with the
eslint(@typescript-eslint/ban-types)
rule, you can useRecord<never, never>
instead of{}
(which may have unexpected behaviors) when usingLiteralUnion
:export type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>);
@a2br This is very cool, never thought of using Record<never, never>
before. Can you clarify what you mean by "which may have unexpected behaviors"?
For those interested, there is a workaround for this called
LiteralUnion
in type-fest.
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).
It's not uncommon to have a list of default values but allow custom ones.
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
AFAIK, none of the proposed workarounds work in javascript (with jsdoc). It's not uncommon to have a list of default values but allow custom ones.
Seems to work (TS 4.6.2).
// @ts-check
/** @type {'default' | string & {}} */
let value = "default"; // autocompletes
Must 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 :)
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'
}
@chavyleung @aquaductape Instead of omitting something from itself and creating basically an undefined behaviour, why not try omitting it from a second template type?
For example:
type HintFix<Sub,Super> = Sub | Omit<Super,Sub>;
type Test = HintFix<"a" | "b" | "c", string>;
const testResult = "a"; //Try editing this one!
TLDR simplification of all examples:
type AnyString = string & {}
function testFn(hintedString: 'hi' | 'bye' | AnyString) {}
There are somthing notable when TypeScript version > 4.7.4
- The
Omit
approch does work, but it's downgrading the type and may break some type checks.
Whereas string & {}
doesn't work when using generic:
Finally, an empty interface works perfect for both:
And, here's the final perfect Utility type:
interface Nothing {}
type Union<T, U> = T | (U & Nothing)
const str: Union<"foo" | "bar", string> = 'qux'
Doesn't work for typeguard though
Instead of omitting something from itself and creating basically an undefined behaviour, why not try omitting it from a second template type?
For example:
type HintFix<Sub,Super> = Sub | Omit<Super,Sub>; type Test = HintFix<"a" | "b" | "c", string>; const testResult :Test = "a"; //Try editing this one!
@kkmuffme can you try this?
@Abrifq doesn't work literally (which has nothing to do with type guard anyway)
https://www.typescriptlang.org/play?#code/FAFwngDgpgBAEgSwHYgGIIB4B4DKBXAIwBp9oAnAPhgF4Z8CYAfGAeQFsERc9yTCKA3KEiwAKlADOIGvGRpMWAEQBDRUxiKCa5ooDGiojCllkAc0HBdAeyRSYISSABKkvABsQALhji7tTYoCMAD0waJkYDBQACacZvYAFggSMDZQAIRAA
And in type guard doesn't work either:
https://www.typescriptlang.org/play?#code/FAFwngDgpgBAEgSwHYgGIIB4B4DKBXAIwBp9oAnAPhgF4Z8CYAfGAeQFsERc9yTCKA3KEiwAKlADOIGvGRpMWAEQBDRUxiKCa5ooDGiojCllkAc0HBgAMzxJdIBAHskRx2yggAFmYAUysqYAXDDiUgCUwQDkypEwAN7AMDAIVjB+ATTUtNGRYfGJSTBkHnhkLv6mQkkAvgXFIKUuOcDVQA
type HintFix<Sub,Super> = Sub | Omit<Super,Sub>;
type Test = HintFix<"a" | "b" | "c", string>;
function something(arg: Test): 'a' {
if (arg === 'a') {
return arg;
}
return 'a'
}
@vaakian Nice, but is there any advantage on what you proposed instead of just using import type {LiteralUnion} from 'type-fest'
?
@papb they are literally the same thing. type-fest
is using a Record<never, never>
instead of an empty Interface.
Just saw this in the iteration plan. This is working in WebStorm (I'm always surprised how much things they developed on top the the default language server).
These Solutions all do not work if you try to use them for a index signature
I am trying to get the same behaviour as part of object, ie kind of const guard, but with no success. But I think my issue is a little bit different, is there a corresponding TS issue?
import { LiteralUnion } from 'type-fest';
type Pet = LiteralUnion<'dog' | 'cat', string>;
type DogHome = {
citizens: Pet & 'dog',
canDo: 'wow'
}
type CatHome = {
citizens: Pet & 'cat',
canDo: 'meuw'
}
type UnknownHome = {
citizens: string;
canDo?: string;
phone?: string;
}
type AllHomes = DogHome | CatHome | UnknownHome;
const home: AllHomes = {
citizens: 'cat',
phone: 'fff' // <- only canDo should be available
}
I am trying to get the same behaviour as part of object, ie kind of const guard, but with no success. But I think my issue is a little bit different, is there a corresponding TS issue?
import { LiteralUnion } from 'type-fest'; type Pet = LiteralUnion<'dog' | 'cat', string>; type DogHome = { citizens: Pet & 'dog', canDo: 'wow' } type CatHome = { citizens: Pet & 'cat', canDo: 'meuw' } type UnknownHome = { citizens: string; canDo?: string; phone?: string; } type AllHomes = DogHome | CatHome | UnknownHome; const home: AllHomes = { citizens: 'cat', phone: 'fff' // <- only canDo should be available }
I have the similar problem, do you solved this problem?
I cannot believe, what a beautiful π
How is this solved now?
The bulk "Close" action I applied to Design Limitation issues doesn't let you pick what kind of "Close" you're doing, so don't read too much into any issue's particular bit on that. I wish GitHub didn't make this distinction without a way to specify it in many UI actions!
The correct state for Design Limitation issues is closed since we don't have any plausible way of fixing them, and "Open" issues are for representing "work left to be done".
is this https://www.typescriptlang.org/play/?#code/FAFwngDgpgBAsgUQCoAkDyARAyjAvDAIgHFkCYAfQgBTSyTIHoGYA6N4USWNAIwCsoAYxAAmPDADewGDJgQATgHsIALhgADACQTEqTFgC+MbQQYBDCAEsGANwCMZSqYvWbIx4XNXbAZg8AKAGcQeUsAOwBzGAAySQMASgN1AG5gAw5BRTDgmDM1XgFhMXwpWTklVUJGZgA5RVyAVxBFTIBbCAAbKBAoNKA related to this issue?
is this https://www.typescriptlang.org/play/?#code/FAFwngDgpgBAsgUQCoAkDyARAyjAvDAIgHFkCYAfQgBTSyTIHoGYA6N4USWNAIwCsoAYxAAmPDADewGDJgQATgHsIALhgADACQTEqTFgC+MbQQYBDCAEsGANwCMZSqYvWbIx4XNXbAZg8AKAGcQeUsAOwBzGAAySQMASgN1AG5gAw5BRTDgmDM1XgFhMXwpWTklVUJGZgA5RVyAVxBFTIBbCAAbKBAoNKA related to this issue?
yes, as a workaround you can use type-fest
's LiteralUnion
@mfulton26 it does not seem to work even with this helper. Adding both unions into a single string literal causes the whole thing to become just string.
@mfulton26 it does not seem to work even with this helper. Adding both unions into a single string literal causes the whole thing to become just string.
it works if you move the template literal type into LiteralUnion<>
(instead of nesting LiteralUnion
inside the template literal type)