0

Is there an way to make typescript show type hint for the following ?
I am trying to get 'test1' or 'test2' type showing when i am typing 'test' but i failed.

type A = "test1" | "test2" | string

// update 1: it does show 'test1' & 'test2' but not in the 'constant' category (e.g. menuItem/spanner/wrench icon ), but in 'string' category (e.g. 'abc' icon )
// <--- doesn't show 'test1' or 'test2' when i am typing 'test'
const a : A = 'test1' 

In Example 1 , type A is wider into string so we lose the type defination of 'test1'/'test2' as string literal

Ideally variable a it should still show up 'test1' & 'test2' for autocompletion like bvariable in Example 2.

Example 1:

test1 and test2 is suggest as string

Example 2:

variable should show autocomplete in this format for test1 and test 2

4
  • 1
    Can you do something like this :type A = 'test1' | 'test2' | Omit<string, 'test1' | 'test2'> to preserve the type of A? Commented May 30, 2021 at 6:48
  • ✨magic, it works !! please post as an answer , so i can accept it . it will even better if u can give a short desciprtion how it work ,but without will still great. Commented May 30, 2021 at 7:23
  • Hmm, the accepted answer works but not at all for the reason it says; Omit<string, 'test1' | 'test2'> does not mean "any string except "test1" or "test2"; if it means anything, it means "any string without known properties named test1 or test2", which is... werid. There is an official issue in GitHub about this at microsoft/TypeScript#29729, and I've submitted an answer which explains what's going on here. Commented May 31, 2021 at 2:34
  • This question is similar to: TypeScript union of string and string literals. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Jan 23 at 1:49

4 Answers 4

4

The canonical answer to this question is at microsoft/TypeScript#29729 and microsoft/TypeScript#33471.


The union of string literal types with string, such as "test1" | "test2" | string, is equivalent to string. There's no value of type A which can't be assigned to string and vice versa. Furthermore, such types are eagerly reduced to string by the compiler. This behavior is absolutely correct from the point of view of the type system, but unfortunately it doesn't really do what you want from the point of view of documentation.

Ideally you'd like a way to prevent the eager reduction of the union (or at least have the compiler keep track of the pieces of the union for IntelliSense); there is no officially supported way to do this, but there are workarounds:


There's a suggestion in microsoft/TypeScript#29729 to use something like a branded primitive (as described in this TS FAQ entry) for this effect:

type OtherString = string & {};

Here, OtherString is of type string & {}; this happens to be equivalent to string (the "empty object type" {} matches all values except null and undefined... yes, even primitives like string) but the compiler doesn't simplify it, and so it keeps track of the union of literals:

type A = "test1" | "test2" | OtherString;
// type A = "test1" | "test2" | OtherString
// not reduced

That gives you both the behavior and the hinting you're looking for:

function foo(a: A) { }
foo("test1"); // okay
foo("test2"); // okay
foo("test3"); // okay

foo("") //
//   ^ hint here, test1 and test2

foo("") hints test1 and test21


Of course, this is just a workaround and there are some situations in which OtherString might fail to behave the way you want; so be careful!

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

3

type A = 'test1' | 'test2' | Omit<string, 'test1' | 'test2'>

type A can be:

  • test1
  • test2
  • any string except test1 and test2

Therefore, 'test1' and 'test2' can be preserved.


If you have more string literal types, you can make a new type for it to avoid duplicating test1, test2 ...

type fixedString = 'test1' | 'test2' | 'test3' ...
type A = fixedString | Omit<string, fixedString>

3 Comments

This works but not for the reason you are saying. The Omit<T, K> utility type is a mapped type that removes known keys from an object. Omit<string, 'test1' | 'test2'> says that you want a type like string except it doesn't have keys named test1 or test2. On the face of it this is meaningless; string doesn't have keys like that, it kind of has keys like toUpperCase and length.
Conceptually you want something like the Exclude<T, U> utility type, but Exclude<string, 'test1' | 'test2'> is just string; there are no negated types in TypeScript, and no way to remove a few string literal types from string.
The reason Omit<string, 'test1' | 'test2'> is because it is similar to making a branded primitive type as mentioned in this FAQ entry, and is the subject of microsoft/TypeScript#33471 and related issues. Something like (string & { tag?: any }) would work just as well as Omit<string, 'test1' | 'test2'> and wouldn't be pretending to use negated types.
0

Your code example shows a type assertion (as A), but the picture shows a type annotation: : A. I would shy away from type assertions, simply because:

type A = 'test1' | 'test2'

const a = 'foo' as A // no error
const aa: A = 'foo' // errors correctly

playground


regarding the auto completion, I don't think it can work with type assertions, because you write as A after you write test, and as you can see, even without having string in the union type, you can assert any string as A.

if you use a type annotation instead, I have no issues with the auto completion:

type hints showing in typescript playground type hints showing in intelliJ idea

5 Comments

my issue with type A = "test1" | "test2" | string is that type A now only has type defination of 'string' (if u hover above 'A') , 'test1' & test2' string litereal defination is lost which is not what i want. i will update the question according to ur suggestion to avoid confusion.
I think this is how union types work. string is wider than any string literal, and every string literal is assignable to string, so it is inferred to string.
yes, that is a big headache. so i am finding alternative way to workaround for it
So what issue are you trying to solve with this union type? Every string will be assignable to it… 🤷‍♂️
updated the question , sorry for the confusion. there is answer fix all the issue. please check them out
-1

yes, hint showing properly.

enter image description here

string literal types combine nicely with union types, type guards, and type aliases. You can use these features together to get enum-like behavior with strings.

type A = "test1" | "test2";


function animate(easing: A) {
  if (easing === "test1") {
      console.log("test1");
    } else if (easing === "test2") {
        console.log("test2");
    } else {
      console.log("ignoring your types though.")
  }
}
animate("test1");
animate("test2");
animate("test3"); // error:Argument of type '"test3"' is not assignable to parameter of type 'A'.

You can pass any of the two allowed strings, but any other string will give the error.

like this:

enter image description here

3 Comments

in my example i included primitive string as my union member so 'test3' should be allow in this case. The end result should show type hint for 'test1' & 'test2' while allowing other string. Is typescript currently smart enough to to this (or workaround) ?
i dont think so, since in ur example animate doesn't allow 'test3' as argument. but i still want to allow other arbitrary string and not losing 'test1' & 'test2' type hint.
That hint isn't what OP is after. The hint in your image has abc next to it which just means this is text is found somewhere else in the document. You would see the menu icon next to it if it were correctly detecting intellisense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.