From dfbe151c4b5678b51041d44d6815a29c6867066c 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 3c58abed..2f021a95 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 1d1a8a20..16eb8a81 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) @@ -7075,17 +7075,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 @@ -9086,7 +9082,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 @@ -10062,6 +10058,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 9bf74695..3877fe43 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 @@ -7075,17 +7075,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 @@ -9086,7 +9082,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 @@ -10062,6 +10058,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