-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Declaration emit widens literal types in readonly properties #15881
Comments
This is not specific to declaration emit. The general idea is that a property declaration (or any other mutable declaration e.g. export const FOO = "FOO";
export class Bar {
readonly type = FOO
} for non-const, non-readonly declaration the type is widened to its base type, in this case You can always put a type annotation to tell the compiler what type you want the property to be. export class Bar {
type: typeof FOO = FOO
} |
Readonly is also widened: cat > foo.ts
export const FOO = 'FOO';
export class Bar {readonly type = FOO;}
$ tsc --declaration foo.ts
$ cat foo.d.ts
export declare const FOO = "FOO";
export declare class Bar {
readonly type: string;
} |
@mhegazy sorry I changed the report to add |
thanks for pointing that. it is indeed a bug in the declaration emitter. |
Workaround: whenever you export a class, write properties with an extra export class Bar {
readonly type: typeof FOO = FOO;
} |
This is actually the correct behaviour for const foo = "FOO" // widening literal type
class C { readonly cfoo = foo } // still widening
let usage = new C().cfoo // widens to 'string' Should behave similarly even if it's split across two files: // @Filename: foo.d.ts
const foo = "FOO" // widening literal type (special-cased: see below)
class C { readonly cfoo: string } // don't use a non-widening literal type
// @Filename: use.ts
import { C } from './foo'
let usage = new C().cfoo // so usage will still be of type 'string' Note that there is a special case for const foo = "FOO"
const bar = "BAR"
const both = foo || bar emits: const foo = "FOO"
const bar = "BAR"
const both: string which is essentially the same as the emit for I believe that simple constant assignment is special-cased for historical reasons, and because people tend to simple constants. I suspect they do this a lot more than they export classes full of readonly literal types. We could change @gcanti @OliverJAsh @evmar can you give examples of use? I propose leaving this issue open until we have good evidence that (1) a lot of people would use this and (2) this pattern is better than what works today. |
My use case is simulating nominal types: class A { readonly _tag = 'A' }
class B { readonly _tag = 'B' } emits export declare class A {
readonly _tag: string;
}
export declare class B {
readonly _tag: string;
} which leads to import { A, B } from 'foo'
declare function f(a: A): void
f(new B()) // <= no error Adding an explicit type annotation class A { readonly _tag: 'A' = 'A' }
class B { readonly _tag: 'A' = 'B' } emits export declare class A {
readonly _tag: 'A';
}
export declare class B {
readonly _tag: 'B';
} and now I get the expected error import { A, B } from 'foo'
declare function f(a: A): void
f(new B()) // <= ERROR: Type '"B"' is not assignable to type '"A"' |
I had the very same use case as @gcanti, which you can see I had to work around with explicit type annotations in OliverJAsh/twitter-api-ts@5282684. |
Is this why I've been playing around with this, since I was hoping that I could do some computation to help generate a type of |
I'm also hitting this, in similar circumstances. I want to define classes with a readonly field with a string literal type for a discriminated union, and use the value of those types as keys in a separate dictionary (minimal example below). This bug means that while this pattern works fine inside a single typescript module, it breaks completely if I try to export type definitions and use those types elsewhere. This is happening because in my case this bug causes tsc to generates invalid type definitions (i.e. definitions will never type check). One of my exported functions' type signatures only compiles when given string literals, so is fine in the normal TS that does use them, but throws |
I'm running into a similar problem but I can't tell if it's exactly what's described here, or if last week's patch will fix it. I'm trying to Playground example is probably easier to follow -- you should see the error Will the patch address this problem? If not, is it already tracked or should I put in a new ticket? ETA: since I'm calling |
TypeScript Version: 2.3.1
Code
EDIT: added readonly to clarify bug report
Expected behavior:
Bar.type is of type
"FOO"
Actual behavior:
The type falls back to
string
The text was updated successfully, but these errors were encountered: