Luau Recap: March 2020

Now that type namespaces are a thing, I think class types should be moved to a separate namespace. For example, you would use Instance.BasePart to define a type with the BasePart class, or Instance.Instance for any instance. Using “Instance” as the namespace creates a natural association with Instance.new.

In general, the following scenario can occur:

  1. A module defines type X.
  2. Subsequently, Roblox predefines type X (e.g. a new class was added).
  3. The module cannot begin using predefined type X because it is shadowed by the self-defined type X.
  4. The module cannot rename the self-defined type X without a major version change because other modules may depend on the exported type name.

New classes are added relatively often, so putting them in their own namespace eliminates this problem, at least for classes. Moreover, it reduces pollution of the top-level namespace.

5 Likes

It wasn’t, although it was added in 5.2
https://www.lua.org/manual/5.2/manual.html#6.4.1
https://www.lua.org/manual/5.1/manual.html#5.4.1
Some reasons for the features:
\0 in patterns seems better than doing %z, would benefit utf8.charpattern as it uses %z and range [%z\x01-\x7F\xC2-\xF4][\x80-\xBF]* which if \0 were allowed in patterns could be simplified to [\x00-\x7F\xC2-\xF4][\x80-\xBF]*.
%p format for whitelists/blacklists, currently to check if an instance is in a table a linear search must be done. With %p the table could be sorted once and then a binary search could be done to check if the instance is in the table. This can be done currently with tables assuming __tostring isn’t used, but all instances over ride __tostring so this isn’t possible.

local function udatasort(a,b)
	return string.format("%p",a) < string.format("%p",b)
end
local function udatafind(tbl,finding)
	-- binary search
	local str = string.format("%p",finding)
	local L = 1
	local R = #tbl
	while L <= R do
		local M = math.floor((L+R)/2)
		local strm = string.format("%p",tbl[M])
		if strm < str then
			L = M+1
		elseif strm > str then
			R = M-1
		else
			return M
		end
	end
	return nil
end
local n = {}
local tbl = {}
tbl[1] = {}
tbl[2] = n
tbl[3] = {}
table.sort(tbl,udatasort)
print(tbl[udatafind(tbl,n)]==n)

A separator would be useful when you want something on every line, but no extra new line

string.rep("abc\n",3)
--abc
--abc
--abc
--
string.rep("abc",3,"\n")
--abc
--abc
--abc

string.gmatch with init argument would be useful for applying a pattern after a prefix

local function processCmd(str)
    if string.sub(str,1,1) == ":" then
        for l in string.gmatch(str,".",2) do -- example
            print(l)
        end
    end
end

Re: udatafind - you should be much better off with either table.find or filling a table with instances as keys and checking if the key is in that table. We’ll take a look at the other bits, %g/string.rep/string.gmatch seem straightforward at least.

table.find is O(n), while a binary search is O(log n). I think I will go with the route of filling the table and checking if the key is present, although wouldn’t this benefit from a hash length argument to table.create? Because it would fill in #tbl hash entries in the table, creating the table with a hash length of #tbl should avoid re allocating the table.

I think I found a bug related to the new VM: Underscores are not parsed/ignored after a number:

image

This is not a bug; we extended Lua number syntax with underscores, as it makes long numbers easier to read, for example 1_000_000. However, we don’t restrict the placement of underscores in any way - this is the standard approach taken by programming languages that have similar number literal support - so 1_0 is the same as 10 which is the same as 10__. Numbers can’t start with an underscore though - _10 is an identifier.

6 Likes

Thanks for the clarification! I’ve never seen this before so I wasn’t sure if this was intentional or a bug.

From what I’ve been aware of this, I look at the script analysis and see this popup most of the time:
image

I can’t tell if this is a bug or not.

If you input this in the command bar, it shows userdata. In the old VM, it would show Color3

