Sync to upstream/release/636 (#1346)

# What's Changed?

- Telemetry support for usage of any type in old/new solver
- Bug fixes and flag removals with the new solver

## New Solver

- Fixed constraint ordering bug to infer types more accurately
- Improved inferring a call to `setmetatable()`

## VM

- Restored global metatable lookup for `typeof` on lightuserdata to fix
unintentional API change (Fixes #1335)

---
### Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Dibri Nsofor <dnsofor@roblox.com>
Co-authored-by: Jeremy Yoo <jyoo@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Junseo Yoo 2024-07-26 10:47:49 -07:00 committed by GitHub
parent a80abdb9b3
commit 5e0779fd57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 2292 additions and 687 deletions

View File

@ -0,0 +1,131 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Config.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/Variant.h"
#include "Luau/Normalize.h"
#include "Luau/TypePack.h"
#include "Luau/TypeArena.h"
#include <mutex>
#include <string>
#include <vector>
#include <optional>
namespace Luau
{
class AstStat;
class ParseError;
struct TypeError;
struct LintWarning;
struct GlobalTypes;
struct ModuleResolver;
struct ParseResult;
struct DcrLogger;
struct TelemetryTypePair
{
std::string annotatedType;
std::string inferredType;
};
struct AnyTypeSummary
{
TypeArena arena;
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
int recursionCount = 0;
std::string root;
int strictCount = 0;
DenseHashMap<const void*, bool> seen{nullptr};
AnyTypeSummary();
void traverse(Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
std::pair<bool, TypeId> checkForAnyCast(Scope* scope, AstExprTypeAssertion* expr);
// Todo: errors resolved by anys
void reportError(Location location, TypeErrorData err);
bool containsAny(TypePackId typ);
bool containsAny(TypeId typ);
bool isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
bool isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
bool hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
TypeId checkForFamilyInhabitance(TypeId instance, Location location);
TypeId lookupType(AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
TypePackId reconstructTypePack(AstArray<AstExpr*> exprs, Module* module, NotNull<BuiltinTypes> builtinTypes);
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
TypeId lookupAnnotation(AstType* annotation, Module* module, NotNull<BuiltinTypes> builtintypes);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, Module* module);
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
enum Pattern : uint64_t
{
Casts,
FuncArg,
FuncRet,
FuncApp,
VarAnnot,
VarAny,
TableProp,
Alias,
Assign
};
struct TypeInfo
{
Pattern code;
std::string node;
TelemetryTypePair type;
std::string debug;
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
};
std::vector<TypeInfo> typeInfo;
/**
* Fabricates a scope that is a child of another scope.
* @param node the lexical node that the scope belongs to.
* @param parent the parent scope of the new scope. Must not be null.
*/
Scope* childScope(AstNode* node, const Scope* parent);
Scope* findInnerMostScope(Location location, Module* module);
void visit(Scope* scope, AstStat* stat, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatBlock* block, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatLocal* local, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatFor* for_, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull<BuiltinTypes> builtinTypes);
void visit(Scope* scope, AstStatError* error, Module* module, NotNull<BuiltinTypes> builtinTypes);
};
} // namespace Luau

View File

@ -61,6 +61,22 @@ private:
AstLocal* local = nullptr;
};
struct FindFullAncestry final : public AstVisitor
{
std::vector<AstNode*> nodes;
Position pos;
Position documentEnd;
bool includeTypes = false;
explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false);
bool visit(AstType* type) override;
bool visit(AstStatFunction* node) override;
bool visit(AstNode* node) override;
};
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos);
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false);

View File

