Skip to content

Commit

Permalink
Check static type at compile time.
Browse files Browse the repository at this point in the history
  • Loading branch information
tarao committed Nov 4, 2023
1 parent ed11a93 commit 71350b5
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ class RecordSpec extends helper.UnitSpec {
describe("Record.lookup") {
it("should return a value by a string key name") {
val r = %(name = "tarao", age = 3)
Record.lookup(r, "name") shouldBe a[String]
Record.lookup(r, "name") shouldStaticallyBe a[String]
Record.lookup(r, "name") shouldBe "tarao"
Record.lookup(r, "age") shouldBe an[Int]
Record.lookup(r, "age") shouldStaticallyBe an[Int]
Record.lookup(r, "age") shouldBe 3
}

Expand All @@ -208,9 +208,9 @@ class RecordSpec extends helper.UnitSpec {

it("should allow shadowed field to be extracted") {
val r = %(toString = 10)
r.toString shouldBe a[String]
r.toString shouldStaticallyBe a[String]
r.toString shouldBe "%(toString = 10)"
Record.lookup(r, "toString") shouldBe an[Int]
Record.lookup(r, "toString") shouldStaticallyBe an[Int]
Record.lookup(r, "toString") shouldBe 10
}
}
Expand Down Expand Up @@ -356,12 +356,12 @@ class RecordSpec extends helper.UnitSpec {
"val t0: Tag[MyType] = r0" shouldNot typeCheck

val r1 = r0.tag[MyType]
r1 shouldBe a[Tag[MyType]]
r1 shouldStaticallyBe a[Tag[MyType]]
val t1: Tag[MyType] = r1

val r2 = r1.tag[AnotherType]
r2 shouldBe a[Tag[MyType]]
r2 shouldBe a[Tag[AnotherType]]
r2 shouldStaticallyBe a[Tag[MyType]]
r2 shouldStaticallyBe a[Tag[AnotherType]]
val t2: Tag[MyType] = r2
val t3: Tag[AnotherType] = r2
}
Expand Down Expand Up @@ -391,24 +391,24 @@ class RecordSpec extends helper.UnitSpec {
val r1 = %(name = "tarao", age = 3).tag[MyType]

val r2 = r1 ++ %(email = "tarao@example.com")
r2 shouldBe a[Tag[MyType]]
r2 shouldStaticallyBe a[Tag[MyType]]
val t2: Tag[MyType] = r2

val r3 = r1 + (email = "tarao@example.com")
r3 shouldBe a[Tag[MyType]]
r3 shouldStaticallyBe a[Tag[MyType]]
val t3: Tag[MyType] = r3

val r4 = r1.tag[AnotherType]

val r5 = r4 ++ %(email = "tarao@example.com")
r5 shouldBe a[Tag[MyType]]
r5 shouldBe a[Tag[AnotherType]]
r5 shouldStaticallyBe a[Tag[MyType]]
r5 shouldStaticallyBe a[Tag[AnotherType]]
val t4: Tag[MyType] = r5
val t5: Tag[AnotherType] = r5

val r6 = r4 + (email1 = "tarao@example.com") + (occupation = "engineer")
r6 shouldBe a[Tag[MyType]]
r6 shouldBe a[Tag[AnotherType]]
r6 shouldStaticallyBe a[Tag[MyType]]
r6 shouldStaticallyBe a[Tag[AnotherType]]
val t6: Tag[MyType] = r6
val t7: Tag[AnotherType] = r6
}
Expand All @@ -423,19 +423,19 @@ class RecordSpec extends helper.UnitSpec {
val r2 = %(age = 3).tag[YourType]

val r3 = r1 ++ r2
r3 shouldBe a[Tag[MyType]]
r3 shouldBe a[Tag[YourType]]
r3 shouldStaticallyBe a[Tag[MyType]]
r3 shouldStaticallyBe a[Tag[YourType]]
val t3: Tag[MyType] = r3
val t4: Tag[YourType] = r3

val r4 = r1.tag[AnotherType]
val r5 = r2.tag[YetAnotherType]

val r6 = r4 ++ r5
r6 shouldBe a[Tag[MyType]]
r6 shouldBe a[Tag[AnotherType]]
r6 shouldBe a[Tag[YourType]]
r6 shouldBe a[Tag[YetAnotherType]]
r6 shouldStaticallyBe a[Tag[MyType]]
r6 shouldStaticallyBe a[Tag[AnotherType]]
r6 shouldStaticallyBe a[Tag[YourType]]
r6 shouldStaticallyBe a[Tag[YetAnotherType]]
val t6: Tag[MyType] = r6
val t7: Tag[AnotherType] = r6
val t8: Tag[YourType] = r6
Expand All @@ -449,24 +449,24 @@ class RecordSpec extends helper.UnitSpec {
val r1 = %(name = "tarao", age = 3).tag[MyType]

val r2 = r1.as
r2 shouldBe a[Tag[MyType]]
r2 shouldStaticallyBe a[Tag[MyType]]
val t2: Tag[MyType] = r2

val r3 = r1.as[% { val name: String } & Tag[MyType]]
r3 shouldBe a[Tag[MyType]]
r3 shouldStaticallyBe a[Tag[MyType]]
val t3: Tag[MyType] = r3

val r4 = r1.tag[AnotherType]

val r5 = r4.as
r5 shouldBe a[Tag[MyType]]
r5 shouldBe a[Tag[AnotherType]]
r5 shouldStaticallyBe a[Tag[MyType]]
r5 shouldStaticallyBe a[Tag[AnotherType]]
val t4: Tag[MyType] = r5
val t5: Tag[AnotherType] = r5

val r6 = r4.as[% { val name: String } & Tag[MyType] & Tag[AnotherType]]
r6 shouldBe a[Tag[MyType]]
r6 shouldBe a[Tag[AnotherType]]
r6 shouldStaticallyBe a[Tag[MyType]]
r6 shouldStaticallyBe a[Tag[AnotherType]]
val t6: Tag[MyType] = r6
val t7: Tag[AnotherType] = r6
}
Expand All @@ -483,21 +483,21 @@ class RecordSpec extends helper.UnitSpec {
it("should extract values of records") {
val r1 = %(name = "tarao", age = 3)
val t1 = r1.values
t1 shouldBe a[(String, Int)]
t1 shouldStaticallyBe a[(String, Int)]
t1._1 shouldBe "tarao"
t1._2 shouldBe 3

val r2: % { val age: Int } = r1
val t2 = r2.values
t2 shouldBe a[Int *: EmptyTuple]
t2 shouldStaticallyBe a[Int *: EmptyTuple]
t2._1 shouldBe 3
}

it("should preserve the order of static type of fields") {
val r1 = %(name = "tarao", age = 3)
val r2: % { val age: Int; val name: String } = r1
val t2 = r2.values
t2 shouldBe a[(Int, String)]
t2 shouldStaticallyBe a[(Int, String)]
t2._1 shouldBe 3
t2._2 shouldBe "tarao"
}
Expand Down Expand Up @@ -545,18 +545,18 @@ class RecordSpec extends helper.UnitSpec {
case class Empty()
val r0 = %()
val e = r0.to[Empty]
e shouldBe an[Empty]
e shouldStaticallyBe an[Empty]

case class Cell(value: Int)
val r1 = %(value = 10)
val c = r1.to[Cell]
c shouldBe a[Cell]
c shouldStaticallyBe a[Cell]
c.value shouldBe 10

case class Person(name: String, age: Int)
val r2 = %(name = "tarao", age = 3)
val p = r2.to[Person]
p shouldBe a[Person]
p shouldStaticallyBe a[Person]
p.name shouldBe "tarao"
p.age shouldBe 3
}
Expand Down Expand Up @@ -601,21 +601,21 @@ class RecordSpec extends helper.UnitSpec {
it("should convert records to tuples") {
val r1 = %(name = "tarao", age = 3)
val t1 = r1.toTuple
t1 shouldBe a[("name", String) *: ("age", Int) *: EmptyTuple]
t1 shouldStaticallyBe a[("name", String) *: ("age", Int) *: EmptyTuple]
t1._1._1 shouldBe "name"
t1._1._2 shouldBe "tarao"
t1._2._1 shouldBe "age"
t1._2._2 shouldBe 3

val r2: % { val age: Int } = r1
val t2 = r2.toTuple
t2 shouldBe a[("age", Int) *: EmptyTuple]
t2 shouldStaticallyBe a[("age", Int) *: EmptyTuple]
t2._1._1 shouldBe "age"
t2._1._2 shouldBe 3

val r3: % { val age: Int; val name: String } = r1
val t3 = r3.toTuple
t3 shouldBe a[("age", Int) *: ("name", String) *: EmptyTuple]
t3 shouldStaticallyBe a[("age", Int) *: ("name", String) *: EmptyTuple]
t3._1._1 shouldBe "age"
t3._1._2 shouldBe 3
t3._2._1 shouldBe "name"
Expand Down
44 changes: 30 additions & 14 deletions modules/core/src/test/scala/external/UseCaseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3)
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"
Expand All @@ -25,7 +27,9 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3)
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"
Expand All @@ -38,7 +42,7 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3, email = "tarao@example.com")
val r1 = addEmail(r0, %(user = "tarao", domain = "example.com"))
r1 shouldBe a[
r1 shouldStaticallyBe a[
% {
val name: String; val age: Int;
val email: % { val user: String; val domain: String }
Expand Down Expand Up @@ -68,14 +72,18 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao fuguta", age = 3).tag[Person]
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao fuguta"
r1.firstName shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"

val r2 = r0.withEmail("tarao@example.com")
r2 shouldBe a[% { val name: String; val age: Int; val email: String }]
r2 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r2.name shouldBe "tarao fuguta"
r2.firstName shouldBe "tarao"
r2.age shouldBe 3
Expand All @@ -91,15 +99,15 @@ class UseCaseSpec extends helper.UnitSpec {
"r1.name" shouldNot typeCheck
"r1.age" shouldNot typeCheck
r1.email shouldBe "tarao@example.com"
r1 shouldBe a[% { val email: String }]
r1 shouldStaticallyBe a[% { val email: String }]

def addEmail2[R <: % { val name: String }](record: R, email: String) =
record ++ %(email = email)
val r2 = addEmail(r0, "tarao@example.com")
"r2.name" shouldNot typeCheck
"r2.age" shouldNot typeCheck
r2.email shouldBe "tarao@example.com"
r2 shouldBe a[% { val email: String }]
r2 shouldStaticallyBe a[% { val email: String }]
}
}

Expand All @@ -114,7 +122,9 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3)
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"
Expand All @@ -127,7 +137,9 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3)
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"
Expand All @@ -140,7 +152,7 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao", age = 3, email = "tarao@example.com")
val r1 = addEmail(r0, %(user = "tarao", domain = "example.com"))
r1 shouldBe a[
r1 shouldStaticallyBe a[
% {
val name: String; val age: Int;
val email: % { val user: String; val domain: String }
Expand Down Expand Up @@ -170,14 +182,18 @@ class UseCaseSpec extends helper.UnitSpec {

val r0 = %(name = "tarao fuguta", age = 3).tag[Person]
val r1 = addEmail(r0, "tarao@example.com")
r1 shouldBe a[% { val name: String; val age: Int; val email: String }]
r1 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r1.name shouldBe "tarao fuguta"
r1.firstName shouldBe "tarao"
r1.age shouldBe 3
r1.email shouldBe "tarao@example.com"

val r2 = r0.withEmail("tarao@example.com")
r2 shouldBe a[% { val name: String; val age: Int; val email: String }]
r2 shouldStaticallyBe a[
% { val name: String; val age: Int; val email: String },
]
r2.name shouldBe "tarao fuguta"
r2.firstName shouldBe "tarao"
r2.age shouldBe 3
Expand All @@ -193,15 +209,15 @@ class UseCaseSpec extends helper.UnitSpec {
"r1.name" shouldNot typeCheck
"r1.age" shouldNot typeCheck
r1.email shouldBe "tarao@example.com"
r1 shouldBe a[% { val email: String }]
r1 shouldStaticallyBe a[% { val email: String }]

def addEmail2[R <: % { val name: String }](record: R, email: String) =
record + (email = email)
val r2 = addEmail(r0, "tarao@example.com")
"r2.name" shouldNot typeCheck
"r2.age" shouldNot typeCheck
r2.email shouldBe "tarao@example.com"
r2 shouldBe a[% { val email: String }]
r2 shouldStaticallyBe a[% { val email: String }]
}
}

Expand Down
16 changes: 16 additions & 0 deletions modules/core/src/test/scala/helper/StaticTypeMatcher.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package helper

import scala.compiletime.summonInline
import org.scalatest.matchers.dsl.{ResultOfATypeInvocation, ResultOfAnTypeInvocation}

trait StaticTypeMatcher {
extension [T1](anything: T1) {
inline def shouldStaticallyBe[T2](r: ResultOfATypeInvocation[T2]): Unit = {
val _ = summonInline[T1 <:< T2]
}

inline def shouldStaticallyBe[T2](r: ResultOfAnTypeInvocation[T2]): Unit = {
val _ = summonInline[T1 <:< T2]
}
}
}
1 change: 1 addition & 0 deletions modules/core/src/test/scala/helper/UnitSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.scalatest.matchers
abstract class UnitSpec
extends AnyFunSpec
with matchers.should.Matchers
with StaticTypeMatcher
with OptionValues
with Inside
with Inspectors

0 comments on commit 71350b5

Please sign in to comment.