Luau Recap: March 2021

There seems to be an issue with relating specific instance types (i.e. ‘BasePart’, ‘ViewportFrame’, etc.) with “Instance”

This works fine:

local function handlesInstance(myInstance: Instance)
	-- ...
end

handlesInstance(Instance.new('ViewportFrame'))

However, this emits a warning:

local function handlesInstance(props: {
	myInstance: Instance
})
	-- ...
end

handlesInstance({
	myInstance = Instance.new('ViewportFrame')
})

image

Out of lack for a better place to post this, I figure I would highlight a basic but annoying bug with the linter that has existed for at least a couple months now.

The linter incorrectly assumes the type of a table is the type of element one when dealing with table literals i.e

--!strict

local t: {string | number} = {
    "a",
    2 --W000: type 'number' could not be converted into 'string'
}

However, table.insert has no such issues and leaking the type of t through errors reveals that it’s correctly typed as {string | number}.

I also ran into an issue while I was playing with variadic annotations where the linter was not throwing an error for a type mismatch for a function return.

--!strict

local function pack(...: any): {any}
	return {n=select("#", ...), ...} --no error even though return type is missing entry n
end

I really want to love Luau and for the most part I do. The optimizations are great and the constant updates and new tools to play with are always fun but static typing is such a miss for me. In my opinion, it never should’ve left beta in a state like this. Hopefully we see some quick improvement.

3 Likes

Maybe you can try type MyType = string | number and then type hinting the array as {MyType} as a workaround.

I agree with you that the typing system barely feels like you could call it a beta, I can’t type hint 90% of my code. I think there was too much hype around it, adding static typing to a dynamic language is a huge challenge. And at the same time they’re working on a new interpeter, and also a JIT, and also maintaining the current language.

1 Like

Unfortunately the workaround doesn’t work either. When I first found the bug it was while I was using generics and those also produce the exact same error. Thy only way I’ve found to get rid of the errors is to insert everything else after defining the table which obviously is not very viable.
Having to do

local t: {string | number} = {}
t[1] = "a"
t[2] = 2

Is so tedious even for this small sample size.

1 Like

Luau seems great, but its extremely confusing for someone who doesn’t already know how it works. There’s no documentation on it other than these monthly recaps, so I have to look through them to find how to use it. Not to mention that some of the messages don’t make much sense. For example, this is telling me that it’s trying to convert it into a NumberValue I think, not really sure if that’s what it’s saying though, when the only mention of a NumberValue is earlier in the script and is in a completely different context. Changing WaitForChild into FindFirstChild fixes it though, which is confusing because they basically do the exact same thing, except one of them waits.

On the other hand, if I remove the type annotation from the NumberValue, I get another error. So, it seems like I can’t use WaitForChild at all without getting errors, unless I ditch strict typing mode altogether, which I don’t want to do.

Either I’m missing something, or I found a bug in Luau. If it is a bug, I hope these screenshots can help to fix it :slight_smile:

Luau has documentation: Luau - Luau

1 Like

We do have this on our list of issues to fix. The other problem you bring up I think is working as expected, since the presence of extra fields doesn’t make the table types incompatible (e.g. if the caller expects an array of anys, returning an array that also has a n field is fine).

Would you mind posting that example in a form that we can copy & paste into Studio, e.g. a full script source or a model file? Typing code from screenshots to reproduce issues like that is a bit tedious.

Here is the script:

--!strict

local g = {
	repStor = game:GetService("ReplicatedStorage"),
	players = game:GetService("Players")
}

local scalingValues = {
	"BodyDepthScale",
	"BodyHeightScale",
	"BodyProportionScale",
	"BodyTypeScale",
	"BodyWidthScale",
	"HeadScale"
}

--<>--

local function onCharacterAdded(character)
	local humanoid = character:WaitForChild("Humanoid")
	
	if humanoid then
		for _, v in pairs(scalingValues) do
			local val: NumberValue = humanoid:WaitForChild(v)
			val.Value /= 2
		end
		
		local rootPart: Part = character:WaitForChild("HumanoidRootPart")
	end
end

local function onPlayerAdded(player)
	if player.Character then
		onCharacterAdded(player.Character)
	end
	player.CharacterAdded:Connect(onCharacterAdded)
end

g.players.PlayerAdded:Connect(onPlayerAdded)
for _, v in pairs(g.players:GetPlayers()) do
	onPlayerAdded(v)
end

Line 28 has the error. I removed a few unrelated sections of the code but left the part with the error.

Thanks! Bug confirmed, WaitForChild type is a bit too special from what it looks like. We’ll fix this.

2 Likes

I know, right? Finally the gradient is gone for one thing!

1 Like

That’s not really what I meant. I want the gradient to be there. :slight_smile:

Alright, this might not be as popular but I’d love to see an automatic convertor between CFrame units and units in actual workspace (when applicable).

I find working with CFrames really interesting but at the same time also confusing, because I can’t really tell the outcome of anything complicated without play-testing a bunch of times which is time consuming.

Maybe something similar to the Color3 pop-up in scripts, where if you put your cursor in a new CFrame value it would show all the contained CFrame values and their converted versions.

1 Like

Can we please get an elegant way of implementing methods when using custom table types as a kind of struct.

Currently in Luau you have to implement the functions/methods per instance of the type like so:

Current Syntax
--!strict


type TestType = {
    x: number,
    method: (TestType)->()
}


local TestTypeMethods = {
    method = function(self: TestType)
        print(self.x)
    end,
}

local instance: TestType = {
    x = 8,
    method = TestTypeMethods.method
}

instance:method()

Yes, you can create a constructor to automate this when creating an instance, but that doesn’t solve having to create an implementation table and manually assigning each method in the constructor whenever any are added to the type. (requiring editing three parts of the script)

I know you’re against adding new keywords so I won’t suggest the impl keyword from Rust.

I don’t know how the type system works under the hood but one solution to this could be allowing the implementation to be done directly inside the type declaration like so:

Idea
-!strict


type TestType = {
    x: number,

    method = function(self: TestType): ()
        print(self.x)
    end,
}


local instance: TestType = {
    x = 8,
}

instance:method()

I’d have a wild guess that wouldn’t work because the type declaration is probably optimised away at runtime.

Since actual OOP support seems to not even be on the horizon for Luau yet, it would at least be of help if we were able to have less verbose custom structs by managing function definitions more elegantly.

2 Likes

I found a bug where --!strict and --!nonstrict mode are no longer catching out-of-scope local variables where --!nocheck mode works fine:

1 Like

Will there be tutorials created for the new features coming out? I feel like there needs to be an entire tutorial dedicated to type checking and generic functions, as they can be pretty confusing for newcomers to learn.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.