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

ngcc - use inner/outer class names as appropriate #33533

Closed

Conversation

petebacondarwin
Copy link
Member

// FW-1651

In the @ngx-formly/material library, there are directives that inherit from other classes. In ES5 format this results in class declarations that have the following form:

var Child = /** @class */ (function (_super) {
    __extends(Child_Inner, _super);
    function Child_Inner() {...}
    return Child_Inner;
}(Parent));

But then ngcc is adding definitions into the class as follows:

var Child = /** @class */ (function (_super) {
    __extends(Child_Inner, _super);
    function Child_Inner() {...}
    Child.ɵfac = ...;
    Child.ɵdir = ...;
    return Child_Inner;
}(Parent));

Note that the new definitions are referring to Child which is the outer class identifier.

Unfortunately this Child identifier has not been initialised at the time the definitions are attached, so we get a Reference error.

One approach was to move the definitions outside of the IIFE as follows:

var Child = /** @class */ (function (_super) {
    __extends(Child_Inner, _super);
    function Child_Inner() {...}
    return Child_Inner;
}(Parent));
Child.ɵfac = ...;
Child.ɵdir = ...;

This works for this case, but fails if the decorator definition contains references to identifiers that are only defined inside the IIFE. For example:

var BrowserModule = /** @class */ (function () {
    function BrowserModule(parentModule) {...}
    BrowserModule_1 = BrowserModule;
    var BrowserModule_1;
    BrowserModule = BrowserModule_1 = __decorate([
      __param(0, Optional()),
      __param(0, SkipSelf()),
      __param(0, Inject(BrowserModule_1)),
      __metadata("design:paramtypes", [Object])
    ], BrowserModule);
    return BrowserModule;
}());
BrowserModule.ɵmod = ɵngcc0.ɵɵdefineNgModule({ type: BrowserModule });
BrowserModule.ɵinj = ɵngcc0.ɵɵdefineInjector({
  factory: function BrowserModule_Factory(t) {
    return new (t || BrowserModule)(ɵngcc0.ɵɵinject(BrowserModule_1, 12));
  },
  providers: BROWSER_MODULE_PROVIDERS,
  imports: [CommonModule, ApplicationModule]
});

Here you can see that the ɵɵdefineInjector() call references BrowserModule_1 which is only defined within the IIFE.

Also this is not correct because it stops the class from being tree shaken, since the IIFE is the unit of tree shaking.

So the proper solution is to teach ngcc/ngtsc which identifiers to use when generating ES5 definitions.

@petebacondarwin petebacondarwin requested review from a team as code owners November 1, 2019 16:38
@googlebot
Copy link

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

@petebacondarwin petebacondarwin added comp: ngcc action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release and removed cla: no labels Nov 1, 2019
@ngbot ngbot bot added this to the needsTriage milestone Nov 1, 2019
@petebacondarwin petebacondarwin force-pushed the ngcc-formly-fix branch 2 times, most recently from a9707ef to 226aada Compare November 1, 2019 19:08
Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

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

👍

Inconsistent "quotation" marks in the first commit message's subject line:
refactor(ivy): split 'type' into 'type', 'internalType' and `adjacentType` --> refactor(ivy): split `type` into `type`, `internalType` and `adjacentType`

packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts Outdated Show resolved Hide resolved
@@ -86,6 +86,23 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return iife.parent.arguments[0];
}

getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
Copy link
Member

Choose a reason for hiding this comment

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

Could this method be called with a TS ClassDeclaration (e.g. from a .d.ts file)?

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 was debating this with myself. I left it as throwing and ran all the ngcc tests and it never threw.

The fact that this never returns null means that we can't try calling the super first. So we possibly could try calling it if the declaration is not in ES5 format.

I think the point is (at least as things stand right now) that ngtsc only ever calls this method when it is working on the source, not typings.

So at the moment I feel confident leaving it as it is.

packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts Outdated Show resolved Hide resolved
alxhub and others added 4 commits November 2, 2019 22:01
…Type`

When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.

Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.

In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.

The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.

This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
…Class()` for ES5

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.
When decorating classes with ivy definitions (e.g. `ɵfac` or `ɵdir`)
the inner name of the class declaration must be used.

