Unfortunately, achieving this in TypeScript is not feasible.
The concept of interface names like Bar
being unobservable poses a challenge. While it may be difficult to find a definitive source on this, references like this comment on Microsoft/TypeScript#3060 and possibly this comment on Microsoft/TypeScript#3628 provide related insights and rationales.
It's essential to grasp that TypeScript's type system is structural rather than nominal. Essentially, if two types A
and B
exhibit the same structure, they are considered the same type, regardless of their names or declarations. For instance:
interface A {x: string}
interface B {x: string}
const c = {x: "hello"};
function acceptA(a: A) {}
acceptA(c); // valid
function acceptB(b: B) {
acceptA(b); // valid
}
In this scenario, A
and B
possess distinct declaration sites and names, while c
's type is inferred as an anonymous type. Yet, passing c
into acceptA()
or any arbitrary value of type B</code into <code>acceptA()
doesn't perturb the compiler, treating A
, B
, and typeof C
as identical types. Despite possible differences in display, they represent the same type underneath.
Therefore, creating any type function type F<T> = ...
wherein F<A>
, F<B>
, and F<typeof c>
yield distinct types fundamentally contradicts the principles of structural typing and is unattainable.
While there are exceptions where the compiler deviates from structural typing, such occurrences are rare and unreliable. It's akin to coaxing out internal details by exploiting edge cases, offering undefined results instead of actual solutions.
Class names differ slightly due to potential runtime access to the name
property for class constructors. However, relying on this property having a specific value at runtime isn't advisable.
An inquiry in microsoft/TypeScript#43325 regarding more strictly typed name
properties within class
es suggests possibilities for achieving this with class Foo
.
Even if runtime assessments of the name
property were guaranteed, TypeScript discourages exposing this feature at the type level to prevent conflicts arising from subtypes inheriting specific characteristics like static name
properties.
In essence, the compiler does not distinguish between Foo
labeled as Foo
and Bar
named as Bar
. To align effectively with TypeScript, replicating this indifference would be prudent. Desiring otherwise might indicate encountering an XY problem, necessitating reassessment of your core objectives.
If you seek representation of string literal types like "Foo"
and
"Bar"</code, preemptively declare them in your code:</p>
<pre><code>class Foo {
fooProp = 123
readonly myName = "Foo"
}
interface Bar {
barProp: string;
myName: "Bar";
}
This explicit inclusion of strongly-typed names under a myName
property enables accessing these identifiers:
type DynamicPropertyName<T extends { myName: string }> =
{ [K in T["myName"]]: any };
type WithPropFoo = DynamicPropertyName<Foo>; // {Foo: any}
type WithPropBar = DynamicPropertyName<Bar>; // {Bar: any}
Though seemingly repetitive, structural typing means this approach avoids redundancy or ambiguity. The name assigned to types like Bar
holds no relevance, making constructs like
interface Qux {barProp: string myName: "Bar"}
indistinguishable from
Bar
concerning functional aspect.
If this method fails to address your needs, exploring alternative strategies becomes imperative. Best of luck!
Link to Playground showcasing this approach