Skip to content

Commit

Permalink
fix: check __index on : style calls
Browse files Browse the repository at this point in the history
Fixes #692.
  • Loading branch information
hishamhm committed Sep 13, 2023
1 parent 3f13595 commit f6fc5dc
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 58 deletions.
9 changes: 9 additions & 0 deletions spec/metamethods/index_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
68 changes: 39 additions & 29 deletions tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
68 changes: 39 additions & 29 deletions tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f6fc5dc

Please sign in to comment.