.d.ts files strip private property types, which can be used to distinguish instance types
tjjfvi opened this issue · 6 comments
TypeScript Version: 4.0.0-dev.20200604
Search Terms:
private class property type declaration
Expected behavior: .d.ts files preserve the types of private properties, as these determine if an instance type is assignable to another
Actual behavior: .d.ts files strip private property type, creating an error
Related Issues: N/A
Code
namespace Original {
class Box<T> {
declare private value: T;
}
type Assert<T, U extends T> = U;
export type False = Assert<false, Box<number> extends Box<string> ? true : false>;
}
// Compiles to:
declare namespace Compiled {
class Box<T> {
private value;
}
type Assert<T, U extends T> = U;
// Errors becuase the type of Box.value was stripped
export type False = Assert<false, Box<number> extends Box<string> ? true : false>;
export {};
}Obviously, this is a contrived example, but this was an issue I ran into.
Output
"use strict";
var Original;
(function (Original) {
class Box {
}
})(Original || (Original = {}));Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": 2,
"target": "ES2017",
"jsx": "React",
"module": "ESNext"
}
}Playground Link: Provided
Removing the types is necessary for compatibility/implementation-hiding reasons. The correct fix would be for us to emit the variance measurement of T in the .d.ts; see #1394
A workaround is to use protected instead of private; it will preserve the type in the .d.ts, while still keeping it nominal.
What if the compiler outputted (variance: any) => any, (variance: any) => T, (variance: T) => any, or (variance: T) => T (multiple generics can be handled with intersections, i.e. (variance: T & U) => U), based on the variance. When #1394 lands, it could be changed to the corresponding types, which wouldn't be a breaking change, as the types of private properties are not relevant for anything.
This leads to confusing behavior where vscode (or your editor of choice) reports errors that the compiler doesn't catch when using project references.
E.g. in this playground the editor can infer the type of x even though it's private. If you've set up vscode with project references, the types will be read from the .ts files rather than the .d.ts files, which means the editor can get the type of x.
However, if the type is imported via a .d.ts file (i.e. at compile time), the type of x is any! This leads to a long path of debugging where you can't figure out why vscode doesn't match tsc.
Given that it seems we can't annotate private members with their types, it would probably make sense to explicitly forbid using types of private members imported from .d.ts files.
2 years later and ran exactly into this issue
However, if the type is imported via a .d.ts file (i.e. at compile time), the type of x is any! This leads to a long path of debugging where you can't figure out why vscode doesn't match tsc.
Would like to clarifications inside docs on protected vs private and/or remove the ability to get types from private methods entirely.
2 years later, exactly the same rabbithole and just stumbled across this 🤣