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

feat(compiler-cli): exclude abstract classes from `strictInjectionPar… #44615

Closed
wants to merge 1 commit into from

Conversation

JoostK
Copy link
Member

@JoostK JoostK commented Jan 4, 2022

…ameters` requirement

In AOT compilations, the strictInjectionParameters compiler option can
be enabled to report errors when an @Injectable annotated class has a
constructor with parameters that do not provide an injection token, e.g.
only a primitive type or interface.

Since Ivy it's become required that any class with Angular behavior
(e.g. the ngOnDestroy lifecycle hook) is decorated using an Angular
decorator, which meant that @Injectable() may need to have been added
to abstract base classes. Doing so would then report an error if
strictInjectionParameters is enabled, if the abstract class has an
incompatible constructor for DI purposes. This may be fine though, as
a subclass may call the constructor explicitly without relying on
Angular's DI mechanism.

Therefore, this commit excludes abstract classes from the
strictInjectionParameters check. This avoids an error from being
reported at compile time. If the constructor ends up being used by
Angular's DI system at runtime, then the factory function of the
abstract class will throw an error by means of the ɵɵinvalidFactory
instruction.

BREAKING CHANGE: Invalid constructors for DI may now report compilation errors

When a class inherits its constructor from a base class, the compiler may now
report an error when that constructor cannot be used for DI purposes. This may
either be because the base class is missing an Angular decorator such as
@Injectable() or @Directive(), or because the constructor contains parameters
which do not have an associated token (such as primitive types like string).
These situations used to behave unexpectedly at runtime, where the class may be
constructed without any of its constructor parameters, so this is now reported
as an error during compilation.

Any new errors that may be reported because of this change can be resolved either
by decorating the base class from which the constructor is inherited, or by adding
an explicit constructor to the class for which the error is reported.

Closes #37914

@JoostK JoostK added feature Issue that requests a new feature area: compiler Issues related to `ngc`, Angular's template compiler target: minor This PR is targeted for the next minor release labels Jan 4, 2022
@ngbot ngbot bot modified the milestone: Backlog Jan 4, 2022
@JoostK JoostK force-pushed the ngtsc/di/strict-params-abstract branch 2 times, most recently from 72cc61c to 7f30792 Compare March 23, 2022 22:13
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR));
expect(diags[0].messageText)
.toEqual(
`The directive AbstractMiddleDir inherits its constructor from ParentClass, but the latter does not have an Angular decorator of its own. Dependency injection will not be able to resolve the parameters of ParentClass's constructor. Either add a @Directive decorator to ParentClass, or add an explicit constructor to AbstractMiddleDir.`);
Copy link
Member Author

Choose a reason for hiding this comment

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

We used to report this report this error for abstract classes and I haven't changed this, but I'm wondering if we should exempt abstract directives from this error.

@Injectable({ providedIn: 'root', useValue: null })
export class UseValueService extends ParentService {}

@Injectable({ providedIn: 'root', useClass: ConcreteServiceWithCtor })
Copy link
Member Author

Choose a reason for hiding this comment

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

Interestingly, we don't check useClass here like we check providers in the providers array! 😱

@JoostK JoostK force-pushed the ngtsc/di/strict-params-abstract branch 3 times, most recently from efbc3f0 to b1db475 Compare April 25, 2022 17:43
@JoostK JoostK marked this pull request as ready for review April 25, 2022 19:20
@JoostK JoostK added the action: review The PR is still awaiting reviews from at least one requested reviewer label Apr 25, 2022
jessicajaniuk
jessicajaniuk previously approved these changes Apr 26, 2022
Copy link
Contributor

@jessicajaniuk jessicajaniuk left a comment

Choose a reason for hiding this comment

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

reviewed-for: public-api

@dylhunn
Copy link
Contributor

dylhunn commented May 2, 2022

@JoostK It looks like this hasn't been updated in a while. Is this a PR we'd like to merge for 14?

@JoostK
Copy link
Member Author

JoostK commented May 2, 2022

@JoostK It looks like this hasn't been updated in a while. Is this a PR we'd like to merge for 14?

It's ready to go from my side, just awaiting review. I'm afraid we're short on time to get this LGTM'd in time.

