Yuescript icon indicating copy to clipboard operation
Yuescript copied to clipboard

Add Compile-Time Type-Checking using tl

Open turbo opened this issue 5 years ago • 13 comments

tl is a static type checker for Lua: https://github.com/teal-language/tl/blob/master/docs/tutorial.md (like TS for JS). It would be great to have optional type annotations in moon+ so that moon+ compiles to tl, which then checks and compiles to Lua.

Two issues come to mind:

  • Figuring out the type annotation syntax in moon+
  • Changing the compiler to keep those annotations. Fortunately, there shouldn't be an issue even for the moonscript-specific class types, because they can be represented by records: https://github.com/teal-language/tl/blob/master/docs/tutorial.md#records

Or, the tl type checker portion could be ported to the moon+ compiler. The majority of code in the tl compiler (https://github.com/teal-language/tl/blob/master/tl.tl) is the actual parser and AST traversal, which could be replaced by the existing parser in moon+.

turbo avatar Jul 27 '20 12:07 turbo

Just considered getting some static type check functions implemented before answering to your issue. But I'm not getting time to finish the work recently. Porting tl type checker is the way I preferred. Since this feature could be done in C++ and speed up compile time a little.

pigpigyyy avatar Aug 05 '20 15:08 pigpigyyy

+1 this will be awesome

CriztianiX avatar Aug 05 '20 19:08 CriztianiX

Played with tl for a while, I find it won't functioning well to just add type annotations support for Moonscript. Because Moonscript is generating some anonymous functions without annotations. For example:

-- assume type annotations is supported like this
join = (strs: {string}): string -> table.concat strs,", "

-- the codes below all look fine to compile
{string} strs = [tostring i for i = 1, 10]
print join strs

print join [tostring i for i = 1, 10] as {string}

compile them to tl:


local join: function(strs: {string}): string
join = function(strs: {string}): string
  return table.concat(strs, ", ")
end

 -- these generated codes can be accepted by tl
local strs: {string}
do
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    if i > 4 then
      _accum_0[_len_0] = tostring(i)
      _len_0 = _len_0 + 1
    end
  end
  strs = _accum_0
end
print(join(strs))

 -- these generated codes won't be accepted by tl
print(join((function() -- no way to annotate this anonymous function
                          -- because it's invisible for user
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    if i > 4 then
      _accum_0[_len_0] = tostring(i)
      _len_0 = _len_0 + 1
    end
  end
  return _accum_0
end)() as {string})) -- without annotations tl sees this anonymous function is returning nil
             -- then compilation fails

pigpigyyy avatar Aug 11 '20 07:08 pigpigyyy

I think there's a type called any to deal with this, but that might break type checking further up the call chain. Really the only realistic option is to perform the type checking in MoonPlus itself, before emitting the code.

turbo avatar Aug 11 '20 15:08 turbo

Implementing static check feature in MoonPlus will take a lot of time and work. So for the moment I've added a workaround feature to support static typing and generate codes for some Lua dialect like teal, for example:

macro text var = (name, type)->
  -- the second return item is a table
  -- containing variable names to be declared
  -- in Moonscript context as local variables
  "local #{name}:#{type}", {name} 

$var num, number
num = 123

$var set, {string:boolean}
set =
  valueA: true
  valueB: false

Compiles to:

local num:number
num = 123;
local set:{string:boolean}
set = {
  valueA = true,
  valueB = false
}

You can find more examples to write codes for teal-lang in MoonPlus. See teal-lang.mp and macro-teal.mp.

pigpigyyy avatar Oct 21 '20 16:10 pigpigyyy

I really don't like it at all. I think moonp should not compile to teal. Moonp should always compile to Lua. Another option is that strong typing is not needed, like in PHP for example. So, I propose if we can define something like this:

Common way: my_function = (a, b) -> a + b

Typed syntax ? my_function = (a : int, b : int) : int -> a + b

Typed syntax ? my_function = (int a, int b) : int -> a + b

class typed syntax?

class Logger

class M
  new: (Logger logger) =>
    @logger = logger

  new: (Logger : logger) =>
    @logger = logger

  method: (a : int, b : int) : int =>
    a + b
  method2: (a, b) => a + b

CriztianiX avatar Oct 22 '20 17:10 CriztianiX

Yes, it's a temporarily ugly approach to introduce the static type check functions by compile codes to tl. Before the built-in static type check functions implemented, I think it's worth to try out more possibilities by introducing some small language features to compile moon codes to tl. We can just allow user to write some macro functions by hand, and offer the users a special mode to insert any text into the complied codes, then they will have the ability to generate codes other than Moonscript or Lua. Customize their compiler functions to do something more.

pigpigyyy avatar Oct 23 '20 10:10 pigpigyyy

The teal-lang type annotation, type, record and 'as' keyword syntax are now supported in Yuescript as optional features. See commit 38e2f978d76b25f626350a1766c7f5377e58e8dc. And the yue compiler tool can now compile Yuescript to tl codes with yue -tl file.tl.yue then generate file.tl, or compile Yuescript with tl annotations to Lua without -tl flag yue file.tl.yue and then get file.lua.

I'm going to make yue tool run teal annotated codes with type check functions directly, and add options to do a type check for Yuescript in a simpler way.

join = (strs::{str})->::str table.concat strs,", "
strs::{str} = [tostring i for i = 1, 10]
print join strs

print join [tostring i for i = 1, 10] as {str}

now compiles to tl:

local join: function({string}): string
join = function(strs: {string}): string
  return table.concat(strs, ", ")
end
local strs: {string}
do
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    _accum_0[_len_0] = tostring(i)
    _len_0 = _len_0 + 1
  end
  strs = _accum_0
end
print(join(strs))
return print(join((function(): any
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    _accum_0[_len_0] = tostring(i)
    _len_0 = _len_0 + 1
  end
  return _accum_0
end)() as {string}))

to Lua:

local join
join = function(strs)
  return table.concat(strs, ", ")
end
local strs
do
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    _accum_0[_len_0] = tostring(i)
    _len_0 = _len_0 + 1
  end
  strs = _accum_0
end
print(join(strs))
return print(join((function()
  local _accum_0 = { }
  local _len_0 = 1
  for i = 1, 10 do
    _accum_0[_len_0] = tostring(i)
    _len_0 = _len_0 + 1
  end
  return _accum_0
end)()))

pigpigyyy avatar Sep 01 '21 01:09 pigpigyyy

Awesome!

turbo avatar Sep 01 '21 15:09 turbo

Played with the type annotations for Yuescript for a while, I felt that it would not be a good idea to introduce such feature in this language. Since Yuescript is a language to be expressive and extremely concise, why don't we just code in teal-lang instead for the static type check functions? And I have revert the main branch to the former implementation without type annotation features.

pigpigyyy avatar Sep 07 '21 02:09 pigpigyyy

Rather than compiling to Teal, it might make more sense to compile to Luau. It seems to be popular and closer to vanilla Lua, with optional type information.

Seirdy avatar Feb 20 '23 22:02 Seirdy

Since Yuescript is a language to be expressive and extremely concise, why don't we just code in teal-lang instead for the static type check functions?

I don't think that a concise and a type-safe language are mutally exclusive. Most of the aspects where Yuescript is more consise than Lua are ones where it pretty much just removes boilerplate. A type system is different to that IMO. A type system makes your code more maintainable and easier to work with down the line.

Also, I think it's a bad idea to compile to any intermediary language, be it Teal or Luau (side note: Luau is only compatible with Lua 5.1) or something else. It should just compile to Lua as ususal. The real benifit of things like TypeScript is the options for advanced IDE tooling. But of course, this tooling doesn't exist (yet?). I think that there would first have to be some kind of "Yuescript language server" for type annotations to really make sense.

SkyyySi avatar Mar 24 '23 08:03 SkyyySi

On Fri, Mar 24, 2023 at 01:47:28AM -0700, Simon B. wrote:

Also, I think it's a bad idea to compile to any intermediary language, be it Teal or Luau (side note: Luau is only compatible with Lua 5.1) or something else.

Note that Luau is not an intermediate language; it has its own (rather fast) interpreter. Type information can aid that interpreter in making optimizations.

Seirdy avatar Mar 24 '23 16:03 Seirdy