From 80723dc09195438323ae752669823abe0a2fec72 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sat, 21 Oct 2023 16:29:40 -0300 Subject: [PATCH] experiment: make type system more nominal Instead of treating nominal records nominally and all other nominal types structurally, with this commit we treat all nominal types nominally except for unions, which are treated structurally. Give this branch a try in your codebase and let me know your impressions! --- spec/declaration/record_spec.lua | 2 +- tl.lua | 22 +++++++++++++--------- tl.tl | 22 +++++++++++++--------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/spec/declaration/record_spec.lua b/spec/declaration/record_spec.lua index 3c58abed7..2f021a95f 100644 --- a/spec/declaration/record_spec.lua +++ b/spec/declaration/record_spec.lua @@ -702,7 +702,7 @@ for i, name in ipairs({"records", "arrayrecords"}) do end function Foo.new(): Foo - return setmetatable({}, Foo) -- typing of arguments is being very permissive here, may change in the future and require a cast + return setmetatable({}, Foo as metatable) end local foo = Foo.new() diff --git a/tl.lua b/tl.lua index 747e8e137..97e2bee52 100644 --- a/tl.lua +++ b/tl.lua @@ -1418,7 +1418,7 @@ end local function new_node(tokens, i, kind) local t = tokens[i] - return { y = t.y, x = t.x, tk = t.tk, kind = kind or t.kind } + return { y = t.y, x = t.x, tk = t.tk, kind = kind or (t.kind) } end local function a_type(t) @@ -7077,17 +7077,13 @@ tl.type_check = function(ast, opts) end return false, terr(t1, "cannot match against any alternatives of the polymorphic type") elseif t1.typename == "nominal" and t2.typename == "nominal" then - local same, err = are_same_nominals(t1, t2) - if same then - return true - end local t1r = resolve_tuple_and_nominal(t1) local t2r = resolve_tuple_and_nominal(t2) - if is_record_type(t1r) and is_record_type(t2r) then - return same, err - else + if t1r.typename == "union" or t2r.typename == "union" then return is_a(t1r, t2r, for_equality) end + + return are_same_nominals(t1, t2) elseif t1.typename == "enum" and t2.typename == "string" then local ok if for_equality then @@ -9088,7 +9084,7 @@ tl.type_check = function(ast, opts) local infertype = infertypes[i] local rt = resolve_tuple_and_nominal(t) - if rt.typename ~= "enum" and not same_type(t, infertype) then + if rt.typename ~= "enum" and (t.typename ~= "nominal" or rt.typename == "union") and not same_type(t, infertype) then add_var(where, var.tk, infer_at(where, infertype), "const", "narrowed_declaration") end end @@ -10064,6 +10060,14 @@ tl.type_check = function(ast, opts) end end + if orig_a.typename == "nominal" and orig_b.typename == "nominal" and not meta_on_operator then + if is_a(orig_a, orig_b) then + node.type = resolve_tuple(orig_a) + else + node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' for distinct nominal types %s and %s", resolve_tuple(orig_a), resolve_tuple(orig_b)) + end + end + if types_op == numeric_binop or node.op.op == ".." then node.known = FACT_TRUTHY end diff --git a/tl.tl b/tl.tl index 730712d24..6b8b0005b 100644 --- a/tl.tl +++ b/tl.tl @@ -1418,7 +1418,7 @@ end local function new_node(tokens: {Token}, i: integer, kind: NodeKind): Node local t = tokens[i] - return { y = t.y, x = t.x, tk = t.tk, kind = kind or t.kind } + return { y = t.y, x = t.x, tk = t.tk, kind = kind or (t.kind as NodeKind) } end local function a_type(t: Type): Type @@ -7077,17 +7077,13 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end return false, terr(t1, "cannot match against any alternatives of the polymorphic type") elseif t1.typename == "nominal" and t2.typename == "nominal" then - local same, err = are_same_nominals(t1, t2) - if same then - return true - end local t1r = resolve_tuple_and_nominal(t1) local t2r = resolve_tuple_and_nominal(t2) - if is_record_type(t1r) and is_record_type(t2r) then - return same, err - else + if t1r.typename == "union" or t2r.typename == "union" then return is_a(t1r, t2r, for_equality) end + + return are_same_nominals(t1, t2) elseif t1.typename == "enum" and t2.typename == "string" then local ok: boolean if for_equality then @@ -9088,7 +9084,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string local infertype = infertypes[i] local rt = resolve_tuple_and_nominal(t) - if rt.typename ~= "enum" and not same_type(t, infertype) then + if rt.typename ~= "enum" and (t.typename ~= "nominal" or rt.typename == "union") and not same_type(t, infertype) then add_var(where, var.tk, infer_at(where, infertype), "const", "narrowed_declaration") end end @@ -10064,6 +10060,14 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end end + if orig_a.typename == "nominal" and orig_b.typename == "nominal" and not meta_on_operator then + if is_a(orig_a, orig_b) then + node.type = resolve_tuple(orig_a) + else + node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' for distinct nominal types %s and %s", resolve_tuple(orig_a), resolve_tuple(orig_b)) + end + end + if types_op == numeric_binop or node.op.op == ".." then node.known = FACT_TRUTHY end