Improve lazy type checking of listings and mappings (#789)

Motivation:
- simplify implementation of lazy type checking
- fix correctness issues of lazy type checking (#785)

Changes:
- implement listing/mapping type cast via amendment (`parent`) instead of delegation (`delegate`)
- handle type checking of *computed* elements/entries in the same way as type checking of computed properties
  - ElementOrEntryNode is the equivalent of TypeCheckedPropertyNode
- remove fields VmListingOrMapping.delegate/typeNodeFrame/cachedMembers/checkedMembers
- fix #785 by executing all type casts between a member's owner and receiver
- fix #823 by storing owner and receiver directly
  instead of storing the mutable frame containing them (typeNodeFrame)
- remove overrides of VmObject methods that are no longer required
  - good for Truffle partial evaluation and JVM inlining
- revert a85a173faa except for added tests
- move `VmUtils.setOwner` and `VmUtils.setReceiver` and make them private
  - these methods aren't generally safe to use

Result:
- simpler code with greater optimization potential
  - VmListingOrMapping can now have both a type node and new members
- fewer changes to surrounding code
- smaller memory footprint
- better performance in some cases
- fixes https://github.com/apple/pkl/issues/785
- fixes https://github.com/apple/pkl/issues/823

Potential future optimizations:
- avoid lazy type checking overhead for untyped listings/mappings
- improve efficiency of forcing a typed listing/mapping
  - currently, lazy type checking will traverse the parent chain once per member,
    reducing the performance benefit of shallow-forcing
	  a listing/mapping over evaluating each member individually
- avoid creating an intermediate untyped listing/mapping in the following cases:
  - `new Listing<X> {...}`
  - amendment of `property: Listing<X>`
This commit is contained in:
odenix
2024-12-06 04:41:33 -08:00
committed by GitHub
parent ad06a96a8a
commit 1bc473ba54
34 changed files with 489 additions and 295 deletions
@@ -0,0 +1,3 @@
function isValid(str): Boolean = str.startsWith("a")
foo: Listing<String(isValid(this))>(isDistinct)
@@ -0,0 +1,6 @@
local a = new Listing { new Listing { 0 } }
local b = a as Listing<Listing<String>>
local c = (b) { new Listing { 1 } }
local d = c as Listing<Listing<Int>>
result = d
@@ -0,0 +1,10 @@
local a = new Listing { new Foo {} }
local b = (a) { new Bar {} }
local c = b as Listing<Bar>
local d = (c) { new Foo {} }
local e = d as Listing<Foo>
result = e
open class Foo
class Bar extends Foo
@@ -0,0 +1,10 @@
local a = new Mapping { [0] = new Foo {} }
local b = (a) { [1] = new Bar {} }
local c = b as Mapping<Int, Bar>
local d = (c) { [2] = new Foo {} }
local e = d as Mapping<Int, Foo>
result = e
open class Foo
class Bar extends Foo
@@ -0,0 +1,7 @@
foo1: Listing<String> = new { "hello" }
foo2: Listing<String|Int> = foo1
res1 = foo1.isDistinct
// steals isDistinct from foo1's VmListing.cachedValues but must not
// perform a String|Int type check because isDistinct is not an element
res2 = foo2.isDistinct
@@ -0,0 +1,8 @@
amends "../../input-helper/listings/cacheStealingTypeCheck.pkl"
// this test covers a regression where the wrong receiver
// and owner was used to typecheck a stolen value
foo {
"abcdx"
"ax"
}
@@ -0,0 +1,5 @@
const local lastName = "Birdo"
typealias Birds = Listing<String(endsWith(lastName))>
typealias Birds2 = Pair<Listing<String(endsWith(lastName))>, Int>
@@ -1,5 +1,7 @@
import "pkl:test"
import "helpers/originalTypealias.pkl"
typealias Simple = String
const function simple(arg: Simple): Simple = arg
@@ -105,3 +107,8 @@ res19: LocalTypeAlias = "abc"
typealias VeryComposite = Pair<Composite, Composite>
res20: VeryComposite = Pair(Map("abc", List("def")), Map("abc", List("def")))
// typealiases should be executed in their original context
res21: originalTypealias.Birds = new { "John Birdo" }
res22: originalTypealias.Birds2 = Pair(new Listing { "John Birdo" }, 0)
@@ -0,0 +1,3 @@
import "helpers/originalTypealias.pkl"
res: originalTypealias.Birds = new { "Jimmy Bird" }
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `String`, but got type `Int`.
Value: 0
x | local b = a as Listing<Listing<String>>
^^^^^^
at listingTypeCheckError8#b (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl)
x | local a = new Listing { new Listing { 0 } }
^
at listingTypeCheckError8#a[#1][#1] (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `listingTypeCheckError9#Bar`, but got type `listingTypeCheckError9#Foo`.
Value: new Foo {}
x | local c = b as Listing<Bar>
^^^
at listingTypeCheckError9#c (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl)
x | local a = new Listing { new Foo {} }
^^^^^^^^^^
at listingTypeCheckError9#a[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Expected value of type `mappingTypeCheckError11#Bar`, but got type `mappingTypeCheckError11#Foo`.
Value: new Foo {}
x | local c = b as Mapping<Int, Bar>
^^^
at mappingTypeCheckError11#c (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl)
x | local a = new Mapping { [0] = new Foo {} }
^^^^^^^^^^
at mappingTypeCheckError11#a[0] (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,8 @@
foo1 {
"hello"
}
foo2 {
"hello"
}
res1 = true
res2 = true
@@ -0,0 +1,4 @@
foo {
"abcdx"
"ax"
}
@@ -38,3 +38,9 @@ res18 = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb
res18b = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb"
res19 = "abc"
res20 = Pair(Map("abc", List("def")), Map("abc", List("def")))
res21 {
"John Birdo"
}
res22 = Pair(new {
"John Birdo"
}, 0)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Type constraint `endsWith(lastName)` violated.
Value: "Jimmy Bird"
x | typealias Birds = Listing<String(endsWith(lastName))>
^^^^^^^^^^^^^^^^^^
at typeAliasContext#res (file:///$snippetsDir/input/types/helpers/originalTypealias.pkl)
x | res: originalTypealias.Birds = new { "Jimmy Bird" }
^^^^^^^^^^^^
at typeAliasContext#res[#1] (file:///$snippetsDir/input/types/typeAliasContext.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)