Skip to content

Commit

Permalink
fix: always elide interfaces and their aliases, even if incorrectly used
Browse files Browse the repository at this point in the history
  • Loading branch information
hishamhm committed Oct 18, 2024
1 parent a8b598f commit a7bfd60
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 98 deletions.
39 changes: 39 additions & 0 deletions spec/lang/code_gen/local_type_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,43 @@ describe("local type code generation", function()
]])
end)

it("always elides local type require used as a variable, even if incorrect use of interfaces or aliases", util.gen([[
local interface IFoo
end
local type Alias = IFoo
local record Foo is IFoo
end
local function register(_id:any, _value:any)
end
local foo:Foo
register(IFoo, foo)
register(Alias, foo)
]], [[
local function register(_id, _value)
end
local foo
register(IFoo, foo)
register(Alias, foo)
]], nil, {
{ y = 14, msg = "interfaces are abstract" },
{ y = 16, msg = "interfaces are abstract" },
}))

end)
17 changes: 13 additions & 4 deletions spec/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -599,13 +599,22 @@ function util.check_types(code, types)
end
end

local function gen(lax, code, expected, gen_target)
local function gen(lax, code, expected, gen_target, type_errors)
return function()
local ast, syntax_errors = tl.parse(code, "foo.tl")
assert.same({}, syntax_errors, "Code was not expected to have syntax errors")
local gen_compat = gen_target == "5.4" and "off" or nil
local result = tl.check(ast, "foo.tl", { feat_lax = lax and "on" or "off", gen_target = gen_target, gen_compat = gen_compat })
assert.same({}, result.type_errors)

if type_errors then
local batch = batch_assertions()
local result_type_errors = combine_result(result, "type_errors")
batch_compare(batch, "type errors", type_errors, result_type_errors)
batch:assert()
else
assert.same({}, result.type_errors)
end

local output_code = tl.pretty_print_ast(ast, gen_target)

local expected_ast, expected_errors = tl.parse(expected, "foo.tl")
Expand All @@ -616,11 +625,11 @@ local function gen(lax, code, expected, gen_target)
end
end

function util.gen(code, expected, gen_target)
function util.gen(code, expected, gen_target, type_errors)
assert(type(code) == "string")
assert(type(expected) == "string")

return gen(false, code, expected, gen_target)
return gen(false, code, expected, gen_target, type_errors)
end

function util.run_check_type_error(...)
Expand Down
95 changes: 49 additions & 46 deletions tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6341,6 +6341,20 @@ function Errors:add_prefixing(w, src, prefix, dst)
end
end

local function ensure_not_abstract(t)
if t.typename == "function" and t.macroexp then
return nil, "macroexps are abstract; consider using a concrete function"
elseif t.typename == "typedecl" then
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
end




Expand Down Expand Up @@ -6368,7 +6382,9 @@ local function check_for_unused_vars(scope, is_global)
end
elseif var.used and t.typename == "typedecl" and var.aliasing then
var.aliasing.used = true
var.aliasing.declared_at.elide_type = false
if ensure_not_abstract(t) then
var.aliasing.declared_at.elide_type = false
end
end
end
if list then
Expand Down Expand Up @@ -7298,20 +7314,6 @@ do
end
end

local function ensure_not_abstract(t)
if t.typename == "function" and t.macroexp then
return nil, "macroexps are abstract; consider using a concrete function"
elseif t.typename == "typedecl" then
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
end

local function ensure_not_method(t)

if t.typename == "function" and t.is_method then
Expand Down Expand Up @@ -7768,10 +7770,9 @@ do
return ret
end

