-
Notifications
You must be signed in to change notification settings - Fork 12.4k
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
Branding number type with string enum simplifies to never #32891
Comments
Intersections of |
Am I reading this right? If you're saying this works, const x : never = 5; Then it's a bug, right? |
You're right, somehow I missed that. Assignment to |
Are you sure this behavior is expected? String enums are not generally treated as strings. Unlike numeric enums, string enums will not silently cast: The following behavior is true under 3.5.1 const enum ϕ { _ = "" }
const enum ψ { _ = "" }
type Foo = number & ϕ;
type Bar = number & ψ;
declare const foo: Foo;
declare const bar: Bar;
let t: Foo;
t = foo; // ✓
t = bar; // ✘ No accidental casting between different branded numbers
t = 5; // ✘ No unintentional branding of strange numbers
t = 5 as Foo; // ✓ If I change t = 5; // ✓ I understand that this is the historic behavior of numeric enums, so I've been opting to brand with string enums instead. I can't switch to void enums: t = bar; // ✓
t = 5; // ✓ |
Under 3.6.0, the first example from above with string enums produces: t = foo; // ✓
t = bar; // ✓
t = 5; // ✘ Cant cast 5 to never
t = 5 as Foo; // ✓ |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Is there a recommendation for type-branding that folks should use? We had previously tried using empty-const-enums as brands in prior versions of TypeScript, and this no longer works for string and boolean base-types as of TypeScript 3.6 due to the intersection reducing to never. |
If you're branding If you're okay with temporary brands, you can use generics to generate unique nominal types: // Either T or U must be an unresolved generic to create
// a temporary nominal type.
type Nom<T, U> = T extends U ? unknown : unknown;
class someClass<T = unknown> {
x!: string & Nom<T, "foo">;
y!: string & Nom<T, "bar">;
constructor() {
// Branded inside the class
this.x = this.y; // ✘ string is not assignable to Nom<T, "foo">
}
}
// Not branded outside
declare let inst: someClass;
inst.x = inst.y; // ✓ (x: string, y: string) These brands reduce to unknown, so they disappear once the class is instantiated. |
I have faced a similar issue while upgrading to Typescript 3.6.2.
which was working fine in 3.5.2, now is raising the following error:
this is because now the type Is this somehow connected to #33164? |
Doesn't look like it. Your issue is that |
Addresses: basarat#521 Uses technique from: microsoft/TypeScript#32891 Also note: This "& string" that remains in this code sample appears to do nothing as of TypeScript 3.6.2, but presumably should be kept for backwards compat.
I've noticed that this can cause problems if we're doing the lazy nil check of
Hat tip to Samuel Sonne on stack overflow for their answer which nudged me in this direction, and whose humorous string choice I copied. https://stackoverflow.com/questions/52897022/why-cant-brand-enums-be-unioned-in-typescript-3 |
TypeScript Version: 3.6.0-dev.20190814
Search Terms: brand, numeric brand, branding
Code
Expected behavior:
Slot
should be an alias of the intersection typenumber & ϕ
and assignment tox
should fail with:This is the behavior in
3.5.1
.Actual behavior:
Slot
is an alias ofnever
and assignment tox
succeeds without error.This is the behavior in
3.6.0
.Playground Link: playground shows the expected behavior because the issue is only present on the development branch.
Related Issues:
The text was updated successfully, but these errors were encountered: