# Runtime type values and guarded branch narrowing.
let $failed = 0
fn type_guard_case($source: String, $stdout: String, $diagnostic: String)
{
let $fixture = $args[0]
let $reply = run_process [`$shelly, "-m", "-c", $source] [
"cwd": $fixture, "env": ["HOME": $fixture, "PATH": "/usr/bin:/bin",
"SHELLY_STD_PATH": "${fixture}/std", "TERM": "dumb", "LANG": "C.UTF-8"],
"stdin_file": "/dev/null", "stdout_file": "${fixture}/out",
"stderr_file": "${fixture}/err", "timeout_ms": 3000]
let $out: String
let $err: String
"${fixture}/out" -> $out
"${fixture}/err" -> $err
let $status = if $diagnostic == "" { 0 } else { 1 }
if ($out != $stdout) || ($reply["exit_code"] != $status) ||
($reply["error"] != ()) || ($reply["signal"] != ()) || $reply["timed_out"] ||
(($diagnostic == "") && ($err != "")) ||
(($diagnostic != "") && (!($err.contains $diagnostic)))
{ echo $source $reply $out $err; $failed = $failed + 1 }
}
let $fixture = $args[0]
printf "%s" "pub struct Item { pub n: Integer }; pub type Count = Integer; pub enum Color { Red }; pub let \$kind: Type = Item; pub let \$value = Item(n: 7)" -> "${fixture}/type_source.shy"
printf "%s" "pub import type_source::{ Item, Count }; pub visible \"Item\"; pub visible \"Count\"" -> "${fixture}/type_bridge.shy"
type_guard_case "fn inspect(\$foo: any): String { match \$foo.type\n{\nInteger => { let \$n: Integer = \$foo; \"integer\" },\nFloat => { let \$n: Float = \$foo; \"float\" }\n_ => { \"other\" }\n} }; echo (inspect 7) (inspect 7.0) (inspect \"7\")" "integer float other\n" ""
type_guard_case "fn check(\$foo: any) { if \$foo.type == Integer\n && \$foo == 1024\n{ let \$n: Integer = \$foo; echo \$n } }; check 1024; check 1; check \"1024\"" "1024\n" ""
type_guard_case "echo ((7).type == Integer) ((7.0).type == Float) ((7).type == Float) ((7).type == (7.0).type)" "true true false false\n" ""
type_guard_case "echo (\"hello\").type (true).type (()).type (0..3).type ([1,2]).type ([\"a\":1]).type" "String Boolean None Range Array HashMap\n" ""
type_guard_case "echo ((7).type == \"Integer\") ((7).type == any) ((7).type == Number)" "false false false\n" ""
type_guard_case "let \$t: Type = Integer; let \$u: Type = (7).type; echo (\$t == \$u) \$t \$t.type" "true Integer Type\n" ""
type_guard_case "let \$t: Type = Float; \$t = Integer; echo (\$t == (7).type)" "true\n" ""
type_guard_case "echo (Integer == Integer) (Integer != Float) ((Integer).type == Type)" "true true true\n" ""
type_guard_case "fn expected(\$t: Type): Type { \$t }; echo (expected (Integer))" "Integer\n" ""
type_guard_case "fn introspect(\$v: any): Type { \$v.type }; echo (introspect 7) (introspect 1.0)" "Integer Float\n" ""
type_guard_case "type Count = Integer; let \$n: Count = 7; echo (\$n.type == Count) (\$n.type == Integer) \$n.type" "true false Count\n" ""
type_guard_case "type Count = Integer; type Other = Count; let \$n: Other = 7; echo (\$n.type == Other) (\$n.type == Count) (\$n.type == Integer)" "true false false\n" ""
type_guard_case "type Either = Integer | String; let \$v: Either = 7; echo (\$v.type == Either) (\$v.type == Integer)" "true false\n" ""
type_guard_case "let \$v: Integer | String = 7; echo (\$v.type == Integer); \$v = \"s\"; echo (\$v.type == String)" "true\ntrue\n" ""
type_guard_case "let \$v: Float = 7; echo (\$v.type == Float) (\$v.type == Integer)" "true false\n" ""
type_guard_case "struct Item { pub n: Integer }; let \$v = Item(n: 1); echo (\$v.type == Item) \$v.type" "true Item\n" ""
type_guard_case "enum Color { Red, Blue }; let \$v = Color::Red; echo (\$v.type == Color) \$v.type \$v.index" "true Color 0\n" ""
type_guard_case "let \$t = Item; struct Item {}; let \$v = Item(); echo (\$v.type == \$t)" "true\n" ""
type_guard_case "let \$types: [Type] = [Integer, Float, String]; for \$t in \$types { echo (\$t.type == Type) \$t }" "true Integer\ntrue Float\ntrue String\n" ""
type_guard_case "let \$h: [Type: String] = [Integer: \"integer\", Float: \"float\"]; echo \$h[(7).type] \$h[(7.0).type]" "integer float\n" ""
type_guard_case "let \$t = Type((7).type); echo (\$t == Integer) String(\$t) Boolean(\$t)" "true Integer true\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { if \$foo.type == Integer { \"integer\" } else { \$foo } }; echo (f 7) (f \"text\")" "integer text\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { if \$foo.type != String { \"integer\" } else { \$foo } }; echo (f 7) (f \"text\")" "integer text\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { if !(\$foo.type == String) { \"integer\" } else { \$foo } }; echo (f 7) (f \"text\")" "integer text\n" ""
type_guard_case "fn f(\$foo: Number): Float { if \$foo.type == Integer { Float(\$foo) } else { \$foo } }; echo (f 7) (f 1.5)" "7.0 1.5\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { match \$foo.type { Integer => { \"int\" } _ => { \$foo } } }; echo (f 7) (f \"text\")" "int text\n" ""
type_guard_case "fn f(\$foo: Integer | Float | String): String { match \$foo.type { Integer => { \"int\" } Float => { \"float\" } _ => { \$foo } } }; echo (f 7) (f 1.0) (f \"text\")" "int float text\n" ""
type_guard_case "fn f(\$foo: Integer | ()): Integer { if \$foo.type == None { 0 } else { \$foo } }; echo (f ()) (f 7)" "0 7\n" ""
type_guard_case "fn f(\$foo: Integer | ()): Integer { match \$foo.type { None => { 0 } _ => { \$foo } } }; echo (f ()) (f 7)" "0 7\n" ""
type_guard_case "fn f(\$foo: any): Integer { if Integer == \$foo.type { \$foo } else { 0 } }; echo (f 7) (f \"s\")" "7 0\n" ""
type_guard_case "fn f(\$foo: any): Integer { if (\$foo.type == Integer) && (\$foo == 7) { \$foo } else { 0 } }; echo (f 7) (f \"s\")" "7 0\n" ""
type_guard_case "fn f(\$foo: any): String { if (\$foo.type == String && \$foo == \"yes\") || (\$foo.type == String && \$foo == \"ok\") { \$foo } else { \"no\" } }; echo (f \"yes\") (f \"ok\") (f 7)" "yes ok no\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { if \$foo.type != String && \$foo == 7 { \"int\" } else if \$foo.type == Integer { \"other\" } else { \$foo } }; echo (f 7) (f 8) (f \"s\")" "int other s\n" ""
type_guard_case "fn f(\$foo: Integer | String): String { if \$foo.type == Integer || \$foo.type == Float { \"number\" } else { \$foo } }; echo (f 7) (f \"s\")" "number s\n" ""
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any): Integer { if \$foo.type == A { \$foo.a } else { 0 } }; echo (f A(a: 7))" "7\n" ""
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any): Integer { if \$foo.type == A && \$foo.a == 7 { \$foo.a } else { 0 } }; echo (f A(a: 7)) (f \"s\")" "7 0\n" ""
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { let \$a: Integer = \$foo.a; let \$b: A = \$foo; echo \$a \$b.a } }; f A(a: 7)" "7 7\n" ""
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: Integer | A): Integer { if \$foo.type != Integer { \$foo.a } else { \$foo } }; echo (f A(a: 7)) (f 3)" "7 3\n" ""
type_guard_case "let \$foo: any = 7; let \$t = Integer; if \$foo.type == \$t { let \$n: Integer = \$foo; echo \$n }" "7\n" ""
type_guard_case "let \$foo: any = 7; let \$t = Float; \$t = Integer; match \$foo.type { \$t => { let \$n: Integer = \$foo; echo \$n } _ => { echo bad } }" "7\n" ""
type_guard_case "let \$foo: any = \"s\"; if \$foo.type == Integer { let \$n: Integer = \$foo } else { let \$s: String = \$foo; echo \$s }" "s\n" ""
type_guard_case "fn f(\$foo: any) { if \$foo.type == Integer { \$foo = \"new\"; let \$s: String = \$foo; echo \$s } }; f 7" "new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Boolean { \$foo = B(b: \"new\"); true }; if \$foo.type == A && (mutate) { echo \$foo.b }" "new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Type { \$foo = B(b: \"new\"); String }; match \$foo.type { (mutate) => { echo bad } A => { echo \$foo.b } }" "new\n" ""
type_guard_case "let \$foo: any = 7; let \$f = fn () { if \$foo.type == Integer { let \$n: Integer = \$foo; \$n } else { 0 } }; echo (\$f); \$foo = \"s\"; echo (\$f)" "7\n0\n" ""
type_guard_case "let \$foo: any = 7; if \$foo.type == Integer { let \$foo: String = \"shadow\"; echo \$foo }; echo \$foo" "shadow\n7\n" ""
type_guard_case "fn show(\$t: String) { echo \$t }; show Integer; show Float" "Integer\nFloat\n" ""
type_guard_case "fn same(\$t: Type): Type { \$t }; let \$f: fn(Type): Type = `same; echo (\$f (Integer))" "Integer\n" ""
type_guard_case "let \$f = fn (): Integer { 7 }; echo (\$f.type == \$f.type); type F = fn(): Integer; let \$g: F = \$f; echo (\$g.type == F)" "true\ntrue\n" ""
type_guard_case "let \$t = (7).type; let \$copy = \$t; echo (\$copy == Integer) (\$copy == \"Integer\")" "true false\n" ""
type_guard_case "let \$foo: any = \"s\"; until \$foo.type == Integer { \$foo = 7 }; echo \$foo.type" "Integer\n" ""
type_guard_case "fn f(\$foo: any) { match \$foo.type { Integer => { let \$bad: String = \$foo } _ => { () } } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: any) { if \$foo.type == Integer && \$foo == 1024 { let \$bad: String = \$foo } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: any): String { if \$foo.type == Integer { \$foo } else { \"s\" } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: any): String { match \$foo.type { Integer => { \$foo } _ => { \"s\" } } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: Integer | String): Integer { if \$foo.type == Integer { 0 } else { \$foo } }" "" "Expected Integer, got String"
type_guard_case "fn f(\$foo: Integer | String): Integer { match \$foo.type { Integer => { 0 } _ => { \$foo } } }" "" "Expected Integer, got String"
type_guard_case "fn f(\$foo: any) { if \$foo.type != Integer { () } else { let \$bad: String = \$foo } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: any) { if !(\$foo.type != Integer) { let \$bad: String = \$foo } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: any) { if Integer == \$foo.type { let \$bad: String = \$foo } }" "" "Expected String, got Integer"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A && \$foo.b == 1 { () } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type != A || \$foo.b == 1 { () } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { match \$foo.type { A => { \$foo.b } _ => { () } } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { let \$n: Integer = \$foo.a; \$foo.b } }" "" "Cannot access field 'b' on A"
type_guard_case "fn f(\$foo: any) { if (\$foo.type == String && \$foo == \"yes\") || (\$foo.type == String && \$foo == \"ok\") { let \$bad: Integer = \$foo } }" "" "Expected Integer, got String"
type_guard_case "let \$foo: any = 7; let \$t = Integer; if \$foo.type == \$t { let \$bad: String = \$foo }" "" "Expected String, got Integer"
type_guard_case "let \$foo: any = 7; let \$t = Integer; match \$foo.type { \$t => { let \$bad: String = \$foo } }" "" "Expected String, got Integer"
type_guard_case "type Count = Integer; fn f(\$foo: any) { if \$foo.type == Count { let \$bad: Integer = \$foo } }" "" "Expected Integer, got Count"
type_guard_case "let \$foo = 7; \$foo.type = String" "" "read-only"
type_guard_case "let \$foo = [7]; \$foo[0].type = String" "" "read-only"
type_guard_case "struct Bad { pub type: Integer }" "" "reserved"
type_guard_case "fn Integer::type(): String { \"no\" }" "" "conflicts with a data member"
type_guard_case "let \$t: Type = \"Integer\"" "" "Expected Type"
type_guard_case "let \$t: Type = 7" "" "Expected Type"
type_guard_case "let \$t: Type" "" "requires an initializer"
type_guard_case "let \$i = Integer; let \$f = Float; echo (\$i + \$f)" "" "Types cannot be used in arithmetic"
type_guard_case "let \$t = Integer; \$t 1" "" "Cannot execute a type"
type_guard_case "let \$t = Integer; let \$run = `\$t" "" "Cannot execute a type"
type_guard_case "echo (Type(7))" "" "Cannot convert Integer to Type"
type_guard_case "let \$foo = 7; match \$foo.type { Float => { \"no\" } }" "" "No arm matched"
type_guard_case "fn f(\$foo: any) { if \$foo.type == Integer { () }; let \$s: String = \$foo; echo \$s }; f \"outside\"" "outside\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Integer { \$foo = B(b: \"new\"); 0 }; if \$foo.type == A { let \$items = [(mutate), \$foo.b]; echo \$items[1] }" "new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Integer { \$foo = B(b: \"new\"); 0 }; if \$foo.type == A { let \$items = [(mutate): \$foo.b]; echo \$items[0] }" "new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; struct Pair { pub a: Integer, pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Integer { \$foo = B(b: \"new\"); 0 }; if \$foo.type == A { let \$item = Pair(a: (mutate), b: \$foo.b); echo \$item.b }" "new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); fn mutate(): Integer { \$foo = B(b: \"new\"); 0 }; if \$foo.type == A { echo (mutate) \$foo.b }" "0 new\n" ""
type_guard_case "struct A { pub a: Integer }; struct B { pub b: String }; let \$foo: any = A(a: 7); let \$f: (fn(): String) | (); if \$foo.type == A { \$f = fn (): String { \$foo.b } }; \$foo = B(b: \"later\"); echo (\$f)" "later\n" ""
type_guard_case "let \$foo: any = 7; while \$foo.type == Integer { let \$i: Integer = \$foo; \$foo = \"done\" }; echo \$foo" "done\n" ""
type_guard_case "let \$foo: Integer | String = 7; until \$foo.type == String { let \$i: Integer = \$foo; \$foo = \"done\" }; echo \$foo" "done\n" ""
type_guard_case "fn f(\$foo: any) { while \$foo.type == Integer { let \$bad: String = \$foo; break } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: Integer | String) { until \$foo.type == String { let \$bad: String = \$foo; break } }" "" "Expected String, got Integer"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { if true { \$foo.b } } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { let \$items = [\$foo.b] } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { let \$items = [0: \$foo.b] } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; struct Pair { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { let \$items = Pair(a: \$foo.b) } }" "" "Cannot access field 'b' on A"
type_guard_case "fn f(\$foo: [Integer]) { if \$foo.type == Array { for \$n in \$foo { let \$i: Integer = \$n; echo \$i } } }; f [1,2]" "1\n2\n" ""
type_guard_case "fn f(\$foo: [Integer]) { if \$foo.type == Array { for \$n in \$foo { let \$bad: String = \$n } } }" "" "Expected String, got Integer"
type_guard_case "fn f(\$foo: [Integer] | String): String { if \$foo.type == Array { \"array\" } else { \$foo } }; echo (f [1]) (f \"s\")" "array s\n" ""
type_guard_case "fn f(\$foo: [String: Integer]) { match \$foo.type { HashMap => { for (\$k, \$v) in \$foo { let \$bad: String = \$v } } _ => { () } } }" "" "Expected String, got Integer"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { if \$foo.type == A { echo \$foo.b } }" "" "Cannot access field 'b' on A"
type_guard_case "struct A { pub a: Integer }; fn f(\$foo: any) { match \$foo.type { A => { echo \$foo.b } _ => { () } } }" "" "Cannot access field 'b' on A"
type_guard_case "import type_source; echo (\$type_source::value.type == type_source::Item) (\$type_source::kind == type_source::Item)" "true true\n" ""
type_guard_case "import type_source::{ Item, \$value }; echo (\$value.type == Item)" "true\n" ""
type_guard_case "import type_source; import type_bridge; echo (type_source::Item == type_bridge::Item); let \$n: type_bridge::Count = 7; echo (\$n.type == type_source::Count)" "true\ntrue\n" ""
type_guard_case "import type_source; let \$foo: any = \$type_source::value; match \$foo.type { type_source::Item => { let \$n: Integer = \$foo.n; echo \$n } _ => { echo bad } }" "7\n" ""
type_guard_case "import type_source; fn f(\$foo: any) { if \$foo.type == type_source::Count { let \$bad: Integer = \$foo } }" "" "Expected Integer, got Count"
if $failed != 0 { fail "type guard mismatches" }