Skip to content

Commit

Permalink
when is operator generates type, it localizes type (#854)
Browse files Browse the repository at this point in the history
Just like `x is integer` already localizes `math` due to
its use of `math.type` in the generated code, we now
localize the `type` standard library global whenever the
code generation for other types uses it.
  • Loading branch information
hishamhm authored Nov 16, 2024
1 parent 418cb70 commit be8ac11
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 18 deletions.
6 changes: 3 additions & 3 deletions spec/lang/operator/is_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ end]]))
end
end
]], [[
local function process(ts)
local type = type; local function process(ts)
local t
t = ts[1]
if type(t) == "number" then
Expand All @@ -635,7 +635,7 @@ end]]))
end
end
]], [[
local type = type
function process(ts)
Expand Down Expand Up @@ -666,7 +666,7 @@ end]]))
return 3
end
]], [[
Foo = {}
local type = type; Foo = {}
Expand Down
31 changes: 24 additions & 7 deletions tl.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local load = _tl_compat and _tl_compat.load or load; local math = _tl_compat and _tl_compat.math or math; local _tl_math_maxinteger = math.maxinteger or math.pow(2, 53); local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local utf8 = _tl_compat and _tl_compat.utf8 or utf8
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local load = _tl_compat and _tl_compat.load or load; local math = _tl_compat and _tl_compat.math or math; local _tl_math_maxinteger = math.maxinteger or math.pow(2, 53); local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local type = type; local utf8 = _tl_compat and _tl_compat.utf8 or utf8
local VERSION = "0.24.1+dev"

local stdlib = [=====[
Expand Down Expand Up @@ -7027,6 +7027,8 @@ local function add_compat_entries(program, used_set, gen_compat)
load_code(name, "local _tl_math_maxinteger = math.maxinteger or math.pow(2,53)")
elseif name == "math.mininteger" then
load_code(name, "local _tl_math_mininteger = math.mininteger or -math.pow(2,53) - 1")
elseif name == "type" then
load_code(name, "local type = type")
else
if not compat_loaded then
load_code("compat", "local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = " .. req("compat53.module") .. "; if p then _tl_compat = m end")
Expand Down Expand Up @@ -9681,30 +9683,34 @@ a.types[i], b.types[i]), }
local node = node_at(var, { kind = "op", op = { op = "is", arity = 2, prec = 3 } })
node.e1 = var
node.e2 = node_at(var, { kind = "cast", casttype = self:infer_at(var, t) })
self:check_metamethod(node, "__is", self:to_structural(v), self:to_structural(t), v, t)
local _, has = self:check_metamethod(node, "__is", self:to_structural(v), self:to_structural(t), v, t)
if node.expanded then
apply_macroexp(node)
end
node.known = IsFact({ var = var.tk, typ = t, w = node })
return node
return node, has
end

local function convert_is_of_union_to_or_of_is(self, node, v, u)
local var = node.e1
node.op.op = "or"
node.op.arity = 2
node.op.prec = 1
node.e1 = make_is_node(self, var, v, u.types[1])
local has_any = nil
node.e1, has_any = make_is_node(self, var, v, u.types[1])
local at = node
local n = #u.types
for i = 2, n - 1 do
at.e2 = node_at(var, { kind = "op", op = { op = "or", arity = 2, prec = 1 } })
at.e2.e1 = make_is_node(self, var, v, u.types[i])
local has
at.e2.e1, has = make_is_node(self, var, v, u.types[i])
has_any = has_any or has
node.known = OrFact({ f1 = at.e1.known, f2 = at.e2.known, w = node })
at = at.e2
end
at.e2 = make_is_node(self, var, v, u.types[n])
node.known = OrFact({ f1 = at.e1.known, f2 = at.e2.known, w = node })
return not not has_any
end

function TypeChecker:match_record_key(tbl, rec, key)
Expand Down Expand Up @@ -12374,21 +12380,32 @@ self:expand_type(node, values, elements) })
end

