Skip to content

Commit 93c83a4

Browse files
aatxehgoldsteinvrn-snayoungbloodrbxmenarulalam
authored
Sync to upstream/release/705 (#2200)
Hello Luaunauts, we're continuing into the second release of the year at a brisk pace! We've got a number of improvements for both native code generation and the new type solver, as well as some other assorted changes, that keep us moving towards the goal of making the best possible scripting language! # Analysis - Refinements against a union of tables would sometimes erroneously refine to a narrower type than intended. The root cause here was a bug in the logic that determines how to "simplify" types (for example knowing that `true | false` is the same as `boolean`). ```luau export type States = "Closed" | "Closing" | "Opening" | "Open" export type MyType<A = any> = { State: States, IsOpen: boolean, Open: (self: MyType<A>) -> (), } local value = {} :: MyType function value:Open() if self.IsOpen == true then elseif self.State == "Closing" or self.State == "Opening" then -- Prior, this line errored as we were erroneously refining -- `self` with `{ State: "Closing" | "Opening" }` rather -- than `{ read State: "Closing" | "Opening" } self:Open() end end ``` - Adds an option in `ToString.cpp` to disable the use of synthetic names, which can be used to improve the quality of hover type. - Fixes a bug where `table.freeze` would incorrectly error on arguments of `any` type (fixes #2181) - Subtyping mistakenly did not allow for us to covariantly check tables with missing properties against table types that gave those properties optional types. This release should fix these issues, including fixes #2164. - Type functions have a global environment that defines all of the type aliases present in the environment around the type function definition, and the API of type functions also allows you to mutate types, specifically table types and function types. Though we never supported the type functions _actually_ mutating the aliases present in the environment around them, the mutable API allowed for users to author type functions that _appeared_ to do so, which could be confusing when they later discover that their mutation did not take place. This release introduces new errors for type functions that attempt to call mutable APIs on types from their environment, e.g. ```luau type myType = {} type function create_table_with_key() myType:setproperty(types.singleton "key", types.optional(types.number)) -- this errors now! return myType end local my_tbl: create_table_with_key<> = {key = "123"} ``` - Bidirectional inference for lambdas on extern types with generic parameters should work more consistently. - #2166 fixes a bug with `findBindingAtPosition` that should enable LSP tools to better support finding references for local functions and their parameters. - This release also includes a broad-strokes improvement to error suppression handling. The New Type Solver should now be more consistent about not reporting (or re-reporting) errors involving `any` or `*error-type*` or directly downstream of an existing type error. - This release removes the experimental `DebugLuauStringSingletonBasedOnQuotes` flag that trialed basing singleton type inference solely on the usage of different quote styles. We do not think we will be proceeding with this approach at this time. # Native Code Generation - Fixes a crash in `getOffsetBase` when the passed in IrOp is not an IrInst. - Improves inlining support by passing the argument value as the initializer for the temporary being allocated in cases like anonymous function arguments or referring to upvalues in a function body. - Fixes the function end byte offset value, along side a number of other internal values that could lead to incorrect code size values. - Adds a more direct implementation of code generation for `vector` equality and inequality that leads to fewer instructions in the IR and a smaller number of produced basic blocks. - Adds a more optimized code generation flow for situations where we're indexing a table with keys that have unexpected type tags. - Fixes a bug where NCG would sometimes mistakenly optimize away necessary entry tag checks. - Introduces a customizable per-VM storage for use from "execution callbacks" system, with NCG as its first consumer for extra spill spaces. This adds 32x 8-byte spill slots on arm64 (on top of existing 22) and 64x 8-byte spill slots on x64 (on top of existing 12*). - Changes codegen for bit32 operations to use guards, rather than normal control flow, leading to significant (~30%) performance improvements in some benchmarks that heavily leverage these operations. - Adds a combined instruction `UINT_TO_FLOAT` to replace instances where we needed to emit `UINT_TO_NUM` and `NUM_TO_FLOAT` paired up. This leads to some very modest performance improvements. # Runtime - We previously added constant string constant folding with the results stored in AST allocator. This release also moves interpolation formatting strings to AST allocator to resolve #1965. - #2069 adds support for using `os.clock` in Emscripten build targets, like the [Luau Playground](https://play.luau.org). - #2149 extends the C++ Require library to support constructing aliases with tags associated with them. - #2054 fixes a bugged comparison in `api_update_top` that would cause an unnecessary runtime error if the API was used in a way that should be a noop. # Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Annie Tang <annietang@roblox.com> Co-authored-by: Ariel Weiss <arielweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: James McNellis <jmcnellis@roblox.com> Co-authored-by: Sora Kanosue <skanosue@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ilya Rezvov <irezvov@roblox.com>
1 parent 750431b commit 93c83a4

File tree

98 files changed

+3527
-846
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+3527
-846
lines changed

Analysis/include/Luau/ConfigResolver.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct NullConfigResolver : ConfigResolver
2020
{
2121
Config defaultConfig;
2222

23-
virtual const Config& getConfig(const ModuleName& name, const TypeCheckLimits& limits) const override
23+
const Config& getConfig(const ModuleName& name, const TypeCheckLimits& limits) const override
2424
{
2525
return defaultConfig;
2626
}

Analysis/include/Luau/Error.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@ class InternalCompilerError : public std::exception
755755
, location(location)
756756
{
757757
}
758-
virtual const char* what() const throw();
758+
const char* what() const throw() override;
759759

760760
const std::string message;
761761
const std::optional<std::string> moduleName;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2+
#pragma once
3+
4+
#include "Luau/TypeFunctionRuntime.h"
5+
#include "Luau/TypeFwd.h"
6+
#include "Luau/Set.h"
7+
8+
namespace Luau
9+
{
10+
11+
struct IterativeTypeFunctionTypeVisitor
12+
{
13+
using SeenSet = Set<const void*>;
14+
15+
// We avoid Luau::Variant here because we can move the tag bit and make this struct 64 bits shorter.
16+
struct WorkItem
17+
{
18+
WorkItem(TypeFunctionTypeId ty, int32_t parent);
19+
WorkItem(TypeFunctionTypePackId tp, int32_t parent);
20+
21+
const TypeFunctionTypeId* asType() const;
22+
const TypeFunctionTypePackId* asTypePack() const;
23+
24+
bool operator==(TypeFunctionTypeId ty) const;
25+
bool operator==(TypeFunctionTypePackId tp) const;
26+
27+
private:
28+
// TypeFunctionTypeId if isType, else TypeFunctionTypePackId
29+
const void* t;
30+
bool isType;
31+
32+
public:
33+
// -1 indicates no parent
34+
int32_t parent;
35+
};
36+
37+
explicit IterativeTypeFunctionTypeVisitor(std::string visitorName);
38+
explicit IterativeTypeFunctionTypeVisitor(std::string visitorName, bool visitOnce);
39+
explicit IterativeTypeFunctionTypeVisitor(std::string visitorName, SeenSet seen, bool visitOnce);
40+
41+
IterativeTypeFunctionTypeVisitor(const IterativeTypeFunctionTypeVisitor&) = delete;
42+
IterativeTypeFunctionTypeVisitor& operator=(const IterativeTypeFunctionTypeVisitor&) = delete;
43+
44+
virtual ~IterativeTypeFunctionTypeVisitor() = default;
45+
46+
virtual void cycle(TypeFunctionTypeId ty);
47+
virtual void cycle(TypeFunctionTypePackId tp);
48+
49+
virtual bool visit(TypeFunctionTypeId ty);
50+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionPrimitiveType& tfpt);
51+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionAnyType& tfat);
52+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionUnknownType& tfut);
53+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionNeverType& tfnt);
54+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionSingletonType& tfst);
55+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionUnionType& tfut);
56+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionIntersectionType& tfit);
57+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionNegationType& tfnt);
58+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionFunctionType& tfft);
59+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionTableType& tftt);
60+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionExternType& tfet);
61+
virtual bool visit(TypeFunctionTypeId ty, const TypeFunctionGenericType& tfgt);
62+
63+
virtual bool visit(TypeFunctionTypePackId tp);
64+
virtual bool visit(TypeFunctionTypePackId tp, const TypeFunctionTypePack& tftp);
65+
virtual bool visit(TypeFunctionTypePackId tp, const TypeFunctionVariadicTypePack& tfvtp);
66+
virtual bool visit(TypeFunctionTypePackId tp, const TypeFunctionGenericTypePack& tfgtp);
67+
68+
void run(TypeFunctionTypeId ty);
69+
void run(TypeFunctionTypePackId tp);
70+
71+
protected:
72+
/// Add this type (or pack) to the queue of things to traverse. Does not
73+
/// immediately process the thing! You cannot use this to effect in-order
74+
/// traversal.
75+
void traverse(TypeFunctionTypeId ty);
76+
void traverse(TypeFunctionTypePackId tp);
77+
78+
private:
79+
void process(TypeFunctionTypeId ty);
80+
void process(TypeFunctionTypePackId tp);
81+
82+
bool hasSeen(const void* tv);
83+
void unsee(const void* tv);
84+
85+
template<typename TID>
86+
bool isCyclic(TID ty) const;
87+
88+
void processWorkQueue();
89+
90+
SeenSet seen{nullptr};
91+
92+
std::vector<WorkItem> workQueue;
93+
int32_t parentCursor = -1;
94+
uint32_t workCursor = 0;
95+
96+
const std::string visitorName;
97+
bool visitOnce = true;
98+
};
99+
100+
} // namespace Luau

Analysis/include/Luau/Subtyping.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,18 @@ struct MappedGenericEnvironment
102102
bool bindGeneric(TypePackId genericTp, TypePackId bindeeTp);
103103
};
104104

105+
enum class SubtypingSuppressionPolicy
106+
{
107+
Any,
108+
All
109+
};
110+
105111
struct SubtypingResult
106112
{
107113
bool isSubtype = false;
108114
bool normalizationTooComplex = false;
109115
bool isCacheable = true;
116+
bool isErrorSuppressing = false;
110117
ErrorVec errors;
111118
/// The reason for isSubtype to be false. May not be present even if
112119
/// isSubtype is false, depending on the input types.
@@ -120,7 +127,7 @@ struct SubtypingResult
120127
/// If any generic bounds were invalid, report them here
121128
std::vector<GenericBoundsMismatch> genericBoundsMismatches;
122129

123-
SubtypingResult& andAlso(const SubtypingResult& other);
130+
SubtypingResult& andAlso(const SubtypingResult& other, SubtypingSuppressionPolicy policy = SubtypingSuppressionPolicy::Any);
124131
SubtypingResult& orElse(const SubtypingResult& other);
125132
SubtypingResult& withBothComponent(TypePath::Component component);
126133
SubtypingResult& withSuperComponent(TypePath::Component component);
@@ -327,6 +334,12 @@ struct Subtyping
327334
const FunctionType* superFunction,
328335
NotNull<Scope> scope
329336
);
337+
SubtypingResult isCovariantWith(
338+
SubtypingEnvironment& env,
339+
const MetatableType* subMt,
340+
const PrimitiveType* superPrim,
341+
NotNull<Scope> scope
342+
);
330343
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope);
331344
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope);
332345
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable, NotNull<Scope> scope);

