Skip to content

Commit f161858

Browse files
authored
add failing tests (#29)
* add failing tests * reformat * add more tests * fix the `with` regression as in dhall-lang/dhall-haskell#2597 * fix regression
1 parent afde259 commit f161858

File tree

3 files changed

+50
-8
lines changed

3 files changed

+50
-8
lines changed

scall-core/src/main/scala/io/chymyst/dhall/TypeCheck.scala

+21-5
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ object TypeCheck {
604604
typeError(
605605
s"Expression `assert` failed: Unequal sides, ${lop.alphaNormalized.betaNormalized.print} does not equal ${rop.alphaNormalized.betaNormalized.print}, in ${exprN.print}"
606606
)
607-
case other => typeError(s"An `assert` expression must have an equality type but has ${other.print}")
607+
case other => typeError(s"An `assert` expression must have an equality type but has type ${other.print}")
608608
}
609609
case errors => errors
610610
}
@@ -630,11 +630,27 @@ object TypeCheck {
630630
}
631631

632632
case tt @ Expression(Application(Expression(ExprBuiltin(Builtin.Optional)), t)) =>
633-
if (pathComponents.head.isOptionalLabel) {
634-
validate(gamma, body, t).map(_ => tt)
635-
} else typeError(s"An Optional value must be updated with `?` but instead found ${pathComponents.head}")
633+
pathComponents.head match {
634+
case PathComponent.Label(fieldName) =>
635+
typeError(s"An Optional value must be updated with `?` but instead found ${fieldName}")
636+
case PathComponent.DescendOptional =>
637+
pathComponents.tail match {
638+
case Seq() => // The expression is (Some x) with ? = body. The type of this expression is Optional Y where body : Y.
639+
// We use `validate` to impose the constraint that body's type is `t`. See https://github.com/dhall-lang/dhall-haskell/issues/2597
640+
validate(gamma, body, t).map(_ => tt)
641+
case tail => // The expression is (Some x) with ?.a.b = body. The type of this expression is Optional Y where Y is the type of `x with a.b = body`. Here, `x` is a new variable that should not be free in `body`.
642+
val newVar = VarName("check_type_of_with")
643+
val newBody = Semantics.shift(positive = true, newVar, BigInt(0), body)
644+
val newWith = With(Expression(Variable(newVar, BigInt(0))), tail, newBody)
645+
val newGamma = gamma.prependAndShift(newVar, t)
646+
// newWith.inferTypeWith(newGamma).map(t1 => Expression(Application(Expression(ExprBuiltin(Builtin.Optional)), t1))) // This would allow changing types after `with`.
647+
validate(newGamma, newWith, t).map(_ => tt)
636648

637-
case other => typeError(s"A `with` expression's arg must have record type, but instead found ${other.print}")
649+
}
650+
651+
}
652+
653+
case other => typeError(s"A `with` expression's argument must have record type or Optional type, but instead found type ${other.print}")
638654
}
639655

640656
case DoubleLiteral(_) => Builtin.Double

scall-core/src/test/scala/io/chymyst/dhall/unit/SimpleSemanticsTest.scala

+25-3
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,6 @@ class SimpleSemanticsTest extends DhallTest {
370370

371371
test("failure in eta expansion with two curried arguments") {
372372
val failure = "λ(f : Bool → Bool → Bool) → assert : f === (λ(x : Bool) → λ(y : Bool) → f y x)".dhall.inferTypeWith(KnownVars.empty)
373-
println(failure)
374373
expect(failure match {
375374
case TypecheckResult.Invalid(errors) => errors exists (_ contains "Unequal sides, f does not equal λ(_ : Bool) → λ(_ : Bool) → f _ _@1")
376375
})
@@ -383,15 +382,13 @@ class SimpleSemanticsTest extends DhallTest {
383382

384383
test("failure 1 with f x in eta expansion with free occurrences of external bound variable") {
385384
val failure = "λ(f : Bool → Bool → Bool) → λ(x : Bool) → assert : f x === (λ(x : Bool) → f x x)".dhall.inferTypeWith(KnownVars.empty)
386-
println(failure)
387385
expect(failure match {
388386
case TypecheckResult.Invalid(errors) => errors exists (_ contains "Unequal sides, f x does not equal λ(_ : Bool) → f _ _, in f x ≡ (λ(x : Bool) → f x x)")
389387
})
390388
}
391389

392390
test("failure 2 with f x in eta expansion with free occurrences of external bound variable") {
393391
val failure = "λ(f : Bool → Bool → Bool) → λ(x : Bool) → assert : f === (λ(x : Bool) → f x x)".dhall.inferTypeWith(KnownVars.empty)
394-
println(failure)
395392
expect(failure match {
396393
case TypecheckResult.Invalid(errors) =>
397394
errors exists (_ contains "Types of two sides of `===` are not equivalent: ∀(_ : Bool) → ∀(_ : Bool) → Bool and ∀(x : Bool) → Bool")
@@ -512,4 +509,29 @@ class SimpleSemanticsTest extends DhallTest {
512509
expect(!Semantics.equivalent(x, y))
513510
}
514511

512+
test("`with` for Optional works if it does not change type") { // https://github.com/dhall-lang/dhall-haskell/issues/2597
513+
val result = "(Some 1) with ? = 2".dhall.typeCheckAndBetaNormalize()
514+
expect(result.unsafeGet.print == "Some 2")
515+
}
516+
517+
test("fail `with` for Optional if it changes type") {
518+
val result = """(Some 1) with ? = "hello"""".dhall.typeCheckAndBetaNormalize()
519+
expect(
520+
Try(
521+
result.unsafeGet
522+
).failed.get.getMessage contains "Inferred type Text differs from the expected type Natural, expression under type inference: \"hello\""
523+
)
524+
}
525+
526+
test("`with` for Optional works if it does not change type, with deep record access") { // https://github.com/dhall-lang/dhall-haskell/issues/2597
527+
val result = "(Some { x.y = 1 }) with ?.x.y = 2".dhall.typeCheckAndBetaNormalize()
528+
expect(result.unsafeGet.print == "Some { x = { y = 2 } }")
529+
}
530+
531+
test("fail `with` for Optional if it changes type, with deep record access") {
532+
val result = """(Some { x.y = 1 }) with ?.x.y = "hello"""".dhall.typeCheckAndBetaNormalize()
533+
// expect(result.unsafeGet.print == "Some { x = { y = \"hello\" } }")
534+
expect(Try(result.unsafeGet).failed.get.getMessage contains "Inferred type { x : { y : Text } } differs from the expected type { x : { y : Natural } }")
535+
}
536+
515537
}

tutorial/programming_dhall.md

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ Identifiers may be arbitrary characters (even keywords or whitespace) if escaped
9090
3
9191
```
9292

93+
The standalone underscore character `_` in Haskell and Scala is a syntax for a special "unused" variable.
94+
But in Dhall, the variable named `_` is a variable like any other.
95+
9396
### Primitive types
9497

9598
Integers must have a sign (`+1` or `-1`) while `Natural` numbers may not have a sign (`123`).
@@ -7027,6 +7030,7 @@ p G unfixf (fmap_K T G (unfold T cT) kt)
70277030
as long as the precondition of the law holds:
70287031

70297032
`fmap_F T G (unfold T cT) (cT x) === unfixf (unfold T cT x)`
7033+
70307034
This equation (after setting `R = T` and `rfr = cT`) was derived in Statement 2 in the section "Properties of co-inductive types".
70317035

70327036
This allows us to complete the proof of item 2:

0 commit comments

Comments
 (0)