Skip to content

Commit

Permalink
fix: subtyping and casting checks for records vs interfaces
Browse files Browse the repository at this point in the history
* abstract types such as interface names cannot be accepted in casts
* record prototypes are accepted where interfaces they implement
  are wanted

Fixes #830.
  • Loading branch information
hishamhm committed Oct 17, 2024
1 parent fbbae10 commit d0a3717
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 43 deletions.
69 changes: 68 additions & 1 deletion spec/lang/subtyping/interface_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,72 @@ describe("subtyping of interfaces:", function()
local r: MyRecord = {}
print(#r)
]]))
end)

it("record <: interface", util.check([[
local interface MyInterface
x: integer
end
local record MyRecord is MyInterface
end
local function f(p: MyInterface)
print(p.x)
end
local r: MyRecord = { x = 2 }
f(r)
]]))

it("prototype record <: interface", util.check([[
local interface MyInterface
x: integer
end
local record MyRecord is MyInterface
end
local function f(p: MyInterface)
print(p.x)
end
MyRecord.x = 2
f(MyRecord)
]]))

it("regression test for #830", util.check_lines([[
local interface IFoo
end
local record Foo is IFoo
end
local function bar(_value:Foo)
end
local function qux(_value:IFoo)
end
local foo:Foo
]], {
{ line = "bar(foo)" },
{ line = "bar(Foo)" },
{ line = "bar(IFoo)", err = "IFoo is not a Foo" },
{ line = "bar(foo as Foo)" },
{ line = "bar(Foo as Foo)" },
{ line = "bar(IFoo as Foo)", err = "interfaces are abstract" },
{ line = "bar(foo as IFoo)", err = "IFoo is not a Foo" },
{ line = "bar(Foo as IFoo)", err = "IFoo is not a Foo" },
{ line = "bar(IFoo as IFoo)", err = "interfaces are abstract" },
{ line = "qux(foo)" },
{ line = "qux(Foo)" },
{ line = "qux(IFoo)", err = "interfaces are abstract" },
{ line = "qux(foo as Foo)" },
{ line = "qux(Foo as Foo)" },
{ line = "qux(IFoo as Foo)", err = "interfaces are abstract" },
{ line = "qux(foo as IFoo)" },
{ line = "qux(Foo as IFoo)" },
{ line = "qux(IFoo as IFoo)", err = "interfaces are abstract" },
}))
end)
47 changes: 26 additions & 21 deletions tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8922,11 +8922,8 @@ a.types[i], b.types[i]), }
["emptytable"] = compare_true_inferring_emptytable,
},
["typedecl"] = {
["record"] = function(self, a, b)
local def = a.def
if def.fields then
return self:subtype_record(def, b)
end
["*"] = function(self, a, b)
return self:is_a(a.def, b)
end,
},
["function"] = {
Expand Down Expand Up @@ -9000,6 +8997,9 @@ a.types[i], b.types[i]), }
["tuple"] = function(self, a, b)
return self:is_a(a_type(a, "tuple", { tuple = { a } }), b)
end,
["typedecl"] = function(self, a, b)
return self:is_a(a, b.def)
end,
["typevar"] = function(self, a, b)
return self:compare_or_infer_typevar(b.typevar, a, nil, self.is_a)
end,
Expand Down Expand Up @@ -9032,27 +9032,28 @@ a.types[i], b.types[i]), }
["self"] = 3,
["tuple"] = 4,
["typevar"] = 5,
["any"] = 6,
["boolean_context"] = 7,
["union"] = 8,
["poly"] = 9,
["typedecl"] = 6,
["any"] = 7,
["boolean_context"] = 8,
["union"] = 9,
["poly"] = 10,

["typearg"] = 10,
["typearg"] = 11,

["nominal"] = 11,
["nominal"] = 12,

["enum"] = 12,
["string"] = 12,
["integer"] = 12,
["boolean"] = 12,
["enum"] = 13,
["string"] = 13,
["integer"] = 13,
["boolean"] = 13,

["interface"] = 13,
["interface"] = 14,

["tupletable"] = 14,
["record"] = 14,
["array"] = 14,
["map"] = 14,
["function"] = 14,
["tupletable"] = 15,
["record"] = 15,
["array"] = 15,
["map"] = 15,
["function"] = 15,
}

local function compare_types(self, relations, t1, t2)
Expand Down Expand Up @@ -12290,6 +12291,10 @@ self:expand_type(node, values, elements) })
return t

elseif node.op.op == "as" then
local ok, err = ensure_not_abstract(ra)
if not ok then
return self.errs:invalid_at(node.e1, err)
end
return gb

elseif node.op.op == "is" and ra.typename == "typedecl" then
Expand Down
47 changes: 26 additions & 21 deletions tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -8922,11 +8922,8 @@ do
["emptytable"] = compare_true_inferring_emptytable,
},
["typedecl"] = {
["record"] = function(self: TypeChecker, a: TypeDeclType, b: RecordType): boolean, {Error}
local def = a.def
if def is RecordLikeType then
return self:subtype_record(def, b) -- record as prototype
end
["*"] = function(self: TypeChecker, a: TypeDeclType, b: RecordType): boolean, {Error}
return self:is_a(a.def, b)
end,
},
["function"] = {
Expand Down Expand Up @@ -9000,6 +8997,9 @@ do
["tuple"] = function(self: TypeChecker, a: Type, b: Type): boolean, {Error}
return self:is_a(a_tuple(a, {a}), b)
end,
["typedecl"] = function(self: TypeChecker, a: Type, b: TypeDeclType): boolean, {Error}
return self:is_a(a, b.def)
end,
["typevar"] = function(self: TypeChecker, a: Type, b: TypeVarType): boolean, {Error}
return self:compare_or_infer_typevar(b.typevar, a, nil, self.is_a)
end,
Expand Down Expand Up @@ -9032,27 +9032,28 @@ do
["self"] = 3,
["tuple"] = 4,
["typevar"] = 5,
["any"] = 6,
["boolean_context"] = 7,
["union"] = 8,
["poly"] = 9,
["typedecl"] = 6,
["any"] = 7,
["boolean_context"] = 8,
["union"] = 9,
["poly"] = 10,
-- then typeargs
["typearg"] = 10,
["typearg"] = 11,
-- then nominals
["nominal"] = 11,
["nominal"] = 12,
-- then base types
["enum"] = 12,
["string"] = 12,
["integer"] = 12,
["boolean"] = 12,
["enum"] = 13,
["string"] = 13,
["integer"] = 13,
["boolean"] = 13,
-- then interfaces
["interface"] = 13,
["interface"] = 14,
-- then special cases of tables
["tupletable"] = 14,
["record"] = 14,
["array"] = 14,
["map"] = 14,
["function"] = 14,
["tupletable"] = 15,
["record"] = 15,
["array"] = 15,
["map"] = 15,
["function"] = 15,
}

local function compare_types(self: TypeChecker, relations: TypeRelations, t1: Type, t2: Type): boolean, {Error}
Expand Down Expand Up @@ -12290,6 +12291,10 @@ do
return t

elseif node.op.op == "as" then
local ok, err = ensure_not_abstract(ra)
if not ok then
return self.errs:invalid_at(node.e1, err)
end
return gb

elseif node.op.op == "is" and ra is TypeDeclType then
Expand Down

0 comments on commit d0a3717

Please sign in to comment.