function TypeChecker:add_to_scope(node, name, t, attribute, narrow, dont_check_redeclaration)
local scope = self.st[#self.st]
local var = scope.vars[name]
if narrow then
do
local function narrow_var(scope, node, name, t, attribute, narrow)
local var = scope.vars[name]
if var then
if var.is_narrowed then
var.t = t
Expand All @@ -7792,42 +7793,44 @@ do
return var
end

if not dont_check_redeclaration and
node and
name ~= "self" and
name ~= "..." and
name:sub(1, 1) ~= "@" then

self:check_if_redeclaration(name, node, t)
end
function TypeChecker:add_var(node, name, t, attribute, narrow)
if self.feat_lax and node and is_unknown(t) and (name ~= "self" and name ~= "...") and not narrow then
self.errs:add_unknown(node, name)
end
if not attribute then
t = drop_constant_value(t)
end

if var and not var.used then
if self.collector and node then
self.collector.add_to_symbol_list(node, name, t)
end

local scope = self.st[#self.st]
if narrow then
return narrow_var(scope, node, name, t, attribute, narrow)
end

self.errs:unused_warning(name, var)
end
if node then
if name ~= "self" and name ~= "..." and name:sub(1, 1) ~= "@" then
self:check_if_redeclaration(name, node, t)
end
if not ensure_not_abstract(t) then
node.elide_type = true
end
end

var = { t = t, attribute = attribute, is_narrowed = nil, declared_at = node }
scope.vars[name] = var
local var = scope.vars[name]
if var and not var.used then

return var
end

function TypeChecker:add_var(node, name, t, attribute, narrow, dont_check_redeclaration)
if self.feat_lax and node and is_unknown(t) and (name ~= "self" and name ~= "...") and not narrow then
self.errs:add_unknown(node, name)
end
if not attribute then
t = drop_constant_value(t)
end
self.errs:unused_warning(name, var)
end

local var = self:add_to_scope(node, name, t, attribute, narrow, dont_check_redeclaration)
var = { t = t, attribute = attribute, declared_at = node }
scope.vars[name] = var

if self.collector and node then
self.collector.add_to_symbol_list(node, name, t)
return var
end

return var
end


Expand Down
99 changes: 51 additions & 48 deletions tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -6341,6 +6341,20 @@ function Errors:add_prefixing(w: Where, src: {Error}, prefix: string, dst?: {Err
end
end

local function ensure_not_abstract(t: Type): boolean, string
if t is FunctionType and t.macroexp then
return nil, "macroexps are abstract; consider using a concrete function"
elseif t is TypeDeclType then
local def = t.def
if def is InterfaceType then
return nil, "interfaces are abstract; consider using a concrete record"
elseif not def is RecordType then
return nil, "cannot use a type definition as a concrete value"
end
end
return true
end

local record Unused
y: integer
x: integer
Expand Down Expand Up @@ -6368,7 +6382,9 @@ local function check_for_unused_vars(scope: Scope, is_global?: boolean): {Unused
end
elseif var.used and t is TypeDeclType and var.aliasing then
var.aliasing.used = true
var.aliasing.declared_at.elide_type = false
if ensure_not_abstract(t) then
var.aliasing.declared_at.elide_type = false
end
end
end
if list then
Expand Down Expand Up @@ -7298,20 +7314,6 @@ do
end
end

local function ensure_not_abstract(t: Type): boolean, string
if t is FunctionType and t.macroexp then
return nil, "macroexps are abstract; consider using a concrete function"
elseif t is TypeDeclType then
local def = t.def
if def is InterfaceType then
return nil, "interfaces are abstract; consider using a concrete record"
elseif not def is RecordType then
return nil, "cannot use a type definition as a concrete value"
end
end
return true
end

local function ensure_not_method(t: Type): Type

if t is FunctionType and t.is_method then
Expand Down Expand Up @@ -7768,10 +7770,9 @@ do
return ret
end

function TypeChecker:add_to_scope(node: Node, name: string, t: Type, attribute: Attribute, narrow: Narrow, dont_check_redeclaration: boolean): Variable
local scope <const> = self.st[#self.st]
local var = scope.vars[name]
if narrow then
do
local function narrow_var(scope: Scope, node: Node, name: string, t: Type, attribute: Attribute, narrow: Narrow): Variable
local var = scope.vars[name]
if var then
if var.is_narrowed then
var.t = t
Expand All @@ -7792,42 +7793,44 @@ do
return var
end

if not dont_check_redeclaration
and node
and name ~= "self"
and name ~= "..."
and name:sub(1, 1) ~= "@"
then
self:check_if_redeclaration(name, node, t)
end
function TypeChecker:add_var(node: Node, name: string, t: Type, attribute?: Attribute, narrow?: Narrow): Variable
if self.feat_lax and node and is_unknown(t) and (name ~= "self" and name ~= "...") and not narrow then
self.errs:add_unknown(node, name)
end
if not attribute then
t = drop_constant_value(t)
end

if var and not var.used then
-- the old var is removed from the scope and won't be checked when it closes,
-- so check it here
self.errs:unused_warning(name, var)
end
if self.collector and node then
self.collector.add_to_symbol_list(node, name, t)
end

var = { t = t, attribute = attribute, is_narrowed = nil, declared_at = node }
scope.vars[name] = var
local scope <const> = self.st[#self.st]
if narrow then
return narrow_var(scope, node, name, t, attribute, narrow)
end

return var
end
if node then
if name ~= "self" and name ~= "..." and name:sub(1, 1) ~= "@" then
self:check_if_redeclaration(name, node, t)
end
if not ensure_not_abstract(t) then
node.elide_type = true
end
end

function TypeChecker:add_var(node: Node, name: string, t: Type, attribute?: Attribute, narrow?: Narrow, dont_check_redeclaration?: boolean): Variable
if self.feat_lax and node and is_unknown(t) and (name ~= "self" and name ~= "...") and not narrow then
self.errs:add_unknown(node, name)
end
if not attribute then
t = drop_constant_value(t)
end
local var = scope.vars[name]
if var and not var.used then
-- the old var is removed from the scope and won't be checked when it closes,
-- so check it here
self.errs:unused_warning(name, var)
end

local var = self:add_to_scope(node, name, t, attribute, narrow, dont_check_redeclaration)
var = { t = t, attribute = attribute, declared_at = node }
scope.vars[name] = var

if self.collector and node then
self.collector.add_to_symbol_list(node, name, t)
return var
end

return var
end

local type CompareTypes = function(TypeChecker, Type, Type): boolean, {Error}
Expand Down

0 comments on commit a7bfd60

Please sign in to comment.