diff --git a/spec/lang/subtyping/interface_spec.lua b/spec/lang/subtyping/interface_spec.lua index 727a17884..d7fe0fd6d 100644 --- a/spec/lang/subtyping/interface_spec.lua +++ b/spec/lang/subtyping/interface_spec.lua @@ -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) diff --git a/tl.lua b/tl.lua index efcf95a98..6e8cbcb61 100644 --- a/tl.lua +++ b/tl.lua @@ -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"] = { @@ -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, @@ -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) @@ -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 diff --git a/tl.tl b/tl.tl index 1af48412e..ad2ffff4c 100644 --- a/tl.tl +++ b/tl.tl @@ -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"] = { @@ -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, @@ -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} @@ -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