Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type error when referring to subinterface in where but not a primitive #837

Open
Hedwig7s opened this issue Oct 24, 2024 · 4 comments
Open
Labels
semantics Unexpected or unsound behaviors

Comments

@Hedwig7s
Copy link

I am not entirely sure what's going on here (title will likely need updating) but minimal test case:

local interface Class
    name: string
end 
local interface Instance
    class: Class
end

local interface BaseClass is Class
    where self.name == "Base"
    ClassTest1: function(self: BaseClass)
end
local interface ExtendedClass is BaseClass
    where self.name == "Extended"
end
local interface Base is Instance
    where self.class.name == "Base"
    Test1: function(test: Base)
end

local interface Extended is Base
    where self.class.name == "Extended"
end

local extendedClass = {} as ExtendedClass
function extendedClass.ClassTest1(self: ExtendedClass) -- Fine

end

local extended = {} as Extended

function extended.Test1(self: Extended) -- type signature of 'Test1' does not match its declaration in Extended: argument 1: Extended is not a Base
    
end
@hishamhm hishamhm added the semantics Unexpected or unsound behaviors label Oct 26, 2024
@hishamhm
Copy link
Member

To get the behavior you expect, where a method is declared in a parent type, and then have it redeclared in the child type with self dispatching to the child type correctly, you need to declare the first self algument as the special self type, like so

local interface Base is Instance
    where self.class.name == "Base"
    Test1: function(self)
end

From https://github.com/teal-language/tl/blob/master/docs/tutorial.md#interfaces :

Note that the definition of my_func used self as a type name. self is a valid type that can be used when declaring arguments in functions declared in interfaces and records. When a record is declared to be a subtype of an interface using is, any function arguments using self in the parent interface type will then resolve to the child record's type.

@Hedwig7s
Copy link
Author

Hedwig7s commented Oct 28, 2024

Found a subsequent issue (might move to another issue)
Because the class system I use (middleclass) defines instance functions on the class I have to (kinda hackily) make a third interface for instance methods which seems to break down the syntax

Granted, I might modify middleclass to make instance functions a subtable (or ditch it entirely and do manual classes), but middleclass and penlight (most common class libraries) by default have instance functions defined on the class

local interface Class
    name: string
end 
local interface Instance
    class: Class
end

local interface testFuns
    Test2: function(self)
end
local interface Base is Instance, testFuns
    where self.class.name == "Base"
    Test1: function(self)
end
local interface testFuns2 is testFuns
    Test2: function(self, a?: number)
end
local interface Extended is Base, testFuns2
    where self.class.name == "Extended"
end

local extended = {} as Extended

function extended.Test1(self: Extended) -- Fine
    
end

function extended.Test2(self: Extended, a?:number) -- type signature of 'Test2' does not match its declaration in Extended: different number of input arguments: method and non-method are not the same type
end

@hishamhm
Copy link
Member

hishamhm commented Oct 28, 2024

The above example works if you replace

local interface testFuns2 is testFuns

with

local interface testFuns2

and perform the inheritance only in Extended, and inherit testfuns2 first:

local interface Extended is testFuns2, Base

@Hedwig7s
Copy link
Author

Hedwig7s commented Oct 28, 2024

I was going to comment on the fact that removing the inheritence would allow the types to become incompatible, but I found removing the optional from a actually marked an error, however not sure if this is a good thing as the interface becomes magically inherited outside its definition (implicit side effect)
Also in general this is getting rather convoluted requiring specific knowledge on teal

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semantics Unexpected or unsound behaviors
Projects
None yet
Development

No branches or pull requests

2 participants