Luau Type Checking Beta!

The update is neutral on runtime performance - type annotations don’t affect generated code. We have future plans to optimize based on type information but have not done that yet.

That said, with strict mode you can probably remove the runtime type assertions which will result in performance improvements.

6 Likes

What about for RemoteEvents and RemoteFunctions? Can we remove type assertions there?

1 Like

This is a great question! The answer is “no”, and we should be more explicit about documenting this.

Type annotations are a tool that helps you make sure your code is correct, when the callers are respecting the type contract. We will statically check that the contract is upheld by all callers.

A remote communication boundary is one where on the other side there’s an entity you don’t control - an exploiter. They are free to manufacture arbitrary arguments to remote functions/events, and we have no way (right now) of ensuring the type safety there.

Moreover, in a remote function, you may want to check more than just the type of the value - if there are any useful validity checks that check the actual value, such as “the damage is between 0 and 10”, you should do so as well. This update doesn’t help with that.

14 Likes

That is reasonable. I myself (despite asking about it) don’t really see any prominent use cases for a constant keyword except the want of skimming quickly over my code to know that the variable I’m looking at never changes.

Of course I could simply just store my variables in a table with a __newindex metamethod to emulate this behavior, or I could just prefix or suffix my variables with a _CONSTANT identifier and just make sure to not attempt to reassign those values.

4 Likes

In addition to my earlier post highlighting issues with untyped code (I hope it doesn’t get lost in the wayside :confused: ), the type system needs a solid way of exporting types between modules.

Admittedly, I’m not sure how this will perform, since I’d imagine it will take the binder a second to cache all type symbols between modules. But, if changes in one module will only affect its dependents in script analysis, this may be feasible. I suppose the real issue is when you have lots of modules dependent on a single module. Then you’ll probably have to show some loading state for script analysis if there are performance issues here.

I don’t quite like the idea of exporting every type defined in a module unless there’s an explicit keyword for doing so (i.e. export type);

One way you could do it is by adding the typeof operator, which only works in type expressions (Similarly, in TypeScript, typeof() is both a function and a keyword that only works in type expressions). This would allow you to export certain types from a module by accessing

local Module = require(static.path.to.module)
type ImportedType = typeof Module.ExportedValue
type ModuleType = typeof Module

However, this method is a bit hacky, and I think there should be a dedicated solution like export type (in addition to typeof).

One thing I would really love to see would be “AmbientScripts” or “TypeScripts” which only allow types to be defined on them (which can then be imported by other scripts via require, a comment directive, or some other special function that only affects static code). It would be even better if these ambient scripts allowed circular dependencies in some way, since they only convey type information, such as class headers. For example, a parent and child class may reference each other’s type, even if they might not both need to construct each other. An AmbientScript could be used as a guarded header in this case; AmbientScripts could also be global, but this would probably clutter things too much, and Roblox has its strengths in modular code.