This is because in ES5 the definitions are inside the class's IIFE
where the outer declaration has not yet been initialized.
@petebacondarwin
Copy link
Member Author

Thanks @gkalpak

@alxhub alxhub added cla: yes and removed cla: no labels Nov 4, 2019
@googlebot
Copy link

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

ℹ️ Googlers: Go here for more info.

@petebacondarwin petebacondarwin added action: presubmit The PR is in need of a google3 presubmit and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Nov 4, 2019
@alxhub
Copy link
Member

alxhub commented Nov 4, 2019

Presubmit
No Ivy presubmit needed - ngcc change

@petebacondarwin petebacondarwin added action: merge The PR is ready for merge by the caretaker and removed action: presubmit The PR is in need of a google3 presubmit labels Nov 5, 2019
@ngbot
Copy link

ngbot bot commented Nov 5, 2019

I see that you just added the PR action: merge label, but the following checks are still failing:
    failure status "google3" is failing

If you want your PR to be merged, it has to pass all the CI checks.

If you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help.

@atscott atscott closed this in 8d0de89 Nov 5, 2019
atscott pushed a commit that referenced this pull request Nov 5, 2019
atscott pushed a commit that referenced this pull request Nov 5, 2019
…Class()` for ES5 (#33533)

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.

PR Close #33533
atscott pushed a commit that referenced this pull request Nov 5, 2019
…33533)

When decorating classes with ivy definitions (e.g. `ɵfac` or `ɵdir`)
the inner name of the class declaration must be used.

This is because in ES5 the definitions are inside the class's IIFE
where the outer declaration has not yet been initialized.

PR Close #33533
atscott pushed a commit that referenced this pull request Nov 5, 2019
…Type` (#33533)

When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.

Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.

In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.

The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.

This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.

PR Close #33533
atscott pushed a commit that referenced this pull request Nov 5, 2019
atscott pushed a commit that referenced this pull request Nov 5, 2019
…Class()` for ES5 (#33533)

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.

PR Close #33533
atscott pushed a commit that referenced this pull request Nov 5, 2019
…33533)

When decorating classes with ivy definitions (e.g. `ɵfac` or `ɵdir`)
the inner name of the class declaration must be used.

This is because in ES5 the definitions are inside the class's IIFE
where the outer declaration has not yet been initialized.

PR Close #33533
@petebacondarwin petebacondarwin deleted the ngcc-formly-fix branch November 6, 2019 09:07
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…Type` (angular#33533)

When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.

Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.

In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.

The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.

This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.

PR Close angular#33533
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…Class()` for ES5 (angular#33533)

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.

PR Close angular#33533
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…ngular#33533)

When decorating classes with ivy definitions (e.g. `ɵfac` or `ɵdir`)
the inner name of the class declaration must be used.

This is because in ES5 the definitions are inside the class's IIFE
where the outer declaration has not yet been initialized.

PR Close angular#33533
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…Type` (angular#33533)

When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.

Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.

In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.

The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.

This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.

PR Close angular#33533
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…Class()` for ES5 (angular#33533)

In ES5 the class consists of an outer variable declaration that is
initialised by an IIFE. Inside the IIFE the class is implemented by
an inner function declaration that is returned from the IIFE.
This inner declaration may have a different name to the outer
declaration.

This commit overrides `getInternalNameOfClass()` and
`getAdjacentNameOfClass()` in `Esm5ReflectionHost` with methods that
can find the correct inner declaration name identifier.

PR Close angular#33533
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…ngular#33533)

When decorating classes with ivy definitions (e.g. `ɵfac` or `ɵdir`)
the inner name of the class declaration must be used.

This is because in ES5 the definitions are inside the class's IIFE
where the outer declaration has not yet been initialized.

PR Close angular#33533
@angular-automatic-lock-bot
Copy link

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 Dec 7, 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 target: patch This PR is targeted for the next patch release type: bug/fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants