Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to pass "unique symbol" as a type parameter (e.g. Opaque<string, unique symbol>). #55691

Closed
5 tasks done
olalonde opened this issue Sep 9, 2023 · 12 comments
Closed
5 tasks done
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@olalonde
Copy link

olalonde commented Sep 9, 2023

✅ Viability Checklist

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

⭐ Suggestion

I have been trying to write a type helper to facilitate the creation of nominal types (aka branded/tagged/opaque types), but I'm a bit stuck by the fact I can't pass unique symbol as a type parameter.

The following doesn't work:

type Opaque<T, TBrand> = T & {
  readonly __brand: TBrand;
}

type UserId = Opaque<string, unique symbol>;
// error: 'unique symbol' types are not allowed here.
type GroupId = Opaque<string, unique symbol>;
// error: 'unique symbol' types are not allowed here.

function printUserId(userId: UserId) {
  console.log(userId)
}

printUserId("abc" as UserId);
printUserId("def" as GroupId);

And if I just get rid of the TBrand parameter, it considers both UserId and GroupId to be compatible.

type Opaque<T, TBrand> = T & {
  readonly __brand: TBrand;
}

type UserId = Opaque<string>;
type GroupId = Opaque<string>;
// error: 'unique symbol' types are not allowed here.

function printUserId(userId: UserId) {
  console.log(userId)
}

printUserId("abc" as UserId);
printUserId("def" as GroupId); // works ... :(

📃 Motivating Example

See suggestion.

💻 Use Cases

  1. What do you want to use this for?

Creating nominal types

  1. What shortcomings exist with current approaches?

Have to memorize how to do it, more code to type.

  1. What workarounds are you using in the meantime?

To do the nominal type without using a type helper.

@nmain
Copy link

nmain commented Sep 11, 2023

You can create each unique symbol.

Or, if you want the symbols to only exist in type space and not at runtime, you can declare each unique symbol.

@fatcerberus
Copy link

^^^ what he said. unique symbol types are tied to a variable declaration, to ensure they always have an identity. Otherwise every utterance of unique symbol in the program would create a new type and you’d have no way to refer back to old ones.

@olalonde
Copy link
Author

olalonde commented Sep 11, 2023

Those are good workarounds but they are workarounds.

unique symbol types are tied to a variable declaration, to ensure they always have an identity.

That's not true. For example:

type UserId =  string & { readonly __brand: unique symbol };

Otherwise every utterance of unique symbol in the program would create a new type and you’d have no way to refer back to old ones.

Not sure what you mean by that.

@fatcerberus
Copy link

It means exactly what I said. Your own example is of creating two Opaque<string, unique symbol> and having them be distinct types despite being the exact same in the source text. But if that were allowed, how could you later refer to the unique symbol type for a particular Opaque if you haven’t given it an identity in the program?

@fatcerberus
Copy link

fatcerberus commented Sep 11, 2023

Those are good workarounds but they are workarounds.

By this reasoning, what you’re trying to do is a workaround too and you ultimately really want #202. You have to compromise somewhere.

@olalonde
Copy link
Author

I am not sure I understand why are you opposed to this feature? Because there are workarounds?

@fatcerberus
Copy link

fatcerberus commented Sep 11, 2023

Not opposed per se, just pointing out the opposite: if you’re opposed to being forced to use workarounds, then adding a feature whose only apparent purpose is to make what’s already a hacky workaround for the stated use case easier doesn’t seem like the way to go.

@olalonde
Copy link
Author

What seems like the way to go?

@fatcerberus
Copy link

#202

@olalonde
Copy link
Author

I agree that #202 would be ideal but it has been opened for almost 10 years and it could be opened for another 10 years. While the feature I propose is not be a big change, it's even arguably a bug.

@andrewbranch andrewbranch added the Working as Intended The behavior described is the intended behavior; this is not a bug label Sep 11, 2023
@andrewbranch
Copy link
Member

This can’t be done without allowing unique symbol types literally anywhere (and so redesigning the entire feature) since the generic type could use the type anywhere:

type Id<T> = T; // obviously legal type alias declaration
type X1 = unique symbol; // error: 'unique symbol' types are not allowed here
type X2 = Id<unique symbol>; // instantiation is legal but results in the same type as X1, which is illegal?

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 14, 2023
@nmain nmain mentioned this issue Jul 27, 2024
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants