Mark the functions that have a magic type#130
Conversation
| find: (s: string, pattern: string, init: number?, plain: boolean?) -> (number?, number?, ...string), | ||
| format: (formatstring: string, ...any) -> string, | ||
| gmatch: (s: string, pattern: string) -> () -> ...string, | ||
| find: (s: string, pattern: string, init: number?, plain: boolean?) -> (number?, number?, ...string), -- magic type |
There was a problem hiding this comment.
--!strict
local _start, _finish, _tag, _text, _extra = string.find(
'monospace:: <code>print("hello regex")</code>. hooray',
"<(%w-)>(.-)</%1>")Magic detects the extra string.find variable assignment:
$ luau-lsp analyze --platform standard magic_string_find.luau
[WARN] No definitions file provided by client
magic_string_find.luau(2,1): TypeError: Function only returns 4 values, but 5 are required here
Without magic, the extra parameter passes silently:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_string_find.luau
[INFO] Loading definitions file: @roblox - sl-vscode-plugin/slua_fixes.d.luau
| format: (formatstring: string, ...any) -> string, | ||
| gmatch: (s: string, pattern: string) -> () -> ...string, | ||
| find: (s: string, pattern: string, init: number?, plain: boolean?) -> (number?, number?, ...string), -- magic type | ||
| format: (formatstring: string, ...any) -> string, -- magic type |
There was a problem hiding this comment.
--!strict
print(string.format("I am %s. I am %d years old.", "Pippin", "six"))Magic knows that the 3rd string.format parameter should be a number:
$ luau-lsp analyze --platform standard magic_string_format.luau
[WARN] No definitions file provided by client
magic_string_format.luau(2,62): TypeError: Expected this to be 'number', but got 'string'
Without magic, it passes silently:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_string_format.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
| gmatch: (s: string, pattern: string) -> () -> ...string, | ||
| find: (s: string, pattern: string, init: number?, plain: boolean?) -> (number?, number?, ...string), -- magic type | ||
| format: (formatstring: string, ...any) -> string, -- magic type | ||
| gmatch: (s: string, pattern: string) -> () -> ...string, -- magic type |
There was a problem hiding this comment.
--!strict
for tag, text, extra in string.gmatch(
'monospace:: <code>print("hello regex")</code>. hooray',
"<(%w-)>(.-)</%1>"
) do
print(tag, text, extra)
endMagic detects the extra string.gmatch variable assignment:
$ luau-lsp analyze --platform standard magic_string_gmatch.luau
[WARN] No definitions file provided by client
magic_string_gmatch.luau(2,1): TypeError: Argument count mismatch. Function expects 3 arguments, but only 2 are specified
Without magic, the extra parameter passes silently:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_string_gmatch.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
| lower: (s: string) -> string, | ||
| match: (s: string, pattern: string, init: number?) -> ...string, | ||
| pack: (fmt: string, ...any) -> string, | ||
| match: (s: string, pattern: string, init: number?) -> ...string, -- magic type |
There was a problem hiding this comment.
--!strict
local _tag, _text, _extra = string.match(
'monospace:: <code>print("hello regex")</code>. hooray',
"<(%w-)>(.-)</%1>")Magic detects the extra string.match variable assignment:
$ luau-lsp analyze --platform standard magic_string_match.luau
[WARN] No definitions file provided by client
magic_string_match.luau(2,1): TypeError: Function only returns 2 values, but 3 are required here
Without magic, the extra parameter passes silently:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_string_match.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
| freeze: <table>(t: table) -> table, -- magic type | ||
| isfrozen: (t: {}) -> boolean, | ||
| clone: <table>(t: table) -> table, | ||
| clone: <table>(t: table) -> table, -- magic type |
There was a problem hiding this comment.
--!strict
type objTable = {x: string}
type hashTable = {[string]: string}
local o: objTable = {x="yo"}
local _o: objTable = table.clone(o)
local h: hashTable = {x="yo"}
local _h: hashTable = table.clone(h)
local _n: number = table.clone(5)Magic knows that table.clone only works on tables:
$ luau-lsp analyze --platform standard magic_table_clone.luau
[WARN] No definitions file provided by client
magic_table_clone.luau(11,32): TypeError: Expected this to be '{- -}', but got 'number'
magic_table_clone.luau(11,1): TypeError: Expected this to be 'number', but got '{- -}'
Without magic, "generic table" is an unexpressable type and the file must resort to "generic anything"
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_table_clone.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
clone magic only works on old solver:
$ luau-lsp analyze --platform standard --flag LuauSolverV2=true magic_table_clone.luau
[WARN] No definitions file provided by client
| clear: (t: {}) -> (), | ||
| shrink: <V>(t: {V}, shrink_sparse: boolean?) -> {V}, | ||
| freeze: <table>(t: table) -> table, | ||
| freeze: <table>(t: table) -> table, -- magic type |
There was a problem hiding this comment.
--!strict
type objTable = {x: string}
type hashTable = {[string]: string}
local o: objTable = {x="yo"}
local _o: objTable = table.freeze(o)
_o.x = "error"
local h: hashTable = {x="yo"}
local _h: hashTable = table.freeze(h)
_h.x = "error"
local _n: number = table.freeze(5)Magic knows that table.freeze only works on tables:
$ luau-lsp analyze --platform standard magic_table_freeze.luau
[WARN] No definitions file provided by client
magic_table_freeze.luau(13,33): TypeError: Expected this to be '{- -}', but got 'number'
magic_table_freeze.luau(13,1): TypeError: Expected this to be 'number', but got '{- -}'
Without magic, "generic table" is an unexpressable type and the file must resort to "generic anything"
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_table_freeze.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
Neither one understands the real purpose of freeze is to make a read-only table:
$ luau magic_table_freeze.luau
./magic_table_freeze.luau:7: attempt to modify a readonly table
stacktrace:
./magic_table_freeze.luau:7
There was a problem hiding this comment.
Luau 0.720 improved the table.freeze magic so that it can warn about read-only access:
--!strict
type objTable = {x: string}
local o: objTable = {x="yo"}
local of = table.freeze(o)
of.x = "error"Magic now knows the table is read-only (new solver only):
$ luau-lsp analyze --platform standard --flag LuauSolverV2=true magic_table_freeze_bug.luau
[WARN] No definitions file provided by client
magic_table_freeze_bug.luau(5,1): TypeError: Property x of table 'objTable' is read-only
Without magic, or in the old solver, table writing is not detected as an error:
$ luau-lsp analyze --platform standard --flag LuauSolverV2=true --defs secondlife.d.luau magic_table_freeze_bug.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
$ luau-lsp analyze --platform standard --flag LuauSolverV2=false magic_table_freeze_bug.luau
[WARN] No definitions file provided by client
| remove: <V>(a: {V}, i: number?) -> V?, | ||
| sort: <V>(a: {V}, f: ((a: V, b: V) -> boolean)?) -> (), | ||
| pack: <V>(...V) -> { n: number, [number]: V }, | ||
| pack: <V>(...V) -> { n: number, [number]: V }, -- magic type |
There was a problem hiding this comment.
--!strict
local _: {string | number} = table.pack("a", 5)Magic knows to union all the table.pack parameters for the return type:
$ luau-lsp analyze --platform standard magic_table_pack.luau
[WARN] No definitions file provided by client
Without magic, this is not supported by the type system:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_table_pack.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
magic_table_pack.luau(2,46): TypeError: Expected this to be 'string', but got 'number'
| declare function select(i: string | number, ...any): any -- magic type | ||
| declare setfenv: nil | ||
| declare function setmetatable(t: { [any]: any }, mt: { [any]: any }?): () | ||
| declare function setmetatable(t: { [any]: any }, mt: { [any]: any }?): () -- magic type |
There was a problem hiding this comment.
In the new solver, setmetatable does not need a magic type function to work, as it instead can be fully defined based on the builtin type function `setmetatable' (fixed in #135):
declare function setmetatable<T, MT>(t: T, mt: MT): setmetatable<T, MT>
In the old solver, setmetatable has both
- an unexpressable builtin type
- a magic type function
It's not possible to untangle those and demonstrate the builtin type without the magic type function (not without editing slua and recompiling), so I didn't make example code here to demonstrate it
|
What's "magic"? I'm assuming it's an internal |
2ba48a9 to
48898eb
Compare
|
|
||
|
|
||
| declare function assert<T>(value: T?, message: string?): T | ||
| -- declare function assert<T>(value: T?, message: string?): T -- magic type |
There was a problem hiding this comment.
--!strict
local _a: string? = "hello"
assert(_a ~= nil)
local _b: string = _aMagic lets assert do type narrowing:
$ luau-lsp analyze --platform standard magic_assert.luau
[WARN] No definitions file provided by client
Without magic, no type narrowing happens:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_assert.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
magic_assert.luau(4,1): TypeError: Expected this to be 'string', but got 'string?'
| declare function rawset<K, V>(t: {[K]: V}, k: K, v: V): {[K]: V} | ||
| declare function require(target: string): any | ||
| declare function select(i: string | number, ...any): any | ||
| -- declare function require(target: string): any -- magic type |
There was a problem hiding this comment.
magic_require.luau
--!strict
local mod = require("magic_require_module")
local _nope = require("non_existent_module")
local _x: number = mod.x
local _hello: string = mod.world magic_require_module.luau
--!strict
return { x = 1, y = 2 }Magic lets require copy types between files:
$ luau-lsp analyze --platform standard magic_require.luau
[WARN] No definitions file provided by client
magic_require.luau(3,15): TypeError: Unknown require: non_existent_module.lua
magic_require.luau(5,24): TypeError: Key 'world' not found in table '{ x: number, y: number }'
Without magic, require never errors:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_require.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
| declare function require(target: string): any | ||
| declare function select(i: string | number, ...any): any | ||
| -- declare function require(target: string): any -- magic type | ||
| -- declare function select(i: string | number, ...any): any -- magic type |
There was a problem hiding this comment.
--!strict
local _s: number = select(2, 5, "hello")Magic knows the return type of select for each argument:
$ luau-lsp analyze --platform standard magic_select.luau
[WARN] No definitions file provided by client
magic_select.luau(2,1): TypeError: Expected this to be 'number', but got 'string'
Without magic, this is not flagged by the type system:
$ luau-lsp analyze --platform standard --defs secondlife.d.luau magic_select.luau
[INFO] Loading definitions file: @roblox - secondlife.d.luau
| toup: (q: quaternion) -> vector, | ||
| } | ||
|
|
||
| --[[ commented out to avoid shadowing magic functions find, format, gmatch, and match |
There was a problem hiding this comment.
If it were possible, I'd also comment out the table module from secondlife.d.luau to avoid shadowing the magic type functions pack, freeze, and clone. But, that is not possible due to the addition of shrink, append, and extend
There was a problem hiding this comment.
That's fine, I imagine we can special-case inside the definition loader to remove things where we want to rely on the built-in logic? Or what do you think.
There was a problem hiding this comment.
that would involve distributing a custom luau-lsp, which I don't think is worth it. Not with the viewer being much lower-hanging fruit where we can customize EmbeddedBuiltinDefinitions.cpp directly, and not worry about definition files.
Still, it might be worth bringing up with lute, once it supports definition files at all. Selene has this feature: standard library files are additive, even inside library tables, unlike luau-lsp definitions, where they replace
HaroldCindy
left a comment
There was a problem hiding this comment.
This seems reasonable to me, thanks! I'd really like for there to be some sort of way to "mix in" additions to table, string, and bit32, but that can be dealt with later.
builtin-functionsintoglobal-functions#133EmbeddedBuiltinDefinitions.cpp#122Mark 2 classes of functions that need special handling in the generators of
secondlife.d.luauandEmbeddedBuiltinDefinitions.cppEmbeddedBuiltinDefinitions.cppmust be left out of both filesBuiltinDefinitions.cppmust be left out of onlysecondlife.d.luau. These all useattachMagicFunction(), hence the name "magic"I also wanted to document what all the magic type functions do, and this PR seems like the most appropriate place to host that documentation for now