From f6fc5dc3e32e579fc655070daa64f874239abb02 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 13 Sep 2023 20:00:54 -0300 Subject: [PATCH] fix: check __index on `:` style calls Fixes #692. --- spec/metamethods/index_spec.lua | 9 +++++ tl.lua | 68 +++++++++++++++++++-------------- tl.tl | 68 +++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 58 deletions(-) diff --git a/spec/metamethods/index_spec.lua b/spec/metamethods/index_spec.lua index 88b9a5e80..de924ea0b 100644 --- a/spec/metamethods/index_spec.lua +++ b/spec/metamethods/index_spec.lua @@ -180,4 +180,13 @@ describe("metamethod __index", function() print(self.x) end ]])) + + it("passes regression test for #692", util.check([[ + local record R + metamethod __index: function(self: R, key: string): function(R) + end + + R.hello(R) + R:hello() + ]])) end) diff --git a/tl.lua b/tl.lua index 050c837c2..0c45f142d 100644 --- a/tl.lua +++ b/tl.lua @@ -7617,6 +7617,40 @@ tl.type_check = function(ast, opts) end end + local function check_metamethod(node, op, a, b) + local method_name + local where_args + local args + local meta_on_operator = 1 + + if lax and ((a and is_unknown(a)) or (b and is_unknown(b))) then + return UNKNOWN, nil + elseif not a.meta_fields and not (b and b.meta_fields) then + return nil, nil + end + + if a and b then + method_name = binop_to_metamethod[op] + where_args = { node.e1, node.e2 } + args = { typename = "tuple", a, b } + else + method_name = unop_to_metamethod[op] + where_args = { node.e1 } + args = { typename = "tuple", a } + end + + local metamethod = a.meta_fields and a.meta_fields[method_name or ""] + if (not metamethod) and b and op ~= "@index" then + metamethod = b.meta_fields and b.meta_fields[method_name or ""] + meta_on_operator = 2 + end + if metamethod then + return resolve_tuple_and_nominal(type_check_function_call(node, where_args, metamethod, args, nil, false, 0)), meta_on_operator + else + return nil, nil + end + end + local function match_record_key(tbl, rec, key) assert(type(tbl) == "table") assert(type(rec) == "table") @@ -7641,6 +7675,11 @@ tl.type_check = function(ast, opts) return tbl.fields[key] end + local meta_t = check_metamethod(rec, "@index", tbl, STRING) + if meta_t then + return meta_t + end + if rec.kind == "variable" then return nil, "invalid key '" .. key .. "' in record '" .. rec.tk .. "' of type %s" else @@ -7926,35 +7965,6 @@ tl.type_check = function(ast, opts) end end - local function check_metamethod(node, op, a, b) - local method_name - local where_args - local args - local meta_on_operator = 1 - if a and b then - method_name = binop_to_metamethod[op] - where_args = { node.e1, node.e2 } - args = { typename = "tuple", a, b } - else - method_name = unop_to_metamethod[op] - where_args = { node.e1 } - args = { typename = "tuple", a } - end - - local metamethod = a.meta_fields and a.meta_fields[method_name or ""] - if (not metamethod) and b and op ~= "@index" then - metamethod = b.meta_fields and b.meta_fields[method_name or ""] - meta_on_operator = 2 - end - if metamethod then - return resolve_tuple_and_nominal(type_check_function_call(node, where_args, metamethod, args, nil, false, 0)), meta_on_operator - elseif lax and ((a and is_unknown(a)) or (b and is_unknown(b))) then - return UNKNOWN, nil - else - return nil, nil - end - end - local function type_check_index(anode, bnode, a, b) local orig_a = a local orig_b = b diff --git a/tl.tl b/tl.tl index 6912a44a5..f434f1f60 100644 --- a/tl.tl +++ b/tl.tl @@ -7617,6 +7617,40 @@ 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 method_name: string + local where_args: {Node} + local args: Type + local meta_on_operator = 1 + + if lax and ((a and is_unknown(a)) or (b and is_unknown(b))) then + return UNKNOWN, nil + elseif not a.meta_fields and not (b and b.meta_fields) then + return nil, nil + end + + if a and b then + method_name = binop_to_metamethod[op] + where_args = { node.e1, node.e2 } + args = { typename = "tuple", a, b } + else + method_name = unop_to_metamethod[op] + where_args = { node.e1 } + args = { typename = "tuple", a } + end + + local metamethod = a.meta_fields and a.meta_fields[method_name or ""] + if (not metamethod) and b and op ~= "@index" then + metamethod = b.meta_fields and b.meta_fields[method_name or ""] + meta_on_operator = 2 + end + if metamethod then + return resolve_tuple_and_nominal(type_check_function_call(node, where_args, metamethod, args, nil, false, 0)), meta_on_operator + else + return nil, nil + end + end + local function match_record_key(tbl: Type, rec: Node, key: string): Type, string assert(type(tbl) == "table") assert(type(rec) == "table") @@ -7641,6 +7675,11 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string return tbl.fields[key] end + local meta_t = check_metamethod(rec, "@index", tbl, STRING) + if meta_t then + return meta_t + end + if rec.kind == "variable" then return nil, "invalid key '" .. key .. "' in record '" .. rec.tk .. "' of type %s" else @@ -7926,35 +7965,6 @@ 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 method_name: string - local where_args: {Node} - local args: Type - local meta_on_operator = 1 - if a and b then - method_name = binop_to_metamethod[op] - where_args = { node.e1, node.e2 } - args = { typename = "tuple", a, b } - else - method_name = unop_to_metamethod[op] - where_args = { node.e1 } - args = { typename = "tuple", a } - end - - local metamethod = a.meta_fields and a.meta_fields[method_name or ""] - if (not metamethod) and b and op ~= "@index" then - metamethod = b.meta_fields and b.meta_fields[method_name or ""] - meta_on_operator = 2 - end - if metamethod then - return resolve_tuple_and_nominal(type_check_function_call(node, where_args, metamethod, args, nil, false, 0)), meta_on_operator - elseif lax and ((a and is_unknown(a)) or (b and is_unknown(b))) then - return UNKNOWN, nil - else - return nil, nil - end - end - local function type_check_index(anode: Node, bnode: Node, a: Type, b: Type): Type local orig_a = a local orig_b = b