@ -9,6 +9,7 @@
#include "Luau/Scope.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/Variant.h"
#include "Luau/AnyTypeSummary.h"
#include <mutex>
#include <string>
@ -31,6 +32,7 @@ struct ParseResult;
struct HotComment;
struct BuildQueueItem;
struct FrontendCancellationToken;
struct AnyTypeSummary;
struct LoadDefinitionFileResult
{

View File

@ -8,6 +8,7 @@
#include "Luau/ParseResult.h"
#include "Luau/Scope.h"
#include "Luau/TypeArena.h"
#include "Luau/AnyTypeSummary.h"
#include <memory>
#include <vector>
@ -18,6 +19,7 @@ namespace Luau
{
struct Module;
struct AnyTypeSummary;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
@ -71,6 +73,10 @@ struct Module
TypeArena interfaceTypes;
TypeArena internalTypes;
// Summary of Ast Nodes that either contain
// user annotated anys or typechecker inferred anys
AnyTypeSummary ats{};
// Scopes and AST types refer to parse data, so we need to keep that alive
std::shared_ptr<Allocator> allocator;
std::shared_ptr<AstNameTable> names;

View File

@ -59,6 +59,7 @@ public:
const_iterator begin() const;
const_iterator end() const;
iterator erase(const_iterator it);
void erase(TypeId ty);
size_t size() const;
bool empty() const;

View File

@ -0,0 +1,820 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Config.h"
#include "Luau/ConstraintGenerator.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/Module.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
#include "Luau/TypePack.h"
#include "Luau/TypeOrPack.h"
#include <algorithm>
#include <memory>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <mutex>
#include <stdexcept>
#include <string>
#include <iostream>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny, false);
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
LUAU_FASTFLAG(DebugLuauMagicTypes);
namespace Luau
{
// TODO: instead of pair just type for solver? generated type
// TODO: see lookupAnnotation in typechecker2. is cleaner than resolvetype
// or delay containsAny() check and do not return pair.
// quick flag in typeid saying was annotation or inferred, would be solid
std::optional<TypeOrPack> getInferredType(AstExpr* expr, Module* module)
{
std::optional<TypeOrPack> inferredType;
if (module->astTypePacks.contains(expr))
{
inferredType = *module->astTypePacks.find(expr);
}
else if (module->astTypes.contains(expr))
{
inferredType = *module->astTypes.find(expr);
}
return inferredType;
}
void AnyTypeSummary::traverse(Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
{
Scope* scope = findInnerMostScope(src->location, module);
visit(scope, src, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStat* stat, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
if (auto s = stat->as<AstStatBlock>())
return visit(scope, s, module, builtinTypes);
else if (auto i = stat->as<AstStatIf>())
return visit(scope, i, module, builtinTypes);
else if (auto s = stat->as<AstStatWhile>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatRepeat>())
return visit(scope, s, module, builtinTypes);
else if (auto r = stat->as<AstStatReturn>())
return visit(scope, r, module, builtinTypes);
else if (auto e = stat->as<AstStatExpr>())
return visit(scope, e, module, builtinTypes);
else if (auto s = stat->as<AstStatLocal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatFor>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatForIn>())
return visit(scope, s, module, builtinTypes);
else if (auto a = stat->as<AstStatAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto a = stat->as<AstStatCompoundAssign>())
return visit(scope, a, module, builtinTypes);
else if (auto f = stat->as<AstStatFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto f = stat->as<AstStatLocalFunction>())
return visit(scope, f, module, builtinTypes);
else if (auto a = stat->as<AstStatTypeAlias>())
return visit(scope, a, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareGlobal>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareFunction>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatDeclareClass>())
return visit(scope, s, module, builtinTypes);
else if (auto s = stat->as<AstStatError>())
return visit(scope, s, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatBlock* block, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
return; // don't report
for (AstStat* stat : block->body)
visit(scope, stat, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (ifStatement->thenbody)
{
Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
}
if (ifStatement->elsebody)
{
Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
Scope* whileScope = findInnerMostScope(while_->location, module);
visit(whileScope, while_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
Scope* repeatScope = findInnerMostScope(repeat->location, module);
visit(repeatScope, repeat->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
// Scope* outScope = findOuterScope(ret->location, module);
Scope* retScope = findInnerMostScope(ret->location, module);
for (auto val : ret->list)
{
if (isAnyCall(retScope, val, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(val, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(ret), types};
typeInfo.push_back(ti);
}
if (isAnyCast(retScope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
auto inf = getInferredType(cast->expr, module);
if (inf)
types.inferredType = toString(*inf);
TypeInfo ti{Pattern::Casts, toString(ret), types};
typeInfo.push_back(ti);
}
}
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstLocal* loc : local->vars)
{
if (local->vars.data[0] == loc && posn < local->values.size)
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
auto inf = getInferredType(local->values.data[posn], module);
if (inf)
types.inferredType = toString(*inf);
TypeInfo ti{Pattern::VarAnnot, toString(local), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (std::min(local->values.size - 1, posn) < head.size())
{
if (loc->annotation)
{
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
if (containsAny(annot))
{
TelemetryTypePair types;
types.annotatedType = toString(annot);
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
TypeInfo ti{Pattern::VarAnnot, toString(local), types};
typeInfo.push_back(ti);
}
}
}
else
{
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::VarAny, toString(local), types};
typeInfo.push_back(ti);
}
}
}
}
++posn;
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatFor* for_, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
Scope* forScope = findInnerMostScope(for_->location, module);
visit(forScope, for_->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
Scope* loopScope = findInnerMostScope(forIn->location, module);
visit(loopScope, forIn->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
auto [head, tail] = flatten(values);
size_t posn = 0;
for (AstExpr* var : assign->vars)
{
TypeId tp = lookupType(var, module, builtinTypes);
if (containsAny(tp))
{
TelemetryTypePair types;
types.annotatedType = toString(tp);
auto loc = std::min(assign->vars.size - 1, posn);
if (head.size() >= assign->vars.size)
{
types.inferredType = toString(head[posn]);
}
else if (loc < head.size())
types.inferredType = toString(head[loc]);
else
types.inferredType = toString(builtinTypes->nilType);
TypeInfo ti{Pattern::Assign, toString(assign), types};
typeInfo.push_back(ti);
}
++posn;
}
for (AstExpr* val : assign->values)
{
if (isAnyCall(scope, val, module, builtinTypes))
{
TelemetryTypePair types;
auto inf = getInferredType(val, module);
if (inf)
types.inferredType = toString(*inf);
TypeInfo ti{Pattern::FuncApp, toString(assign), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, val, module, builtinTypes))
{
if (auto cast = val->as<AstExprTypeAssertion>())
{
TelemetryTypePair types;
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
auto inf = getInferredType(val, module);
if (inf)
types.inferredType = toString(*inf);
TypeInfo ti{Pattern::Casts, toString(assign), types};
typeInfo.push_back(ti);
}
}
}
if (tail)
{
if (containsAny(*tail))
{
TelemetryTypePair types;
types.inferredType = toString(*tail);
TypeInfo ti{Pattern::Assign, toString(assign), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
if (module->astTypes.contains(assign->var))
{
if (containsAny(*module->astTypes.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(assign), types};
typeInfo.push_back(ti);
}
}
else if (module->astTypePacks.contains(assign->var))
{
if (containsAny(*module->astTypePacks.find(assign->var)))
{
TypeInfo ti{Pattern::Assign, toString(assign), types};
typeInfo.push_back(ti);
}
}
if (isAnyCall(scope, assign->value, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncApp, toString(assign), types};
typeInfo.push_back(ti);
}
if (isAnyCast(scope, assign->value, module, builtinTypes))
{
if (auto cast = assign->value->as<AstExprTypeAssertion>())
{
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
auto inf = getInferredType(cast->expr, module);
if (inf)
types.inferredType = toString(*inf);
TypeInfo ti{Pattern::Casts, toString(assign), types};
typeInfo.push_back(ti);
}
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::VarAny, toString(function), types};
typeInfo.push_back(ti);
}
if (hasArgAnys(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncArg, toString(function), types};
typeInfo.push_back(ti);
}
if (hasAnyReturns(scope, function->func, module, builtinTypes))
{
TypeInfo ti{Pattern::FuncRet, toString(function), types};
typeInfo.push_back(ti);
}
if (function->func->body->body.size > 0)
visit(scope, function->func->body, module, builtinTypes);
}
void AnyTypeSummary::visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
if (containsAny(annot))
{
// no expr => no inference for aliases
TelemetryTypePair types;
types.annotatedType = toString(annot);
TypeInfo ti{Pattern::Alias, toString(alias), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (isAnyCall(scope, expr->expr, module, builtinTypes))
{
TelemetryTypePair types;
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
TypeInfo ti{Pattern::FuncApp, toString(expr), types};
typeInfo.push_back(ti);
}
}
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
void AnyTypeSummary::visit(Scope* scope, AstStatError* error, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
TypeId AnyTypeSummary::checkForFamilyInhabitance(TypeId instance, Location location)
{
if (seenTypeFamilyInstances.find(instance))
return instance;
seenTypeFamilyInstances.insert(instance);
return instance;
}
TypeId AnyTypeSummary::lookupType(AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
TypeId* ty = module->astTypes.find(expr);
if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location);
TypePackId* tp = module->astTypePacks.find(expr);
if (tp)
{
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
return checkForFamilyInhabitance(*fst, expr->location);
else if (finite(*tp) && size(*tp) == 0)
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
}
return builtinTypes->errorRecoveryType();
}
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (exprs.size == 0)
return arena.addTypePack(TypePack{{}, std::nullopt});
std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i)
{
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
}
TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
if (tail)
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
else
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
}
bool AnyTypeSummary::isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto call = expr->as<AstExprCall>())
{
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
if (containsAny(args))
return true;
TypeId func = lookupType(call->func, module, builtinTypes);
if (containsAny(func))
return true;
}
return false;
}
bool AnyTypeSummary::hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->vararg && expr->varargAnnotation)
{
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (expr->args.size > 0)
{
for (const AstLocal* arg : expr->args)
{
if (arg->annotation)
{
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
}
}
return false;
}
bool AnyTypeSummary::hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (!expr->returnAnnotation)
{
return false;
}
for (AstType* ret : expr->returnAnnotation->types)
{
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
{
return true;
}
}
if (expr->returnAnnotation->tailType)
{
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
if (annot && containsAny(*annot))
{
return true;
}
}
return false;
}
bool AnyTypeSummary::isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
{
if (auto cast = expr->as<AstExprTypeAssertion>())
{
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
if (containsAny(annot))
{
return true;
}
}
return false;
}
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, Module* module, NotNull<BuiltinTypes> builtintypes)
{
if (FFlag::DebugLuauMagicTypes)
{
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
{
if (auto ann = ref->parameters.data[0].type)
{
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
return follow(argTy);
}
}
}
TypeId* ty = module->astResolvedTypes.find(annotation);
if (ty)
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
else
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
}
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(TypeId instance, Location location)
{
if (seenTypeFunctionInstances.find(instance))
return instance;
seenTypeFunctionInstances.insert(instance);
return instance;
}
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, Module* module)
{
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
return {follow(*tp)};
return {};
}
bool AnyTypeSummary::containsAny(TypeId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
RecursionCounter counter{&recursionCount};
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
{
return false;
}
bool found = false;
if (auto ty = get<AnyType>(typ))
{
found = true;
}
else if (auto ty = get<UnknownType>(typ))
{
found = true;
}
else if (auto ty = get<TableType>(typ))
{
for (auto& [_name, prop] : ty->props)
{
if (FFlag::DebugLuauDeferredConstraintResolution)
{
if (auto newT = follow(prop.readTy))
{
if (containsAny(*newT))
found = true;
}
else if (auto newT = follow(prop.writeTy))
{
if (containsAny(*newT))
found = true;
}
}
else
{
if (containsAny(prop.type()))
found = true;
}
}
}
else if (auto ty = get<IntersectionType>(typ))
{
for (auto part : ty->parts)
{
if (containsAny(part))
{
found = true;
}
}
}
else if (auto ty = get<UnionType>(typ))
{
for (auto option : ty->options)
{
if (containsAny(option))
{
found = true;
}
}
}
else if (auto ty = get<FunctionType>(typ))
{
if (containsAny(ty->argTypes))
found = true;
else if (containsAny(ty->retTypes))
found = true;
}
seen[typ] = found;
return found;
}
bool AnyTypeSummary::containsAny(TypePackId typ)
{
typ = follow(typ);
if (auto t = seen.find(typ); t && !*t)
{
return false;
}
seen[typ] = false;
auto [head, tail] = flatten(typ);
bool found = false;
for (auto tp : head)
{
if (containsAny(tp))
found = true;
}
if (tail)
{
if (auto vtp = get<VariadicTypePack>(tail))
{
if (auto ty = get<AnyType>(follow(vtp->ty)))
{
found = true;
}
}
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
{
for (TypePackId tp : tftp->packArguments)
{
if (containsAny(tp))
{
found = true;
}
}
for (TypeId t : tftp->typeArguments)
{
if (containsAny(t))
{
found = true;
}
}
}
}
seen[typ] = found;
return found;
}
Scope* AnyTypeSummary::findInnerMostScope(Location location, Module* module)
{
Scope* bestScope = module->getModuleScope().get();
bool didNarrow = false;
do
{
didNarrow = false;
for (auto scope : bestScope->children)
{
if (scope->location.encloses(location))
{
bestScope = scope.get();
didNarrow = true;
break;
}
}
} while (didNarrow && bestScope->children.size() > 0);
return bestScope;
}
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
: code(code)
, node(node)
, type(type)
{
}
AnyTypeSummary::AnyTypeSummary() {}
} // namespace Luau

View File

@ -177,61 +177,53 @@ struct FindNode : public AstVisitor
}
};
struct FindFullAncestry final : public AstVisitor
{
std::vector<AstNode*> nodes;
Position pos;
Position documentEnd;
bool includeTypes = false;
explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false)
: pos(pos)
, documentEnd(documentEnd)
, includeTypes(includeTypes)
{
}
bool visit(AstType* type) override
{
if (includeTypes)
return visit(static_cast<AstNode*>(type));
else
return false;
}
bool visit(AstStatFunction* node) override
{
visit(static_cast<AstNode*>(node));
if (node->name->location.contains(pos))
node->name->visit(this);
else if (node->func->location.contains(pos))
node->func->visit(this);
return false;
}
bool visit(AstNode* node) override
{
if (node->location.contains(pos))
{
nodes.push_back(node);
return true;
}
// Edge case: If we ask for the node at the position that is the very end of the document
// return the innermost AST element that ends at that position.
if (node->location.end == documentEnd && pos >= documentEnd)
{
nodes.push_back(node);
return true;
}
return false;
}
};
} // namespace
FindFullAncestry::FindFullAncestry(Position pos, Position documentEnd, bool includeTypes)
: pos(pos)
, documentEnd(documentEnd)
, includeTypes(includeTypes)
{
}
bool FindFullAncestry::visit(AstType* type)
{
if (includeTypes)
return visit(static_cast<AstNode*>(type));
else
return false;
}
bool FindFullAncestry::visit(AstStatFunction* node)
{
visit(static_cast<AstNode*>(node));
if (node->name->location.contains(pos))
node->name->visit(this);
else if (node->func->location.contains(pos))
node->func->visit(this);
return false;
}
bool FindFullAncestry::visit(AstNode* node)
{
if (node->location.contains(pos))
{
nodes.push_back(node);
return true;
}
// Edge case: If we ask for the node at the position that is the very end of the document
// return the innermost AST element that ends at that position.
if (node->location.end == documentEnd && pos >= documentEnd)
{
nodes.push_back(node);
return true;
}
return false;
}
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
{
return findAncestryAtPositionForAutocomplete(source.root, pos);

View File

@ -29,7 +29,6 @@
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
namespace Luau
@ -757,7 +756,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
scope->lvalueTypes[def] = assignee;
}
Checkpoint start = checkpoint(this);
TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp;
Checkpoint end = checkpoint(this);
if (hasAnnotation)
{
@ -791,6 +792,12 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
forEachConstraint(start, end, this,
[&uc](const ConstraintPtr& runBefore)
{
uc->dependencies.push_back(NotNull{runBefore.get()});
});
for (TypeId t : valueTypes)
getMutable<BlockedType>(t)->setOwner(uc);
}
@ -981,7 +988,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
scope->bindings[function->name] = Binding{functionType, function->name->location};
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
bool sigFullyDefined = !hasFreeType(sig.signature);
if (sigFullyDefined)
@ -1516,7 +1523,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->isCheckedFunction = FFlag::LuauAttributeSyntax ? global->isCheckedFunction() : false;
ftv->isCheckedFunction = global->isCheckedFunction();
ftv->argNames.reserve(global->paramNames.size);
for (const auto& el : global->paramNames)
@ -1759,6 +1766,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment
scope->rvalueRefinements[def] = resultTy; // TODO: typestates: track this as an assignment
// HACK: If we have a targetLocal, it has already been added to the
// inferredBindings table. We want to replace it so that we don't
// infer a weird union like tbl | { @metatable something, tbl }
if (InferredBinding* ib = inferredBindings.find(targetLocal->local))
ib->types.erase(target);
recordInferredBinding(targetLocal->local, resultTy);
}
@ -2932,7 +2945,7 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
// TODO: FunctionType needs a pointer to the scope so that we know
// how to quantify/instantiate it.
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
ftv.isCheckedFunction = FFlag::LuauAttributeSyntax ? fn->isCheckedFunction() : false;
ftv.isCheckedFunction = fn->isCheckedFunction();
// This replicates the behavior of the appropriate FunctionType
// constructors.

View File

@ -1,231 +1,9 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions2, false);
LUAU_FASTFLAG(LuauAttributeSyntax);
namespace Luau
{
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare buffer: {
create: (size: number) -> buffer,
fromstring: (str: string) -> buffer,
tostring: (b: buffer) -> string,
len: (b: buffer) -> number,
copy: (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: (b: buffer, offset: number) -> number,
readu8: (b: buffer, offset: number) -> number,
readi16: (b: buffer, offset: number) -> number,
readu16: (b: buffer, offset: number) -> number,
readi32: (b: buffer, offset: number) -> number,
readu32: (b: buffer, offset: number) -> number,
readf32: (b: buffer, offset: number) -> number,
readf64: (b: buffer, offset: number) -> number,
writei8: (b: buffer, offset: number, value: number) -> (),
writeu8: (b: buffer, offset: number, value: number) -> (),
writei16: (b: buffer, offset: number, value: number) -> (),
writeu16: (b: buffer, offset: number, value: number) -> (),
writei32: (b: buffer, offset: number, value: number) -> (),
writeu32: (b: buffer, offset: number, value: number) -> (),
writef32: (b: buffer, offset: number, value: number) -> (),
writef64: (b: buffer, offset: number, value: number) -> (),
readstring: (b: buffer, offset: number, count: number) -> string,
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
}
declare bit32: {
band: (...number) -> number,
bor: (...number) -> number,
bxor: (...number) -> number,
btest: (number, ...number) -> boolean,
rrotate: (x: number, disp: number) -> number,
lrotate: (x: number, disp: number) -> number,
lshift: (x: number, disp: number) -> number,
arshift: (x: number, disp: number) -> number,
rshift: (x: number, disp: number) -> number,
bnot: (x: number) -> number,
extract: (n: number, field: number, width: number?) -> number,
replace: (n: number, v: number, field: number, width: number?) -> number,
countlz: (n: number) -> number,
countrz: (n: number) -> number,
byteswap: (n: number) -> number,
}
declare math: {
frexp: (n: number) -> (number, number),
ldexp: (s: number, e: number) -> number,
fmod: (x: number, y: number) -> number,
modf: (n: number) -> (number, number),
pow: (x: number, y: number) -> number,
exp: (n: number) -> number,
ceil: (n: number) -> number,
floor: (n: number) -> number,
abs: (n: number) -> number,
sqrt: (n: number) -> number,
log: (n: number, base: number?) -> number,
log10: (n: number) -> number,
rad: (n: number) -> number,
deg: (n: number) -> number,
sin: (n: number) -> number,
cos: (n: number) -> number,
tan: (n: number) -> number,
sinh: (n: number) -> number,
cosh: (n: number) -> number,
tanh: (n: number) -> number,
atan: (n: number) -> number,
acos: (n: number) -> number,
asin: (n: number) -> number,
atan2: (y: number, x: number) -> number,
min: (number, ...number) -> number,
max: (number, ...number) -> number,
pi: number,
huge: number,
randomseed: (seed: number) -> (),
random: (number?, number?) -> number,
sign: (n: number) -> number,
clamp: (n: number, min: number, max: number) -> number,
noise: (x: number, y: number?, z: number?) -> number,
round: (n: number) -> number,
}
type DateTypeArg = {
year: number,
month: number,
day: number,
hour: number?,
min: number?,
sec: number?,
isdst: boolean?,
}
type DateTypeResult = {
year: number,
month: number,
wday: number,
yday: number,
day: number,
hour: number,
min: number,
sec: number,
isdst: boolean,
}
declare os: {
time: (time: DateTypeArg?) -> number,
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
clock: () -> number,
}
declare function require(target: any): any
declare function getfenv(target: any): { [string]: any }
declare _G: any
declare _VERSION: string
declare function gcinfo(): number
declare function print<T...>(...: T...)
declare function type<T>(value: T): string
declare function typeof<T>(value: T): string
-- `assert` has a magic function attached that will give more detailed type information
declare function assert<T>(value: T, errorMessage: string?): T
declare function error<T>(message: T, level: number?): never
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
declare function rawlen<K, V>(obj: {[K]: V} | string): number
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
-- FIXME: The actual type of `xpcall` is:
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
-- Since we can't represent the return value, we use (boolean, R1...).
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
-- `select` has a magic function attached to provide more detailed type information
declare function select<A...>(i: string | number, ...: A...): ...any
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
-- (nil, string).
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
declare function newproxy(mt: boolean?): any
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
running: () -> thread,
status: (co: thread) -> "dead" | "running" | "normal" | "suspended",
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
yield: <A..., R...>(A...) -> R...,
isyieldable: () -> boolean,
close: (co: thread) -> (boolean, any)
}
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
maxn: <V>(t: {V}) -> number,
remove: <V>(t: {V}, number?) -> V?,
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
create: <V>(count: number, value: V?) -> {V},
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
pack: <V>(...V) -> { n: number, [number]: V },
getn: <V>(t: {V}) -> number,
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
foreachi: <V>({V}, (number, V) -> ()) -> (),
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
clear: <K, V>(table: {[K]: V}) -> (),
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
}
declare debug: {
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
}
declare utf8: {
char: (...number) -> string,
charpattern: string,
codes: (str: string) -> ((string, number) -> (number, number), string, number),
codepoint: (str: string, i: number?, j: number?) -> ...number,
len: (s: string, i: number?, j: number?) -> (number?, number?),
offset: (s: string, n: number?, i: number?) -> number,
}
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
declare bit32: {
@ -449,12 +227,7 @@ declare buffer: {
std::string getBuiltinDefinitionSource()
{
std::string result = kBuiltinDefinitionLuaSrc;
// Annotates each non generic function as checked
if (FFlag::LuauCheckedEmbeddedDefinitions2 && FFlag::LuauAttributeSyntax)
result = kBuiltinDefinitionLuaSrcChecked;
std::string result = kBuiltinDefinitionLuaSrcChecked;
return result;
}

View File

@ -1,6 +1,7 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Frontend.h"
#include "Luau/AnyTypeSummary.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Clone.h"
#include "Luau/Common.h"
@ -10,13 +11,15 @@
#include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h"
#include "Luau/FileResolver.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/Parser.h"
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h"
#include "Luau/Transpiler.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
@ -44,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
LUAU_FASTFLAG(StudioReportLuauAny)
namespace Luau
{
@ -488,6 +493,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
if (item.name == name)
checkResult.lintResult = item.module->lintResult;
if (FFlag::StudioReportLuauAny && item.options.retainFullTypeGraphs)
{
if (item.module)
{
const SourceModule& sourceModule = *item.sourceModule;
if (sourceModule.mode == Luau::Mode::Strict)
{
item.module->ats.root = toString(sourceModule.root);
}
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
}
}
}
return checkResult;

View File

@ -16,7 +16,6 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauAttribute)
LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute, false)
@ -2929,7 +2928,6 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
@ -2956,7 +2954,6 @@ struct LintRedundantNativeAttribute : AstVisitor
public:
LUAU_NOINLINE static void process(LintContext& context)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
LUAU_ASSERT(FFlag::LuauNativeAttribute);
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
@ -3071,7 +3068,7 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
LintComparisonPrecedence::process(context);
if (FFlag::LuauAttributeSyntax && FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute &&
if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute &&
context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
{
if (hasNativeCommentDirective(hotcomments))

View File

@ -115,6 +115,15 @@ TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it)
return order.erase(it);
}
void TypeIds::erase(TypeId ty)
{
const_iterator it = std::find(order.begin(), order.end(), ty);
if (it == order.end())
return;
erase(it);
}
size_t TypeIds::size() const
{
return order.size();

View File

@ -33,7 +33,6 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
@ -350,20 +349,17 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
else if (auto repeat = program.as<AstStatRepeat>())
return check(scope, *repeat);
else if (program.is<AstStatBreak>())
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Breaks : ControlFlow::None;
return ControlFlow::Breaks;
else if (program.is<AstStatContinue>())
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Continues : ControlFlow::None;
return ControlFlow::Continues;
else if (auto return_ = program.as<AstStatReturn>())
return check(scope, *return_);
else if (auto expr = program.as<AstStatExpr>())
{
checkExprPack(scope, *expr->expr);
if (FFlag::LuauTinyControlFlowAnalysis)
{
if (auto call = expr->expr->as<AstExprCall>(); call && doesCallError(call))
return ControlFlow::Throws;
}
if (auto call = expr->expr->as<AstExprCall>(); call && doesCallError(call))
return ControlFlow::Throws;
return ControlFlow::None;
}
@ -740,41 +736,25 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
ScopePtr thenScope = childScope(scope, statement.thenbody->location);
resolve(result.predicates, thenScope, true);
if (FFlag::LuauTinyControlFlowAnalysis)
{
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
resolve(result.predicates, elseScope, false);
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
resolve(result.predicates, elseScope, false);
ControlFlow thencf = check(thenScope, *statement.thenbody);
ControlFlow elsecf = ControlFlow::None;
if (statement.elsebody)
elsecf = check(elseScope, *statement.elsebody);
ControlFlow thencf = check(thenScope, *statement.thenbody);
ControlFlow elsecf = ControlFlow::None;
if (statement.elsebody)
elsecf = check(elseScope, *statement.elsebody);
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inheritRefinements(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inheritRefinements(thenScope);
if (thencf != ControlFlow::None && elsecf == ControlFlow::None)
scope->inheritRefinements(elseScope);
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
scope->inheritRefinements(thenScope);
if (FFlag::LuauTinyControlFlowAnalysis && thencf == elsecf)
return thencf;
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
return ControlFlow::Returns;
else
return ControlFlow::None;
}
if (thencf == elsecf)
return thencf;
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
return ControlFlow::Returns;
else
{
check(thenScope, *statement.thenbody);
if (statement.elsebody)
{
ScopePtr elseScope = childScope(scope, statement.elsebody->location);
resolve(result.predicates, elseScope, false);
check(elseScope, *statement.elsebody);
}
return ControlFlow::None;
}
}
template<typename Id>
@ -906,12 +886,12 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatReturn& retur
if (!errors.empty())
currentModule->getModuleScope()->returnType = addTypePack({anyType});
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
return ControlFlow::Returns;
}
unify(retPack, scope->returnType, scope, return_.location, CountMismatch::Context::Return);
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
return ControlFlow::Returns;
}
template<typename Id>

