Skip to content

fix(ngcc): consistently use outer declaration for classes - #32539

Closed
JoostK wants to merge 3 commits into
angular:masterfrom
JoostK:ngcc-class-symbol
Closed

JoostK wants to merge 3 commits into
angular:masterfrom
JoostK:ngcc-class-symbol

Conversation

@JoostK

@JoostK JoostK commented Sep 7, 2019

Copy link
Copy Markdown
Member

In ngcc's reflection hosts for compiled JS bundles, such as ESM2015,
special care needs to be taken for classes as there may be an outer
declaration (referred to as "declaration") and an inner declaration
(referred to as "implementation") for a given class. Therefore, there
will also be two ts.Symbols bound per class, and ngcc needs to switch
between those declarations and symbols depending on where certain
information can be found.

Prior to this commit, the NgccReflectionHost interface had methods
getClassSymbol and findClassSymbols that would return a ts.Symbol.
These class symbols would be used to kick off compilation of components
using ngtsc, so it is important for these symbols to correspond with the
publicly visible outer declaration of the class. However, the ESM2015
reflection host used to return the ts.Symbol for the inner
declaration, if the class was declared as follows:

var MyClass = class MyClass {};

For the above code, Esm2015ReflectionHost.getClassSymbol would return
the ts.Symbol corresponding with the class MyClass {} declaration,
whereas it should have corresponded with the var MyClass declaration.
As a consequence, no NgModule could be resolved for the component, so
no components/directives would be in scope for the component. This
resulted in errors during runtime.

This commit resolves the issue by introducing a NgccClassSymbol that
contains references to both the outer and inner ts.Symbol, instead of
just a single ts.Symbol. This avoids the unclarity of whether a
ts.Symbol corresponds with the outer or inner declaration.

More details can be found here: https://hackmd.io/7nkgWOFWQlSRAuIW_8KPPw

Fixes #32078
Closes FW-1507

@JoostK JoostK added type: bug/fix action: review The PR is still awaiting reviews from at least one requested reviewer effort1: hours workaround4: none freq2: medium severity3: broken target: major This PR is targeted for the next major release risk: medium labels Sep 7, 2019
@ngbot ngbot Bot modified the milestone: Backlog Sep 7, 2019
@JoostK
JoostK marked this pull request as ready for review September 7, 2019 19:31
@JoostK
JoostK requested review from a team September 7, 2019 19:31

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Both Esm2015ReflectionHost and Esm5ReflectionHost seem to be unused here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ClassSymbol (as well as CtorParameter and isFromDtsFile above) seem to be unused.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I generally find type casting easier to "manually verify", if it is as close as possible to the variable declaration. Consider:

