Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to get a string representation of a type name to use as a template literal type?

I'm trying to return a more concise compile error message from a type function and was wondering if it's possible to get a string representation of a type name?

So using this solution (unfortunately the article does not use anchor links so you'll need to search for ErrorBrand) to create an error message, is it possible to do the following:

type ErrorBrand<Err extends string> = Readonly<{
  [k in Err]: void;
}>;

type MyErrorMsgType<T extends string> = ErrorBrand<`The following type caused an error - ${T}`>
type MyType = {field:string};

type GetStringNameOfType<T> = // converts Type to string name of type

type MyTypeName = GetStringNameOfType<MyType> // outputs 'MyType'

type MyTypeError = MyErrorMsgType<MyTypeName>
// output - The following type caused an error - MyType

Playground

like image 700
james Avatar asked Dec 17 '25 23:12

james


1 Answers

There is currently no feature in TypeScript that converts a type name into a string literal type. I can't find an existing request for this exact feature (there are somewhat similar things like the declined microsoft/TypeScript#29944 asking for emitting the type name as a string literal value at runtime, which is definitely against the rules) so possibly you could file your own.

I wouldn't be very optimistic about it being accepted, because type names are essentially an implementation detail and while they are useful for documentation and compiler messages, they're not the sort of thing that you really want observable in the type system itself. (Consider this comment on a request to convert union types into tuple types, or the PR for implementing labeled tuple elements which makes sure to say that these labels don't affect the type system and are just for documentation/display).

Let's imagine that there were a TypeToString<T> utility type that would produce the following results:

interface A { x: string }
interface B { x: string }
type NameOfA = `It is ${TypeToString<A>}`;
// type NameOfA = "It is A"
type NameOfB = `It is ${TypeToString<B>}`;
// type NameOfB = "It is B"

Now people could do funny things like this:

function whoAmI<T>(x: T, name: TypeToString<T>): void { }
const a: A = { x: "" };
whoAmI(a, "A"); // okay

or

interface Wha extends Record<TypeToString<B>, string> { }
const wha: Wha = { B: "hmm" }; // okay?

In both cases the output of TypeToString<T> is leaking into the type system in places that would normally be reserved for actual literal string values. And because A and B are structurally identical, the compiler considers them the same type:

const b: B = a; // okay

The name of a type doesn't matter to the type system. It would be very weird for TypeToString<T> to be able to extract different information from identical types, and allowing it to do so has some unfortunate implications for assignability in a structural type system.


But I don't think you really want these names as string literal types that you would use as key names in an interface or as discriminant values of a discriminated union, or as types in declaration files, right? Those above examples with whoAmI and Wha are misuses of the feature.

You just want to be able to display type names in messages and do other string manipulation or interpolation to format these messages. It seems to me that the feature you truly want is "invalid types" as requested in microsoft/TypeScript#23689 or possibly "throw types" as experimentally implemented in microsoft/TypeScript#40468 (key word is "experimentally"; this particular PR is labeled "Draft" and there's no reason to think it'll be incorporated into a TypeScript release, at least in this form).

The idea is that there'd be a type function like Invalid<T> that would throw a compiler error if the type were ever instantiated, and where the error message would be derived from T in some way. For example:

function noFunctions<T>(
  t: T extends Function ? 
    Invalid<["Hey,", T, "is a function type. I SAID NO FUNCTIONS!!"]> : 
    T
): void { }

noFunctions(123); // okay
noFunctions(String); // error!
// -------> ~~~~~~
// Hey, 'StringConstructor' is a function type. I SAID NO FUNCTIONS!!

You don't need to turn StringConstructor into a string literal type "StringConstructor"; you just want that string to appear in the error message.

Interestingly enough, the PR at microsoft/TypeScript#40468 actually implemented a TypeToString<T> utility type that has the exact behavior you're looking for! Since this PR has not been merged, and might not ever be merged, it doesn't look like the implications of having an observable TypeToString<T> string literal type have been thoroughly discussed or even really considered there. The existence of the PR makes me a little less confident that TypeToString<T> would be declined, but who knows.

In any case you might want to give ms/TS#23689 a 👍.


But for now, there is currently no such feature in the language. All we have are workarounds. The one I tend to use is a tuple type like I passed to Invalid<> above, and just have a property of that type unlikely to be assignable to any real value:

type Invalid<T> = { __errMsg: T };

noFunctions(String); // error!
// -------> ~~~~~~
// Argument of type 'StringConstructor' is not assignable to parameter of 
// type 'Invalid<["Hey,", StringConstructor, "is a function. I SAID NO FUNCTIONS!!"]>'.

That's not a great error message, but

["Hey,", StringConstructor, "is a function. I SAID NO FUNCTIONS!!"]

appears in there and is at least somewhat human readable if you squint enough. Tuple types are probably the lightest weight syntax I can think of which places things in a desired order.

Notice how this avoids the TypeToString<T> problem, since ["It is", A] and ["It is", B] are the same type if A and B are identical. I still don't intend these message types to appear anywhere else in the type system or at runtime, but at least assignability doesn't break.


Playground link to code

like image 56
jcalz Avatar answered Dec 19 '25 19:12

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!