View File

@ -3,7 +3,6 @@
#include "Luau/Common.h"
LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauNativeAttribute);
namespace Luau
@ -764,8 +763,6 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
bool AstStatDeclareFunction::isCheckedFunction() const
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
for (const AstAttr* attr : attributes)
{
if (attr->type == AstAttr::Type::Checked)
@ -901,8 +898,6 @@ void AstTypeFunction::visit(AstVisitor* visitor)
bool AstTypeFunction::isCheckedFunction() const
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
for (const AstAttr* attr : attributes)
{
if (attr->type == AstAttr::Type::Checked)

View File

@ -8,7 +8,6 @@
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false)
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntax, false)
namespace Luau
{
@ -201,7 +200,6 @@ std::string Lexeme::toString() const
return "comment";
case Attribute:
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
return name ? format("'%s'", name) : "attribute";
case BrokenString:
@ -1007,11 +1005,8 @@ Lexeme Lexer::readNext()
}
case '@':
{
if (FFlag::LuauAttributeSyntax)
{
std::pair<AstName, Lexeme::Type> attribute = readName();
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
}
std::pair<AstName, Lexeme::Type> attribute = readName();
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
}
default:
if (isDigit(peekch()))

View File

@ -17,8 +17,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAGVARIABLE(LuauLeadingBarAndAmpersand2, false)
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
LUAU_FASTFLAGVARIABLE(LuauDeclarationExtraPropData, false)
@ -321,8 +319,7 @@ AstStat* Parser::parseStat()
case Lexeme::ReservedBreak:
return parseBreak();
case Lexeme::Attribute:
if (FFlag::LuauAttributeSyntax)
return parseAttributeStat();
return parseAttributeStat();
default:;
}
@ -692,8 +689,6 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
std::pair<bool, AstAttr::Type> Parser::validateAttribute(const char* attributeName, const TempVector<AstAttr*>& attributes)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
AstAttr::Type type;
// check if the attribute name is valid
@ -739,8 +734,6 @@ std::pair<bool, AstAttr::Type> Parser::validateAttribute(const char* attributeNa
// attribute ::= '@' NAME
void Parser::parseAttribute(TempVector<AstAttr*>& attributes)
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute);
Location loc = lexer.current().location;
@ -757,8 +750,6 @@ void Parser::parseAttribute(TempVector<AstAttr*>& attributes)
// attributes ::= {attribute}
AstArray<AstAttr*> Parser::parseAttributes()
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
Lexeme::Type type = lexer.current().type;
LUAU_ASSERT(type == Lexeme::Attribute);
@ -777,8 +768,6 @@ AstArray<AstAttr*> Parser::parseAttributes()
// declare Name '{' Name ':' attributes `(' [parlist] `)' [`:` Type] '}'
AstStat* Parser::parseAttributeStat()
{
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
AstArray<AstAttr*> attributes = parseAttributes();
Lexeme::Type type = lexer.current().type;
@ -834,7 +823,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
}
else
{
if (FFlag::LuauAttributeSyntax && attributes.size != 0)
if (attributes.size != 0)
{
return reportStatError(lexer.current().location, {}, {}, "Expected 'function' after local declaration with attribute, but got %s instead",
lexer.current().toString().c_str());
@ -980,7 +969,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
{
// `declare` token is already parsed at this point
if (FFlag::LuauAttributeSyntax && (attributes.size != 0) && (lexer.current().type != Lexeme::ReservedFunction))
if ((attributes.size != 0) && (lexer.current().type != Lexeme::ReservedFunction))
return reportStatError(lexer.current().location, {}, {}, "Expected a function type declaration after attribute, but got %s instead",
lexer.current().toString().c_str());
@ -1707,10 +1696,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
{
TempVector<AstType*> parts(scratchType);
if (!FFlag::LuauLeadingBarAndAmpersand2 || type != nullptr)
{
if (type != nullptr)
parts.push_back(type);
}
incrementRecursionCounter("type annotation");
@ -1769,7 +1756,7 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
}
if (parts.size() == 1)
return FFlag::LuauLeadingBarAndAmpersand2 ? parts[0] : type;
return parts[0];
if (isUnion && isIntersection)
{
@ -1816,30 +1803,19 @@ AstType* Parser::parseType(bool inDeclarationContext)
Location begin = lexer.current().location;
if (FFlag::LuauLeadingBarAndAmpersand2)
AstType* type = nullptr;
Lexeme::Type c = lexer.current().type;
if (c != '|' && c != '&')
{
AstType* type = nullptr;
Lexeme::Type c = lexer.current().type;
if (c != '|' && c != '&')
{
type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type;
recursionCounter = oldRecursionCount;
}
AstType* typeWithSuffix = parseTypeSuffix(type, begin);
type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type;
recursionCounter = oldRecursionCount;
return typeWithSuffix;
}
else
{
AstType* type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type;
recursionCounter = oldRecursionCount;
AstType* typeWithSuffix = parseTypeSuffix(type, begin);
recursionCounter = oldRecursionCount;
return parseTypeSuffix(type, begin);
}
return typeWithSuffix;
}
// Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}'
@ -1854,7 +1830,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext)
if (lexer.current().type == Lexeme::Attribute)
{
if (!inDeclarationContext || !FFlag::LuauAttributeSyntax)
if (!inDeclarationContext)
{
return {reportTypeError(start, {}, "attributes are not allowed in declaration context")};
}
@ -2431,7 +2407,7 @@ AstExpr* Parser::parseSimpleExpr()
AstArray<AstAttr*> attributes{nullptr, 0};
if (FFlag::LuauAttributeSyntax && FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute)
if (FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute)
{
attributes = parseAttributes();

View File

@ -12,8 +12,8 @@ inline bool isFlagExperimental(const char* flag)
// or critical bugs that are found after the code has been submitted.
static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny", // takes telemetry data for usage of any types
// makes sure we always have at least one entry
nullptr,
};

View File

@ -32,7 +32,6 @@ Id UnionFind::find(Id id)
parents[size_t(id)] = set;
id = parent;
}
return set;
}
@ -48,7 +47,6 @@ void UnionFind::merge(Id a, Id b)
std::swap(aSet, bSet);
parents[size_t(bSet)] = aSet;
if (ranks[size_t(aSet)] == ranks[size_t(bSet)])
ranks[size_t(aSet)]++;
}

