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

Fix crash in JS declaration emit #38508

Merged
merged 2 commits into from
May 15, 2020
Merged

Fix crash in JS declaration emit #38508

merged 2 commits into from
May 15, 2020

Conversation

rbuckton
Copy link
Member

Our binder has a heuristic that detects X.prototype.y assignment in a JS file and treats X as a class, even if X isn't constructible. That results in a crash in JS declaration emit. This adds a check to ensure that the symbol for X actually has valid signatures.

Fixes #36273

// A = {};
// A.prototype.b = {};
const type = getTypeOfSymbol(symbol);
return some(getSignaturesOfType(type, SignatureKind.Construct))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, thinking about it, is there a way to adjust the binder to only apply the Class flag if the thing is actually constructable?

Alternatively, should we still emit this as a class, but with a private constructor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binder has no access to type information, so there's no way to be sure because of augmentations, merging globals, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the private constructor suggestion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can look into it.

Copy link
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of cataloging our behavior, adding

class B extends A {}

(new B()).b;

to the test might be worthwhile, too. I honestly don't know how we type check it offhand, but checking that the declarations are somewhat sensible (or at least in line with any errors we may produce) would seem to make sense.

@rbuckton
Copy link
Member Author

Hmm. Seems like there's more to do here:

// .js
let A;
A = {};
A.prototype.b = {};
class B extends A {}
const x = (new B()).b;

// .d.ts
declare class A {
    private constructor();
    b: {};
}
declare class B {
}
declare const x: any;

The private constructor heuristic is only used in declaration emit. We should probably extend it to type checking class B extends A to make it an error since the constructor is "private".

For the equivalent TS, the output is this:

// .ts
class A {
    private constructor() { }
    b: "x" = "x";
}
// @ts-ignore
class B extends A { }
// @ts-ignore
const x = (new B()).b;

// .d.ts
declare class A {
    private constructor();
    b: "x";
}
declare class B extends A {
}
declare const x: any;

So we're consistent on getting any for (new B()).b, but not in the emit and we don't report errors in JS at the two locations where we do report errors in the TS (labeled above with // @ts-ignore comments.

@weswigham
Copy link
Member

Hm, the emitting errors part is probably important (declaration emit in the presence thereof a little less so). I agree, we should probably add the error. It's certainly odd that we allow it as a base class when it's not actually constructable... The js does error at runtime, too, right?

@rbuckton
Copy link
Member Author

Yes, the JS would error at runtime. The JS decl emit differs because the original declaration of A isn't actually a class and a lot of our code in the checker for resolving base constructor types depends on it being a class, regardless of what the binder says. Its probably fine to have the decl emit be wrong here if we report an error when you subclass.

@rbuckton rbuckton merged commit 1cbe7ef into master May 15, 2020
@rbuckton rbuckton deleted the fix36273 branch May 15, 2020 21:01
cangSDARM added a commit to cangSDARM/TypeScript that referenced this pull request May 18, 2020
* upstream/master:
  Use control flow analysis to check 'super(...)' call before 'this' access (microsoft#38612)
  LEGO: check in for master to temporary branch.
  Make `processTaggedTemplateExpression` visit a returned node
  goToDefinition: find only the value if it's the RHS of an assignment
  Fix regression organize imports duplicates comments (microsoft#38599)
  Fix crash in JS declaration emit (microsoft#38508)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cannot read property 'heritageClauses' of undefined
3 participants