Skip to content

Commit

Permalink
fix: disallow passing non-record types as arguments
Browse files Browse the repository at this point in the history
Fixes #813.
  • Loading branch information
hishamhm committed Oct 16, 2024
1 parent 2e40cd4 commit 908ce16
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 108 deletions.
9 changes: 5 additions & 4 deletions spec/lang/assignment/to_interface_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ describe("assignment", function()
err = {}
if outer == "interface" and scope:match("with outer def") then
table.insert(err, { y = 6, msg = "interfaces are abstract; consider using a concrete record" })
end
if inner == "record" then
table.insert(err, { y = 6, msg = "cannot reassign a type" })
else
table.insert(err, { y = 6, msg = "interfaces are abstract; consider using a concrete record" })
if inner == "record" then
table.insert(err, { y = 6, msg = "cannot reassign a type" })
else
table.insert(err, { y = 6, msg = "interfaces are abstract; consider using a concrete record" })
end
end
elseif outer == "interface" and scope == "to inner var with outer def" then -- 4
err = { { y = 6, msg = "interfaces are abstract; consider using a concrete record" } }
Expand Down
24 changes: 23 additions & 1 deletion spec/lang/call/function_spec.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
util = require("spec.util")

describe("function calls", function()
describe("function call", function()
it("does not crash attempting to infer an emptytable when there's no return type", util.check_type_error([[
local function f()
end
Expand Down Expand Up @@ -33,6 +33,28 @@ describe("function calls", function()
end
]]))

it("does not allow passing non-record type definitions as values (#813)" , util.check_type_error([[
local enum TestEnum
"test1"
end
local function testFunc(param1:TestEnum)
end
testFunc(TestEnum)
local interface X
end
local function testFunc2(param1:X)
end
testFunc2(X)
]], {
{ y = 8, msg = "argument 1: cannot use a type definition as a concrete value" },
{ y = 16, msg = "argument 1: interfaces are abstract" },
}))

describe("check the arity of functions:", function()
it("when excessive", util.check_type_error([[
local function f(n: number, m: number): number
Expand Down
9 changes: 3 additions & 6 deletions spec/lang/declaration/record_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -741,8 +741,9 @@ for i, name in ipairs({"records", "arrayrecords", "interfaces", "arrayinterfaces
proto.x = 2
]], {
{ y = 16, msg = "cannot use a nested type alias as a concrete value" },
{ y = 20, msg = "cannot use a nested type alias as a concrete value" },
{ y = 16, msg = "cannot use a type definition as a concrete value" },
{ y = 18, msg = "cannot use a type definition as a concrete value" },
{ y = 20, msg = "cannot use a type definition as a concrete value" },
}))
else
it("cannot use nested type aliases as values (see #416)", util.check_type_error([[
Expand All @@ -768,11 +769,7 @@ for i, name in ipairs({"records", "arrayrecords", "interfaces", "arrayinterfaces
proto.x = 2
]], {
{ y = 16, msg = "interfaces are abstract" },
{ y = 16, msg = "cannot use a nested type alias as a concrete value" },
{ y = 18, msg = "interfaces are abstract" },
{ y = 18, msg = "interfaces are abstract" },
{ y = 18, msg = "interfaces are abstract" },
{ y = 20, msg = "cannot use a nested type alias as a concrete value" },
}))
end

Expand Down
6 changes: 3 additions & 3 deletions spec/lang/operator/is_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,10 @@ describe("flow analysis with is", function()
end
]], {
{ msg = "can only use 'is' on variables, not types" },
{ msg = "cannot use operator '..'" },
{ msg = "cannot use a type definition as a concrete value" },
{ msg = "can only use 'is' on variables, not types" },
{ msg = "cannot use operator '+'" },
{ msg = "cannot index" },
{ msg = "cannot use a type definition as a concrete value" },
{ msg = "cannot use a type definition as a concrete value" },
}))

it("produces no errors or warnings for checks on unions of records", util.check_warnings([[
Expand Down
106 changes: 59 additions & 47 deletions tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7305,6 +7305,8 @@ do
local def = t.def
if def.typename == "interface" then
return nil, "interfaces are abstract; consider using a concrete record"
elseif not (def.typename == "record") then
return nil, "cannot use a type definition as a concrete value"
end
end
return true
Expand Down Expand Up @@ -7830,46 +7832,6 @@ do
















function TypeChecker:arg_check(w, all_errs, a, b, v, mode, n)
local ok, errs

if v == "covariant" then
ok, errs = self:is_a(a, b)
elseif v == "contravariant" then
ok, errs = self:is_a(b, a)
elseif v == "bivariant" then
ok, errs = self:is_a(a, b)
if ok then
return true
end
ok = self:is_a(b, a)
if ok then
return true
end
elseif v == "invariant" then
ok, errs = self:same_type(a, b)
end

if not ok then
self.errs:add_prefixing(w, errs, mode .. (n and " " .. n or "") .. ": ", all_errs)
return false
end
return true
end

function TypeChecker:has_all_types_of(t1s, t2s)
for _, t1 in ipairs(t1s) do
local found = false
Expand Down Expand Up @@ -8125,6 +8087,54 @@ do
end
end














function TypeChecker:arg_check(w, all_errs, a, b, v, mode, n)
local ok, err, errs

if v == "covariant" then
ok, errs = self:is_a(a, b)
elseif v == "contravariant" then
ok, errs = self:is_a(b, a)
elseif v == "bivariant" then
ok, errs = self:is_a(a, b)
if ok then
return true
end
ok = self:is_a(b, a)
if ok then
return true
end
elseif v == "invariant" then
ok, errs = self:same_type(a, b)
end

if ok and b.typename == "nominal" then
local rb = self:resolve_nominal(b)
ok, err = ensure_not_abstract(rb)
if not ok then
errs = { Err_at(w, err) }
end
end

if not ok then
self.errs:add_prefixing(w, errs, mode .. (n and " " .. n or "") .. ": ", all_errs)
return false
end
return true
end

do
local function are_same_unresolved_global_type(self, t1, t2)
if t1.names[1] == t2.names[1] then
Expand Down Expand Up @@ -12251,13 +12261,14 @@ self:expand_type(node, values, elements) })

elseif node.op.op == "as" then
return gb
end

local expected = node.expected and self:to_structural(resolve_tuple(node.expected))
elseif node.op.op == "is" and ra.typename == "typedecl" then
return self.errs:invalid_at(node, "can only use 'is' on variables, not types")
end

local ok, err = ensure_not_abstract(ra)
if not ok then
self.errs:add(node.e1, err)
return self.errs:invalid_at(node.e1, err)
end
if ra.typename == "typedecl" and ra.def.typename == "record" then
ra = ra.def
Expand All @@ -12270,7 +12281,7 @@ self:expand_type(node, values, elements) })
rb = self:to_structural(ub)
ok, err = ensure_not_abstract(rb)
if not ok then
self.errs:add(node.e2, err)
return self.errs:invalid_at(node.e2, err)
end
if rb.typename == "typedecl" and rb.def.typename == "record" then
rb = rb.def
Expand Down Expand Up @@ -12309,9 +12320,7 @@ self:expand_type(node, values, elements) })
if rb.typename == "integer" then
self.all_needs_compat["math"] = 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
if node.e1.kind == "variable" then
self:check_metamethod(node, "__is", ra, resolve_typedecl(rb), ua, ub)
node.known = IsFact({ var = node.e1.tk, typ = ub, w = node })
else
Expand Down Expand Up @@ -12352,6 +12361,9 @@ self:expand_type(node, values, elements) })

if node.op.op == "or" then
local t

local expected = node.expected and self:to_structural(resolve_tuple(node.expected))

if ub.typename == "nil" then
node.known = nil
t = ua
Expand Down
Loading

0 comments on commit 908ce16

Please sign in to comment.