-
Notifications
You must be signed in to change notification settings - Fork 51
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
Validate assignability rules for ComponentLike
and friends
#470
Conversation
Now that we don't have to always have a slot for named args even when the type is empty, we no longer need `EmptyObject` to trigger EPC.
Now that we no longer have to contend with `EmptyObject`, we can safely determine when we're working with `{}` and produce more accurate `[Invoke]` types.
This will enable us to have correct assignability/variance rules for `ComponentLike` w.r.t `Element`
These tests ensure `HelperLike`, `ModifierLike` and `ComponentLike` all have appropriate variance relative to their signatures' constituent parts.
af78188
to
cab62b4
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.
Retroactive review: this looks great overall and I think the changes all broadly make sense. I left some questions inline about removing rather than updating certain tests, but my one slightly bigger concern here is with the null
→ unknown
transition for our internal representation on Element
. The motivation made sense; is our error reporting going to be confusing, though?
packages/environment-ember-loose/__tests__/type-tests/ember-component.test.ts
Show resolved
Hide resolved
packages/environment-ember-loose/__tests__/type-tests/glimmer-component.test.ts
Show resolved
Hide resolved
packages/environment-ember-loose/__tests__/type-tests/intrinsics/mut.test.ts
Show resolved
Hide resolved
Our error reporting is already pretty confusing in the "you didn't define an I'm inclined to say that subbing |
Makes sense to me! |
Background
Now that we've overhauled
[Invoke]
to have a simpler internal representation, we're better positioned to ensure that variousComponentLike
values property reflects the real-world subtyping relationship between their types.As a concrete example, a value with the bare
ComponentLike
type represents a component that can be invoked with no args, no blocks, and no modifiers. Given a definition like this:The value
MyComponent
should be assignable toComponentLike
, because it is legal to invoke without passing any args, blocks or modifiers.If, however, the
name
arg were not optional, thenMyComponent
should no longer be assignable toComponentLike
, as it would no longer be legal to invoke without any arguments.The goal of this PR is to capture this and similar intuitions about the subtyping relationship between
ComponentLike
,HelperLike
andModifierLike
values in the type system, and to put tests in place to ensure we maintain those relationships moving forward.Details
The steps taken for this change also had a couple of side effects that were nice in their own right.
First, landing #447 meant we no longer need the
EmptyObject
type, because now when an entity accepts no named args, we just don't include a parameter for them in[Invoke]
at all, so we no longer need to worry about triggering EPC for{}
cases.Removing
EmptyObject
also, in an ouroboric way, enabled us to clean up the last place where we did create a parameter unnecessarily for named args inInvokableArgs
. This site was already EPC-enabled even withoutEmptyObject
because of theNamedArgs
wrapper, but it's nice not to have the parameter at all.These two changes together ensure that the pairwise subtyping relationship between
Args
andBlocks
works as expected, leaving onlyElement
.Internally we've used the same
null
marker as the public signature type does to represent the element of a component that never spreads its...attributes
. This doesn't work for assignability purposes, as we want a component that does spread...attributes
on a particular element type to be usable in a context where we aren't going to bother passing attributes or modifiers. Accordingly, we now useunknown
internally for this scenario.