v/vlib/v/tests/sum_type_test.v

760 lines
13 KiB
V

type Expr = IfExpr | IntegerLiteral
type Stmt = FnDecl | StructDecl
type ExprStmt = Expr | Stmt
struct FnDecl {
pos int
}
struct StructDecl {
pos int
}
struct IfExpr {
pos int
}
struct IntegerLiteral {
val string
}
fn test_sum_type_match() {
// TODO: Remove these casts
assert is_gt_simple('3', int(2))
assert !is_gt_simple('3', int(5))
assert is_gt_simple('3', f64(1.2))
assert !is_gt_simple('3', f64(3.5))
assert is_gt_nested('3', int(2))
assert !is_gt_nested('3', int(5))
assert is_gt_nested('3', f64(1.2))
assert !is_gt_nested('3', f64(3.5))
assert concat('3', int(2)) == '3(int)2'
assert concat('3', int(5)) == '3(int)5'
assert concat('3', f64(1.2)) == '3(float)1.2'
assert concat('3', f64(3.5)) == '3(float)3.5'
assert get_sum('3', int(2)) == 5.0
assert get_sum('3', int(5)) == 8.0
assert get_sum('3', f64(1.2)) == 4.2
assert get_sum('3', f64(3.5)) == 6.5
assert verify_complex_expr(int(1), false)
assert !verify_complex_expr(int(2), false)
assert verify_complex_expr(f64(2.5), false)
assert !verify_complex_expr(f64(1.5), false)
assert verify_complex_expr(int(0), true)
assert as_string(int(1)) == 'This is the string representation of "1"'
assert as_string(f64(3.14)) == 'This is the string representation of "3.14"'
assert as_string('String') == 'This is the string representation of "String"'
assert as_string(IntAndStr{
foo: 2
bar: 'hi'
baz: &IntAndStr{
foo: 3
bar: 'hello'
baz: 0
}
}) == 'This is the string representation of "5_hi_hello"'
assert as_string(true) == 'This is the string representation of "true"'
assert as_string(CommonType(Color.red)) == 'This is the string representation of "enum1_red"'
assert as_string(CommonType(Color.green)) == 'This is the string representation of "enum2_green"'
assert as_string(CommonType(Color.blue)) == 'This is the string representation of "enum3_blue"'
assert sumtype_match_with_string_interpolation(1) == "it's an int: 5"
assert sumtype_match_with_string_interpolation(2) == "it's a string: hello"
assert sumtype_match_with_string_interpolation(3) == 'green_green'
assert sumtype_match_with_string_interpolation(4) == "it's a f64: 1.5"
assert sumtype_match_with_string_interpolation(5) == "it's a bool: false"
assert sumtype_match_with_string_interpolation(6) == "it's an IntAndStr: 2_hi_3_hello"
}
// Test moving structs between master/sub arrays
type Master = Sub1 | Sub2
struct Sub1 {
mut:
val int
name string
}
struct Sub2 {
name string
val int
}
fn test_converting_down() {
mut out := []Master{}
out << Sub1{
val: 1
name: 'one'
}
out << Sub2{
val: 2
name: 'two'
}
out << Sub2{
val: 3
name: 'three'
}
mut res := []Sub2{cap: out.len}
for d in out {
match d {
Sub2 { res << d }
else {}
}
}
assert res[0].val == 2
assert res[0].name == 'two'
assert res[1].val == 3
assert res[1].name == 'three'
}
fn test_assignment_and_push() {
mut expr1 := Expr(IfExpr{})
mut arr1 := []Expr{}
expr := IntegerLiteral{
val: '111'
}
arr1 << expr
match arr1[0] {
IntegerLiteral {
arr1 << arr1[0]
expr1 = arr1[0]
}
else {}
}
}
fn test_expr() {
expr := IntegerLiteral{
val: '12'
}
assert handle(expr) == 'int'
// assert expr is IntegerLiteral // TODO
}
struct Outer2 {
e Expr
}
fn test_zero_value_init() {
// no c compiler error then it's successful
_ := Outer2{}
}
type Bar = Test | string
type Xyz = int | string
struct Test {
x string
xyz Xyz
}
fn test_assignment() {
y := 5
mut x := Xyz(y)
x = 'test'
if x is string {
x2 := x as string
assert x2 == 'test'
}
}
struct Foo {
y Bar
}
fn test_as_cast() {
f := Foo{
y: Bar('test')
}
y := f.y as string
assert y == 'test'
}
fn test_typeof() {
x := Expr(IfExpr{})
assert x.type_name() == 'IfExpr'
}
type Food = Eggs | Milk
struct FoodWrapper {
mut:
food Food
}
struct Milk {
mut:
name string
}
struct Eggs {
mut:
name string
}
fn test_match_aggregate() {
f := Food(Milk{'test'})
match f {
Milk, Eggs {
assert f.name == 'test'
}
}
}
fn test_if() {
mut f := Food(Milk{'test'})
if f is Milk {
// only works without smartcast
assert f is Milk
}
}
fn test_match() {
mut f := Food(Milk{'test'})
match f {
Eggs {
// only works without smartcast
assert f is Eggs
}
Milk {
// only works without smartcast
assert f is Milk
}
}
}
type WrapperType = FoodWrapper | string
fn test_non_mut_ident_mut_selector_cast_match() {
w := WrapperType(FoodWrapper{Food(Eggs{'test'})})
match w {
FoodWrapper {
match w.food {
Eggs {
assert w.food.name + '2' == 'test2'
}
else {
assert false
}
}
}
else {
assert false
}
}
}
fn test_non_mut_ident_mut_selector_cast_if() {
w := WrapperType(FoodWrapper{Food(Eggs{'test'})})
if w is FoodWrapper {
if w.food is Eggs {
assert w.food.name + '2' == 'test2'
} else {
assert false
}
} else {
assert false
}
}
type Abc = int | string
fn test_string_cast_to_sumtype() {
a := Abc('test')
match a {
int {
assert false
}
string {
assert true
}
}
}
fn test_int_cast_to_sumtype() {
// literal
a := Abc(111)
match a {
int {
assert true
}
string {
assert false
}
}
// var
i := 111
b := Abc(i)
match b {
int {
assert true
}
string {
assert false
}
}
}
type Number = f64 | int
fn is_gt_simple(val string, dst Number) bool {
match dst {
int {
return val.int() > dst
}
f64 {
return dst < val.f64()
}
}
}
fn is_gt_nested(val string, dst Number) bool {
dst2 := dst
match dst {
int {
match dst2 {
int {
return val.int() > dst
}
// this branch should never been hit
else {
return val.int() < dst
}
}
}
f64 {
match dst2 {
f64 {
return dst < val.f64()
}
// this branch should never been hit
else {
return dst > val.f64()
}
}
}
}
}
fn concat(val string, dst Number) string {
match dst {
int {
mut res := val + '(int)'
res += dst.str()
return res
}
f64 {
mut res := val + '(float)'
res += dst.str()
return res
}
}
}
fn get_sum(val string, dst Number) f64 {
match dst {
int {
mut res := val.int()
res += dst
return res
}
f64 {
mut res := val.f64()
res += dst
return res
}
}
}
fn verify_complex_expr(dst Number, get_final bool) bool {
if !get_final {
match dst {
int {
dst2 := dst
dst3 := dst2
dst4 := (dst2 + dst3).str()
temp := 2 * dst3 + 1
res := temp - 3
foo := 1 + dst - dst2
dst5 := foo.str().int().str()
return (foo + res) * res - res == 0 && dst4.len == 1 && dst5.len == 1
}
f64 {
dst2 := dst
dst3, foo := dst2, 2
mut dst4 := dst3 + 1
dst4 = dst / 1
dst4 -= dst2
mut temp := foo - 4
temp += foo * (foo - 1)
bar := !(dst2 < 1) && dst3 - foo - temp > 0 && dst4 == 0
return bar
}
}
}
foo := 10
temp := foo
return temp + 10 == 20
}
struct IntAndStr {
foo int
bar string
baz &IntAndStr
}
enum Color {
red
green
blue
}
type CommonType = Color | IntAndStr | bool | f64 | int | string
fn as_string(val CommonType) string {
return 'This is the string representation of "' + val.str() + '"'
}
fn (c CommonType) str() string {
match c {
string {
d := c.int()
_ := d
return c
}
int {
d := c
e := c + d - d
return e.str()
}
f64 {
return c.str()
}
IntAndStr {
return (c.foo + c.baz.foo).str() + '_' + c.bar + '_' + c.baz.bar
}
bool {
d := c
return if d { 'true' } else { 'false' }
}
Color {
d := c
match d {
.red { return 'enum1_' + d.str() }
.green { return 'enum2_' + d.str() }
.blue { return 'enum3_' + d.str() }
}
}
}
}
fn sumtype_match_with_string_interpolation(code int) string {
match code {
1 {
bar := CommonType(5)
match bar {
f64 { return "shouldn't happen" }
bool { return "shouldn't happen" }
IntAndStr { return "shouldn't happen" }
int { return "it's an int: $bar" }
string { return "shouldn't happen" }
Color { return "shouldn't happen" }
}
}
2 {
bar := CommonType('hello')
match bar {
string { return "it's a string: $bar" }
int { return "shouldn't happen" }
Color { return "shouldn't happen" }
f64 { return "shouldn't happen" }
bool { return "shouldn't happen" }
IntAndStr { return "shouldn't happen" }
}
}
3 {
bar := CommonType(Color.green)
match bar {
string {
return "shouldn't happen"
}
int {
return "shouldn't happen"
}
f64 {
return "shouldn't happen"
}
bool {
return "shouldn't happen"
}
IntAndStr {
return "shouldn't happen"
}
Color {
match bar {
.red { return 'red_$bar' }
.green { return 'green_$bar' }
.blue { return 'blue_$bar' }
}
}
}
}
4 {
bar := CommonType(1.5)
match bar {
string { return "shouldn't happen" }
int { return "shouldn't happen" }
Color { return "shouldn't happen" }
f64 { return "it's a f64: $bar" }
bool { return "shouldn't happen" }
IntAndStr { return "shouldn't happen" }
}
}
5 {
mut bar := CommonType('hello')
bar = CommonType(false)
match bar {
string { return "shouldn't happen" }
int { return "shouldn't happen" }
Color { return "shouldn't happen" }
f64 { return "shouldn't happen" }
bool { return "it's a bool: $bar" }
IntAndStr { return "shouldn't happen" }
}
}
6 {
// TODO: this should work
// mut bar := CommonType(100)
// bar = CommonType(IntAndStr{foo: 2, bar: 'hi', baz: &IntAndStr{foo: 3, bar: 'hello', baz: 0}})
bar := CommonType(IntAndStr{
foo: 2
bar: 'hi'
baz: &IntAndStr{
foo: 3
bar: 'hello'
baz: 0
}
})
match bar {
string { return "shouldn't happen" }
int { return "shouldn't happen" }
Color { return "shouldn't happen" }
f64 { return "shouldn't happen" }
bool { return "shouldn't happen" }
IntAndStr { return "it's an IntAndStr: ${bar.foo}_${bar.bar}_${bar.baz.foo}_$bar.baz.bar" }
}
}
else {
return 'wrong'
}
}
}
fn handle(e Expr) string {
is_literal := e is IntegerLiteral
assert is_literal
assert e is IntegerLiteral
if e is IntegerLiteral {
assert typeof(e.val).name == 'string'
}
match e {
IntegerLiteral {
assert e.val == '12'
// assert e.val == '12' // TODO
return 'int'
}
IfExpr {
return 'if'
}
}
return ''
}
// Binary Search Tree test by @SleepyRoy
// TODO: make Node.value generic once it's robust enough
// TODO: `return match` instead of returns everywhere inside match
struct Empty {}
struct Node {
value f64
left Tree
right Tree
}
type Tree = Empty | Node
// return size(number of nodes) of BST
fn size(tree Tree) int {
return match tree {
// TODO: remove int() once match gets smarter
Empty { int(0) }
Node { 1 + size(tree.left) + size(tree.right) }
}
}
// insert a value to BST
fn insert(tree Tree, x f64) Tree {
match tree {
Empty {
return Node{x, tree, tree}
}
Node {
return if x == tree.value {
tree
} else if x < tree.value {
Node{
...tree
left: insert(tree.left, x)
}
} else {
Node{
...tree
right: insert(tree.right, x)
}
}
}
}
}
// whether able to find a value in BST
fn search(tree Tree, x f64) bool {
match tree {
Empty {
return false
}
Node {
return if x == tree.value {
true
} else if x < tree.value {
search(tree.left, x)
} else {
search(tree.right, x)
}
}
}
}
// find the minimal value of a BST
fn min(tree Tree) f64 {
match tree {
Empty { return 1e100 }
Node { return if tree.value < min(tree.left) { tree.value } else { min(tree.left) } }
}
}
// delete a value in BST (if nonexist do nothing)
fn delete(tree Tree, x f64) Tree {
match tree {
Empty {
return tree
}
Node {
if tree.left is Node && tree.right is Node {
return if x < tree.value {
Node{
...tree
left: delete(tree.left, x)
}
} else if x > tree.value {
Node{
...tree
right: delete(tree.right, x)
}
} else {
Node{
...tree
value: min(tree.right)
right: delete(tree.right, min(tree.right))
}
}
} else if tree.left is Node {
return if x == tree.value {
tree.left
} else {
Node{
...tree
left: delete(tree.left, x)
}
}
} else {
if x == tree.value {
return tree.right
} else {
return Node{
...tree
right: delete(tree.right, x)
}
}
}
}
}
}
fn test_binary_search_tree() {
mut tree := Tree(Empty{})
input := [0.3, 0.2, 0.5, 0.0, 0.6, 0.8, 0.9, 1.0, 0.1, 0.4, 0.7]
for i in input {
tree = insert(tree, i)
}
assert size(tree) == 11
del := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5]
for i in del {
tree = delete(tree, i)
}
assert size(tree) == 7
mut deleted := []f64{}
for i in input {
if !search(tree, i) {
deleted << i
}
}
deleted.sort()
assert deleted == [0.0, 0.3, 0.6, 1.0]
}
struct Common {
a int
b int
}
struct Common2 {
Common
}
struct Aa {
Common
x int
}
struct Bb {
Common
x int
}
struct Cc {
a int
}
struct Dd {
Common2
}
type MySum = Aa | Bb | Cc | Dd
fn test_sumtype_access_embed_fields() {
a := MySum(Aa{
a: 1
})
assert a.a == 1
}
fn test_sumtype_access_nested_embed_fields() {
a := MySum(Dd{
Common2: Common2{
a: 2
}
})
assert a.a == 2
}