Execute typechecks eagerly when within a constraint (#964)

This changes the language to check all types eagerly when within a type constraint.

This addresses two regressions in the language:

1. Type constraints are too relaxed (listing/mapping parameters may not be checked)
2. Failing type constraints hide members that were forced during execution of the constraint
This commit is contained in:
Daniel Chao
2025-02-19 12:51:52 -08:00
committed by GitHub
parent 227f0637fc
commit 643c6f5a76
17 changed files with 266 additions and 63 deletions
@@ -0,0 +1,9 @@
typealias EmailAddress = String(matches(Regex(#".+@\S+|.+<\S+@\S+>"#)))
class MyClass {
emails: Listing<EmailAddress>
}
myClass: MyClass
others: Listing<module(this != module)>
@@ -0,0 +1,7 @@
amends ".../input-helper/classes/MyClass.pkl"
myClass {
emails {
"baz@bar.com"
}
}
@@ -0,0 +1,16 @@
import "pkl:test"
const local isOddLengthOfBirds = (it: Listing<Bird>) -> it.length.isOdd
class Bird
class MyTest {
// function parameter type should be checked eagerly
birds: Listing(isOddLengthOfBirds) = new {
1
2
3
}
}
res = test.catch(() -> new MyTest {}.birds)
@@ -0,0 +1,14 @@
// This test executes constraint `EmailAddress` within `MyClass` from two different root nodes:
// - ListingOrMappingTypeCastNode
// - PropertyTypeNode
amends ".../input-helper/classes/MyClass.pkl"
myClass {
emails {
"foo@bar.com"
}
}
others {
import(".../input-helper/classes/myClass1.pkl")
}
@@ -0,0 +1,9 @@
// Error message should include `new Bird { name = "Bob" }`
birds: Listing(firstOneIsSandy) = new {
new Bird { name = "Bob" }
new Bird { name = "Bob" }
}
hidden firstOneIsSandy = (it: Listing<Bird>) -> it[0].name == "Sandy"
class Bird { name: String }
@@ -0,0 +1,11 @@
// Error message should include `new Bird { name = "Bob" }`
birds: Listing(
let (myself: Listing<Bird> = this)
myself[0].name == "Sandy"
) =
new {
new Bird { name = "Bob" }
new Bird { name = "Bob" }
}
class Bird { name: String }
@@ -0,0 +1,9 @@
// typechecks within child frames should also be eagerly checked
foo: Listing(toList().every((it: Listing<Bird>) -> it[0].name == "Bob")) = new {
new Listing {
new Bird { name = "Eagle" }
new Bird { name = "Quail" }
}
}
class Bird { name: String }
@@ -0,0 +1 @@
res = "Expected value of type `constraints13#Bird`, but got type `Int`. Value: 1"
@@ -0,0 +1,15 @@
myClass {
emails {
"foo@bar.com"
}
}
others {
new {
myClass {
emails {
"baz@bar.com"
}
}
others {}
}
}
@@ -0,0 +1,15 @@
–– Pkl Error ––
Type constraint `firstOneIsSandy` violated.
Value: new Listing { new Bird { name = "Bob" }; new Bird { name = ? } }
x | birds: Listing(firstOneIsSandy) = new {
^^^^^^^^^^^^^^^
at constraintDetails1#birds (file:///$snippetsDir/input/errors/constraintDetails1.pkl)
x | birds: Listing(firstOneIsSandy) = new {
^^^^^
at constraintDetails1#birds (file:///$snippetsDir/input/errors/constraintDetails1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,16 @@
–– Pkl Error ––
Type constraint `let (myself: Listing<Bird> = this)
myself[0].name == "Sandy"` violated.
Value: new Listing { new Bird { name = "Bob" }; new Bird { name = ? } }
x | let (myself: Listing<Bird> = this)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at constraintDetails2#birds (file:///$snippetsDir/input/errors/constraintDetails2.pkl)
x | new {
^^^^^
at constraintDetails2#birds (file:///$snippetsDir/input/errors/constraintDetails2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
@@ -0,0 +1,15 @@
–– Pkl Error ––
Type constraint `toList().every((it: Listing<Bird>) -> it[0].name == "Bob")` violated.
Value: new Listing { new Listing { new Bird { name = "Eagle" }; new Bird { name = ? ...
x | foo: Listing(toList().every((it: Listing<Bird>) -> it[0].name == "Bob")) = new {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at constraintDetails3#foo (file:///$snippetsDir/input/errors/constraintDetails3.pkl)
x | foo: Listing(toList().every((it: Listing<Bird>) -> it[0].name == "Bob")) = new {
^^^^^
at constraintDetails3#foo (file:///$snippetsDir/input/errors/constraintDetails3.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)