if node.op.op == "is" then
local add_type = false
if rb.typename == "integer" then
self.all_needs_compat["math"] = true
elseif rb.typename ~= "nil" then
add_type = true
end
if ra.typename == "typedecl" then
self.errs:add(node, "can only use 'is' on variables, not types")
elseif node.e1.kind == "variable" then
local has_meta
if rb.typename == "union" then
convert_is_of_union_to_or_of_is(self, node, ra, rb)
has_meta = convert_is_of_union_to_or_of_is(self, node, ra, rb)
else
self:check_metamethod(node, "__is", ra, resolve_typedecl(rb), ua, ub)
local _, meta = self:check_metamethod(node, "__is", ra, resolve_typedecl(rb), ua, ub)
node.known = IsFact({ var = node.e1.tk, typ = ub, w = node })
has_meta = not not meta
end
if has_meta then
add_type = false
end
else
self.errs:add(node, "can only use 'is' on variables")
end
if add_type then
self.all_needs_compat["type"] = true
end
return a_type(node, "boolean", {})
end

Expand Down
33 changes: 25 additions & 8 deletions tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -7027,6 +7027,8 @@ local function add_compat_entries(program: Node, used_set: {string: boolean}, ge
load_code(name, "local _tl_math_maxinteger = math.maxinteger or math.pow(2,53)")
elseif name == "math.mininteger" then
load_code(name, "local _tl_math_mininteger = math.mininteger or -math.pow(2,53) - 1")
elseif name == "type" then
load_code(name, "local type = type")
else
if not compat_loaded then
load_code("compat", "local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = " .. req("compat53.module") .. "; if p then _tl_compat = m end")
Expand Down Expand Up @@ -9677,34 +9679,38 @@ do
end
end

local function make_is_node(self: TypeChecker, var: Node, v: Type, t: Type): Node
local function make_is_node(self: TypeChecker, var: Node, v: Type, t: Type): Node, integer
local node = node_at(var, { kind = "op", op = { op = "is", arity = 2, prec = 3 } })
node.e1 = var
node.e2 = node_at(var, { kind = "cast", casttype = self:infer_at(var, t) })
self:check_metamethod(node, "__is", self:to_structural(v), self:to_structural(t), v, t)
local _, has = self:check_metamethod(node, "__is", self:to_structural(v), self:to_structural(t), v, t)
if node.expanded then
apply_macroexp(node)
end
node.known = IsFact { var = var.tk, typ = t, w = node }
return node
return node, has
end

local function convert_is_of_union_to_or_of_is(self: TypeChecker, node: Node, v: Type, u: UnionType)
local function convert_is_of_union_to_or_of_is(self: TypeChecker, node: Node, v: Type, u: UnionType): boolean
local var = node.e1
node.op.op = "or"
node.op.arity = 2
node.op.prec = 1
node.e1 = make_is_node(self, var, v, u.types[1])
local has_any: integer = nil
node.e1, has_any = make_is_node(self, var, v, u.types[1])
local at = node
local n = #u.types
for i = 2, n - 1 do
at.e2 = node_at(var, { kind = "op", op = { op = "or", arity = 2, prec = 1 } })
at.e2.e1 = make_is_node(self, var, v, u.types[i])
local has: integer
at.e2.e1, has = make_is_node(self, var, v, u.types[i])
has_any = has_any or has
node.known = OrFact { f1 = at.e1.known, f2 = at.e2.known, w = node }
at = at.e2
end
at.e2 = make_is_node(self, var, v, u.types[n])
node.known = OrFact { f1 = at.e1.known, f2 = at.e2.known, w = node }
return not not has_any
end

function TypeChecker:match_record_key(tbl: Type, rec: Node, key: string): Type, string
Expand Down Expand Up @@ -12374,21 +12380,32 @@ do
end

if node.op.op == "is" then
local add_type = false
if rb.typename == "integer" then
self.all_needs_compat["math"] = true
elseif rb.typename ~= "nil" then
add_type = true
end
if ra is TypeDeclType then
self.errs:add(node, "can only use 'is' on variables, not types")
elseif node.e1.kind == "variable" then
local has_meta: boolean
if rb is UnionType then
convert_is_of_union_to_or_of_is(self, node, ra, rb)
has_meta = convert_is_of_union_to_or_of_is(self, node, ra, rb)
else
self:check_metamethod(node, "__is", ra, resolve_typedecl(rb), ua, ub)
local _, meta = self:check_metamethod(node, "__is", ra, resolve_typedecl(rb), ua, ub)
node.known = IsFact { var = node.e1.tk, typ = ub, w = node }
has_meta = not not meta
end
if has_meta then
add_type = false
end
else
self.errs:add(node, "can only use 'is' on variables")
end
if add_type then
self.all_needs_compat["type"] = true
end
return a_type(node, "boolean", {})
end

Expand Down

0 comments on commit be8ac11

Please sign in to comment.