GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Support some non-structural (nominal) type matching · Issue #202 · microsoft/TypeScript

Support some non-structural (nominal) type matching #202

New issue
Copy link
New issue
Copy link
Open
Open
Support some non-structural (nominal) type matching#202
Copy link
Labels
In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript

Description

@iislucas
iislucas (iislucas)
opened on Jul 22, 2014
Issue body actions

Proposal: support non-structural typing (e.g. new user-defined base-types, or some form of basic nominal typing). This allows programmer to have more refined types supporting frequently used idioms such as:

  1. Indexes that come from different tables. Because all indexes are strings (or numbers), it's easy to use the an index variable (intended for one table) with another index variable intended for a different table. Because indexes are the same type, no error is given. If we have abstract index classes this would be fixed.

  2. Certain classes of functions (e.g. callbacks) can be important to be distinguished even though they have the same type. e.g. "() => void" often captures a side-effect producing function. Sometimes you want to control which ones are put into an event handler. Currently there's no way to type-check them.

  3. Consider having 2 different interfaces that have different optional parameters but the same required one. In typescript you will not get a compiler error when you provide one but need the other. Sometimes this is ok, but very often this is very not ok and you would love to have a compiler error rather than be confused at run-time.

Proposal (with all type-Error-lines removed!):

// Define FooTable and FooIndex
nominal FooIndex = string;  // Proposed new kind of nominal declaration.
interface FooTable {
  [i: FooIndex]: { foo: number };
}
let s1: FooIndex;
let t1: FooTable;

// Define BarTable and BarIndex
nominal BarIndex = string; // Proposed new kind of nominal declaration.
interface BarTable {
  [i: BarIndex]: { bar: string };
}
let s2: BarIndex;
let t2: BarTable;

// For assignment from base-types and basic structures: no type-overloading is needed.
s1 = 'foo1';
t1 = {};
t1[s1] = { foo: 1 };

s2 = 'bar1';
t2 = { 'bar1': { bar: 'barbar' }};

console.log(s2 = s1); // Proposed to be type error.
console.log(s2 == s1); // Proposed to be type error.
console.log(s2 === s1); // Proposed to be type error.

t1[s2].foo = 100; // Gives a runtime error. Proposed to be type error.
t1[s1].foo = 100;

function BadFooTest(t: FooTable) {
  if (s2 in t) {  // Proposed to be type error.
    console.log('cool');
    console.log(t[s2].foo); // Proposed to be type error.
  }
}

function GoodBarTest(t: BarTable) {
  if (s2 in t) {
    console.log('cool');
    console.log(t[s2].bar);
  }
}

BadFooTest(t1); // Gives runtime error;
BadFooTest(t2); // No runtime error, Proposed to be type error.
GoodBarTest(t1); // Gives runtime error; Proposed to be type error.
GoodBarTest(t2);
Reacted by Yaroslav Admin, Elephant-Vessel, ZpdDG4gta, Emma Ffion Davies, ulrichb, Andrey Tsvetkov, Tingan Ho, Caleb Meredith, Steve Sewell, Katashin and 829 more
Reacted by CoenraadS, Isao Yagi, spion, Shu Uesugi, Rainer Martinez, Arnaud Blouin, Ilya, Wolfgang Ederer, Eva Ogbe, Ray Thurn Void and 1 more
Reacted by LongYinan, Remo H. Jansen, SlurpTheo, daniel-chappell-q2, Ron Newcomb, Steve Sewell, ulrichb, Eliza Zhang, CJRE, gabomgp and 158 more
Reacted by Artyom Suchkov, Pavlo Belevtsev, Vitalii Kryvenko, Tom Rathbone, Vova Kulikov, Nicholas Guyett, AwesomeObserver, Matthew Gamble, Xavier Haniquaut, Vladislav Botvin and 40 more
Reacted by Brian Kim, Peter Jah, Vladislav Botvin, Ricard Barnes, Sergey S. Volkov, JomoPipi, Alexey Berezin, Vitaliy Ryaboy, Trevin Miller, Sarina Corrigan and 10 more

Activity

