From a5dd26b57eac911fc537c07fec5859aa92fac256 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sat, 21 Oct 2023 14:27:57 -0300 Subject: [PATCH] fix: check records nominally when resolving metamethods for operators (#710) --- spec/metamethods/add_spec.lua | 31 +++++++++++++++++++++++++++++++ tl.lua | 14 +++++++------- tl.tl | 14 +++++++------- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/spec/metamethods/add_spec.lua b/spec/metamethods/add_spec.lua index aa8466981..89555a9c6 100644 --- a/spec/metamethods/add_spec.lua +++ b/spec/metamethods/add_spec.lua @@ -69,4 +69,35 @@ describe("binary metamethod __add", function() print((10 + s).x) ]])) + + it("preserves nominal type checking when resolving metamethods for operators", util.check_type_error([[ + local type Temperature = record + n: number + metamethod __add: function(t1: Temperature, t2: Temperature): Temperature + end + + local type Date = record + n: number + metamethod __add: function(t1: Date, t2: Date): Date + end + + local temp2: Temperature = { n = 45 } + local birthday2 : Date = { n = 34 } + + setmetatable(temp2, { + __add = function(t1: Temperature, t2: Temperature): Temperature + return { n = t1.n + t2.n } + end, + }) + + setmetatable(birthday2, { + __add = function(t1: Date, t2: Date): Date + return { n = t1.n + t2.n } + end, + }) + + print((temp2 + birthday2).n) + ]], { + { y = 26, msg = "Date is not a Temperature" }, + })) end) diff --git a/tl.lua b/tl.lua index a17a96991..1288f5297 100644 --- a/tl.lua +++ b/tl.lua @@ -7621,7 +7621,7 @@ tl.type_check = function(ast, opts) end end - local function check_metamethod(node, op, a, b) + local function check_metamethod(node, op, a, b, orig_a, orig_b) local method_name local where_args local args @@ -7636,11 +7636,11 @@ tl.type_check = function(ast, opts) if a and b then method_name = binop_to_metamethod[op] where_args = { node.e1, node.e2 } - args = { typename = "tuple", a, b } + args = { typename = "tuple", orig_a, orig_b } else method_name = unop_to_metamethod[op] where_args = { node.e1 } - args = { typename = "tuple", a } + args = { typename = "tuple", orig_a } end local metamethod = a.meta_fields and a.meta_fields[method_name or ""] @@ -7679,7 +7679,7 @@ tl.type_check = function(ast, opts) return tbl.fields[key] end - local meta_t = check_metamethod(rec, "@index", tbl, STRING) + local meta_t = check_metamethod(rec, "@index", tbl, STRING, tbl, STRING) if meta_t then return meta_t end @@ -8050,7 +8050,7 @@ tl.type_check = function(ast, opts) errm, erra, errb = "cannot index object of type %s with %s", orig_a, orig_b end - local meta_t = check_metamethod(anode, "@index", a, orig_b) + local meta_t = check_metamethod(anode, "@index", a, orig_b, orig_a, orig_b) if meta_t then return meta_t end @@ -10004,7 +10004,7 @@ tl.type_check = function(ast, opts) node.type = types_op[a.typename] local meta_on_operator if not node.type then - node.type, meta_on_operator = check_metamethod(node, node.op.op, a) + node.type, meta_on_operator = check_metamethod(node, node.op.op, a, nil, orig_a, nil) if not node.type then node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' on type %s", resolve_tuple(orig_a)) end @@ -10050,7 +10050,7 @@ tl.type_check = function(ast, opts) node.type = types_op[a.typename] and types_op[a.typename][b.typename] local meta_on_operator if not node.type then - node.type, meta_on_operator = check_metamethod(node, node.op.op, a, b) + node.type, meta_on_operator = check_metamethod(node, node.op.op, a, b, orig_a, orig_b) if not node.type then node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' for types %s and %s", resolve_tuple(orig_a), resolve_tuple(orig_b)) if node.op.op == "or" and is_valid_union(unite({ orig_a, orig_b })) then diff --git a/tl.tl b/tl.tl index 1c857442a..ecc7ab67e 100644 --- a/tl.tl +++ b/tl.tl @@ -7621,7 +7621,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end end - local function check_metamethod(node: Node, op: string, a: Type, b: Type): Type, integer + local function check_metamethod(node: Node, op: string, a: Type, b: Type, orig_a: Type, orig_b: Type): Type, integer local method_name: string local where_args: {Node} local args: Type @@ -7636,11 +7636,11 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string if a and b then method_name = binop_to_metamethod[op] where_args = { node.e1, node.e2 } - args = { typename = "tuple", a, b } + args = { typename = "tuple", orig_a, orig_b } else method_name = unop_to_metamethod[op] where_args = { node.e1 } - args = { typename = "tuple", a } + args = { typename = "tuple", orig_a } end local metamethod = a.meta_fields and a.meta_fields[method_name or ""] @@ -7679,7 +7679,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string return tbl.fields[key] end - local meta_t = check_metamethod(rec, "@index", tbl, STRING) + local meta_t = check_metamethod(rec, "@index", tbl, STRING, tbl, STRING) if meta_t then return meta_t end @@ -8050,7 +8050,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string errm, erra, errb = "cannot index object of type %s with %s", orig_a, orig_b end - local meta_t = check_metamethod(anode, "@index", a, orig_b) + local meta_t = check_metamethod(anode, "@index", a, orig_b, orig_a, orig_b) if meta_t then return meta_t end @@ -10004,7 +10004,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string node.type = types_op[a.typename] local meta_on_operator: integer if not node.type then - node.type, meta_on_operator = check_metamethod(node, node.op.op, a) + node.type, meta_on_operator = check_metamethod(node, node.op.op, a, nil, orig_a, nil) if not node.type then node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' on type %s", resolve_tuple(orig_a)) end @@ -10050,7 +10050,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string node.type = types_op[a.typename] and types_op[a.typename][b.typename] local meta_on_operator: integer if not node.type then - node.type, meta_on_operator = check_metamethod(node, node.op.op, a, b) + node.type, meta_on_operator = check_metamethod(node, node.op.op, a, b, orig_a, orig_b) if not node.type then node_error(node, "cannot use operator '" .. node.op.op:gsub("%%", "%%%%") .. "' for types %s and %s", resolve_tuple(orig_a), resolve_tuple(orig_b)) if node.op.op == "or" and is_valid_union(unite({orig_a, orig_b})) then