microsoft / microsoft/TypeScript

Type discrimination by static readonly symbol

Offen
#60,318 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Awaiting More Feedback Suggestion
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

🔍 Search Terms

"type discrimination", "discrimination", "symbol discriminator"

✅ Viability Checklist
⭐ Suggestion

AFAIK it is impossible to discriminate types by a static symbol like this:

class Animal {
    public static readonly symbol = Symbol()
}

if (obj[Animal.symbol] === something) {
    // here types are not deduced correctly
}

In contrast, code like this works fine:

const animal_symbol = Symbol()

if (obj[animal_symbol] === something) {
    // here types are not deduced correctly
}

Not sure if this is a bug or just not implemented due to some complications.

📃 Motivating Example

By using unique symbol in a class I can discriminate objects like this:

class Cat {
    public static readonly symbol = Symbol(`Cat.symbol`)

    public get symbol() : typeof Cat.symbol {
        return Cat.symbol
    }

    public meow() {}
}

class Dog {
    public static readonly symbol = Symbol(`Dog.symbol`)

    public get symbol() : typeof Dog.symbol {
        return Dog.symbol
    }

    public woof() {}
}

type AnimalUnion = Cat | Dog

function take_animal(animal : AnimalUnion) {
    if (animal.symbol === Cat.symbol) animal.meow()
    else if (animal.symbol === Dog.symbol) animal.woof()
    else assert_never(animal, new Error)
}

function assert_never(never : never, error : Error) : never {
    throw error
}

The main focus here is inside take_animal: we can know that animal has meow() method if animal.symbol === Cat.symbol.

In this particular case I used symbol key to store the symbol. But what if key is also a symbol?
Like this:

const animal_symbol = Symbol(`Animal.symbol`)

class Cat {
    public static readonly [animal_symbol] = Symbol(`Cat.symbol`)

    public get [animal_symbol]() : typeof Cat[typeof animal_symbol] {
        return Cat[animal_symbol]
    }

    public meow() {}
}

class Dog {
    public static readonly [animal_symbol] = Symbol(`Dog.symbol`)

    public get [animal_symbol]() : typeof Dog[typeof animal_symbol] {
        return Dog[animal_symbol]
    }

    public woof() {}
}

type AnimalUnion = Cat | Dog

function take_animal(animal : AnimalUnion) {
    if (animal[animal_symbol] === Cat[animal_symbol]) animal.meow()
    else if (animal[animal_symbol] === Dog[animal_symbol]) animal.woof()
    else assert_never(animal, new Error)
}

function assert_never(never : never, error : Error) : never {
    throw error
}

This example works fine and guarantee no name collisions between string key symbol and possible field in derived class.

But for some reason if I try to put animal_symbol in a static readonly property;

class Animal {
    public static readonly symbol = Symbol(`Animal.symbol`)
}

class Cat {
    public static readonly [Animal.symbol] = Symbol(`Cat.symbol`)

    public get [Animal.symbol]() : typeof Cat[typeof Animal.symbol] {
        return Cat[Animal.symbol]
    }

    public meow() {}
}

class Dog {
    public static readonly [Animal.symbol] = Symbol(`Dog.symbol`)

    public get [Animal.symbol]() : typeof Dog[typeof Animal.symbol] {
        return Dog[Animal.symbol]
    }

    public woof() {}
}

type AnimalUnion = Cat | Dog

function take_animal(animal : AnimalUnion) {
    if (animal[Animal.symbol] === Cat[Animal.symbol]) animal.meow()
    else if (animal[Animal.symbol] === Dog[Animal.symbol]) animal.woof()
    else assert_never(animal, new Error)
}

function assert_never(never : never, error : Error) : never {
    throw error
}

The trick doesn't work anymore. Now take_animal() can't deduce meow() from animal[Animal.symbol] === Cat[Animal.symbol].
But I think it should.

💻 Use Cases
  1. What do you want to use this for?
    I want to use this to introduce segregation not by string key, but by a symbol key stored as static readonly.
  2. What shortcomings exist with current approaches?
    It cannot use static readonly symbols.
  3. What workarounds are you using in the meantime?
    I have to use symbols as global variables.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Das Issue nennt keine Dateien oder Tests; beginne mit den take_animal-Beispielen für Cat/Dog und reproduziere den Unterschied zwischen einem Symbol auf Modulebene und Animal.symbol. Verfolge das Verhalten des Type-Checkers bei statischen schreibgeschützten Symbol-Eigenschaften und füge gezielte Testabdeckung hinzu, die das beabsichtigte Narrowing und das vollständige assert_never-Verhalten zeigt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.