-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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 class expressions as type literals #15932
Conversation
This works pretty well. Note that circular references bottom out as `any`. Right now this happens for all type writing, not just for declaration emit, but this is probably an improvement on average.
Add test and update baselines
new (): { | ||
tags(): void; | ||
}; | ||
prototype: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kinda weird that we keep this property. can we filter prototype
out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -3,4 +3,4 @@ | |||
////var [|{| "isWriteAccess": true, "isDefinition": true |}Base|] = class { }; | |||
////class C extends [|Base|] { } | |||
|
|||
verify.singleReferenceGroup("var Base: typeof Base"); | |||
verify.singleReferenceGroup("var Base: {\n new (): {};\n prototype: {};\n}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a way to keep this as typeof Base it would be better I think. The completionlist/quickinfoes would get unreadable with this change otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
export class Test extends WithTags(FooItem) {} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please add example like:
class Base {
}
export function foo() {
return class extends Base {
}
}
This should report error that Base is private and used here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried adding an ad-hoc check, but I couldn't get it to distinguish between this case, where the class expression extends a class declaration, and the mixin case, where the class expression extends a parameter. There are two problems with the latter case:
- The mixed-in class could easily be a non-exported class declaration.
- Class declarations that call the mix-in are no longer enclosed in the mix-in function, making it difficult to track back to the original class expression's context in the displayType machinery.
This export will not cause errors in any case, because the class expression is exported as a type literal, so Base
isn't actually referenced — just its contents.
tl;dr — I don't think it's worthwhile to issue an error here.
// @declaration: true | ||
export var simpleExample = class { | ||
static getTags() { } | ||
tags() { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add private member to this? Are we suppose to see private member in the type literal? If yes we should not be writing type of that private property/method because then it would likely start showing errors (with inaccessible symbol)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test, and removed privates from declaration emit.
I am still working on the error for class expressions that extend from a non-exported base.
src/compiler/checker.ts
Outdated
@@ -3459,6 +3479,11 @@ namespace ts { | |||
buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); | |||
buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); | |||
for (const p of resolved.properties) { | |||
if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral && | |||
(p.name === "prototype" || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about instead checking SymbolFlags.Prototype
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
All right, I think this change is ready to go in now. @mhegazy mind looking at the latest changes to emit errors for private properties that are part of exported class expressions? |
Is it possible to apply this solution to mixins that use generics? I have the following mixins working, as long as I don't try to use them from within another module: // broker.ts
export class BasicBroker<E> {
doSomethingBasic(): E {
//...
}
}
export function SpecialBrokerMixin<E>() {
return function <B extends Constructor<{}>>(Base: B) {
// allows mixins to use decorators and be declared abstract
class WithSpecialBroker extends Base {
doSomethingSpecial(): E {
//...
}
}
return WithSpecialBroker;
}
}
export function BasicSpecialBroker<E>() {
// Can't pass a generic to a function. so make concrete first.
class ConcreteBaseBroker extends BasicBroker<E> { }
return SpecialBrokerMixin<E>()(ConcreteBaseBroker);
} Here's what happens if I try to import them: // app.ts
class AppEntity {
//...
}
// Error TS4020: 'extends' clause of exported class 'AppBroker' has or is using private name 'ConcreteBaseBroker'.
// Error TS4020: 'extends' clause of exported class 'AppBroker' has or is using private name 'WithSpecialBroker'.
export class AppBroker extends BasicSpecialBroker<AppEntity>() {
//...
} I don't see how to define either The following doesn't work, giving me the same errors, even in dev 2.8.0: // broker.d.ts
export interface Constructor<C> {
new (...args: any[]): C;
prototype: C;
}
export interface BasicBroker<E> {
doSomethingBasic(): E;
}
export interface BasicSpecialBroker<E> extends BasicBroker<E> {
doSomethingSpecial(): E;
}
declare function BasicSpecialBrokerMixin<E>(): () => Constructor<BasicSpecialBroker<E>>; |
I found the problem. The compiler |
Fixes #15066, although the resulting d.ts file is a bit ugly. After discussion, @mhegazy and I decided that adding a new "named class expression" type would not be enough to solve the problem on its own, making any complete solution extremely complex. Instead, this change just writes out class expressions as object types, resulting in types like this:
Notes:
any
.