Respect fixed properties when converting Map or Dynamic to Typed#1733
Respect fixed properties when converting Map or Dynamic to Typed#1733nileshpatil6 wants to merge 2 commits into
Conversation
toTyped() on Map and Dynamic built its delegating members the same way for every property, including ones marked fixed. That let a caller supply any value for a fixed property through the source Map or Dynamic, silently overriding the value fixed by the class declaration. createDelegatingMembers is now told whether it is building members for a conversion into Typed. When that is the case, fixed properties are skipped just like hidden properties already are, so the class prototype's own fixed value is used instead of whatever is in the source Map or Dynamic. The Typed to Dynamic direction is unaffected since it already reads from the evaluated Typed value. Added snippet tests in map.pkl and dynamic.pkl covering toTyped() on a class with a fixed property. Fixes apple#573 Signed-off-by: nileshpatil6 <technil6436@gmail.com>
|
A couple questions:
|
|
Good questions.
Let me know on point 1 and I'll push a change if needed. |
|
I'm interested in input from other maintainers on (1), so hold tight there. For (2), this seems like blatantly incorrect behavior in the exact same way that this PR is addressing for class Foo {
const a = 1
}
b = Map("a", 2).toTyped(Foo)Result: b {
a = 2
} |
const properties have the same override-immunity requirement as fixed properties: a value supplied by the source Map/Dynamic must not replace the class's own declared value. Change the isFixed() check in createDelegatingMembers to isConstOrFixed(), and add matching test cases alongside the existing fixed ones in map.pkl and dynamic.pkl. Signed-off-by: nileshpatil6 <technil6436@gmail.com>
|
Good catch, you're right and I was wrong in my earlier reply, const does need the same treatment here, it's the same bug shape as fixed. I dug into it more and found that Pushed a commit that swaps the Still waiting on other maintainers for point 1 like you said, just wanted to get point 2 sorted since your repro made it obvious it was actually broken, not a style question. |
Fixes #573
The bug:
toTyped()on Map and Dynamic builds a set of delegating members thatread their value from the source Map/Dynamic. This is done the same way for
every property, regardless of whether the property is marked
fixed. Socalling
Map("id", 99).toTyped(SomeClass)on a class whereidis declaredfixed id: Int = 1produces a Typed object withid = 99, silentlyoverriding the fixed value.
Root cause:
VmClass.createDelegatingMembersalready special-caseshiddenproperties by skipping them when building the delegating member map, so the
resulting Typed instance falls back to its prototype's value. It does not do
the same for
fixedproperties, even thoughgetMapToTypedMembers()andgetDynamicToTypedMembers()both go through the same method asgetTypedToDynamicMembers().Fix:
createDelegatingMembersnow takes a boolean telling it whether it isbuilding members for a conversion into Typed (dynamic/map -> typed) versus
out of Typed (typed -> dynamic). When converting into Typed,
fixedproperties are skipped the same way
hiddenones already are, so the class'sown fixed value wins instead of whatever happens to be in the source Map or
Dynamic. The typed -> dynamic direction is untouched, since that path already
reads off the already-evaluated Typed value and does not need this check.
Testing: I added cases to the existing snippet tests in
pkl-core/src/test/files/LanguageSnippetTests/input/api/map.pklanddynamic.pkl, with a smallFixedPersonclass that has afixed id: Int = 1property, converting from both Map and Dynamic with a different
idvalue inthe source and asserting the output keeps
id = 1. I could not finishrunning the full Gradle test suite in this environment, the build was still
in the dependency resolution/compile phase after several minutes and I did
not want to sit on the fix while it might get picked up by someone else. I did
carefully review the change by hand: it's a two-line addition to an existing
early-continue check plus a threaded boolean parameter, all three call sites
updated consistently, and I confirmed
ClassProperty.isFixed()alreadyexists and is used elsewhere in the codebase (
UnresolvedPropertyNode), so nonew imports or methods were needed.