Next
  1. RyanCavanaugh commented on Jul 23, 2014

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Jul 23, 2014
    Member
    More actions

    Is there a better keyword here than "abstract" ? People are going to confuse it with "abstract class".

    +Needs Proposal

    Reacted by Markus, Lucas da Costa, Frederic Barthelemy, Victor Malov, Adam Fiedler, csha, Alex Rao, JomoPipi, Felipe S. S. Schneider, Djakson and 14 more
    Reacted by akopchinskiy, Adrian and Huzaifa Abdurahman
  2. Ryan Cavanaugh (RyanCavanaugh)
    added
    Suggestion
    on Jul 23, 2014
  3. iislucas commented on Jul 23, 2014

    @iislucas
    iislucas (iislucas)
    on Jul 23, 2014
    Author
    More actions

    w.r.t. Needs Proposal: do you mean how to implement it? For compilation to JS, nothing needs to be changed. But would need internal identifiers for new types being introduced and an extra check at assignment.

  4. samwgoldman commented on Jul 23, 2014

    @samwgoldman
    Sam Goldman (samwgoldman)
    on Jul 23, 2014
    More actions

    Regarding a name, what about "nominal" types? Seems pretty common in literature.

    Reacted by bykbtzr, Chris Talman, Mohsen Azimi, Frederik Batuna, Jan Aagaard, Abasov Akif, James Meyers, Markus, Daniel Lo Nigro, Karthik Iyengar and 66 more
  5. RyanCavanaugh commented on Jul 23, 2014

    @RyanCavanaugh
    Ryan Cavanaugh (RyanCavanaugh)
    on Jul 23, 2014
    Member
    More actions

    We're still writing up the exact guidelines on suggestions, but basically "Needs Proposal" means that we're looking for someone to write up a detailed formal explanation of what the suggestion means so that it can be more accurately evaluated.

    In this case, that would mean a description of how these types would fit in to all the various type algorithms in the spec, defining in precise language any "special case" things, listing motivating examples, and writing out error and non-error cases for each new or modified rule.

  6. iislucas commented on Jul 23, 2014

    @iislucas
    iislucas (iislucas)
    on Jul 23, 2014
    Author
    More actions

    Ryan Cavanaugh (@RyanCavanaugh) Thanks! Not sure I have time for that this evening :) but if the idea would be seriously considered I can either do it, to get someone on my team to do so. Would you want an implementation also? Or would a clear design proposal suffice?

  7. danquirk commented on Jul 23, 2014

    @danquirk
    Dan Quirk (danquirk)
    on Jul 23, 2014
    Member
    More actions

    iislucas (@iislucas) no implementation is necessary for "Needs Proposal" issues, just something on the more formal side like Ryan described. No rush ;)

  8. iislucas (iislucas)
    changed the title [-]Support non-structural (abstract) types[/-] [+]Support some non-structural (nominal) type matching[/+] on Jul 23, 2014
  9. zpdDG4gta8XKpMCd commented on Jul 25, 2014

    @zpdDG4gta8XKpMCd
    ZpdDG4gta (zpdDG4gta8XKpMCd)
    on Jul 25, 2014
    More actions

    There is a workaround that I use a lot in my code to get nominal typing, consider:

    interface NominalA {
       'I am a nominal type A, nobody can match me to anything I am not': NominalA;
        value: number;
    }
    
    interface NominalB {
       'I am a nominal type B, mostly like A but yet quite different': NominalB;
       value: number;
    }
    
    // using <any> on constructing instances of such nominal types is the price you have to pay
    // I use special constructor functions that do casting internally producing a nominal object to avoid doing it everywhere
    var a : NominalA = <any>  { value: 1 };
    var b : NominalB = <any>  { value: 2 };
    
    a = b; // <-- problema
    
    Reacted by Siraaj Khandkar, Vitaliy Smolyak, Isao Yagi, Zev Spitz, Tre', bradleat, Anjun Wang, Artur Grzesiak, SlurpTheo, Toni Neubert and 17 more
    Reacted by Hrach and Martyn
    Reacted by Chayim Refael Friedman, Jingle Huang, Craig P Hicks, Sean Alunni, Gulg and Mariksel Azemaj
    Reacted by David Stellini, Remo H. Jansen, enternet, Artur Grzesiak, Toni Neubert, montella1507, Frederic Barthelemy, Mina Luke and Sean Alunni
  10. iislucas commented on Jul 26, 2014

    @iislucas
    iislucas (iislucas)
    on Jul 26, 2014
    Author
    More actions

    Neat trick! Slight optimization, you can use:

    var a = <NominalA>  { value: 1 };
    var b = <NominalB>  { value: 2 };
    

    (Slightly nicer/safer looking syntax)
    [Shame it doesn't work for creating distinct types for string that you want to be indexable]

    Reacted by Jiri Spac, Melle, Remo H. Jansen, Siraaj Khandkar, Kirtan Gajjar, bradleat, Spencer Bliven, nawfalhasan, montella1507, Nicole Rauch and 7 more
  11. basarat commented on Jul 26, 2014

    @basarat
    Basarat Ali Syed (basarat)
    on Jul 26, 2014
    Contributor
    More actions

    Aleksey-Bykov nice trick. We have nominal Id types on the server (c#) that are serialized as strings (and we like this serialization). We've wondered of a good way to do that without it all being string on the client. We haven't seen bugs around this on the client but we still would have liked that safety. Based on your code the following looks promising (all interfaces will be codegened):

    // FOO 
    interface FooId{
        'FooId':string; // To prevent type errors
    }
    interface String{   // To ease client side assignment from string
        'FooId':string;
    }
    // BAR
    interface BarId{
        'BarId':string; // To prevent type errors
    }
    interface String{   // To ease client side assignment from string
        'BarId':string;
    }
    
    
    var fooId: FooId;
    var barId: BarId;
    
    // Safety!
    fooId = barId; // error 
    barId = fooId; // error 
    fooId = <FooId>barId; // error 
    barId = <BarId>fooId; // error
    
    // client side assignment. Think of it as "new Id"
    fooId = <FooId>'foo';
    barId = <BarId>'bar';
    
    // If you need the base string 
    // (for generic code that might operate on base identity)
    var str:string;
    str = <string>fooId;
    str = <string>barId;  
    Reacted by Frederic Barthelemy, Dominik Markiewicz and Gjum
  12. Steve-Fenton commented on Jul 31, 2014

    @Steve-Fenton
    Steve Fenton (Steve-Fenton)
    on Jul 31, 2014
    More actions

    We could look at an implementation that largely left the syntax untouched: perhaps we could add a single new keyword that switches on "nominality" for a given interface. That would leave the TypeScript syntax largely unchanged and familiar.

    class Customer {
        lovesUs: boolean;
    }
    
    named class Client {
        lovesUs: boolean;
    }
    
    function exampleA(customer: Customer) {
    
    }
    
    function exampleB(customer: Client) {
    
    }
    
    var customer = new Customer();
    var client = new Client();
    
    exampleA(customer);
    exampleA(client);
    
    exampleB(customer); // <- Not allowed
    exampleB(client);
    

    So you can use a Client where a Customer is needed, but not vice versa.

    You could fix the error in this example by having Customer extend Client, or by using the correct named type - at which point the error goes away.

    You could use the "named" switch on classes and interfaces.

    Reacted by Tre', fenduru, kgtkr, Thomas Rognon, Zev Spitz, Josh McDonald, Chris Talman, Jan Aagaard, Dillon Shook, Brian Ingles and 13 more
  13. basarat commented on Jul 31, 2014

    @basarat
    Basarat Ali Syed (basarat)
    on Jul 31, 2014
    Contributor
    More actions

    You could use the "named" switch on classes and interfaces.

    👍

    Reacted by Tre', Damian Esteban, Jan Aagaard, Binh Nguyen, Tufan Al-Aqsa and player2I0
  14. Steve-Fenton commented on Jul 31, 2014

    @Steve-Fenton
    Steve Fenton (Steve-Fenton)
    on Jul 31, 2014
    More actions

    You could also use it to make a type nominal in a specific context, even if the type was not marked as nominal:

    function getById(id: named CustomerId) {
        //...
    
    Reacted by Iku-turso and Paul Maréchal
  15. 504 remaining items

    Load more actions
  16. craigphicks commented on Apr 11, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Apr 11, 2024
    More actions

    Nathan Chappell (@nathan-chappell) - In 5.4.5 the second line is an compile error (also a runtime error)

    new DataView(new ArrayBuffer(2))
    new DataView(new Uint8Array([1,2])) // Error
    
    Reacted by Martin Johns
  17. nathan-chappell commented on Apr 11, 2024

    @nathan-chappell
    Nathan Chappell (nathan-chappell)
    on Apr 11, 2024
    More actions

    Craig P Hicks (@craigphicks) this is the issue:

    const arrayBuffer: ArrayBuffer = new Uint8Array()
    const dataView = new DataView(arrayBuffer)
    Reacted by Craig P Hicks
  18. craigphicks commented on Apr 12, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Apr 12, 2024
    More actions

    Nathan Chappell (@nathan-chappell) I see.

    I guess the js run time error is caused by js using a test equivalent to instanceof ArrayBuffer, that uses the constructor associated with passed object inside the constructor for DataView

    However, in the TS library for JS objects, type representing instances of constructed objects do not have associations back to the constructors.

    For example ArrayBuffer is declared a variable of type ArrayBufferConstructor, but the type returned by new ArrayBuffer() doesn't have a reference to the constructor. It's not in the type on <object>.prototype.constructor.

    Similarly for Uint8Array.

    That's a design choice, and behind it is the presumption that the constructor doesn't matter - all that matters is the object structure.

    Perhaps it could be fixed by making the prototype.constructor public on the interface of objects for which assignability should require the same constructor.

    I think that's off topic for "non-structural (nominal) type matching" - which is the subject of this thread, because it is structural.

    Reacted by snarbies
  19. nathan-chappell commented on Apr 12, 2024

    @nathan-chappell
    Nathan Chappell (nathan-chappell)
    on Apr 12, 2024
    More actions

    Craig P Hicks (@craigphicks) Well I already agreed with the design choice. I haven't read all 600 posts here, but the point is that the structural subtyping is incorrect in this case due to unexposed details, and nominal subtyping would solve the issue. Here is a real case where this functionality is needed to avoid runtime errors that could ostensibly be found by typescript without overhauling the entire philosophy - that is, gradual nominal types, initially only where it is required for correctness.

    This isn't appropriate for this thread?

    Reacted by Craig P Hicks, yieldOnly, G. Torreilles, Clarity and Ricardo Fernández Serrata
  20. snarbies commented on Apr 12, 2024

    @snarbies
    snarbies
    on Apr 12, 2024
    More actions

    Tying an interface to a specific class sounds to me like the definition of nominal typing. Branding, or any moral equivalent (e.g. prototype.constructor), is an attempt to emulate nominal typing in a structural system. And in general the prototype.constructor solution would fail if two types had structurally equivalent constructors. The situation here is genuinely two nominally distinct types.

    Reacted by Nathan Chappell, yieldOnly, autosquash, Christopher Muller and Ricardo Fernández Serrata
  21. craigphicks commented on Apr 12, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Apr 12, 2024
    More actions

    Nathan Chappell (@nathan-chappell) snarbies

    Nominal typing for object types is currently available to users on a case-by-case using using symbols.

    const symbol1 = Symbol("1");
    const symbol2 = Symbol("2");
    interface I1 {
        symbol: typeof symbol1
    }
    interface I2 {
        symbol: typeof symbol2
    }
    declare function f1(x:I1): void;
    f1(0 as any as I1); // No Error
    f1(0 as any as I2); // Error
    

    Using symbols also has the advantage that it corresponds to a runtime usable discriminant.

    (Note: Above I wrote "making the prototype.constructor public" but that was wrong because obviously that would only share the type of the constructor).

    Typescript could allow a unique constructor representative thing similar in behavior to a unique symbol to be optionally made public and automatically used for assignability checking when it is present.

    That would be helpful in a number of cases - e.g.

    1. A class with an otherwise empty public interface which currently has no type checking at all. It wouldn't be a lie because instanceof does actually expose that uniqueness.
    2. It would allow TS (or some other library) to write something like the following overload definition for the standard JS-array function Array.filter,
    type Array {
      ...
      filter<T,R>(x:T, f: Boolean, ...): Exclude<T, 0|-0|typeof NaN|null|undefined|false|"">[];
      filter<T>(x:T, f: (x:T, f: ...)=>any, ...): T[];
      ...
    }
    

    That can't be done presently because anything Boolean-like would pass the assignment test but it could be a different class with different behavior.

    Reacted by Ricardo Fernández Serrata
    Reacted by Ricardo Fernández Serrata
  22. snarbies commented on Apr 12, 2024

    @snarbies
    snarbies
    on Apr 12, 2024
    More actions

    I think I might have misunderstood part of your original comment. I thought you were suggesting that the distinction between ArrayBuffer and UInt8Array was structural and thus off-topic, but perhaps you meant your prototype.constructor idea was off-topic. If that's the case, sorry for misunderstanding.

    Reacted by Craig P Hicks and Ricardo Fernández Serrata
  23. craigphicks commented on Apr 12, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Apr 12, 2024
    More actions

    snarbies - I was actually interpreting "non-structural nominal typing" as applying only to TypeScript functionality of discriminating types for which there is no runtime way to distinguish between them (That may be an incorrect assumption about this thread and if so I apologize). That's certainly a topic in it's own right. In contrast this ArrayBuffer vs. UInt8Array problem is about a case where runtime can discriminate, but TypeScript cannot. Even if overlaps with this thread, it has a component worth of being in another thread.

    Reacted by Ricardo Fernández Serrata
  24. craigphicks commented on Apr 13, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Apr 13, 2024
    More actions

    Thanks Nathan Chappell (@nathan-chappell) and snarbies for your thought provoking feedback. I've submitted a proposal #58181 as an attempt to resolve the problem.

    Reacted by Nathan Chappell, snarbies and btoo
  25. nathan-chappell commented on Apr 13, 2024

    @nathan-chappell
    Nathan Chappell (nathan-chappell)
    on Apr 13, 2024
    More actions

    Craig P Hicks (@craigphicks) Great, thanks, cheers.

    Reacted by Craig P Hicks
  26. emilioplatzer commented on May 10, 2025

    @emilioplatzer
    Emilio (emilioplatzer)
    on May 10, 2025
    More actions

    Workarround

    I use a lot this workarround. Here is an example of 800+ commits and 5 years of use

    The folowing example is in Playground

    Typing by example

    Let's say that I'm writing a Survey App. I have surveys, questions and options (possible answers to the questions). I store all of it in the DB, each of them has a string key (but can be an integer one).

    Then I declare the types

    type SurveyId = 'S1'|'S2'|'etc...'
    type QuestionId = 'Q1'|'Q2'|'etc...'
    type OptionId = 'O1'|'O2'|'etc...'

    I can declare indexes and functions and all works (and checks) as expected.

    The only caveat is declaring some values in the code, then you must cast it (but I think that in nominal typing you also must):

    var NOANSWER: OptionId = 'N/A' as OptionId;

    See an example (with lots of assumptions about the db):

    type SurveyStructure = {
        id: SurveyId
        questions: QuestionsId[],
        answersPerQuestion: Record<QuestionId, OptionId[]>
    }
    
    async function retrieveSurvey(surveyId: SurveyId):Promise<SurveyStructure>{
        var result: SurveyStructure = {id: surveyId, questions:[], answersPerQuestion:{}}
        type Question = {id: QuestionId, name:string}
        type Answers = {questionId: QuestionId, optionId: OptionId, description:string}
        var questions = await dbQuery<Question>('select * from questions where surveyId = $1', [surveyId]);
        var answers = await dbQuery<Answers>('select * from answers where surveyId = $1', [surveyId]);
        for (var question of questions) {
            result.questions.push(question.id);
            result.answersPerQuestion[question.id]=[]
            answers.filter(a => a.questionId = question.id).forEach(a => {
                result.answersPerQuestion[question.id].push(a.optionId)
            })
        }
        return result
    }

    Details

    1. Of course, you must declare each type with some examples (real or not) but each set of examples must have differences with any other, if not the check could fail for those.
    2. The error messages are currently nice. For example, if we make a type mistake in the push argument:
      result.answersPerQuestion[question.id].push(question.id)
      you got Argument of type 'QuestionId' is not assignable to parameter of type 'OptionId'. Type '"Q1"' is not assignable to type 'OptionId'.
    3. It can be done with integers too, less readable but still works.
    Reacted by Nathan Phillip Brink, EVA (Entity Value Attribute), Claudia Meadows, Peter Abrahamsen and Marco Massarotto
    Reacted by Ricardo Fernández Serrata and Sean Vieira
  27. Robert Balicki (rbalicki2)
    added a commit that references this issue on Sep 12, 2025

    add `BrandedLink` (#620)

    54508c1
  28. bluepnume commented on May 26, 2026

    @bluepnume
    Daniel Brain (bluepnume)
    on May 26, 2026
    More actions

    Real-world example of where opaque types would provide concrete value.

    In production we simulate opaque types via intersection types across many numeric primitives: Milliseconds, Seconds, Minutes, Hours, Days, Cents, Percentage, Bytes, Pixels, and more. It works, but maintaining it requires custom tooling: a Tagged<> helper, an ESLint rule to confine casts to designated boundaries, and a library of math wrappers (add, subtract, etc.) to preserve opaque identity across arithmetic.

    If opaque types shipped, the type identity becomes a first-class feature:

    opaque type Milliseconds extends number = number;
    opaque type Seconds      extends number = number;
    
    const ms: Milliseconds = 5000;
    const s: Seconds = ms;   // Error: Seconds is not assignable from Milliseconds

    And operator overloading could layer naturally on top, so opaque identity survives arithmetic:

    declare operator "+"(lhs: Milliseconds, rhs: Milliseconds): Milliseconds;
    declare operator "*"(lhs: Milliseconds, rhs: number):      Milliseconds;
    
    const a: Milliseconds = 1000;
    const b: Milliseconds = 2000;
    
    const c = a + b;   // Milliseconds (matches the declared overload)
    const d = a * 2;   // Milliseconds
    const e = a + s;   // number (no matching overload, falls back to number + number)

    Critically, this is strictly additive: existing opaque-typed arithmetic that relies on the implicit upcast to number keeps working unchanged. Opaque identity only survives when a matching overload is in scope.

    #42218 is an alternative path to the same result, bundling opaque types and operator overloading into a single primitive declaration. Detailed writeup of how that would look in our codebase: #42218 (comment).

    Either path turns a widely-used workaround into a first-class language feature.

    Strong support.

    Reacted by iislucas, Grzegorz Szeliga, Nex Zhu, Laszlo Boros, Rudolph Gottesheim, yieldOnly, centigrade-julian-lang, JohnGurin, Jared Rieger, arseniy-lumana and 6 more
  29. irfanstract commented on Aug 27, 2026

    @irfanstract
    irfanstract
    on Aug 27, 2026
    More actions

    Allow extends clause in interfaces to name union types in place of known-name object types. This avoids the self-dealiasing nature of type-aliases (eg type T = ..., type T = T1 | T2 | ...), instead acting as nominal types, preserved across assignments, calls and inference (interfaces are generally inferred as-is, not dealiased).

    /** a trivially-supportible Action. */
    interface CommonAction extends (GlobalRefreshAction | FetchAction | UpdateAction) { }
    function doCommon(a: CommonAction) {
      const b /*: CommonAction */ = a /* infers CommonAction */
      const c /*: CommonAction */ = b /* still infers CommonAction */
      return c
    } /* still CommonAction as its return-type, that's it */

    the compiler should then see:

    /* borrowing Scala's `=:=` operator (Mutual Subtypes) */
    CommonAction =:= (GlobalRefreshAction | FetchAction | UpdateAction)

    just as if it had been type CommonAction = GlobalRefreshAction | FetchAction | UpdateAction, except that it's now a nominal type (interface) instead.

    if wanted, we can introduce a 'special marker type' Opaque, permitted in extends clause of interfaces (C extends Opaque<T>) to mark distinct opaque type (Opaque<T> upper-bounded by T). usage of Opaque elsewhere to be rejected.

    /** a trivially-supportible Action. */
    interface CommonAction extends Opaque<(GlobalRefreshAction | FetchAction | UpdateAction)> { }
    const c = new Array</* ERROR: `Opaque` in unintended place */ Opaque<Action> >() /* ILLEGAL! */
    Reacted by Homa Wong
    Reacted by snarbles2
  30. Shinigami92 commented on Nov 5, 2026

    @Shinigami92
    Shinigami (Shinigami92)
    on Nov 5, 2026
    More actions

    Shinigami (@Shinigami92) (I) and Claude (@claude) are cooking something 👀

    • Prototype: nominal type aliases (nominal type X = Base)

    I haven’t created a PR yet, as I’ve carefully read through the CONTRIBUTING.md, and I’d rather not get blocked by Microsoft 😶‍🌫️

    Reacted by Christian Svensson, musjj and Omid K. Rad
    Reacted by Martin Johns
    Reacted by Christof Fritz, Yiğit Levent, Piotr Kapera, musjj and Omri Luzon
    Reacted by Christof Fritz and musjj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.