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
Ivy compiler incorrectly allows undefined to be passed to any component input even without explicit undefined in input type when strictNullChecks is on #32690
Comments
This is https://angular-team.atlassian.net/browse/FW-1606. @JoostK has a fix :) |
Unfortunately I do not have access to that, so I'm not sure what you're referencing. :( However if there's a fix, that's great! Any idea when it will be shipped? |
@compeek the idea is that we will remove the Expect a PR to be out later this week. |
@JoostK, when you say |
Oh these changes will all be in the generated code that's used for type checking, which will never actually execute. The generated code that the compiler generates for runtime execution will not be affected. |
Oh, I see now. Excellent. Thank you for the information! I look forward to this fix coming out in a "next" release so I can start using it in my project. |
Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606
Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606
Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606
Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606
…lar#33066) Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606 PR Close angular#33066
…lar#33066) Prior to this change, the template type checker would always allow a value of type `undefined` to be passed into a directive's inputs, even if the input's type did not allow for it. This was due to how the type constructor for a directive was generated, where a `Partial` mapped type was used to allow for inputs to be unset. This essentially introduces the `undefined` type as acceptable type for all inputs. This commit removes the `Partial` type from the type constructor, which means that we can no longer omit any properties that were unset. Instead, any properties that are not set will still be included in the type constructor call, having their value assigned to `any`. Before: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): NgForOf<T>; } NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); ``` After: ```typescript class NgForOf<T> { static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgForOf<T>; } NgForOf.ngTypeCtor(init: { ngForOf: ['foo', 'bar'], ngForTrackBy: null as any, ngForTemplate: null as any, }); ``` This change only affects generated type check code, the generated runtime code is not affected. Fixes angular#32690 Resolves FW-1606 PR Close angular#33066
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. |
🐞 bug report
Affected Package
Not exactly sure, but probably:
@angular/compiler
@angular/compiler-cli
Is this a regression?
No, from my experience, the old compiler didn't enforce strictNullChecks in templates at all, so this is new to Ivy.
Description
With
strictNullChecks
turned on, if you have a component input with a certain type that does not explicitly includeundefined
, the Ivy compiler does not complain if you passundefined
to that input from another component. This defeats a lot of the purpose of usingstrictNullChecks
. (Note that it does complain about passingnull
ifnull
isn't explicitly in the type, so I knowstrictNullChecks
is enabled properly.)I believe this is due to the use of
Partial<Pick<...>>
in the type check block for the template as described here: https://blog.angularindepth.com/type-checking-templates-in-angular-viewengine-and-ivy-77f8536359f5Partial<...>
causes each property to be made optional, which in effect addsundefined
to the type of each property as well, as described in the TypeScript docs:Optional parameters and properties automatically have undefined added to their types, even when their type annotations don’t specifically include undefined.
. (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#optional-parameters-and-properties)So, what seems to be happening is the Ivy compiler is adding
undefined
to the type of every input when considering the bindings, which means it will never complain about passingundefined
to the input of any component. That really makesstrictNullChecks
a lot less useful.Please understand that although nobody in practice would probably pass a hardcoded
undefined
to a component input, this is an actual problem any time you useundefined
to represent the absence of a value being passed to a component. In my case, I have a store I subscribe to using theasync
pipe, and then some of the state gets passed into child components from there. Various properties on my state have types likeSomeModel | undefined
.undefined
could mean the model is not yet loaded, for example. Although some people may usenull
instead, there seems to be a significant group that prefers to useundefined
in all cases. Wherever you fall on that debate, my point is that this definitely comes into play in real projects, and in fact I'm having the issue after switching to Ivy in my own project. I can't have a lot of confidence in what I'm passing to my components' inputs ifundefined
is always allowed by the compiler regardless of the actual type of the inputs.🔬 Minimal Reproduction
https://github.com/compeek/angular-ivy-input-undefined-issue
If you compile this project, you should only see a compiler error about
null
and not aboutundefined
as well. Seeindex.html
for some more explanation of the example.You can reproduce this easily in any project with
strictNullChecks
turned on and the Ivy compiler enabled with these two components (from my minimal reproduction project):The compiler should complain about the values passed to both inputs, but in fact it only complains about the
null
value and notundefined
.🔥 Exception or Error
I would expect an error like:
Type 'undefined' is not assignable to type 'string'.
But none is thrown.
And in fact if you do it with
null
, the error looks like this:Type 'null' is not assignable to type 'string | undefined'.
Notice how it says it's not assignable to
string | undefined
even though the input type is juststring
.🌍 Your Environment
Angular Version:
Anything else relevant?
The text was updated successfully, but these errors were encountered: