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
Type Constructors for Generic Directives #41043
Conversation
85cf47f
to
9ba69df
Compare
d4dff54
to
a0fba5a
Compare
7cd920d
to
6acd6d9
Compare
4c42512
to
6b2d963
Compare
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.
You have a pretty good overview of the issue in the comment. Please add a similar description to the commit message. The commit messages should contain all the same information about why a change is being made - it shouldn't only be in the PR comment.
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
* Executing this operation returns a reference to the directive instance variable with its generic | ||
* type parameters set to `any`. | ||
*/ | ||
class TcbDirectiveTypeOpWithGenericParams extends TcbOp { |
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.
All of our TCB operations have names of the form Tcb*Op
, and so should this one.
I'm thinking TcbGenericDirectiveTypeWithAnyParamsOp
(it's kind of unfortunate that "any" has a confusing meaning in a short name like this).
We could also rename TcbDirectiveTypeOp
to TcbNonGenericDirectiveTypeOp
to more cleanly separate the two.
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_file.ts
Outdated
Show resolved
Hide resolved
@@ -690,6 +690,7 @@ export class NgCompiler { | |||
useContextGenericType: strictTemplates, | |||
strictLiteralTypes: true, | |||
enableTemplateTypeChecker: this.enableTemplateTypeChecker, | |||
useInlineTypeConstructors: false, |
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.
This should definitely not be false
. Inline type constructors are required for how the compiler normally operates.
This should probably be set based on whether the current TypeCheckingProgramStrategy
supports inline operations (it exposes a supportsInlineOperations
property to report this).
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.
🤦 , that is correct.
For strict mode, I made it
useInlineTypeConstructors: this.typeCheckingProgramStrategy.supportsInlineOperations,
That's because inlining gives us strict type checking with bound generics because we do not have to set the generic params to any
.
For non-strict mode, I set it to false. That's because we want to be as lenient as possible in non-strict mode. When useInlineTypeConstructors
is false
, we use any for the generic params of bound generics instead of trying to infer the types. It's better to avoid inlining unless we have to because it's problematic ( perf issues, complications with editing user's files).
513df2f
to
e87111c
Compare
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
Outdated
Show resolved
Hide resolved
class TcbGenericDirectiveTypeWithAnyParamsOp extends TcbDirectiveTypeBaseOp { | ||
execute(): ts.Identifier { | ||
const dirRef = this.dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>; | ||
if (dirRef.node.typeParameters === undefined) { |
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.
Should we check !this.dir.isGeneric
too?
packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
Outdated
Show resolved
Hide resolved
d8deebd
to
d959c06
Compare
For the tests in //packages/compiler-cli/src/ngtsc/typecheck, this commits uses a `TypeCheckFile` for the environment, rather than a `FakeEnvironment`. Using a real environment gives us more flexibility with testing.
d959c06
to
960d404
Compare
This commit fixes the behavior when creating a type constructor for a directive when the following conditions are met. 1. The directive has bound generic parameters. 2. Inlining is not available. (This happens for language service compiles). Previously, we would throw an error saying 'Inlining is not supported in this environment.' The compiler would stop type checking, and the developer could lose out on getting errors after the compiler gives up. This commit adds a useInlineTypeConstructors to the type check config. When set to false, we use `any` type for bound generic parameters to avoid crashing. When set to true, we inline the type constructor when inlining is required. Addresses angular#40963
960d404
to
d630414
Compare
) This commit fixes the behavior when creating a type constructor for a directive when the following conditions are met. 1. The directive has bound generic parameters. 2. Inlining is not available. (This happens for language service compiles). Previously, we would throw an error saying 'Inlining is not supported in this environment.' The compiler would stop type checking, and the developer could lose out on getting errors after the compiler gives up. This commit adds a useInlineTypeConstructors to the type check config. When set to false, we use `any` type for bound generic parameters to avoid crashing. When set to true, we inline the type constructor when inlining is required. Addresses #40963 PR Close #41043
@zarend This had a merge conflict on patch branch. Please create a new PR and label accordingly |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Background Knowledge Required
This requires a bit of background knowledge
-- type constructors
--
TcbOp
s-- inlining, specifically of type constructors
Motivation
This PR came from an edge case with generic directives – specifically directives with bound generic parameters.
Creating a generic directive with a bound parameters creates an edge case for TCB generation. Let's see the behavior in VSCode for this situation.
In this screenshot, we have a two confusing error messages.
ng serve
reports that we cannot bind tonotAnInput
because that property does not exist. This error is expected, but it doesn't show up in the IDE.the language service threw an exception with
This component requires inline type checking which is not available in this environment.
The type generation code cannot inline the type constructor for this component, which causes it to throw an exception before finishing type checking the template. This means that part of the template will not be typechecked and VSCode will not be able to present diagnostics in the skipped sections.
Why bound generics are a "no-go" for compiling TCBs
Bound generics can reference private members which are impossible for the compiler to import into the tcb. They can also contain other things such as symbols which would be impossible to import.
Application Code
TCB bad.component.typecheck.ts
The solutions approach is to assign
any
to generic arguments for constructors with bound parameters.Why inline type constructors impact performance.
When we compile a program that requires inlining type constructors, we change add generated code to the component file that the user originally wrote.
bad.component.ts
Changing this file can trigger a second compile, which increases the total compilation time.
Also, we cannot change a user's file while it's been edited, which is why inlining is not available for the language service. It's only available when we compile a build target for the program.
Solution
This is a 2-part PR.
The first commit refactors our tests, which allows use to use TDD in the next commit, which fixes the bug.
Part 1: refactor tests
For the tests in //packages/compiler-cli/src/ngtsc/typecheck, this
commits uses a
TypeCheckFile
for the environment, rather than aFakeEnvironment
. Using a real environment gives us more flexibilitywith testing.
Part 2: implement fix for generic directives with bound parameters
This commit fixes the behavior when creating a type constructor for a directive when the following conditions are met.
Previously, we would throw an error saying 'Inlining is not supported in this environment.' The compiler would stop type checking, and the developer could lose out on getting errors after the compiler gives up.
This commit adds a
useInlineTypeConstructors
to the type check config. When set to false, we useany
type for bound generic parameters to avoid crashing. When set to true, we inline the type constructor when inlining is required.