teal-language / tl

The compiler for Teal, a typed dialect of Lua
MIT License
2.12k stars 107 forks source link

__index cannot return a method #692

Closed sloonz closed 1 year ago

sloonz commented 1 year ago

Using __index to return a function ("."-style call) works, but returning a method (":"-style call does not)

local record test
    metamethod __index: function(self: test, key: string): function()
end

test.hello() -- No error
test:hello() -- invalid key 'hello' in record 'test' of type test

I’m not sure why the two are handled differently here and here. If I shunt the special : handling by adding or node.op.op == ":" in the . code (as shown bellow), this problem disappears, and the test suite still passes.

diff --git a/tl.tl b/tl.tl
index 54082ee..8c6eea0 100644
--- a/tl.tl
+++ b/tl.tl
@@ -9870,7 +9870,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string
                   end
                end
                node.type = type_check_funcall(node, a, b)
-            elseif node.op.op == "." then
+            elseif node.op.op == "." or node.op.op == ":" then
                assert(node.e2.kind == "identifier")
                local bnode: Node = {
                   y = node.e2.y,
@@ -9912,20 +9912,6 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string
                   node_error(node, "can only use 'is' on variables")
                end
                node.type = BOOLEAN
-            elseif node.op.op == ":" then
-               if lax and (is_unknown(a) or a.typename == "typevar") then
-                  if node.e1.kind == "variable" then
-                     add_unknown_dot(node.e1, node.e1.tk .. "." .. node.e2.tk)
-                  end
-                  node.type = UNKNOWN
-               else
-                  local t, e = match_record_key(a, node.e1, node.e2.conststr or node.e2.tk)
-                  if not t then
-                     node.type = INVALID
-                     return node_error(node.e2, e, a == INVALID and a or resolve_tuple(orig_a))
-                  end
-                  node.type = t
-               end
             elseif node.op.op == "not" then
                node.known = facts_not(node, node.e1.known)
                node.type = BOOLEAN
hishamhm commented 1 year ago

Hi, thanks for the report! It took me a bit of digging the back history to try to remember why . and : have separate handlers.

Of course, in Lua, : is just syntactic sugar. In Teal, however, we have in the language semantics separate concepts for functions and record-functions/methods, because the latter are statically bound to their records.

For example, want to be able to detect that the programmer made a typo and a method name is invalid, and that means that we need, at one point of the code, to declare that the declarations of methods are done. So you can only do function my_record:mymethod in the same scope where you created your record, for instance.

Internally, Teal does keep track if a function "is a method" (that is, if it was declared with : or .; in more recent versions, we even check if a function is method-like by having a first argument self with its own type). This is to keep the door open for future enhancements to the type system, such as interfaces (see this comment: https://github.com/teal-language/tl/issues/97#issuecomment-623795757).

So, it's an explicit decision to not make : just a syntactic sugar for ., like Lua.

Having said that, the problem you hit is that the current implementation of : does not support __index. That's solvable; I'll look into it! Thanks for reporting!