-const declarationSymbol = this.checker.getSymbolAtLocation(outerDeclaration.name);
+const declarationSymbol = this.checker.getSymbolAtLocation(outerDeclaration.name) as ClassSymbol | undefined;
 ...
 return {
   name: declarationSymbol.name,
-  declaration: declarationSymbol as ClassSymbol,
+  declaration: declarationSymbol,

@petebacondarwin petebacondarwin self-assigned this Sep 10, 2019

@petebacondarwin petebacondarwin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work @JoostK both on this PR and the design work that went into it.
A few nits and I agree with @gkalpak's comments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

superSymbol seems a strange name here. It implies (to me) that we are getting a symbol for the super class declaration. But in reality we are asking the super host for the symbol that represents the class. Perhaps classSymbol or es2015ClassSymbol would be preferable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, I see how this can cause confusion. I don't personally like es2015ClassSymbol, as it could as well be the TypeScript symbol, so went with classSymbol instead 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo: change node to declaration here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo: change node to declaration here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

superSymbol => classSymbol ?

@petebacondarwin petebacondarwin added the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Sep 10, 2019
In ngcc's reflection hosts for compiled JS bundles, such as ESM2015,
special care needs to be taken for classes as there may be an outer
declaration (referred to as "declaration") and an inner declaration
(referred to as "implementation") for a given class. Therefore, there
will also be two `ts.Symbol`s bound per class, and ngcc needs to switch
between those declarations and symbols depending on where certain
information can be found.

Prior to this commit, the `NgccReflectionHost` interface had methods
`getClassSymbol` and `findClassSymbols` that would return a `ts.Symbol`.
These class symbols would be used to kick off compilation of components
using ngtsc, so it is important for these symbols to correspond with the
publicly visible outer declaration of the class. However, the ESM2015
reflection host used to return the `ts.Symbol` for the inner
declaration, if the class was declared as follows:

```javascript
var MyClass = class MyClass {};
```

For the above code, `Esm2015ReflectionHost.getClassSymbol` would return
the `ts.Symbol` corresponding with the `class MyClass {}` declaration,
whereas it should have corresponded with the `var MyClass` declaration.
As a consequence, no `NgModule` could be resolved for the component, so
no components/directives would be in scope for the component. This
resulted in errors during runtime.

This commit resolves the issue by introducing a `NgccClassSymbol` that
contains references to both the outer and inner `ts.Symbol`, instead of
just a single `ts.Symbol`. This avoids the unclarity of whether a
`ts.Symbol` corresponds with the outer or inner declaration.

More details can be found here: https://hackmd.io/7nkgWOFWQlSRAuIW_8KPPw

Fixes angular#32078
Closes FW-1507
@JoostK JoostK added action: merge The PR is ready for merge by the caretaker action: presubmit The PR is in need of a google3 presubmit and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews action: review The PR is still awaiting reviews from at least one requested reviewer action: merge The PR is ready for merge by the caretaker labels Sep 10, 2019
@JoostK JoostK removed the action: presubmit The PR is in need of a google3 presubmit label Sep 10, 2019
@JoostK JoostK mentioned this pull request Sep 10, 2019
1 task done
@JoostK JoostK added action: merge The PR is ready for merge by the caretaker action: presubmit The PR is in need of a google3 presubmit labels Sep 12, 2019
@kara

kara commented Sep 12, 2019

Copy link
Copy Markdown
Contributor

presubmit

@kara kara removed the action: presubmit The PR is in need of a google3 presubmit label Sep 12, 2019
@kara kara unassigned alxhub Sep 12, 2019
@kara kara closed this in 2279cb8 Sep 12, 2019
kara pushed a commit that referenced this pull request Sep 12, 2019
In ngcc's reflection hosts for compiled JS bundles, such as ESM2015,
special care needs to be taken for classes as there may be an outer
declaration (referred to as "declaration") and an inner declaration
(referred to as "implementation") for a given class. Therefore, there
will also be two `ts.Symbol`s bound per class, and ngcc needs to switch
between those declarations and symbols depending on where certain
information can be found.

Prior to this commit, the `NgccReflectionHost` interface had methods
`getClassSymbol` and `findClassSymbols` that would return a `ts.Symbol`.
These class symbols would be used to kick off compilation of components
using ngtsc, so it is important for these symbols to correspond with the
publicly visible outer declaration of the class. However, the ESM2015
reflection host used to return the `ts.Symbol` for the inner
declaration, if the class was declared as follows:

```javascript
var MyClass = class MyClass {};
```

For the above code, `Esm2015ReflectionHost.getClassSymbol` would return
the `ts.Symbol` corresponding with the `class MyClass {}` declaration,
whereas it should have corresponded with the `var MyClass` declaration.
As a consequence, no `NgModule` could be resolved for the component, so
no components/directives would be in scope for the component. This
resulted in errors during runtime.

This commit resolves the issue by introducing a `NgccClassSymbol` that
contains references to both the outer and inner `ts.Symbol`, instead of
just a single `ts.Symbol`. This avoids the unclarity of whether a
`ts.Symbol` corresponds with the outer or inner declaration.

More details can be found here: https://hackmd.io/7nkgWOFWQlSRAuIW_8KPPw

Fixes #32078
Closes FW-1507

PR Close #32539
JoostK added a commit to JoostK/angular that referenced this pull request Sep 12, 2019
In ESM2015 bundles, a class with decorators may be emitted as follows:

```javascript
var MyClass_1;
let MyClass = MyClass_1 = class MyClass {};
MyClass.decorators = [/* here be decorators */];
```

Such a class has two declarations: the publicly visible `let MyClass`
and the implementation `class MyClass {}` node. In angular#32539 a refactoring
took place to handle such classes more consistently, however the logic
to find static properties was mistakenly kept identical to its broken
state before the refactor, by looking for static properties on the
implementation symbol (the one for `class MyClass {}`) whereas the
static properties need to be obtained from the symbol corresponding with
the `let MyClass` declaration, as that is where the `decorators`
property is assigned to in the example above.

This commit fixes the behavior by looking for static properties on the
public declaration symbol. This fixes an issue where decorators were not
found for classes that do in fact have decorators, therefore preventing
the classes from being compiled for Ivy.

Fixes angular#31791
kara pushed a commit that referenced this pull request Sep 12, 2019
In ESM2015 bundles, a class with decorators may be emitted as follows:

```javascript
var MyClass_1;
let MyClass = MyClass_1 = class MyClass {};
MyClass.decorators = [/* here be decorators */];
```

Such a class has two declarations: the publicly visible `let MyClass`
and the implementation `class MyClass {}` node. In #32539 a refactoring
took place to handle such classes more consistently, however the logic
to find static properties was mistakenly kept identical to its broken
state before the refactor, by looking for static properties on the
implementation symbol (the one for `class MyClass {}`) whereas the
static properties need to be obtained from the symbol corresponding with
the `let MyClass` declaration, as that is where the `decorators`
property is assigned to in the example above.

This commit fixes the behavior by looking for static properties on the
public declaration symbol. This fixes an issue where decorators were not
found for classes that do in fact have decorators, therefore preventing
the classes from being compiled for Ivy.

Fixes #31791

PR Close #32619
@JoostK
JoostK deleted the ngcc-class-symbol branch September 15, 2019 13:01
@angular-automatic-lock-bot

Copy link
Copy Markdown

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot Bot locked and limited conversation to collaborators Oct 16, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker cla: yes effort1: hours freq2: medium risk: medium target: major This PR is targeted for the next major release type: bug/fix workaround4: none

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ivy] ERROR Error: Template error: Can't bind to 'ngForOf' since it isn't a known property of 'div'.

6 participants