Analysis/include/Luau/ToString.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct ToStringOptions
4747
bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self
4848
bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded
4949
bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil.
50+
bool ignoreSyntheticName = false; // If true, ignore synthetic names on table types.
5051
size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes
5152
size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength);
5253
size_t compositeTypesSingleLineLimit = 5; // The number of type elements permitted on a single line when printing type unions/intersections

Analysis/include/Luau/TypeChecker2.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ struct TypeChecker2
104104
Reasonings explainReasonings(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& r);
105105
Reasonings explainReasonings(TypePackId subTp, TypePackId superTp, Location location, const SubtypingResult& r);
106106

107+
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location);
108+
bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location);
109+
107110
private:
108111
static bool allowsNoReturnValues(const TypePackId tp);
109112
static Location getEndLocation(const AstExprFunction* function);
@@ -196,9 +199,6 @@ struct TypeChecker2
196199

197200
bool testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType);
198201

199-
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location);
200-
bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location);
201-
202202
void maybeReportSubtypingError(TypeId subTy, TypeId superTy, const Location& location);
203203
// Tests whether subTy is a subtype of superTy in the context of a function iterator for a for-in statement.
204204
// Includes some extra logic to help locate errors to the values and variables of the for-in statement.

Analysis/include/Luau/TypeFunctionRuntime.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ struct TypeFunctionProperty
194194
static TypeFunctionProperty rw(TypeFunctionTypeId ty); // Shared read-write type.
195195
static TypeFunctionProperty rw(TypeFunctionTypeId read, TypeFunctionTypeId write); // Separate read-write type.
196196

197+
bool isShared() const;
197198
bool isReadOnly() const;
198199
bool isWriteOnly() const;
199200

@@ -256,8 +257,9 @@ using TypeFunctionTypeVariant = Luau::Variant<
256257
struct TypeFunctionType
257258
{
258259
TypeFunctionTypeVariant type;
260+
bool frozen = false;
259261

260-
TypeFunctionType(TypeFunctionTypeVariant type)
262+
explicit TypeFunctionType(TypeFunctionTypeVariant type)
261263
: type(std::move(type))
262264
{
263265
}
@@ -327,7 +329,7 @@ TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunction
327329
void pushType(lua_State* L, TypeFunctionTypeId type);
328330
void pushTypePack(lua_State* L, TypeFunctionTypePackId tp);
329331

330-
void allocTypeUserData(lua_State* L, TypeFunctionTypeVariant type);
332+
void allocTypeUserData(lua_State* L, TypeFunctionTypeVariant type, bool frozen = false);
331333

332334
bool isTypeUserData(lua_State* L, int idx);
333335
TypeFunctionTypeId getTypeUserData(lua_State* L, int idx);

Analysis/include/Luau/TypeUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor
398398
bool visit(TypeId ty) override;
399399
bool visit(TypePackId ty) override;
400400

401+
bool visit(TypeId ty, const ExternType&) override;
402+
401403
/**
402404
* @returns if there is _any_ generic in `ty`
403405
*/

Analysis/src/AstQuery.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ struct FindExprOrLocal : public AstVisitor
487487
return true;
488488
}
489489

490-
virtual bool visit(AstExprFunction* fn) override
490+
bool visit(AstExprFunction* fn) override
491491
{
492492
for (size_t i = 0; i < fn->args.size; ++i)
493493
{
@@ -496,13 +496,13 @@ struct FindExprOrLocal : public AstVisitor
496496
return visit((class AstExpr*)fn);
497497
}
498498

499-
virtual bool visit(AstStatFor* forStat) override
499+
bool visit(AstStatFor* forStat) override
500500
{
501501
visitLocal(forStat->var);
502502
return true;
503503
}
504504

505-
virtual bool visit(AstStatForIn* forIn) override
505+
bool visit(AstStatForIn* forIn) override
506506
{
507507
for (AstLocal* var : forIn->vars)
508508
{

Analysis/src/BuiltinDefinitions.cpp

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
LUAU_FASTFLAG(LuauSolverV2)
3535
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType4)
3636
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
37-
LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal)
3837
LUAU_FASTFLAGVARIABLE(LuauCloneForIntersectionsUnions)
3938
LUAU_FASTFLAG(LuauStorePolarityInline)
39+
LUAU_FASTFLAGVARIABLE(LuauTableFreezeCheckIsSubtype)
4040

4141
namespace Luau
4242
{
@@ -116,6 +116,7 @@ struct MagicFreeze final : MagicFunction
116116
WithPredicate<TypePackId>
117117
) override;
118118
bool infer(const MagicFunctionCallContext& ctx) override;
119+
bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override;
119120
};
120121

121122
struct MagicFormat final : MagicFunction
@@ -362,16 +363,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
362363
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
363364
NotNull<Scope> globalScope{globals.globalScope.get()};
364365

365-
if (FFlag::LuauBuiltinTypeFunctionsArentGlobal)
366-
{
367-
if (frontend.getLuauSolverMode() == SolverMode::New)
368-
builtinTypes->typeFunctions->addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
369-
}
370-
else
371-
{
372-
if (frontend.getLuauSolverMode() == SolverMode::New)
373-
builtinTypeFunctions_DEPRECATED().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
374-
}
366+
if (frontend.getLuauSolverMode() == SolverMode::New)
367+
builtinTypes->typeFunctions->addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
375368

376369

377370
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
@@ -445,13 +438,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
445438
if (frontend.getLuauSolverMode() == SolverMode::New)
446439
{
447440
// getmetatable : <T>(T) -> getmetatable<T>
448-
TypeId getmtReturn = arena.addType(
449-
TypeFunctionInstanceType{
450-
FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->getmetatableFunc
451-
: builtinTypeFunctions_DEPRECATED().getmetatableFunc,
452-
{genericT}
453-
}
454-
);
441+
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypes->typeFunctions->getmetatableFunc, {genericT}});
455442
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {getmtReturn}), "@luau");
456443
}
457444
else
@@ -463,13 +450,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
463450
if (frontend.getLuauSolverMode() == SolverMode::New)
464451
{
465452
// setmetatable<T: {}, MT>(T, MT) -> setmetatable<T, MT>
466-
TypeId setmtReturn = arena.addType(
467-
TypeFunctionInstanceType{
468-
FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->setmetatableFunc
469-
: builtinTypeFunctions_DEPRECATED().setmetatableFunc,
470-
{genericT, genericMT}
471-
}
472-
);
453+
TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypes->typeFunctions->setmetatableFunc, {genericT, genericMT}});
473454
addGlobalBinding(
474455
globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau"
475456
);
@@ -503,12 +484,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
503484

504485
TypeId refinedTy = arena.addType(
505486
TypeFunctionInstanceType{
506-
NotNull{
507-
FFlag::LuauBuiltinTypeFunctionsArentGlobal ? &builtinTypes->typeFunctions->intersectFunc
508-
: &builtinTypeFunctions_DEPRECATED().intersectFunc
509-
},
510-
{genericT, arena.addType(NegationType{builtinTypes->falsyType})},
511-
{}
487+
NotNull{&builtinTypes->typeFunctions->intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {}
512488
}
513489
);
514490

@@ -1710,7 +1686,10 @@ static std::optional<TypeId> freezeTable(TypeId inputType, const MagicFunctionCa
17101686
return resultType;
17111687
}
17121688

1713-
context.solver->reportError(TypeMismatch{context.solver->builtinTypes->tableType, inputType}, context.callSite->argLocation);
1689+
if (!FFlag::LuauTableFreezeCheckIsSubtype)
1690+
{
1691+
context.solver->reportError(TypeMismatch{context.solver->builtinTypes->tableType, inputType}, context.callSite->argLocation);
1692+
}
17141693
return std::nullopt;
17151694
}
17161695

@@ -1768,6 +1747,55 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context)
17681747
return true;
17691748
}
17701749

1750+
// MagicFreeze is a magic function because table.freeze is a bounded version of the identity function with a custom output (accepts any subtype of `table` and returns a read-only version of that table).
1751+
bool MagicFreeze::typeCheck(const MagicFunctionTypeCheckContext& ctx)
1752+
{
1753+
if (!FFlag::LuauTableFreezeCheckIsSubtype)
1754+
return false;
1755+
1756+
const auto& [paramTypes, paramTail] = flatten(ctx.arguments);
1757+
1758+
if (paramTypes.size() < 1 && !paramTail)
1759+
{
1760+
ctx.typechecker->reportError(CountMismatch{1, 1, 0, CountMismatch::Arg, false, "table.freeze"}, ctx.callSite->location);
1761+
return true;
1762+
}
1763+
1764+
std::optional<TypeId> firstParamType;
1765+
1766+
if (paramTypes.size() > 0)
1767+
{
1768+
firstParamType = paramTypes[0];
1769+
}
1770+
else if (paramTail)
1771+
{
1772+
// TODO (CLI-185019): We ideally want to report a Count Mismatch error if there's no head but a variadic tail, but CountMismatch requires actual count size, which we don't have with variadic tails, so we can't report it properly yet.
1773+
// Instead, we continue to typecheck with the first argument in the variadic tail and report a type mismatch error based on that, which is more informative than reporting a count mismatch where the head (paramTypes.size()) is 0.
1774+
firstParamType = first(*paramTail);
1775+
}
1776+
1777+
if (firstParamType)
1778+
{
1779+
// If a type is found, check if it is a subtype of table.
1780+
ctx.typechecker->testIsSubtype(follow(*firstParamType), ctx.builtinTypes->tableType, ctx.callSite->location);
1781+
}
1782+
else
1783+
{
1784+
// If we can't get a type from the type or type pack, we testIsSubtype against the entire context's argument type pack to report a Type Pack Mismatch error.
1785+
TypePackId tableTyPack = ctx.typechecker->module->internalTypes.addTypePack({ctx.typechecker->builtinTypes->tableType});
1786+
ctx.typechecker->testIsSubtype(follow(ctx.arguments), tableTyPack, ctx.callSite->location);
1787+
return true;
1788+
}
1789+
1790+
// Also report error if there's more than 1 argument explicitly provided to table.freeze.
1791+
if (paramTypes.size() > 1)
1792+
{
1793+
ctx.typechecker->reportError(CountMismatch{1, 1, ctx.callSite->args.size, CountMismatch::Arg, false, "table.freeze"}, ctx.callSite->location);
1794+
}
1795+
1796+
return true;
1797+
}
1798+
17711799
static bool checkRequirePath(TypeChecker& typechecker, AstExpr* expr)
17721800
{
17731801
// require(foo.parent.bar) will technically work, but it depends on legacy goop that

0 commit comments

Comments
 (0)