View File

@ -163,6 +163,7 @@ target_sources(Luau.CodeGen PRIVATE
# Luau.Analysis Sources
target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Anyification.h
Analysis/include/Luau/AnyTypeSummary.h
Analysis/include/Luau/ApplyTypeFunction.h
Analysis/include/Luau/AstJsonEncoder.h
Analysis/include/Luau/AstQuery.h
@ -236,6 +237,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/VisitType.h
Analysis/src/Anyification.cpp
Analysis/src/AnyTypeSummary.cpp
Analysis/src/ApplyTypeFunction.cpp
Analysis/src/AstJsonEncoder.cpp
Analysis/src/AstQuery.cpp
@ -408,6 +410,7 @@ endif()
if(TARGET Luau.UnitTest)
# Luau.UnitTest Sources
target_sources(Luau.UnitTest PRIVATE
tests/AnyTypeSummary.test.cpp
tests/AssemblyBuilderA64.test.cpp
tests/AssemblyBuilderX64.test.cpp
tests/AstJsonEncoder.test.cpp

View File

@ -10,6 +10,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauPreserveLudataRenaming, false)
// clang-format off
const char* const luaT_typenames[] = {
// ORDER TYPE
@ -122,34 +124,74 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event)
const TString* luaT_objtypenamestr(lua_State* L, const TValue* o)
{
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
if (FFlag::LuauPreserveLudataRenaming)
{
const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]);
if (ttisstring(type))
return tsvalue(type);
}
else if (ttislightuserdata(o))
{
int tag = lightuserdatatag(o);
if (unsigned(tag) < LUA_LUTAG_LIMIT)
// Userdata created by the environment can have a custom type name set in the individual metatable
// If there is no custom name, 'userdata' is returned
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
{
const TString* name = L->global->lightuserdataname[tag];
const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]);
if (name)
return name;
if (ttisstring(type))
return tsvalue(type);
return L->global->ttname[ttype(o)];
}
// Tagged lightuserdata can be named using lua_setlightuserdataname
if (ttislightuserdata(o))
{
int tag = lightuserdatatag(o);
if (unsigned(tag) < LUA_LUTAG_LIMIT)
{
if (const TString* name = L->global->lightuserdataname[tag])
return name;
}
}
// For all types except userdata and table, a global metatable can be set with a global name override
if (Table* mt = L->global->mt[ttype(o)])
{
const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]);
if (ttisstring(type))
return tsvalue(type);
}
return L->global->ttname[ttype(o)];
}
else if (Table* mt = L->global->mt[ttype(o)])
else
{
const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]);
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
{
const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]);
if (ttisstring(type))
return tsvalue(type);
if (ttisstring(type))
return tsvalue(type);
}
else if (ttislightuserdata(o))
{
int tag = lightuserdatatag(o);
if (unsigned(tag) < LUA_LUTAG_LIMIT)
{
const TString* name = L->global->lightuserdataname[tag];
if (name)
return name;
}
}
else if (Table* mt = L->global->mt[ttype(o)])
{
const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]);
if (ttisstring(type))
return tsvalue(type);
}
return L->global->ttname[ttype(o)];
}
return L->global->ttname[ttype(o)];
}
const char* luaT_objtypename(lua_State* L, const TValue* o)

View File