The way TypeScript does this is worth looking into, and it seems like this type system might not support a deferred complete binding of symbols (I see right now the metatable types aren’t particularly robust or user-friendly, being typed like {|__meta: {__index: <CYCLE>}|}.

It also looks like this isn’t yet supported, which is really what would be necessary for ambient (type statement only) scripts to be useful:

type C = {[any]: A}
type A = string
4 Likes

ATTENTION

Because of the compatibility issue with as pseudo-keyword and existing scripts, we may have to disable type annotation support on client/server. However the beta is still on!

Please DO NOT publish places with type annotations just yet as they will not work on production!

However, please continue to experiment in Studio and give us feedback. We are reading everything and will be fixing reported bugs and discussing syntax / semantics issues some people brought up.

We have updated the top post to indicate this.

15 Likes

I can assign a BodyVelocity type variable to the baseplate with no warning. Is this something that will change with the game hierarchy knowledge necessary for module support, or is this a bug?

1 Like

Roblox Lua is my favorite language again.

Big question: are these types first class? It’d be really cool to be able to make type constructors but that might damage type inference ability. Maybe if you had a type constructor, something like

function LinkedList(t : Type) => Type
   return type {
       head : t,
       --consideration: this would have to be evaluated lazily due to unbounded recursion for this to work, maybe even as late as type-check time.
       tail : LinkedList(t)? 
   }
end
3 Likes

I love it already! Been waiting for something like this for a while. I hope in the future this can be extended to userdata for some really good intellisense.

1 Like

So I’ve been thinking about the syntax for a while, and I’d like to suggest some updates to it. (keep in mind this is a mixture of multiple different ideas of varying quality, mainly just throwing stuff at the wall to see what sticks)


Type aliases


Current:

type SimpleRotation = {pitch: number, yaw: number}

Proposed:

type SimpleRotation: {pitch: number, yaw: number}

The logic behind this change is that all other areas where types appear use the colon. For consistency, it might be a good idea to add it here too. (the argument could however be made that since this defines a type instead of annotating a type, equals would be more suitable?)


Function types


Current:

type NumberPredicate = (number) => boolean

Proposed:

type NumberPredicate: function(number) => boolean

The logic behind this change is that, in the current system, it’s not completely obvious that function types represent functions. By adding the function keyword, it’d make it a lot more clear that it is, indeed, representing a function.


Union types


Current:

local metadata: string | number = "I am metadata!"

Proposed:

local metadata: string or number = "I am metadata!"

The logic behind this change is that, by using the existing ‘or’ keyword in place of the vertical bar, it makes it more obvious that the type can be either the type on the left, or the type on the right. It would additionally fit better with Lua’s more keyword-oriented syntax.


Any types


Current:

local anyValue: any = nil

Proposed:

local anyValue: any? = nil
local definedValue: any = 2

The logic behind this is that you might want to define a value with any type except nil. It could be productive to make any non-optional by default, and instead define untyped things as any?. Maybe a different keyword would work better for non-optional any here?


Optional types


Current:

local function sample(x: number, y: number?, z: number?)
    ...
end

Proposed:

local function sample(x: number, y: number or nil, z: number or nil)
    ...
end

The logic behind this is that, following the design philosophy of Lua, it might be more appropriate to forgo a dedicated optional syntax and instead reuse union types for this purpose. It’s also way more obvious that a type is optional in this case; question marks are only single characters and can sometimes get lost among the rest of the characters (at least in my experience), which makes the code harder to understand quickly.


Just some random ideas. Feel free to pick any you like and criticise any you don’t :grinning:

14 Likes

https://www.lua.org/source/5.1/lbaselib.c.html#luaB_setmetatable
Unless roblox changed setmetatable (they might have), __meta won’t be set. Most likely a bug with it expecting __meta to be set.

3 Likes

Love this new feature so far, only complaint about it is that it’s kind of unstable and crashes often.

1 Like

Well, this is luau for one thing. And secondly, I have no explanation for why __meta would be expected to be set by setmetatable. It’s very strange especially because it only happens in certain contexts as I showed above.

A few bugs I’ve encountered so far (strict turned off)

image




image


image

1 Like

That last one appears to confirm my suspicion above. It seems like internal features are being “leaked” through type checking. What it looks like to me is that type checking is being done on compiled lua. Could some of the crashes be related to memory corruption of some kind? Very weird stuff.

4 Likes

You can’t confirm or deny that from just looking at my last screenshot, as the bug doesn’t occur when you replace the last line with foo().hello = nil, which is compiled into the same thing

1 Like

The type signature string ? works as in local foo: string ? and opposed to string?. I’m writing a parser for the new syntax, is this intentional?

4 Likes

To mirror some of the more common feedback, I found myself consistently wanting to use a colon instead of an equals sign for type declarations. It felt more natural to write type foo: string than type foo = string.

7 Likes

Types aren’t first class however…

type LinkedList<T> = { head: T, tail: LinkedList<T>? }

works.

4 Likes

We are going to discuss this, but there’s a specific reason for the current syntax:

Consistently, : in the new syntax means “the value to the left has a type specified on the right”

For example,

local a: number

“the value a has a type number”

{ foo: number }

“the table has a key foo which is of type number”

Unlike this, the type alias type X = ... doesn’t say “X is of type …”, it says “X names the type …”, and X isn’t a value - it’s a type name.

4 Likes