-
Notifications
You must be signed in to change notification settings - Fork 879
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
Don't throw the shadowed properties dev-mode error when a property is intentionally not reactive. #2160
Conversation
📊 Tachometer Benchmark ResultsSummary⏳ Benchmarks are currently running. Results below are out of date. nop-update
render
update
Results⏳ Benchmarks are currently running. Results below are out of date. ⏱ lit-element-list
render
update
update-reflect
⏱ lit-html-kitchen-sink
render
update
nop-update
⏱ lit-html-repeat
render
update
⏱ lit-html-template-heavy
render
update
⏱ reactive-element-list
render
update
update-reflect
|
@@ -3075,4 +3075,95 @@ suite('ReactiveElement', () => { | |||
assert.equal(el2.attrValue, 'custom'); | |||
}); | |||
}); | |||
|
|||
if (DEV_MODE) { |
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 be moved to reactive-element_dev_mode_test
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.
Ok, I moved these into that file.
🦋 Changeset detectedLatest commit: ad9043c The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@@ -1140,7 +1153,13 @@ export abstract class ReactiveElement | |||
const shadowedProperties: string[] = []; | |||
(this.constructor as typeof ReactiveElement).elementProperties.forEach( | |||
(_v, p) => { | |||
if (this.hasOwnProperty(p) && !this.__instanceProperties?.has(p)) { | |||
if ( |
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.
Rather than loop over elementProperties
, this can just loop over prototypeToReactivePropertyNames.get(this.constructor.prototype)
, right?
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.
Yeah, I think you're right: in createProperty
, the elementProperties
entry is always set before the prototypeToReactivePropertyNames
entry might be set, so prototypeToReactivePropertyNames
should always be the subset we want here. Updated.
…`elementProperties`.
const reactivePropertyNames = | ||
prototypeToReactivePropertyNames.get(this.prototype) ?? new Set(); | ||
reactivePropertyNames.add(name); | ||
prototypeToReactivePropertyNames.set( |
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.
I don't think this will catch the case of shadowing a property defined in the superclass. We can probably just copy the superclass set of properties when the set is created.
e.g. something like new Set(prototypeToReactivePropertyNames.get(this.prototype.prototype))
We should add a test for that as well.
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.
I've added a test for the case where a property defined by some ancestor class would be shadowed by a class field. Also, I had to move the initialization of this set into finalize
so that the set will always exist, even if that class doesn't define any properties itself.
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.
There's one thing I was confused about when moving the initialization: why is it safe to assume that the parent class is something with a finalize
function? Wouldn't it eventually try to call finalize
on HTMLElement
?
It seems like I need to update this initialization to handle this case, but the surrounding code doesn't seem to need to do that. Pretty sure that it's currently lucking out on my probably-incorrect use of !
in that new Set(undefined)
happens to produce an empty set.
…m its parent's set.
@@ -644,6 +652,17 @@ export abstract class ReactiveElement | |||
this.elementProperties = new Map(superCtor.elementProperties); | |||
// initialize Map populated in observedAttributes | |||
this.__attributeToPropertyMap = new Map(); | |||
if (DEV_MODE) { |
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.
I'd rather this be in createProperty
just to keep the DEV_MODE code less spread out.
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.
I moved this here to avoid walking the prototype chain because createProperty
isn't guaranteed to be called at least once for each class. Is walking it preferable to splitting these?
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.
Also, I'm not sure when that would imply combining the list for classes where ancestors define the properties and some tail portion of the hierarchy prevents those properties from being reactive without themselves defining any properties. (The throws when reactive properties defined by an ancestor class are shadowed by class fields
test is the smallest example of 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.
Good point. What about storing the set on the prototype rather than looking it up in the map. That way you'll get it on whatever superclass has it?
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.
Ok, I moved the set initialization back into createProperty
and make the class create a new one if it doesn't have its own.
…on back to `createProperty`.
This PR prevents the dev-mode error about shadowed properties from being thrown if the property is configured not to have a generated descriptor. This includes setting
noAccessor
in the properties block /@property
decorator and returning undefined fromgetPropertyDescriptor
.This PR also adds
undefined
as a possible return type forgetPropertyDescriptor
, which is documented as if it should be able to do this, but currently isn't allowed due to the inferred return type. This change required adding a few!
assertions in some existing tests.