print(type(Color.fromRGB(1,248,248))
-- Output: userdata

Are you sure you’re not thinking of typeof?

type(Color3.new()) -> userdata
typeof(Color3.new()) -> Color3
2 Likes

Could we get a notification for the change for the new Luau type checking syntax is out? (In script analysis saying the syntax changed.) I’m already using it in a game and it’d be helpful to know when the function syntax for returns is changed so I can update my code.

Also I’m currently having an issue with Luau type checking in the same game where I get these from script analysis today: (Wasn’t happening before today.) (Isn’t happening with just chat scripts, doesn’t show any syntax issues on my actual scripts either.)


I’ve restarted studio twice and these are still here, I’m guessing there’s an issue with a recent change.

I don’t know if the new type annotation syntax change have been rolled out, but the function type declaration seems to produce error while function definition does not.

image

It will be fully enabled later today.

3 Likes

I have a basic function:

--!strict

function test(foo: string): number
	print(typeof(foo))
	return #foo
end

print(test(1))

This is the output for the above code:
image

I’m assuming this is some sort of bug? What’s going on here?

Edit: Some bonus snippets as well, clarification here would be great, I’m at a loss:

--!strict

local test: NotAType = 1
print(test) --> 1
--!strict

type foo = {bar: string}
local test: foo = {foo = "Hello World", bar = 10}
print(test.foo) --> Hello World
print(test.bar) --> 10

Shouldn’t all of these scenarios not work in strict mode? I can’t find anything about the syntax for enabling strict mode changed, am I missing something here?

1 Like

Do you have the Beta feature enabled?

image

If you do, you should have both of these display issues in Script Analysis widget. Note that type errors don’t block your ability to run the code right now.

2 Likes

Studio disabled all of my beta options in the recent studio build and I was not aware of this until now. I had them all enabled. I didn’t think to check for that reason. I have no clue why, but that’s a separate issue.
edit: Studio did crash mid-session when trying to update, I can provide a crash report if you’d like, it could be related to the issue.

I’ve been rewriting my framework with type checking, and it’s been pretty smooth other than two major issues I’ve faced:

  1. Requiring third party modules without type checking implemented, in non-strict mode, such as eryn’s “Promise” and Quenty’s “Maid” take exactly one second to require for the first time, every time. This means that it can take up to 10 seconds for any code to run, as the script is stuck requiring these modules for a long time. This is not the case with my custom libraries which are scripted with type checking. No code in the project is currently using strict mode, however all the first party code that I am writing is designed to be strict mode ready later.
  2. I forgot I didn’t have studio API access enabled in my test place, and trying to use DataStoreService:GetGlobalDataStore() did not error, but rather the script requiring the module threw a “Requested module experienced an error while loading” error which would always throw approximately 2 seconds after initially requiring the module.
    After further investigation, I’ve found niche cases where my own functions can have this behavior, too, making debugging with modules no easy task.

These two issues have made my experience really daunting, but other than that it’s been smooth sailing. Could you advise on why I’m getting such behaviors? This did not occur when all the scripts were written without heavy type checking support.

2 Likes

I don’t really like this update, broke some of my code:

local function noyield(f,...)
	return select(2,assert(xpcall(f,debug.traceback,...)))
end

It was imo a clean wrapper to call functions and error if the function yields (very useful for non-production code). Now I have to switch to __index or a for loop iterator call (or is there a cleaner way?) to get this same behavior.

I think we can always make a yielding xpcall ourselves (is this wrong?):

But ideally the above noyield is Roblox provided and imo xpcall shouldn’t exist (if the above is just as powerful).

For loop iterator call is probably the fastest alternative. A cleaner alternative is to use coroutine.create/resume (and check status) but that can be expensive.

You could not make a yielding xpcall yourself, as the callback in xpcall executes in context of the error, making debug.traceback work.

Sorry this affected you, these things happen - it’s much better for us long term to have uniform expectations about pcall and xpcall though, and for every use case when xpcall was used to block yields there’s several opposite use cases where xpcall is desireable but didn’t work and had to be emulated.

1 Like

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