@ -0,0 +1,745 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/RequireTracer.h"
#include "Fixture.h"
#include "doctest.h"
#include <algorithm>
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(StudioReportLuauAny);
struct ATSFixture: BuiltinsFixture
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
ATSFixture()
{
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
}
};
TEST_SUITE_BEGIN("AnyTypeSummaryTest");
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
type A = (number, string) -> ...any
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
}
}
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(1, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
}
}
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
type Pair<T> = {first: T, second: any}
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
}
}
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
{
fileResolver.source["game/Gui/Modules/B"] = R"(
local function greetings(name: string)
return "Hello, " .. name, nil
end
local x, y = greetings("Dibri")
local x, y = greetings("Dibri"), nil
local x, y, z = greetings("Dibri") -- mismatch
)";
CheckResult result1 = frontend.check("game/Gui/Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
}
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
-- type Pair<T> = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed?
type Pair<T> = (boolean, T) -> ...any
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
}
}
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local function foo<A>(a: (...A) -> any, ...: A)
return a(...)
end
local function addNumbers(num1, num2)
local result = num1 + num2
return result
end
foo(addNumbers)
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 3); // FuncApp
}
}
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local character = script.Parent
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
}
TEST_CASE_FIXTURE(ATSFixture, "if_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
function f(x: any)
if not x then
x = {
y = math.random(0, 2^31-1),
left = nil,
right = nil
}
else
local expected = x * 5
end
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
}
}
TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(): (number, ...any) -- double count "any" ret if varags
return 1, 5
end
local x, y, z = f() -- not catching this any because no annot
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1); // do we need this to be the var arg pattern?
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Func Ret
}
}
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
type XCoord = {x: number}
type YCoord = {y: any}
type Vector2 = XCoord & YCoord -- table type intersections do not get normalized
local vec2: Vector2 = {x = 1, y = 2}
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT((int)module->ats.typeInfo[2].code == 4); // Variable Annotation
}
}
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(...: any)
end
local function f(x: number?, y, z: any)
end
function f(x: number?, y, z: any)
end
function f(...: any)
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any
}
}
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local function f(...: any)
end
f("string", 123)
f("string")
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any
}
}
TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local CAR_COLLISION_GROUP = "Car"
-- Set the car collision group
for _, descendant in carTemplate:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.CollisionGroup = CAR_COLLISION_GROUP
end
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(3, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
}
}
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local function manageRace(raceContainer: Model)
RaceManager.new(raceContainer)
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
}
}
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local CollectionService = game:GetService("CollectionService")
local RaceManager = require(script.RaceManager)
local RACE_TAG = "Race"
local function manageRace(raceContainer: Model)
RaceManager.new(raceContainer)
end
local function initialize()
CollectionService:GetInstanceAddedSignal(RACE_TAG):Connect(manageRace)
for _, raceContainer in CollectionService:GetTagged(RACE_TAG) do
manageRace(raceContainer)
end
end
initialize()
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 5); //unknown Model -- why are we inferring so many anys here --prbably something with the data model. worth looking into
}
}
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local PhysicsService = game:GetService("PhysicsService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local safePlayerAdded = require(script.safePlayerAdded)
local CAR_COLLISION_GROUP = "Car"
local CHARACTER_COLLISION_GROUP = "Character"
local carTemplate = ReplicatedStorage.Car
local function onCharacterAdded(character: Model)
-- Set the collision group for any parts that are added to the character
character.DescendantAdded:Connect(function(descendant)
if descendant:IsA("BasePart") then
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
end
end)
-- Set the collision group for any parts currently in the character
for _, descendant in character:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
end
end
end
local function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(onCharacterAdded)
if player.Character then
onCharacterAdded(player.Character)
end
end
local function initialize()
-- Setup collision groups
PhysicsService:RegisterCollisionGroup(CAR_COLLISION_GROUP)
PhysicsService:RegisterCollisionGroup(CHARACTER_COLLISION_GROUP)
-- Stop the collision groups from colliding with each other
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CAR_COLLISION_GROUP, false)
PhysicsService:CollisionGroupSetCollidable(CHARACTER_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
-- Set the car collision group
for _, descendant in carTemplate:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.CollisionGroup = CAR_COLLISION_GROUP
end
end
-- Set character collision groups for all players
safePlayerAdded(onPlayerAdded)
end
initialize()
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(5, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
}
}
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local spawnCar = require(script.spawnCar)
local destroyPlayerCars = require(script.destroyPlayerCars)
local spawnPromptTemplate = script.SpawnPrompt
local KIOSK_TAG = "CarSpawnKiosk"
local function setupKiosk(kiosk: Model)
local spawnLocation = kiosk:FindFirstChild("SpawnLocation")
assert(spawnLocation, `{kiosk:GetFullName()} has no SpawnLocation part`)
local promptPart = kiosk:FindFirstChild("Prompt")
assert(promptPart, `{kiosk:GetFullName()} has no Prompt part`)
-- Hide the car spawn location
spawnLocation.Transparency = 1
-- Create a new prompt to spawn the car
local spawnPrompt = spawnPromptTemplate:Clone()
spawnPrompt.Parent = promptPart
spawnPrompt.Triggered:Connect(function(player: Player)
-- Remove any existing cars the player has spawned
destroyPlayerCars(player)
-- Spawn a new car at the spawnLocation, owned by the player
spawnCar(spawnLocation.CFrame, player)
end)
end
local function initialize()
-- Remove cars owned by players whenever they leave
Players.PlayerRemoving:Connect(destroyPlayerCars)
-- Setup all car spawning kiosks
CollectionService:GetInstanceAddedSignal(KIOSK_TAG):Connect(setupKiosk)
for _, kiosk in CollectionService:GetTagged(KIOSK_TAG) do
setupKiosk(kiosk)
end
end
initialize()
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(5, result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
}
}
TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!strict
type T<a> = { f: a, g: U<a> }
type U<a> = { h: a, i: T<a>? }
local x: T<number> = { f = 37, g = { h = 5, i = nil } }
x.g.i = x
local y: T<string> = { f = "hi", g = { h = "lo", i = nil } }
y.g.i = y
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
}
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
type Foo<T...> = (T...) -> () -- also want to see how these are used.
type Bar = Foo<(number, any)>
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
}
}
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local x = 2
local x: any = 2, 3
local x: any, y = 1, 2
local x: number, y: any, z, h: nil = 1, nil
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
}
}
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: any = 0
local y: number
local z = {x=x, y=y} -- not catching this
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
}
}
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: any = 0
function some1(x: typeof(x))
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 1); // Function Arguments
}
}
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local x: { x: any?} = {x = 1}
local z: { x : any, y : number? } -- not catching this
z.x = "bigfatlongstring"
z.y = nil
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2); // double counting variable annot again
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 8); // Assign
}
}
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
function some(x: any)
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments
}
}
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
{
// TODO: should only return 1
fileResolver.source["game/Gui/Modules/A"] = R"(
function other(y: number): any
return "gotcha!"
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Function Return
}
}
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
function cool(y: number): number
local g: any = "gratatataaa"
return y
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1); // should be one, double counitng annot
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
}
}
TEST_CASE_FIXTURE(ATSFixture, "generic_func")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
function reverse<T>(a: {T}, b: any): {T}
return a
end
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments
}
}
TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
type Clear = any
local z: Clear = "zip"
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result1);
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
if (FFlag::StudioReportLuauAny)
{
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
}
}
TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
{
fileResolver.source["game/A"] = R"(
export type MyFunction = (number, string) -> (any)
)";
fileResolver.source["game/B"] = R"(
local MyFunc = require(script.Parent.A)
type Clear = any
local z: Clear = "zip"
)";
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
CheckResult result = frontend.check("game/B");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_SUITE_END();

View File

@ -345,4 +345,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_binding_at_position_global_start_of_fil
CHECK_EQ(binding->location, Location{Position{0, 0}, Position{0, 0}});
}
TEST_CASE_FIXTURE(Fixture, "interior_binding_location_is_consistent_with_exterior_binding")
{
CheckResult result = check(R"(
local function abcd(arg)
abcd(arg)
end
abcd(0)
)");
LUAU_REQUIRE_NO_ERRORS(result);
// FIXME CLI-114385: findBindingByPosition does not properly handle AstStatLocalFunction.
// std::optional<Binding> declBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {1, 26});
// REQUIRE(declBinding);
// CHECK(declBinding->location == Location{{1, 25}, {1, 28}});
std::optional<Binding> innerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {2, 15});
REQUIRE(innerCallBinding);
CHECK(innerCallBinding->location == Location{{1, 23}, {1, 27}});
std::optional<Binding> outerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {5, 8});
REQUIRE(outerCallBinding);
CHECK(outerCallBinding->location == Location{{1, 23}, {1, 27}});
}
TEST_SUITE_END();

View File

@ -33,8 +33,8 @@ void luaC_validate(lua_State* L);
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauAttributeSyntax)
LUAU_FASTFLAG(LuauNativeAttribute)
LUAU_FASTFLAG(LuauPreserveLudataRenaming)
static lua_CompileOptions defaultOptions()
{
@ -2114,6 +2114,21 @@ TEST_CASE("LightuserdataApi")
lua_pop(L, 1);
if (FFlag::LuauPreserveLudataRenaming)
{
// Still possible to rename the global lightuserdata name using a metatable
lua_pushlightuserdata(L, value);
CHECK(strcmp(luaL_typename(L, -1), "userdata") == 0);
lua_createtable(L, 0, 1);
lua_pushstring(L, "luserdata");
lua_setfield(L, -2, "__type");
lua_setmetatable(L, -2);
CHECK(strcmp(luaL_typename(L, -1), "luserdata") == 0);
lua_pop(L, 1);
}
globalState.reset();
}
@ -2711,7 +2726,7 @@ TEST_CASE("NativeAttribute")
if (!codegen || !luau_codegen_supported())
return;
ScopedFastFlag sffs[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauNativeAttribute, true}};
ScopedFastFlag sffs[] = {{FFlag::LuauNativeAttribute, true}};
std::string source = R"R(
@native

View File

@ -776,12 +776,18 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation")
if typeof(almostBar.x.y) ~= "number" then
almostFoo = almostBar
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
compareTypesNe("foo", "almostFoo",
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.x.y.Negation has type string, while the right type at <unlabeled-symbol>.x.y.Negation has type number)");
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { x: { y: ~string } }, while the right type at <unlabeled-symbol> is a union missing type { x: { y: ~string } })");
// TODO: a more desirable expected error here is as below, but `Differ` requires improvements to
// dealing with unions to get something like this (recognizing that the union is identical
// except in one component where they differ).
//
// compareTypesNe("foo", "almostFoo",
// R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.x.y.Negation has type string, while the right type at <unlabeled-symbol>.x.y.Negation has type number)");
}
TEST_CASE_FIXTURE(DifferFixture, "union_missing_right")

View File

