From 5d4ec0aa9a404c4df466974b408d835303cfdecd Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 13 Sep 2023 18:36:30 -0300 Subject: [PATCH] fix `is` inference taking into account that unions imply nil This behavior will change one day when we make types not imply nil, but for now we need to be consistent with the fact that they do. So silence the warning on the last `elseif V is T` and drop the error in the `else` case. Fixes #695. --- spec/inference/if_spec.lua | 13 +++++++++++++ spec/operator/is_spec.lua | 3 ++- tl.lua | 7 ++++--- tl.tl | 7 ++++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/spec/inference/if_spec.lua b/spec/inference/if_spec.lua index 911924819..20e0f9462 100644 --- a/spec/inference/if_spec.lua +++ b/spec/inference/if_spec.lua @@ -131,4 +131,17 @@ describe("flow typing in 'if' statements", function() ]], { { msg = "cannot use operator '+' for types integer | boolean | string and integer" }, })) + + -- see also pending test "detects empty unions" in spec/operators/is_spec.lua + it("detects a union value to be nil if all types are exhausted (regression test for #695)", util.check_warnings([[ + global function f2(val: string|number) + if val is string then + print(val) + elseif val is number then + print(val) + else + error("string or number expected") + end + end + ]], {}, {})) end) diff --git a/spec/operator/is_spec.lua b/spec/operator/is_spec.lua index 36e057596..23ccb9a20 100644 --- a/spec/operator/is_spec.lua +++ b/spec/operator/is_spec.lua @@ -233,7 +233,8 @@ describe("flow analysis with is", function() { y = 5, msg = [[cannot use operator '+' for types string (inferred at foo.tl:4:10) and integer]] }, })) - it("detects empty unions", util.check_type_error([[ + -- this is not an empty union because `number | string` implies nil. + pending("detects empty unions", util.check_type_error([[ local t: number | string if t is number then t = t + 1 diff --git a/tl.lua b/tl.lua index 1b3ead9be..050c837c2 100644 --- a/tl.lua +++ b/tl.lua @@ -8238,7 +8238,7 @@ tl.type_check = function(ast, opts) elseif is_a(t2, t1) then return t2 else - return INVALID + return NIL end end end @@ -8279,7 +8279,7 @@ tl.type_check = function(ast, opts) end if #types == 0 then - return INVALID + return NIL end return unite(types) @@ -8403,7 +8403,8 @@ tl.type_check = function(ast, opts) end if typ.typename ~= "typevar" then if is_a(typ, f.typ) then - node_warning("branch", f.where, f.var .. " (of type %s) is always a %s", show_type(typ), show_type(f.typ)) + + return { [f.var] = f } elseif not is_a(f.typ, typ) then node_error(f.where, f.var .. " (of type %s) can never be a %s", typ, f.typ) diff --git a/tl.tl b/tl.tl index fdf537b75..6912a44a5 100644 --- a/tl.tl +++ b/tl.tl @@ -8238,7 +8238,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string elseif is_a(t2, t1) then return t2 else - return INVALID + return NIL -- because of implicit nil in all unions end end end @@ -8279,7 +8279,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end if #types == 0 then - return INVALID + return NIL -- because of implicit nil in all unions end return unite(types) @@ -8403,7 +8403,8 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end if typ.typename ~= "typevar" then if is_a(typ, f.typ) then - node_warning("branch", f.where, f.var .. " (of type %s) is always a %s", show_type(typ), show_type(f.typ)) + -- drop this warning because of implicit nil in all unions + -- node_warning("branch", f.where, f.var .. " (of type %s) is always a %s", show_type(typ), show_type(f.typ)) return { [f.var] = f } elseif not is_a(f.typ, typ) then node_error(f.where, f.var .. " (of type %s) can never be a %s", typ, f.typ)