Comment on lines 45 to 51
const ctorDeps = getConstructorDependencies(declaration, this.host, this.isCore);
return {
ctorDeps: unwrapConstructorDependencies(ctorDeps),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this call registerInjectable before returning?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm yeah that might make sense to avoid potential repeated work 👍

AndrewKushnir
AndrewKushnir previously approved these changes May 2, 2022
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

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

Reviewed-for: public-api

@pullapprove pullapprove bot requested a review from atscott May 2, 2022 22:14
dylhunn
dylhunn previously approved these changes May 2, 2022
Copy link
Contributor

@dylhunn dylhunn left a comment

Choose a reason for hiding this comment

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

reviewed-for: public-api

@JoostK JoostK force-pushed the ngtsc/di/strict-params-abstract branch 3 times, most recently from 48d4344 to 4714782 Compare September 12, 2022 19:07
@dylhunn
Copy link
Contributor

dylhunn commented Sep 13, 2022

TGP is running.

@dylhunn
Copy link
Contributor

dylhunn commented Sep 21, 2022

We've resolved all the TGP issues inside of google3, and have inflight CLs now.

…meters` requirement

In AOT compilations, the `strictInjectionParameters` compiler option can
be enabled to report errors when an `@Injectable` annotated class has a
constructor with parameters that do not provide an injection token, e.g.
only a primitive type or interface.

Since Ivy it's become required that any class with Angular behavior
(e.g. the `ngOnDestroy` lifecycle hook) is decorated using an Angular
decorator, which meant that `@Injectable()` may need to have been added
to abstract base classes. Doing so would then report an error if
`strictInjectionParameters` is enabled, if the abstract class has an
incompatible constructor for DI purposes. This may be fine though, as
a subclass may call the constructor explicitly without relying on
Angular's DI mechanism.

Therefore, this commit excludes abstract classes from the
`strictInjectionParameters` check. This avoids an error from being
reported at compile time. If the constructor ends up being used by
Angular's DI system at runtime, then the factory function of the
abstract class will throw an error by means of the `ɵɵinvalidFactory`
instruction.

In addition to the runtime error, this commit also analyzes the inheritance
chain of an injectable without a constructor to verify that their inherited
constructor is valid.

BREAKING CHANGE: Invalid constructors for DI may now report compilation errors

When a class inherits its constructor from a base class, the compiler may now
report an error when that constructor cannot be used for DI purposes. This may
either be because the base class is missing an Angular decorator such as
`@Injectable()` or `@Directive()`, or because the constructor contains parameters
which do not have an associated token (such as primitive types like `string`).
These situations used to behave unexpectedly at runtime, where the class may be
constructed without any of its constructor parameters, so this is now reported
as an error during compilation.

Any new errors that may be reported because of this change can be resolved either
by decorating the base class from which the constructor is inherited, or by adding
an explicit constructor to the class for which the error is reported.

Closes angular#37914
@JoostK JoostK force-pushed the ngtsc/di/strict-params-abstract branch from 4714782 to 699f9b5 Compare October 7, 2022 16:38
@JoostK JoostK added target: major This PR is targeted for the next major release and removed target: minor This PR is targeted for the next minor release labels Oct 7, 2022
@dylhunn
Copy link
Contributor

dylhunn commented Oct 10, 2022

This should now be green based on the previous TGP. Running a TAP Train to double-check before submitting.

@dylhunn
Copy link
Contributor

dylhunn commented Oct 10, 2022

TAP Train was green, we're good to go!

@dylhunn dylhunn removed the request for review from alxhub October 10, 2022 21:35
@dylhunn dylhunn added merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note and removed state: blocked labels Oct 10, 2022
@ngbot ngbot bot added the action: merge The PR is ready for merge by the caretaker label Oct 10, 2022
@dylhunn
Copy link
Contributor

dylhunn commented Oct 10, 2022

merge-assistance: this already got all the needed approvals and TAP Train was green.

Copy link
Contributor

@jessicajaniuk jessicajaniuk left a comment

Choose a reason for hiding this comment

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

reviewed-for: public-api

@dylhunn dylhunn self-requested a review October 10, 2022 21:43
Copy link
Contributor

@dylhunn dylhunn left a comment

Choose a reason for hiding this comment

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

reviewed-for: public-api

@jessicajaniuk jessicajaniuk removed action: global presubmit The PR is in need of a google3 global presubmit merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note labels Oct 10, 2022
@jessicajaniuk
Copy link
Contributor

This PR was merged into the repository by commit bc54687.

@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 Nov 10, 2022
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 area: compiler Issues related to `ngc`, Angular's template compiler target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compiling @Injectable abstract class fails when constructor params cannot be resolved
6 participants