@ -295,6 +295,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
return {hello = A.hello}
)";
fileResolver.source["game/Gui/Modules/C"] = R"(
--!strict
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
local B = require(Modules.B)
@ -911,7 +912,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE_EQ(
"Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]));
R"(Type
'{ count: string }'
could not be converted into
'{ Count: number }')", toString(result.errors[0]));
else
REQUIRE_EQ(
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
@ -919,6 +923,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode")
{
// The new non-strict mode is not currently expected to signal any errors here.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
fileResolver.source["Module/A"] = R"(
--!nonstrict
local module = {}
@ -968,13 +976,25 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
local foo: Foo = 1
)";
fileResolver.source["C"] = R"(
--!strict
local foo: Foo = 1
)";
fileResolver.environments["A"] = "test";
CheckResult resultA = frontend.check("A");
LUAU_REQUIRE_NO_ERRORS(resultA);
CheckResult resultB = frontend.check("B");
LUAU_REQUIRE_ERROR_COUNT(1, resultB);
// In the new non-strict mode, we do not currently support error reporting for unknown symbols in type positions.
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_NO_ERRORS(resultB);
else
LUAU_REQUIRE_ERROR_COUNT(1, resultB);
CheckResult resultC = frontend.check("C");
LUAU_REQUIRE_ERROR_COUNT(1, resultC);
}
TEST_CASE_FIXTURE(FrontendFixture, "ast_node_at_position")
@ -1075,6 +1095,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types")
TEST_CASE_FIXTURE(FrontendFixture, "imported_table_modification_2")
{
// This test describes non-strict mode behavior that is just not currently present in the new non-strict mode.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
frontend.options.retainFullTypeGraphs = false;
fileResolver.source["Module/A"] = R"(

View File

@ -8,7 +8,6 @@
#include "doctest.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauNativeAttribute);
LUAU_FASTFLAG(LintRedundantNativeAttribute);
@ -1960,7 +1959,7 @@ local _ = a <= (b == 0)
TEST_CASE_FIXTURE(Fixture, "RedundantNativeAttribute")
{
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
ScopedFastFlag sff[] = {{FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
LintResult result = lint(R"(
--!native

View File

@ -15,8 +15,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauAttributeSyntax);
#define NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, idx) \
do \
{ \
@ -70,7 +68,6 @@ struct NonStrictTypeCheckerFixture : Fixture
{
ScopedFastFlag flags[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::LuauAttributeSyntax, true},
};
LoadDefinitionFileResult res = loadDefinition(definitions);
LUAU_ASSERT(res.success);
@ -81,7 +78,6 @@ struct NonStrictTypeCheckerFixture : Fixture
{
ScopedFastFlag flags[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::LuauAttributeSyntax, true},
};
LoadDefinitionFileResult res = loadDefinition(definitions);
LUAU_ASSERT(res.success);
@ -562,10 +558,6 @@ local E = require(script.Parent.A)
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_shouldnt_warn_on_valid_buffer_use")
{
ScopedFastFlag flags[] = {
{FFlag::LuauAttributeSyntax, true},
};
loadDefinition(R"(
declare buffer: {
create: @checked (size: number) -> buffer,

View File

@ -16,8 +16,6 @@ LUAU_FASTINT(LuauRecursionLimit);
LUAU_FASTINT(LuauTypeLengthLimit);
LUAU_FASTINT(LuauParseErrorLimit);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAttributeSyntax);
LUAU_FASTFLAG(LuauLeadingBarAndAmpersand2);
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
@ -3070,7 +3068,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_top_level_checked_fn")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
std::string src = R"BUILTIN_SRC(
@checked declare function abs(n: number): number
@ -3090,7 +3087,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_declared_table_checked_member")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
const std::string src = R"BUILTIN_SRC(
declare math : {
@ -3118,7 +3114,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseResult pr = tryParse(R"(
local @checked = 3
@ -3132,7 +3127,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
auto pr = tryParse(R"(
local @checked = 3
@ -3148,7 +3142,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
auto pr = tryParse(R"(
@checked function(x: number) : number
@ -3162,7 +3155,6 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
{
ParseOptions opts;
opts.allowDeclarationSyntax = true;
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
auto pr = tryParse(R"(
local @blah = 3
@ -3193,28 +3185,6 @@ TEST_CASE_FIXTURE(Fixture, "read_write_table_properties")
LUAU_ASSERT(pr.errors.size() == 0);
}
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_bar_unions_successfully")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
parse(R"(type A = | "Hello" | "World")");
}
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_ampersand_intersections_successfully")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
parse(R"(type A = & { string } & { number })");
}
TEST_CASE_FIXTURE(Fixture, "mixed_leading_intersection_and_union_not_allowed")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
matchParseError("type A = & number | string | boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
matchParseError("type A = | number & string & boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
}
void checkAttribute(const AstAttr* attr, const AstAttr::Type type, const Location& location)
{
CHECK_EQ(attr->type, type);
@ -3232,7 +3202,6 @@ void checkFirstErrorForAttributes(const std::vector<ParseError>& errors, const s
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_stat")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
AstStatBlock* stat = parse(R"(
@checked
@ -3254,7 +3223,7 @@ end)");
TEST_CASE_FIXTURE(Fixture, "parse_attribute_for_function_expression")
{
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauAttributeSyntaxFunExpr, true}};
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntaxFunExpr, true}};
AstStatBlock* stat1 = parse(R"(
local function invoker(f)
@ -3293,8 +3262,6 @@ local f = @checked function(x) return (x + 2) end
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_local_function_stat")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
AstStatBlock* stat = parse(R"(
@checked
local function hello(x, y)
@ -3315,8 +3282,6 @@ end)");
TEST_CASE_FIXTURE(Fixture, "empty_attribute_name_is_not_allowed")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseResult result = tryParse(R"(
@
function hello(x, y)
@ -3328,8 +3293,6 @@ end)");
TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_stat")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseResult pr1 = tryParse(R"(
@checked
if a<0 then a = 0 end)");
@ -3401,7 +3364,7 @@ function foo1 () @checked return 'a' end
TEST_CASE_FIXTURE(Fixture, "dont_parse_attribute_on_argument_non_function")
{
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauAttributeSyntaxFunExpr, true}};
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntaxFunExpr, true}};
ParseResult pr = tryParse(R"(
local function invoker(f, y)
@ -3417,8 +3380,6 @@ invoker(function(x) return (x + 2) end, @checked 1)
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_type_declaration")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseOptions opts;
opts.allowDeclarationSyntax = true;
@ -3445,8 +3406,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_type_declaration")
TEST_CASE_FIXTURE(Fixture, "parse_attributes_on_function_type_declaration_in_table")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseOptions opts;
opts.allowDeclarationSyntax = true;
@ -3482,8 +3441,6 @@ declare bit32: {
TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_type_declarations")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseOptions opts;
opts.allowDeclarationSyntax = true;
@ -3517,8 +3474,6 @@ declare bit32: {
TEST_CASE_FIXTURE(Fixture, "attributes_cannot_be_duplicated")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseResult result = tryParse(R"(
@checked
@checked
@ -3531,8 +3486,6 @@ end)");
TEST_CASE_FIXTURE(Fixture, "unsupported_attributes_are_not_allowed")
{
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
ParseResult result = tryParse(R"(
@checked
@cool_attribute
@ -3545,22 +3498,16 @@ end)");
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_bar_unions_successfully")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
parse(R"(type A = | "Hello" | "World")");
}
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_ampersand_intersections_successfully")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
parse(R"(type A = & { string } & { number })");
}
TEST_CASE_FIXTURE(Fixture, "mixed_leading_intersection_and_union_not_allowed")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
matchParseError("type A = & number | string | boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
matchParseError("type A = | number & string & boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
}

View File

@ -13,7 +13,6 @@ using namespace Luau;
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauAttributeSyntax);
TEST_SUITE_BEGIN("ToString");
@ -977,7 +976,6 @@ TEST_CASE_FIXTURE(Fixture, "checked_fn_toString")
{
ScopedFastFlag flags[] = {
{FFlag::DebugLuauDeferredConstraintResolution, true},
{FFlag::LuauAttributeSyntax, true},
};
auto _result = loadDefinition(R"(

View File

@ -698,8 +698,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Argument count mismatch. Function '_' expects at least 1 argument, but none are specified", toString(result.errors[0]));
CHECK_EQ("Argument count mismatch. Function 'select' expects 1 argument, but none are specified", toString(result.errors[1]));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// The argument count is the same, but the errors are currently cyclic type family instance ones.
// This isn't great, but the desired behavior here was that it didn't cause a crash and that is still true.
// The larger fix for this behavior will likely be integration of egraph-based normalization throughout the new solver.
}
else
{
CHECK_EQ("Argument count mismatch. Function '_' expects at least 1 argument, but none are specified", toString(result.errors[0]));
CHECK_EQ("Argument count mismatch. Function 'select' expects 1 argument, but none are specified", toString(result.errors[1]));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
@ -942,7 +952,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)", toString(result.errors[0]));
else
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type2")
@ -991,7 +1004,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
else
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")

View File

@ -4,14 +4,10 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
TEST_SUITE_BEGIN("ControlFlowAnalysis");
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
if not x then
@ -28,8 +24,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -48,8 +42,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -68,8 +60,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
if not x then
@ -90,8 +80,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -115,8 +103,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -140,8 +126,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -165,8 +149,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -190,8 +172,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
if not x then
@ -214,8 +194,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -241,8 +219,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_br
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -268,8 +244,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
if not x then
@ -292,8 +266,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fallthrough")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -319,8 +291,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fa
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -346,8 +316,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?, z: string?)
if not x then
@ -372,8 +340,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_not_z_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
for i, recordX in x do
@ -402,8 +368,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_n
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
for i, recordX in x do
@ -432,8 +396,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_eli
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
for i, recordX in x do
@ -462,8 +424,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
for i, recordX in x do
@ -492,8 +452,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
do
@ -512,8 +470,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -534,8 +490,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -556,8 +510,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
while math.random() > 0.5 do
@ -579,8 +531,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
repeat
@ -602,8 +552,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first_2")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
for i = 1, 10 do
@ -625,8 +573,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
if not x then
@ -643,8 +589,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
if not x then
@ -661,8 +605,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?, y: string?)
if not x then
@ -685,8 +627,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -712,8 +652,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -739,8 +677,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -766,8 +702,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}}, y: {{value: string?}})
for i, recordX in x do
@ -793,8 +727,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
if typeof(x) == "string" then
@ -816,8 +748,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -841,8 +771,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -866,8 +794,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
// In CG, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
// That second walk assumes that the name occurs in the same `Scope` that the prototype walk had. If we arbitrarily change scope midway
// through, we'd invoke UB.
@ -892,8 +818,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_breaking")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -917,8 +841,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_continuing")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: {{value: string?}})
for _, record in x do
@ -942,8 +864,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
type Err<E> = { tag: "err", error: E }
@ -977,8 +897,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
type Err<E> = { tag: "err", error: E }
@ -1010,8 +928,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
type Ok<T> = { tag: "ok", value: T }
type Err<E> = { tag: "err", error: E }
@ -1043,8 +959,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
CheckResult result = check(R"(
local function f(x: string?)
do

View File

@ -2763,5 +2763,18 @@ local function captureDependencies(
)");
}
TEST_CASE_FIXTURE(Fixture, "unpack_depends_on_rhs_pack_to_be_fully_resolved")
{
CheckResult result = check(R"(
--!strict
local function id(x)
return x
end
local u,v = id(3), id(id(44))
)");
CHECK_EQ(builtinTypes->numberType, requireType("v"));
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View File

@ -632,7 +632,12 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_pack_parentheses")
function f<a...>(...: a...): any return (...) end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
// This should really error, but the error from the old solver is wrong.
// `a...` is a generic type pack, and we don't know that it will be non-empty, thus this code may not work.
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_NO_ERRORS(result);
else
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(Fixture, "better_mismatch_error_messages")
@ -647,13 +652,27 @@ TEST_CASE_FIXTURE(Fixture, "better_mismatch_error_messages")
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
SwappedGenericTypeParameter* fErr = get<SwappedGenericTypeParameter>(result.errors[0]);
SwappedGenericTypeParameter* fErr;
SwappedGenericTypeParameter* gErr;
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(3, result);
// The first error here is an unknown symbol that is redundant with the `fErr`.
fErr = get<SwappedGenericTypeParameter>(result.errors[1]);
gErr = get<SwappedGenericTypeParameter>(result.errors[2]);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
fErr = get<SwappedGenericTypeParameter>(result.errors[0]);
gErr = get<SwappedGenericTypeParameter>(result.errors[1]);
}
REQUIRE(fErr);
CHECK_EQ(fErr->name, "T");
CHECK_EQ(fErr->kind, SwappedGenericTypeParameter::Pack);
SwappedGenericTypeParameter* gErr = get<SwappedGenericTypeParameter>(result.errors[1]);
REQUIRE(gErr);
CHECK_EQ(gErr->name, "T");
CHECK_EQ(gErr->kind, SwappedGenericTypeParameter::Type);
@ -1111,7 +1130,10 @@ local a: Self<Table>
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "Table");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(requireType("a")), "Table<Table>");
else
CHECK_EQ(toString(requireType("a")), "Table");
}
TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
@ -1127,7 +1149,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
std::optional<TypeId> t0 = lookupType("t0");
REQUIRE(t0);
CHECK_EQ("*error-type*", toString(*t0));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("any", toString(*t0));
else
CHECK_EQ("*error-type*", toString(*t0));
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
return get<OccursCheckFailed>(err);
@ -1137,20 +1162,40 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
{
CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a)
return f(x, y)
end
return sum(2, 3, function(a, b) return a + b end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> add<a>)
return f(x, y)
end
return sum(2, 3, function<T>(a: T, b: T): add<T> return a + b end)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
InternalError* ie = get<InternalError>(result.errors[0]);
REQUIRE(ie);
CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message);
}
else
{
CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a)
return f(x, y)
end
return sum(2, 3, function(a, b) return a + b end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2")
{
CheckResult result = check(R"(
local function map<a, b>(arr: {a}, f: (a) -> b)
local function map<a, b>(arr: {a}, f: (a) -> b): {b}
local r = {}
for i,v in ipairs(arr) do
table.insert(r, f(v))
@ -1158,7 +1203,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2")
return r
end
local a = {1, 2, 3}
local r = map(a, function(a) return a + a > 100 end)
local r = map(a, function(a: number) return a + a > 100 end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
@ -1176,11 +1221,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_3")
return r
end
local a = {1, 2, 3}
local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} end)
local r = foldl(a, {s=0,c=0}, function(a: {s: number, c: number}, b: number) return {s = a.s + b, c = a.c + 1} end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r")));
if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE_EQ("{ c: number, s: number } | { c: number, s: number }", toString(requireType("r")));
else
REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r")));
}
TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded")
@ -1214,22 +1262,48 @@ table.sort(a, function(x, y) return x.x < y.x end)
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "do_not_infer_generic_functions")
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
{
CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
local function sumrec(f: typeof(sum))
return sum(2, 3, function(a, b) return a + b end)
end
CheckResult result;
local b = sumrec(sum) -- ok
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
LUAU_REQUIRE_NO_ERRORS(result);
local function sumrec(f: typeof(sum))
return sum(2, 3, function<T>(a: T, b: T): add<T> return a + b end)
end
local b = sumrec(sum) -- ok
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
InternalError* ie = get<InternalError>(result.errors[0]);
REQUIRE(ie);
CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message);
}
else
{
result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
local function sumrec(f: typeof(sum))
return sum(2, 3, function(a, b) return a + b end)
end
local b = sumrec(sum) -- ok
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
}
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
{
CheckResult result = check(R"(
@ -1246,6 +1320,8 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1")
{
// CLI-114507: temporarily changed to have a cast for `object` to silence false positive error
// https://github.com/luau-lang/luau/issues/484
CheckResult result = check(R"(
--!strict
@ -1256,7 +1332,7 @@ local object: MyObject = {
getReturnValue = function<U>(cb: () -> U): U
return cb()
end,
}
} :: MyObject
type ComplexObject<T> = {
id: T,

View File

@ -12,7 +12,6 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
using namespace Luau;
@ -492,8 +491,6 @@ return unpack(l0[_])
TEST_CASE_FIXTURE(BuiltinsFixture, "check_imported_module_names")
{
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
fileResolver.source["game/A"] = R"(
return function(...) end
)";

View File

@ -515,4 +515,35 @@ TEST_CASE_FIXTURE(Fixture, "method_should_not_create_cyclic_type")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_metatable")
{
fileResolver.source["game/A"] = R"(
--!strict
local cls = {}
cls.__index = cls
function cls:abc() return 4 end
return cls
)";
fileResolver.source["game/B"] = R"(
--!strict
local cls = require(game.A)
local tbl = {}
setmetatable(tbl, cls)
)";
CheckResult result = frontend.check("game/B");
LUAU_REQUIRE_NO_ERRORS(result);
ModulePtr b = frontend.moduleResolver.getModule("game/B");
REQUIRE(b);
std::optional<Binding> clsBinding = b->getModuleScope()->linearSearchForBinding("tbl");
REQUIRE(clsBinding);
TypeId clsType = clsBinding->typeId;
CHECK("{ @metatable cls, tbl }" == toString(clsType));
}
TEST_SUITE_END();

View File

@ -1472,8 +1472,8 @@ TEST_CASE_FIXTURE(Fixture, "add_type_function_works")
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(toString(requireType("a")) == "number");
CHECK(toString(requireType("b")) == "Add<string, string>");
CHECK(toString(result.errors[0]) == "Type function instance Add<string, string> is uninhabited");
CHECK(toString(requireType("b")) == "add<string, string>");
CHECK(toString(result.errors[0]) == "Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "normalize_strings_comparison")

View File

@ -2059,6 +2059,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
TEST_CASE_FIXTURE(BuiltinsFixture, "less_exponential_blowup_please")
{
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
@ -3275,6 +3276,7 @@ TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
{
ScopedFastFlag sff[] = {
{FFlag::DebugLuauSharedSelf, true},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
check(R"(
@ -3304,16 +3306,25 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
local t: { [string]: number } = { 5, 6, 7 }
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" == toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[0]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[1]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[0]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[1]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2]));
}
}
TEST_CASE_FIXTURE(Fixture, "shared_selfs")
{
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local t = {}
@ -3335,6 +3346,7 @@ TEST_CASE_FIXTURE(Fixture, "shared_selfs")
TEST_CASE_FIXTURE(Fixture, "shared_selfs_from_free_param")
{
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function f(t)
@ -3351,6 +3363,7 @@ TEST_CASE_FIXTURE(Fixture, "shared_selfs_from_free_param")
TEST_CASE_FIXTURE(BuiltinsFixture, "shared_selfs_through_metatables")
{
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local t = {}
@ -3430,6 +3443,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
{
ScopedFastFlag sff[]{
{FFlag::DebugLuauSharedSelf, true},
{FFlag::DebugLuauDeferredConstraintResolution, false},
};
CheckResult result = check(R"(
@ -3460,6 +3474,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
TEST_CASE_FIXTURE(Fixture, "quantify_even_that_table_was_never_exported_at_all")
{
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local T = {}
@ -3568,7 +3583,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
{
CheckResult result = check(R"(
local function f(s): string
local foo = s:absolutely_no_scalar_has_this_method()
@ -3576,27 +3590,38 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(toString(result.errors.at(0)) ==
"Type pack 't1 where t1 = { absolutely_no_scalar_has_this_method: (t1) -> (unknown, a...) }' could not be converted into 'string'; at "
"[0], t1 where t1 = { absolutely_no_scalar_has_this_method: (t1) -> (unknown, a...) } is not a subtype of string");
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value.");
// FIXME: These free types should have been generalized by now.
CHECK(toString(result.errors[1]) == "Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: ('a <: (never) -> ('b, c...)) -}' here.");
CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here.");
CHECK(get<CannotCallNonFunction>(result.errors[3]));
CHECK_EQ("(never) -> string", toString(requireType("f")));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
caused by:
The former's metatable does not satisfy the requirements.
Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
CHECK_EQ(expected, toString(result.errors[0]));
}
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
{
// We need egraphs to simplify the type of `out` here. CLI-114134
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function stringByteList(str)
local out = {}
@ -3621,27 +3646,41 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
CheckResult result = check(R"(
--!strict
local t = {}
function t.m(x) return x end
function t.m<T>(x: T) return x end
local a : string = t.m("hi")
local b : number = t.m(5)
function f(x : { m : (number)->number })
x.m = function(x) return 1+x end
x.m = function(x: number) return 1+x end
end
f(t) -- This shouldn't typecheck
local c : string = t.m("hi")
)");
LUAU_REQUIRE_NO_ERRORS(result);
// TODO: test behavior is wrong until we can re-enable the covariant requirement for instantiation in subtyping
// LUAU_REQUIRE_ERRORS(result);
// CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
// caused by:
// Property 'm' is not compatible. Type '<a>(a) -> a' could not be converted into '(number) -> number'; different number of generic type
// parameters)");
// // this error message is not great since the underlying issue is that the context is invariant,
// and `(number) -> number` cannot be a subtype of `<a>(a) -> a`.
}
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// FIXME. We really should be reporting just one error in this case. CLI-114509
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK(get<TypePackMismatch>(result.errors[0]));
CHECK(get<TypeMismatch>(result.errors[1]));
CHECK(get<TypeMismatch>(result.errors[2]));
}
else
{
// TODO: test behavior is wrong until we can re-enable the covariant requirement for instantiation in subtyping
// LUAU_REQUIRE_ERRORS(result);
// CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
// caused by:
// Property 'm' is not compatible. Type '<a>(a) -> a' could not be converted into '(number) -> number'; different number of generic type
// parameters)");
// // this error message is not great since the underlying issue is that the context is invariant,
// and `(number) -> number` cannot be a subtype of `<a>(a) -> a`.
LUAU_REQUIRE_NO_ERRORS(result);
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_table_instantiation_potential_regression")
{
@ -3657,11 +3696,22 @@ local g : ({ p : number, q : string }) -> ({ p : number, r : boolean }) = f
LUAU_REQUIRE_ERROR_COUNT(1, result);
MissingProperties* error = get<MissingProperties>(result.errors[0]);
REQUIRE(error != nullptr);
REQUIRE(error->properties.size() == 1);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
const TypeMismatch* error = get<TypeMismatch>(result.errors[0]);
REQUIRE_MESSAGE(error, "Expected TypeMismatch but got " << result.errors[0]);
CHECK_EQ("r", error->properties[0]);
CHECK("({ p: number, q: string }) -> { p: number, r: boolean }" == toString(error->wantedType));
CHECK("({ p: number }) -> { p: number }" == toString(error->givenType));
}
else
{
const MissingProperties* error = get<MissingProperties>(result.errors[0]);
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << result.errors[0]);
REQUIRE(error->properties.size() == 1);
CHECK_EQ("r", error->properties[0]);
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect")
@ -3804,7 +3854,10 @@ TEST_CASE_FIXTURE(Fixture, "when_augmenting_an_unsealed_table_with_an_indexer_ap
CHECK(tt->props.empty());
REQUIRE(tt->indexer);
CHECK("string" == toString(tt->indexer->indexType));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("unknown" == toString(tt->indexer->indexType));
else
CHECK("string" == toString(tt->indexer->indexType));
LUAU_REQUIRE_NO_ERRORS(result);
}
@ -3829,7 +3882,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_extend_unsealed_tables_in_rvalue_position")
CHECK(0 == ttv->props.count(""));
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_ERROR_COUNT(1, result);
else
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "extend_unsealed_table_with_metatable")
@ -3979,21 +4035,38 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
LUAU_REQUIRE_ERROR_COUNT(2, result);
TypeError& err1 = result.errors[0];
MissingProperties* error1 = get<MissingProperties>(err1);
REQUIRE(error1);
REQUIRE(error1->properties.size() == 1);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
const TypeMismatch* err1 = get<TypeMismatch>(result.errors[0]);
REQUIRE_MESSAGE(err1, "Expected TypeMismatch but got " << result.errors[0]);
CHECK_EQ("prop", error1->properties[0]);
CHECK("{Thing}" == toString(err1->wantedType));
CHECK("{{ name: string }}" == toString(err1->givenType));
TypeError& err2 = result.errors[1];
TypeMismatch* mismatch = get<TypeMismatch>(err2);
REQUIRE(mismatch);
MissingProperties* error2 = get<MissingProperties>(*mismatch->error);
REQUIRE(error2);
REQUIRE(error2->properties.size() == 1);
const TypeMismatch* err2 = get<TypeMismatch>(result.errors[1]);
REQUIRE_MESSAGE(err2, "Expected TypeMismatch but got " << result.errors[1]);
CHECK_EQ("prop", error2->properties[0]);
CHECK("{ [string]: Thing }" == toString(err2->wantedType));
CHECK("{ [string]: { name: string } }" == toString(err2->givenType));
}
else
{
TypeError& err1 = result.errors[0];
MissingProperties* error1 = get<MissingProperties>(err1);
REQUIRE(error1);
REQUIRE(error1->properties.size() == 1);
CHECK_EQ("prop", error1->properties[0]);
TypeError& err2 = result.errors[1];
TypeMismatch* mismatch = get<TypeMismatch>(err2);
REQUIRE(mismatch);
MissingProperties* error2 = get<MissingProperties>(*mismatch->error);
REQUIRE(error2);
REQUIRE(error2->properties.size() == 1);
CHECK_EQ("prop", error2->properties[0]);
}
}
TEST_CASE_FIXTURE(Fixture, "simple_method_definition")
@ -4314,7 +4387,8 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("({number}, unknown) -> ()", toString(requireType("f")));
// FIXME CLI-114134. We need to simplify types more consistently.
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f")));
}
TEST_CASE_FIXTURE(Fixture, "write_to_union_property_not_all_present")

View File

@ -19,7 +19,6 @@
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
LUAU_FASTFLAG(LuauLeadingBarAndAmpersand2)
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTINT(LuauNormalizeCacheLimit);
LUAU_FASTINT(LuauRecursionLimit);
@ -1575,7 +1574,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_iter_metamethod")
TEST_CASE_FIXTURE(Fixture, "leading_bar")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
CheckResult result = check(R"(
type Bar = | number
)");
@ -1586,7 +1584,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar")
TEST_CASE_FIXTURE(Fixture, "leading_bar_question_mark")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
CheckResult result = check(R"(
type Bar = |?
)");
@ -1598,7 +1595,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar_question_mark")
TEST_CASE_FIXTURE(Fixture, "leading_ampersand")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
CheckResult result = check(R"(
type Amp = & string
)");
@ -1609,7 +1605,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_ampersand")
TEST_CASE_FIXTURE(Fixture, "leading_bar_no_type")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
CheckResult result = check(R"(
type Bar = |
)");
@ -1621,7 +1616,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar_no_type")
TEST_CASE_FIXTURE(Fixture, "leading_ampersand_no_type")
{
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
CheckResult result = check(R"(
type Amp = &
)");

View File

@ -51,6 +51,9 @@ static bool skipFastFlag(const char* flagName)
if (strncmp(flagName, "Debug", 5) == 0)
return true;
if (strcmp(flagName, "StudioReportLuauAny") == 0)
return true;
return false;
}

View File

@ -8,10 +8,8 @@ AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_suggestion_for_overloads
AutocompleteTest.type_correct_suggestion_in_table
BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
BuiltinTests.bad_select_should_not_crash
BuiltinTests.coroutine_resume_anything_goes
BuiltinTests.gmatch_capture_types
BuiltinTests.gmatch_capture_types2
@ -31,20 +29,8 @@ BuiltinTests.string_format_correctly_ordered_types
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
BuiltinTests.string_format_use_correct_argument2
BuiltinTests.table_freeze_is_generic
BuiltinTests.tonumber_returns_optional_number_type
Differ.metatable_metamissing_left
Differ.metatable_metamissing_right
Differ.metatable_metanormal
Differ.negation
FrontendTest.environments
FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1
GenericsTests.better_mismatch_error_messages
GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.do_not_always_instantiate_generic_intersection_types
GenericsTests.do_not_infer_generic_functions
GenericsTests.dont_substitute_bound_types
GenericsTests.error_detailed_function_mismatch_generic_pack
GenericsTests.error_detailed_function_mismatch_generic_types
@ -54,19 +40,13 @@ GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories
GenericsTests.generic_functions_in_types
GenericsTests.generic_type_functions_work_in_subtyping
GenericsTests.generic_type_pack_parentheses
GenericsTests.generic_type_pack_unification1
GenericsTests.generic_type_pack_unification2
GenericsTests.generic_type_pack_unification3
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
GenericsTests.infer_generic_function_function_argument
GenericsTests.infer_generic_function_function_argument_2
GenericsTests.infer_generic_function_function_argument_3
GenericsTests.instantiated_function_argument_names
GenericsTests.no_stack_overflow_from_quantifying
GenericsTests.properties_can_be_instantiated_polytypes
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
GenericsTests.self_recursive_instantiated_param
IntersectionTypes.error_detailed_intersection_all
IntersectionTypes.error_detailed_intersection_part
IntersectionTypes.intersect_bool_and_false
@ -145,22 +125,18 @@ RefinementTest.typeguard_cast_free_table_to_vector
RefinementTest.typeguard_in_assert_position
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part
TableTests.a_free_shape_can_turn_into_a_scalar_directly
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
TableTests.array_factory_function
TableTests.casting_tables_with_props_into_table_with_indexer2
TableTests.casting_tables_with_props_into_table_with_indexer3
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
TableTests.checked_prop_too_early
TableTests.cli_84607_missing_prop_in_array_or_dict
TableTests.common_table_element_general
TableTests.common_table_element_union_in_call_tail
TableTests.confusing_indexing
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
TableTests.dont_extend_unsealed_tables_in_rvalue_position
TableTests.dont_leak_free_table_props
TableTests.dont_suggest_exact_match_keys
TableTests.error_detailed_indexer_key
@ -170,20 +146,15 @@ TableTests.explicitly_typed_table
TableTests.explicitly_typed_table_error
TableTests.explicitly_typed_table_with_indexer
TableTests.generalize_table_argument
TableTests.generic_table_instantiation_potential_regression
TableTests.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexers_get_quantified_too
TableTests.infer_indexer_from_array_like_table
TableTests.infer_indexer_from_its_variable_type_and_unifiable
TableTests.inferred_return_type_of_free_table
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
TableTests.less_exponential_blowup_please
TableTests.meta_add
TableTests.meta_add_inferred
TableTests.metatable_mismatch_should_fail
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
TableTests.mixed_tables_with_implicit_numbered_keys
TableTests.nil_assign_doesnt_hit_indexer
TableTests.ok_to_provide_a_subtype_during_construction
TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr
@ -191,22 +162,15 @@ TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.open_table_unification_2
TableTests.parameter_was_set_an_indexer_and_bounded_by_another_parameter
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
TableTests.persistent_sealed_table_is_immutable
TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
TableTests.recursive_metatable_type_call
TableTests.right_table_missing_key2
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.sealed_table_indexers_must_unify
TableTests.setmetatable_has_a_side_effect
TableTests.shared_selfs
TableTests.shared_selfs_from_free_param
TableTests.shared_selfs_through_metatables
TableTests.table_call_metamethod_basic
TableTests.table_call_metamethod_must_be_callable
TableTests.table_param_width_subtyping_2
@ -219,7 +183,6 @@ TableTests.table_unifies_into_map
TableTests.type_mismatch_on_massive_table_is_cut_short
TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
ToDot.function
ToString.exhaustive_toString_of_cyclic_table
ToString.free_types
@ -363,7 +326,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.promise_type_error_too_complex
TypeInferOperators.add_type_function_works
TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compound_assign_result_must_be_compatible_with_var
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs