Merge branch 'upstream' into merge

This commit is contained in:
Arseny Kapoulkine 2022-06-23 18:45:15 -07:00
commit 4bf3ace2a6
54 changed files with 968 additions and 693 deletions

View File

@ -1,10 +1,10 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Location.h"
#include "Luau/NotNull.h"
#include "Luau/Variant.h"
#include <string>
#include <memory>
#include <vector>
@ -47,18 +47,24 @@ struct InstantiationConstraint
TypeId superType;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint>;
// name(namedType) = name
struct NameConstraint
{
TypeId namedType;
std::string name;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, NameConstraint>;
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct Constraint
{
Constraint(ConstraintV&& c, Location location);
explicit Constraint(ConstraintV&& c);
Constraint(const Constraint&) = delete;
Constraint& operator=(const Constraint&) = delete;
ConstraintV c;
Location location;
std::vector<NotNull<Constraint>> dependencies;
};

View File

@ -17,20 +17,7 @@
namespace Luau
{
struct Scope2
{
// The parent scope of this scope. Null if there is no parent (i.e. this
// is the module-level scope).
Scope2* parent = nullptr;
// All the children of this scope.
std::vector<Scope2*> children;
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
TypePackId returnType;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
std::optional<TypeId> lookup(Symbol sym);
};
struct Scope2;
struct ConstraintGraphBuilder
{
@ -47,6 +34,10 @@ struct ConstraintGraphBuilder
// A mapping of AST node to TypePackId.
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
// Types resolved from type annotations. Analogous to astTypes.
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
// Type packs resolved from type annotations. Analogous to astTypePacks.
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
explicit ConstraintGraphBuilder(TypeArena* arena);
@ -73,9 +64,8 @@ struct ConstraintGraphBuilder
* Adds a new constraint with no dependencies to a given scope.
* @param scope the scope to add the constraint to. Must not be null.
* @param cv the constraint variant to add.
* @param location the location to attribute to the constraint.
*/
void addConstraint(Scope2* scope, ConstraintV cv, Location location);
void addConstraint(Scope2* scope, ConstraintV cv);
/**
* Adds a constraint to a given scope.
@ -99,6 +89,7 @@ struct ConstraintGraphBuilder
void visit(Scope2* scope, AstStatReturn* ret);
void visit(Scope2* scope, AstStatAssign* assign);
void visit(Scope2* scope, AstStatIf* ifStatement);
void visit(Scope2* scope, AstStatTypeAlias* alias);
TypePackId checkExprList(Scope2* scope, const AstArray<AstExpr*>& exprs);
@ -124,6 +115,24 @@ struct ConstraintGraphBuilder
* @param fn the function expression to check.
*/
void checkFunctionBody(Scope2* scope, AstExprFunction* fn);
/**
* Resolves a type from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param ty the AST annotation to resolve.
* @return the type of the AST annotation.
**/
TypeId resolveType(Scope2* scope, AstType* ty);
/**
* Resolves a type pack from its AST annotation.
* @param scope the scope that the type annotation appears within.
* @param tp the AST annotation to resolve.
* @return the type pack of the AST annotation.
**/
TypePackId resolveTypePack(Scope2* scope, AstTypePack* tp);
TypePackId resolveTypePack(Scope2* scope, const AstTypeList& list);
};
/**

View File

@ -55,6 +55,7 @@ struct ConstraintSolver
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/**
@ -85,7 +86,7 @@ struct ConstraintSolver
* @param subType the sub-type to unify.
* @param superType the super-type to unify.
*/
void unify(TypeId subType, TypeId superType, Location location);
void unify(TypeId subType, TypeId superType);
/**
* Creates a new Unifier and performs a single unification operation. Commits
@ -93,7 +94,7 @@ struct ConstraintSolver
* @param subPack the sub-type pack to unify.
* @param superPack the super-type pack to unify.
*/
void unify(TypePackId subPack, TypePackId superPack, Location location);
void unify(TypePackId subPack, TypePackId superPack);
private:
/**

View File

@ -1,6 +1,8 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/Constraint.h"
#include "Luau/NotNull.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include <optional>

View File

@ -169,6 +169,13 @@ struct GenericError
bool operator==(const GenericError& rhs) const;
};
struct InternalError
{
std::string message;
bool operator==(const InternalError& rhs) const;
};
struct CannotCallNonFunction
{
TypeId ty;
@ -293,12 +300,12 @@ struct NormalizationTooComplex
}
};
using TypeErrorData =
Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods, DuplicateTypeDefinition,
CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, IncorrectGenericParameterCount, SyntaxError,
CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed,
ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter, CannotInferBinaryOperation,
MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated, NormalizationTooComplex>;
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
TypesAreUnrelated, NormalizationTooComplex>;
struct TypeError
{
@ -339,7 +346,13 @@ T* get(TypeError& e)
using ErrorVec = std::vector<TypeError>;
struct TypeErrorToStringOptions
{
FileResolver* fileResolver = nullptr;
};
std::string toString(const TypeError& error);
std::string toString(const TypeError& error, TypeErrorToStringOptions options);
bool containsParseErrorName(const TypeError& error);
@ -356,4 +369,24 @@ struct InternalErrorReporter
[[noreturn]] void ice(const std::string& message);
};
class InternalCompilerError : public std::exception {
public:
explicit InternalCompilerError(const std::string& message, const std::string& moduleName)
: message(message)
, moduleName(moduleName)
{
}
explicit InternalCompilerError(const std::string& message, const std::string& moduleName, const Location& location)
: message(message)
, moduleName(moduleName)
, location(location)
{
}
virtual const char* what() const throw();
const std::string message;
const std::string moduleName;
const std::optional<Location> location;
};
} // namespace Luau

View File

@ -30,6 +30,7 @@ std::ostream& operator<<(std::ostream& lhs, const OccursCheckFailed& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownRequire& error);
std::ostream& operator<<(std::ostream& lhs, const UnknownPropButFoundLikeProp& e);
std::ostream& operator<<(std::ostream& lhs, const GenericError& error);
std::ostream& operator<<(std::ostream& lhs, const InternalError& error);
std::ostream& operator<<(std::ostream& lhs, const FunctionExitsWithoutReturning& error);
std::ostream& operator<<(std::ostream& lhs, const MissingProperties& error);
std::ostream& operator<<(std::ostream& lhs, const IllegalRequire& error);

View File

@ -1,10 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Error.h"
#include "Luau/FileResolver.h"
#include "Luau/ParseOptions.h"
#include "Luau/Error.h"
#include "Luau/ParseResult.h"
#include "Luau/Scope.h"
#include "Luau/TypeArena.h"
#include <memory>
@ -19,7 +20,9 @@ struct Module;
using ScopePtr = std::shared_ptr<struct Scope>;
using ModulePtr = std::shared_ptr<Module>;
struct Scope2;
class AstType;
class AstTypePack;
/// Root of the AST of a parsed source file
struct SourceModule
@ -73,6 +76,8 @@ struct Module
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
std::unordered_map<Name, TypeId> declaredGlobals;
ErrorVec errors;

View File

@ -9,8 +9,8 @@ namespace Luau
struct InternalErrorReporter;
bool isSubtype(TypeId superTy, TypeId subTy, InternalErrorReporter& ice);
bool isSubtype(TypePackId superTy, TypePackId subTy, InternalErrorReporter& ice);
bool isSubtype(TypeId subTy, TypeId superTy, InternalErrorReporter& ice);
bool isSubtype(TypePackId subTy, TypePackId superTy, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, TypeArena& arena, InternalErrorReporter& ice);
std::pair<TypeId, bool> normalize(TypeId ty, const ModulePtr& module, InternalErrorReporter& ice);

View File

@ -6,8 +6,6 @@
#include <stdexcept>
#include <exception>
LUAU_FASTFLAG(LuauRecursionLimitException);
namespace Luau
{
@ -39,21 +37,12 @@ private:
struct RecursionLimiter : RecursionCounter
{
// TODO: remove ctx after LuauRecursionLimitException is removed
RecursionLimiter(int* count, int limit, const char* ctx)
RecursionLimiter(int* count, int limit)
: RecursionCounter(count)
{
LUAU_ASSERT(ctx);
if (limit > 0 && *count > limit)
{
if (FFlag::LuauRecursionLimitException)
throw RecursionLimitException();
else
{
std::string m = "Internal recursion counter limit exceeded: ";
m += ctx;
throw std::runtime_error(m);
}
throw RecursionLimitException();
}
}
};

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
#pragma once
#include "Luau/Constraint.h"
#include "Luau/Location.h"
#include "Luau/TypeVar.h"
@ -64,4 +65,21 @@ struct Scope
std::unordered_map<Name, TypePackId> typeAliasTypePackParameters;
};
struct Scope2
{
// The parent scope of this scope. Null if there is no parent (i.e. this
// is the module-level scope).
Scope2* parent = nullptr;
// All the children of this scope.
std::vector<Scope2*> children;
std::unordered_map<Symbol, TypeId> bindings; // TODO: I think this can be a DenseHashMap
std::unordered_map<Name, TypeId> typeBindings;
TypePackId returnType;
// All constraints belonging to this scope.
std::vector<ConstraintPtr> constraints;
std::optional<TypeId> lookup(Symbol sym);
std::optional<TypeId> lookupTypeBinding(const Name& name);
};
} // namespace Luau

View File

@ -287,7 +287,6 @@ struct FunctionTypeVar
bool hasSelf;
Tags tags;
bool hasNoGenerics = false;
bool generalized = false;
};
enum class TableState

View File

@ -117,6 +117,7 @@ struct Generic
explicit Generic(const Name& name);
explicit Generic(Scope2* scope);
Generic(TypeLevel level, const Name& name);
Generic(Scope2* scope, const Name& name);
int index;
TypeLevel level;

View File

@ -79,12 +79,8 @@ private:
void tryUnifySingletons(TypeId subTy, TypeId superTy);
void tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall = false);
void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
void DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false);
void tryUnifyFreeTable(TypeId subTy, TypeId superTy);
void tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection);
void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed);
void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
TypeId widen(TypeId ty);
TypePackId widen(TypePackId tp);

View File

@ -169,7 +169,7 @@ struct GenericTypeVarVisitor
void traverse(TypeId ty)
{
RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit, "TypeVarVisitor"};
RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit};
if (visit_detail::hasSeen(seen, ty))
{

View File

@ -317,7 +317,7 @@ TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
if (tp->persistent)
return tp;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit, "cloning TypePackId");
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypePackId& res = cloneState.seenTypePacks[tp];
@ -335,7 +335,7 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent)
return typeId;
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit, "cloning TypeId");
RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
TypeId& res = cloneState.seenTypes[typeId];

View File

@ -5,9 +5,8 @@
namespace Luau
{
Constraint::Constraint(ConstraintV&& c, Location location)
Constraint::Constraint(ConstraintV&& c)
: c(std::move(c))
, location(location)
{
}

View File

@ -2,28 +2,13 @@
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/Scope.h"
namespace Luau
{
const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp
std::optional<TypeId> Scope2::lookup(Symbol sym)
{
Scope2* s = this;
while (true)
{
auto it = s->bindings.find(sym);
if (it != s->bindings.end())
return it->second;
if (s->parent)
s = s->parent;
else
return std::nullopt;
}
}
ConstraintGraphBuilder::ConstraintGraphBuilder(TypeArena* arena)
: singletonTypes(getSingletonTypes())
, arena(arena)
@ -59,10 +44,10 @@ Scope2* ConstraintGraphBuilder::childScope(Location location, Scope2* parent)
return borrow;
}
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv, Location location)
void ConstraintGraphBuilder::addConstraint(Scope2* scope, ConstraintV cv)
{
LUAU_ASSERT(scope);
scope->constraints.emplace_back(new Constraint{std::move(cv), location});
scope->constraints.emplace_back(new Constraint{std::move(cv)});
}
void ConstraintGraphBuilder::addConstraint(Scope2* scope, std::unique_ptr<Constraint> c)
@ -79,6 +64,13 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block)
rootScope = scopes.back().second.get();
rootScope->returnType = freshTypePack(rootScope);
// TODO: We should share the global scope.
rootScope->typeBindings["nil"] = singletonTypes.nilType;
rootScope->typeBindings["number"] = singletonTypes.numberType;
rootScope->typeBindings["string"] = singletonTypes.stringType;
rootScope->typeBindings["boolean"] = singletonTypes.booleanType;
rootScope->typeBindings["thread"] = singletonTypes.threadType;
visit(rootScope, block);
}
@ -102,6 +94,8 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStat* stat)
checkPack(scope, e->expr);
else if (auto i = stat->as<AstStatIf>())
visit(scope, i);
else if (auto a = stat->as<AstStatTypeAlias>())
visit(scope, a);
else
LUAU_ASSERT(0);
}
@ -114,8 +108,14 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
for (AstLocal* local : local->vars)
{
// TODO annotations
TypeId ty = freshType(scope);
if (local->annotation)
{
TypeId annotation = resolveType(scope, local->annotation);
addConstraint(scope, SubtypeConstraint{ty, annotation});
}
varTypes.push_back(ty);
scope->bindings[local] = ty;
}
@ -136,14 +136,14 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocal* local)
{
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack}, local->location);
addConstraint(scope, PackSubtypeConstraint{exprPack, tailPack});
}
}
else
{
TypeId exprType = check(scope, local->values.data[i]);
if (i < varTypes.size())
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType}, local->vars.data[i]->location);
addConstraint(scope, SubtypeConstraint{varTypes[i], exprType});
}
}
}
@ -188,7 +188,7 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatLocalFunction* function
checkFunctionBody(innerScope, function->func);
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}, function->location}};
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
addConstraints(c.get(), innerScope);
addConstraint(scope, std::move(c));
@ -240,7 +240,7 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatFunction* function)
checkFunctionBody(innerScope, function->func);
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}, function->location}};
std::unique_ptr<Constraint> c{new Constraint{GeneralizationConstraint{functionType, actualFunctionType, innerScope}}};
addConstraints(c.get(), innerScope);
addConstraint(scope, std::move(c));
@ -251,13 +251,26 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatReturn* ret)
LUAU_ASSERT(scope);
TypePackId exprTypes = checkPack(scope, ret->list);
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}, ret->location);
addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType});
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatBlock* block)
{
LUAU_ASSERT(scope);
// In order to enable mutually-recursive type aliases, we need to
// populate the type bindings before we actually check any of the
// alias statements. Since we're not ready to actually resolve
// any of the annotations, we just use a fresh type for now.
for (AstStat* stat : block->body)
{
if (auto alias = stat->as<AstStatTypeAlias>())
{
TypeId initialType = freshType(scope);
scope->typeBindings[alias->name.value] = initialType;
}
}
for (AstStat* stat : block->body)
visit(scope, stat);
}
@ -267,7 +280,7 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatAssign* assign)
TypePackId varPackId = checkExprList(scope, assign->vars);
TypePackId valuePack = checkPack(scope, assign->values);
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}, assign->location);
addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId});
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatIf* ifStatement)
@ -284,6 +297,28 @@ void ConstraintGraphBuilder::visit(Scope2* scope, AstStatIf* ifStatement)
}
}
void ConstraintGraphBuilder::visit(Scope2* scope, AstStatTypeAlias* alias)
{
// TODO: Exported type aliases
// TODO: Generic type aliases
auto it = scope->typeBindings.find(alias->name.value);
// This should always be here since we do a separate pass over the
// AST to set up typeBindings. If it's not, we've somehow skipped
// this alias in that first pass.
LUAU_ASSERT(it != scope->typeBindings.end());
TypeId ty = resolveType(scope, alias->type);
// Rather than using a subtype constraint, we instead directly bind
// the free type we generated in the first pass to the resolved type.
// This prevents a case where you could cause another constraint to
// bind the free alias type to an unrelated type, causing havoc.
asMutable(it->second)->ty.emplace<BoundTypeVar>(ty);
addConstraint(scope, NameConstraint{ty, alias->name.value});
}
TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstArray<AstExpr*> exprs)
{
LUAU_ASSERT(scope);
@ -350,13 +385,13 @@ TypePackId ConstraintGraphBuilder::checkPack(Scope2* scope, AstExpr* expr)
astOriginalCallTypes[call->func] = fnType;
TypeId instantiatedType = freshType(scope);
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType}, expr->location);
addConstraint(scope, InstantiationConstraint{instantiatedType, fnType});
TypePackId rets = freshTypePack(scope);
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
TypeId inferredFnType = arena->addType(ftv);
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType}, expr->location);
addConstraint(scope, SubtypeConstraint{inferredFnType, instantiatedType});
result = rets;
}
else
@ -413,7 +448,7 @@ TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExpr* expr)
TypePack onePack{{typeResult}, freshTypePack(scope)};
TypePackId oneTypePack = arena->addTypePack(std::move(onePack));
addConstraint(scope, PackSubtypeConstraint{packResult, oneTypePack}, expr->location);
addConstraint(scope, PackSubtypeConstraint{packResult, oneTypePack});
return typeResult;
}
@ -454,7 +489,7 @@ TypeId ConstraintGraphBuilder::check(Scope2* scope, AstExprIndexName* indexName)
TypeId expectedTableType = arena->addType(std::move(ttv));
addConstraint(scope, SubtypeConstraint{obj, expectedTableType}, indexName->location);
addConstraint(scope, SubtypeConstraint{obj, expectedTableType});
return result;
}
@ -465,8 +500,7 @@ TypeId ConstraintGraphBuilder::checkExprTable(Scope2* scope, AstExprTable* expr)
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
LUAU_ASSERT(ttv);
auto createIndexer = [this, scope, ttv](
TypeId currentIndexType, TypeId currentResultType, Location itemLocation, std::optional<Location> keyLocation) {
auto createIndexer = [this, scope, ttv](TypeId currentIndexType, TypeId currentResultType) {
if (!ttv->indexer)
{
TypeId indexType = this->freshType(scope);
@ -474,8 +508,8 @@ TypeId ConstraintGraphBuilder::checkExprTable(Scope2* scope, AstExprTable* expr)
ttv->indexer = TableIndexer{indexType, resultType};
}
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType}, keyLocation ? *keyLocation : itemLocation);
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType}, itemLocation);
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexType, currentIndexType});
addConstraint(scope, SubtypeConstraint{ttv->indexer->indexResultType, currentResultType});
};
for (const AstExprTable::Item& item : expr->items)
@ -495,13 +529,13 @@ TypeId ConstraintGraphBuilder::checkExprTable(Scope2* scope, AstExprTable* expr)
}
else
{
createIndexer(keyTy, itemTy, item.value->location, item.key->location);
createIndexer(keyTy, itemTy);
}
}
else
{
TypeId numberType = singletonTypes.numberType;
createIndexer(numberType, itemTy, item.value->location, std::nullopt);
createIndexer(numberType, itemTy);
}
}
@ -514,15 +548,29 @@ std::pair<TypeId, Scope2*> ConstraintGraphBuilder::checkFunctionSignature(Scope2
TypePackId returnType = freshTypePack(innerScope);
innerScope->returnType = returnType;
if (fn->returnAnnotation)
{
TypePackId annotatedRetType = resolveTypePack(innerScope, *fn->returnAnnotation);
addConstraint(innerScope, PackSubtypeConstraint{returnType, annotatedRetType});
}
std::vector<TypeId> argTypes;
for (AstLocal* local : fn->args)
{
TypeId t = freshType(innerScope);
argTypes.push_back(t);
innerScope->bindings[local] = t; // TODO annotations
innerScope->bindings[local] = t;
if (local->annotation)
{
TypeId argAnnotation = resolveType(innerScope, local->annotation);
addConstraint(innerScope, SubtypeConstraint{t, argAnnotation});
}
}
// TODO: Vararg annotation.
FunctionTypeVar actualFunction{arena->addTypePack(argTypes), returnType};
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
LUAU_ASSERT(actualFunctionType);
@ -541,10 +589,171 @@ void ConstraintGraphBuilder::checkFunctionBody(Scope2* scope, AstExprFunction* f
if (nullptr != getFallthrough(fn->body))
{
TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty}, fn->body->location);
addConstraint(scope, PackSubtypeConstraint{scope->returnType, empty});
}
}
TypeId ConstraintGraphBuilder::resolveType(Scope2* scope, AstType* ty)
{
TypeId result = nullptr;
if (auto ref = ty->as<AstTypeReference>())
{
// TODO: Support imported types w/ require tracing.
// TODO: Support generic type references.
LUAU_ASSERT(!ref->prefix);
LUAU_ASSERT(!ref->hasParameterList);
// TODO: If it doesn't exist, should we introduce a free binding?
// This is probably important for handling type aliases.
result = scope->lookupTypeBinding(ref->name.value).value_or(singletonTypes.errorRecoveryType());
}
else if (auto tab = ty->as<AstTypeTable>())
{
TableTypeVar::Props props;
std::optional<TableIndexer> indexer;
for (const AstTableProp& prop : tab->props)
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type);
// TODO: Fill in location.
props[name] = {propTy};
}
if (tab->indexer)
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, tab->indexer->indexType),
resolveType(scope, tab->indexer->resultType),
};
}
// TODO: Remove TypeLevel{} here, we don't need it.
result = arena->addType(TableTypeVar{props, indexer, TypeLevel{}, TableState::Sealed});
}
else if (auto fn = ty->as<AstTypeFunction>())
{
// TODO: Generic functions.
// TODO: Scope (though it may not be needed).
// TODO: Recursion limit.
TypePackId argTypes = resolveTypePack(scope, fn->argTypes);
TypePackId returnTypes = resolveTypePack(scope, fn->returnTypes);
// TODO: Is this the right constructor to use?
result = arena->addType(FunctionTypeVar{argTypes, returnTypes});
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(result);
ftv->argNames.reserve(fn->argNames.size);
for (const auto& el : fn->argNames)
{
if (el)
{
const auto& [name, location] = *el;
ftv->argNames.push_back(FunctionArgument{name.value, location});
}
else
{
ftv->argNames.push_back(std::nullopt);
}
}
}
else if (auto tof = ty->as<AstTypeTypeof>())
{
// TODO: Recursion limit.
TypeId exprType = check(scope, tof->expr);
result = exprType;
}
else if (auto unionAnnotation = ty->as<AstTypeUnion>())
{
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
// TODO: Recursion limit.
parts.push_back(resolveType(scope, part));
}
result = arena->addType(UnionTypeVar{parts});
}
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
{
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
// TODO: Recursion limit.
parts.push_back(resolveType(scope, part));
}
result = arena->addType(IntersectionTypeVar{parts});
}
else if (auto boolAnnotation = ty->as<AstTypeSingletonBool>())
{
result = arena->addType(SingletonTypeVar(BooleanSingleton{boolAnnotation->value}));
}
else if (auto stringAnnotation = ty->as<AstTypeSingletonString>())
{
result = arena->addType(SingletonTypeVar(StringSingleton{std::string(stringAnnotation->value.data, stringAnnotation->value.size)}));
}
else if (ty->is<AstTypeError>())
{
result = singletonTypes.errorRecoveryType();
}
else
{
LUAU_ASSERT(0);
result = singletonTypes.errorRecoveryType();
}
astResolvedTypes[ty] = result;
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(Scope2* scope, AstTypePack* tp)
{
TypePackId result;
if (auto expl = tp->as<AstTypePackExplicit>())
{
result = resolveTypePack(scope, expl->typeList);
}
else if (auto var = tp->as<AstTypePackVariadic>())
{
TypeId ty = resolveType(scope, var->variadicType);
result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}});
}
else if (auto gen = tp->as<AstTypePackGeneric>())
{
result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}});
}
else
{
LUAU_ASSERT(0);
result = singletonTypes.errorRecoveryTypePack();
}
astResolvedTypePacks[tp] = result;
return result;
}
TypePackId ConstraintGraphBuilder::resolveTypePack(Scope2* scope, const AstTypeList& list)
{
std::vector<TypeId> head;
for (AstType* headTy : list.types)
{
head.push_back(resolveType(scope, headTy));
}
std::optional<TypePackId> tail = std::nullopt;
if (list.tailType)
{
tail = resolveTypePack(scope, list.tailType);
}
return arena->addTypePack(TypePack{head, tail});
}
void collectConstraints(std::vector<NotNull<Constraint>>& result, Scope2* scope)
{
for (const auto& c : scope->constraints)

View File

@ -2,6 +2,7 @@
#include "Luau/ConstraintSolver.h"
#include "Luau/Instantiation.h"
#include "Luau/Location.h"
#include "Luau/Quantify.h"
#include "Luau/ToString.h"
#include "Luau/Unifier.h"
@ -179,6 +180,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*gc, constraint, force);
else if (auto ic = get<InstantiationConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint))
success = tryDispatch(*nc, constraint);
else
LUAU_ASSERT(0);
@ -197,7 +200,7 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
else if (isBlocked(c.superType))
return block(c.superType, constraint);
unify(c.subType, c.superType, constraint->location);
unify(c.subType, c.superType);
unblock(c.subType);
unblock(c.superType);
@ -207,7 +210,7 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
{
unify(c.subPack, c.superPack, constraint->location);
unify(c.subPack, c.superPack);
unblock(c.subPack);
unblock(c.superPack);
@ -222,7 +225,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
if (isBlocked(c.generalizedType))
asMutable(c.generalizedType)->ty.emplace<BoundTypeVar>(c.sourceType);
else
unify(c.generalizedType, c.sourceType, constraint->location);
unify(c.generalizedType, c.sourceType);
TypeId generalized = quantify(arena, c.sourceType, c.scope);
*asMutable(c.sourceType) = *generalized;
@ -243,12 +246,28 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
std::optional<TypeId> instantiated = inst.substitute(c.superType);
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
unify(c.subType, *instantiated, constraint->location);
unify(c.subType, *instantiated);
unblock(c.subType);
return true;
}
bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
{
if (isBlocked(c.namedType))
return block(c.namedType, constraint);
TypeId target = follow(c.namedType);
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
ttv->name = c.name;
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(target))
mtv->syntheticName = c.name;
else
return block(c.namedType, constraint);
return true;
}
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
{
blocked[target].push_back(constraint);
@ -321,19 +340,19 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
}
void ConstraintSolver::unify(TypeId subType, TypeId superType, Location location)
void ConstraintSolver::unify(TypeId subType, TypeId superType)
{
UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, location, Covariant, sharedState};
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
u.tryUnify(subType, superType);
u.log.commit();
}
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, Location location)
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
{
UnifierSharedState sharedState{&iceReporter};
Unifier u{arena, Mode::Strict, location, Covariant, sharedState};
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
u.tryUnify(subPack, superPack);
u.log.commit();

View File

@ -7,6 +7,9 @@
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchModuleNameResolution, false)
LUAU_FASTFLAGVARIABLE(LuauUseInternalCompilerErrorException, false)
static std::string wrongNumberOfArgsString(size_t expectedCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{
std::string s = "expects ";
@ -49,6 +52,8 @@ namespace Luau
struct ErrorConverter
{
FileResolver* fileResolver = nullptr;
std::string operator()(const Luau::TypeMismatch& tm) const
{
std::string givenTypeName = Luau::toString(tm.givenType);
@ -62,8 +67,18 @@ struct ErrorConverter
{
if (auto wantedDefinitionModule = getDefinitionModuleName(tm.wantedType))
{
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
"' from '" + *wantedDefinitionModule + "'";
if (FFlag::LuauTypeMismatchModuleNameResolution && fileResolver != nullptr)
{
std::string givenModuleName = fileResolver->getHumanReadableModuleName(*givenDefinitionModule);
std::string wantedModuleName = fileResolver->getHumanReadableModuleName(*wantedDefinitionModule);
result = "Type '" + givenTypeName + "' from '" + givenModuleName + "' could not be converted into '" + wantedTypeName +
"' from '" + wantedModuleName + "'";
}
else
{
result = "Type '" + givenTypeName + "' from '" + *givenDefinitionModule + "' could not be converted into '" + wantedTypeName +
"' from '" + *wantedDefinitionModule + "'";
}
}
}
}
@ -78,7 +93,14 @@ struct ErrorConverter
if (!tm.reason.empty())
result += tm.reason + " ";
result += Luau::toString(*tm.error);
if (FFlag::LuauTypeMismatchModuleNameResolution)
{
result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver});
}
else
{
result += Luau::toString(*tm.error);
}
}
else if (!tm.reason.empty())
{
@ -280,6 +302,11 @@ struct ErrorConverter
return e.message;
}
std::string operator()(const Luau::InternalError& e) const
{
return e.message;
}
std::string operator()(const Luau::CannotCallNonFunction& e) const
{
return "Cannot call non-function " + toString(e.ty);
@ -598,6 +625,11 @@ bool GenericError::operator==(const GenericError& rhs) const
return message == rhs.message;
}
bool InternalError::operator==(const InternalError& rhs) const
{
return message == rhs.message;
}
bool CannotCallNonFunction::operator==(const CannotCallNonFunction& rhs) const
{
return ty == rhs.ty;
@ -685,7 +717,12 @@ bool TypesAreUnrelated::operator==(const TypesAreUnrelated& rhs) const
std::string toString(const TypeError& error)
{
ErrorConverter converter;
return toString(error, TypeErrorToStringOptions{});
}
std::string toString(const TypeError& error, TypeErrorToStringOptions options)
{
ErrorConverter converter{options.fileResolver};
return Luau::visit(converter, error.data);
}
@ -773,6 +810,9 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
else if constexpr (std::is_same_v<T, GenericError>)
{
}
else if constexpr (std::is_same_v<T, InternalError>)
{
}
else if constexpr (std::is_same_v<T, CannotCallNonFunction>)
{
e.ty = clone(e.ty);
@ -847,22 +887,51 @@ void copyErrors(ErrorVec& errors, TypeArena& destArena)
void InternalErrorReporter::ice(const std::string& message, const Location& location)
{
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
if (FFlag::LuauUseInternalCompilerErrorException)
{
InternalCompilerError error(message, moduleName, location);
if (onInternalError)
onInternalError(error.what());
if (onInternalError)
onInternalError(error.what());
throw error;
throw error;
}
else
{
std::runtime_error error("Internal error in " + moduleName + " at " + toString(location) + ": " + message);
if (onInternalError)
onInternalError(error.what());
throw error;
}
}
void InternalErrorReporter::ice(const std::string& message)
{
std::runtime_error error("Internal error in " + moduleName + ": " + message);
if (FFlag::LuauUseInternalCompilerErrorException)
{
InternalCompilerError error(message, moduleName);
if (onInternalError)
onInternalError(error.what());
if (onInternalError)
onInternalError(error.what());
throw error;
throw error;
}
else
{
std::runtime_error error("Internal error in " + moduleName + ": " + message);
if (onInternalError)
onInternalError(error.what());
throw error;
}
}
const char* InternalCompilerError::what() const throw()
{
return this->message.data();
}
} // namespace Luau

View File

@ -801,6 +801,8 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const Sco
result->astTypes = std::move(cgb.astTypes);
result->astTypePacks = std::move(cgb.astTypePacks);
result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes);
result->astResolvedTypes = std::move(cgb.astResolvedTypes);
result->astResolvedTypePacks = std::move(cgb.astResolvedTypePacks);
result->clonePublicInterface(iceHandler);

View File

@ -111,6 +111,8 @@ static void errorToString(std::ostream& stream, const T& err)
}
else if constexpr (std::is_same_v<T, GenericError>)
stream << "GenericError { " << err.message << " }";
else if constexpr (std::is_same_v<T, InternalError>)
stream << "InternalError { " << err.message << " }";
else if constexpr (std::is_same_v<T, CannotCallNonFunction>)
stream << "CannotCallNonFunction { " << toString(err.ty) << " }";
else if constexpr (std::is_same_v<T, ExtraInformation>)

View File

@ -11,7 +11,6 @@
#include "Luau/TypePack.h"
#include "Luau/TypeVar.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/ConstraintGraphBuilder.h" // FIXME: For Scope2 TODO pull out into its own header
#include <algorithm>

View File

@ -2,11 +2,10 @@
#include "Luau/Quantify.h"
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
#include "Luau/TxnLog.h"
#include "Luau/Scope.h"
#include "Luau/Substitution.h"
#include "Luau/TxnLog.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
LUAU_FASTFLAG(LuauAlwaysQuantify);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
@ -177,8 +176,6 @@ void quantify(TypeId ty, TypeLevel level)
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
void quantify(TypeId ty, Scope2* scope)
@ -201,8 +198,6 @@ void quantify(TypeId ty, Scope2* scope)
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
struct PureQuantifier : Substitution

View File

@ -121,4 +121,36 @@ std::optional<Binding> Scope::linearSearchForBinding(const std::string& name, bo
return std::nullopt;
}
std::optional<TypeId> Scope2::lookup(Symbol sym)
{
Scope2* s = this;
while (true)
{
auto it = s->bindings.find(sym);
if (it != s->bindings.end())
return it->second;
if (s->parent)
s = s->parent;
else
return std::nullopt;
}
}
std::optional<TypeId> Scope2::lookupTypeBinding(const Name& name)
{
Scope2* s = this;
while (s)
{
auto it = s->typeBindings.find(name);
if (it != s->typeBindings.end())
return it->second;
s = s->parent;
}
return std::nullopt;
}
} // namespace Luau

View File

@ -1411,6 +1411,12 @@ std::string toString(const Constraint& c, ToStringOptions& opts)
opts.nameMap = std::move(superStr.nameMap);
return subStr.name + " ~ inst " + superStr.name;
}
else if (const NameConstraint* nc = Luau::get<NameConstraint>(c))
{
ToStringResult namedStr = toStringDetailed(nc->namedType, opts);
opts.nameMap = std::move(namedStr.nameMap);
return "@name(" + namedStr.name + ") = " + nc->name;
}
else
{
LUAU_ASSERT(false);

View File

@ -7,6 +7,9 @@
#include "Luau/AstQuery.h"
#include "Luau/Clone.h"
#include "Luau/Normalize.h"
#include "Luau/ConstraintGraphBuilder.h" // FIXME move Scope2 into its own header
#include "Luau/Unifier.h"
#include "Luau/ToString.h"
namespace Luau
{
@ -39,6 +42,104 @@ struct TypeChecker2 : public AstVisitor
return follow(*ty);
}
TypeId lookupAnnotation(AstType* annotation)
{
TypeId* ty = module->astResolvedTypes.find(annotation);
LUAU_ASSERT(ty);
return follow(*ty);
}
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena)
{
std::vector<TypeId> head;
for (size_t i = 0; i < exprs.size - 1; ++i)
{
head.push_back(lookupType(exprs.data[i]));
}
TypePackId tail = lookupPack(exprs.data[exprs.size - 1]);
return arena.addTypePack(TypePack{head, tail});
}
Scope2* findInnermostScope(Location location)
{
Scope2* bestScope = module->getModuleScope2();
Location bestLocation = module->scope2s[0].first;
for (size_t i = 0; i < module->scope2s.size(); ++i)
{
auto& [scopeBounds, scope] = module->scope2s[i];
if (scopeBounds.encloses(location))
{
if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end)
{
bestScope = scope.get();
bestLocation = scopeBounds;
}
}
else
{
// TODO: Is this sound? This relies on the fact that scopes are inserted
// into the scope list in the order that they appear in the AST.
break;
}
}
return bestScope;
}
bool visit(AstStatLocal* local) override
{
for (size_t i = 0; i < local->values.size; ++i)
{
AstExpr* value = local->values.data[i];
if (i == local->values.size - 1)
{
if (i < local->values.size)
{
TypePackId valueTypes = lookupPack(value);
auto it = begin(valueTypes);
for (size_t j = i; j < local->vars.size; ++j)
{
if (it == end(valueTypes))
{
break;
}
AstLocal* var = local->vars.data[i];
if (var->annotation)
{
TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(*it, varType, ice))
{
reportError(TypeMismatch{varType, *it}, value->location);
}
}
++it;
}
}
}
else
{
TypeId valueType = lookupType(value);
AstLocal* var = local->vars.data[i];
if (var->annotation)
{
TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(varType, valueType, ice))
{
reportError(TypeMismatch{varType, valueType}, value->location);
}
}
}
}
return true;
}
bool visit(AstStatAssign* assign) override
{
size_t count = std::min(assign->vars.size, assign->values.size);
@ -62,6 +163,30 @@ struct TypeChecker2 : public AstVisitor
return true;
}
bool visit(AstStatReturn* ret) override
{
Scope2* scope = findInnermostScope(ret->location);
TypePackId expectedRetType = scope->returnType;
TypeArena arena;
TypePackId actualRetType = reconstructPack(ret->list, arena);
UnifierSharedState sharedState{&ice};
Unifier u{&arena, Mode::Strict, ret->location, Covariant, sharedState};
u.anyIsTop = true;
u.tryUnify(actualRetType, expectedRetType);
const bool ok = u.errors.empty() && u.log.empty();
if (!ok)
{
for (const TypeError& e : u.errors)
module->errors.push_back(e);
}
return true;
}
bool visit(AstExprCall* call) override
{
TypePackId expectedRetType = lookupPack(call);
@ -91,6 +216,35 @@ struct TypeChecker2 : public AstVisitor
return true;
}
bool visit(AstExprFunction* fn) override
{
TypeId inferredFnTy = lookupType(fn);
const FunctionTypeVar* inferredFtv = get<FunctionTypeVar>(inferredFnTy);
LUAU_ASSERT(inferredFtv);
auto argIt = begin(inferredFtv->argTypes);
for (const auto& arg : fn->args)
{
if (argIt == end(inferredFtv->argTypes))
break;
if (arg->annotation)
{
TypeId inferredArgTy = *argIt;
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
if (!isSubtype(annotatedArgTy, inferredArgTy, ice))
{
reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location);
}
}
++argIt;
}
return true;
}
bool visit(AstExprIndexName* indexName) override
{
TypeId leftType = lookupType(indexName->expr);
@ -144,6 +298,25 @@ struct TypeChecker2 : public AstVisitor
return true;
}
bool visit(AstType* ty) override
{
return true;
}
bool visit(AstTypeReference* ty) override
{
Scope2* scope = findInnermostScope(ty->location);
// TODO: Imported types
// TODO: Generic types
if (!scope->lookupTypeBinding(ty->name.value))
{
reportError(UnknownSymbol{ty->name.value, UnknownSymbol::Context::Type}, ty->location);
}
return true;
}
void reportError(TypeErrorData&& data, const Location& location)
{
module->errors.emplace_back(location, sourceModule->name, std::move(data));

View File

@ -35,13 +35,9 @@ LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false)
LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false)
LUAU_FASTFLAGVARIABLE(LuauOnlyMutateInstantiatedTables, false)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAG(LuauNormalizeFlagIsConservative)
LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false)
LUAU_FASTFLAGVARIABLE(LuauRecursionLimitException, false);
LUAU_FASTFLAGVARIABLE(LuauApplyTypeFunctionFix, false);
LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false);
LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false)
LUAU_FASTFLAG(LuauQuantifyConstrained)
@ -275,22 +271,15 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
{
if (FFlag::LuauRecursionLimitException)
{
try
{
return checkWithoutRecursionCheck(module, mode, environmentScope);
}
catch (const RecursionLimitException&)
{
reportErrorCodeTooComplex(module.root->location);
return std::move(currentModule);
}
}
else
try
{
return checkWithoutRecursionCheck(module, mode, environmentScope);
}
catch (const RecursionLimitException&)
{
reportErrorCodeTooComplex(module.root->location);
return std::move(currentModule);
}
}
ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
@ -445,22 +434,15 @@ void TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
reportErrorCodeTooComplex(block.location);
return;
}
if (FFlag::LuauRecursionLimitException)
{
try
{
checkBlockWithoutRecursionCheck(scope, block);
}
catch (const RecursionLimitException&)
{
reportErrorCodeTooComplex(block.location);
return;
}
}
else
try
{
checkBlockWithoutRecursionCheck(scope, block);
}
catch (const RecursionLimitException&)
{
reportErrorCodeTooComplex(block.location);
return;
}
}
void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& block)
@ -1917,7 +1899,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
for (TypeId t : utv)
{
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit, "getIndexTypeForType unions");
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
// Not needed when we normalize types.
if (get<AnyTypeVar>(follow(t)))
@ -1967,7 +1949,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
for (TypeId t : itv->parts)
{
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit, "getIndexTypeFromType intersections");
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, false))
parts.push_back(*ty);
@ -2190,7 +2172,7 @@ TypeId TypeChecker::checkExprTable(
}
}
TableState state = (expr.items.size == 0 || isNonstrictMode() || FFlag::LuauUnsealedTableLiteral) ? TableState::Unsealed : TableState::Sealed;
TableState state = TableState::Unsealed;
TableTypeVar table = TableTypeVar{std::move(props), indexer, scope->level, state};
table.definitionModuleName = currentModuleName;
return addType(table);
@ -5175,9 +5157,7 @@ TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack
bool ApplyTypeFunction::isDirty(TypeId ty)
{
if (FFlag::LuauApplyTypeFunctionFix && typeArguments.count(ty))
return true;
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypeVar>(ty))
if (typeArguments.count(ty))
return true;
else if (const FreeTypeVar* ftv = get<FreeTypeVar>(ty))
{
@ -5191,9 +5171,7 @@ bool ApplyTypeFunction::isDirty(TypeId ty)
bool ApplyTypeFunction::isDirty(TypePackId tp)
{
if (FFlag::LuauApplyTypeFunctionFix && typePackArguments.count(tp))
return true;
else if (!FFlag::LuauApplyTypeFunctionFix && get<GenericTypePack>(tp))
if (typePackArguments.count(tp))
return true;
else
return false;
@ -5218,29 +5196,15 @@ bool ApplyTypeFunction::ignoreChildren(TypePackId tp)
TypeId ApplyTypeFunction::clean(TypeId ty)
{
TypeId& arg = typeArguments[ty];
if (FFlag::LuauApplyTypeFunctionFix)
{
LUAU_ASSERT(arg);
return arg;
}
else if (arg)
return arg;
else
return addType(FreeTypeVar{level});
LUAU_ASSERT(arg);
return arg;
}
TypePackId ApplyTypeFunction::clean(TypePackId tp)
{
TypePackId& arg = typePackArguments[tp];
if (FFlag::LuauApplyTypeFunctionFix)
{
LUAU_ASSERT(arg);
return arg;
}
else if (arg)
return arg;
else
return addTypePack(FreeTypePack{level});
LUAU_ASSERT(arg);
return arg;
}
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
@ -5273,7 +5237,7 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
TypeId target = follow(instantiated);
bool needsClone = follow(tf.type) == target;
bool shouldMutate = (!FFlag::LuauOnlyMutateInstantiatedTables || getTableType(tf.type));
bool shouldMutate = getTableType(tf.type);
TableTypeVar* ttv = getMutableTableType(target);
if (shouldMutate && ttv && needsClone)

View File

@ -23,7 +23,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSubtypingAddOptPropsToUnsealedTables)
LUAU_FASTFLAG(LuauNonCopyableTypeVarFields)
namespace Luau
@ -172,22 +171,15 @@ bool isString(TypeId ty)
// Returns true when ty is a supertype of string
bool maybeString(TypeId ty)
{
if (FFlag::LuauSubtypingAddOptPropsToUnsealedTables)
{
ty = follow(ty);
ty = follow(ty);
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
return true;
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty))
return true;
if (auto utv = get<UnionTypeVar>(ty))
return std::any_of(begin(utv), end(utv), maybeString);
if (auto utv = get<UnionTypeVar>(ty))
return std::any_of(begin(utv), end(utv), maybeString);
return false;
}
else
{
return isString(ty);
}
return false;
}
bool isThread(TypeId ty)
@ -369,7 +361,7 @@ bool maybeSingleton(TypeId ty)
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
{
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit, "hasLength");
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit);
ty = follow(ty);

View File

@ -53,6 +53,14 @@ Generic::Generic(TypeLevel level, const Name& name)
{
}
Generic::Generic(Scope2* scope, const Name& name)
: index(++nextIndex)
, scope(scope)
, name(name)
, explicitName(true)
{
}
int Generic::nextIndex = 0;
Error::Error()

View File

@ -17,11 +17,8 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTINT(LuauTypeInferIterationLimit);
LUAU_FASTFLAG(LuauAutocompleteDynamicLimits)
LUAU_FASTINTVARIABLE(LuauTypeInferLowerBoundsIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
LUAU_FASTFLAG(LuauLowerBoundsCalculation);
LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAGVARIABLE(LuauSubtypingAddOptPropsToUnsealedTables, false)
LUAU_FASTFLAGVARIABLE(LuauTxnLogRefreshFunctionPointers, false)
LUAU_FASTFLAG(LuauQuantifyConstrained)
namespace Luau
@ -354,7 +351,7 @@ void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool i
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
{
RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit, "TypeId tryUnify_");
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit);
++sharedState.counters.iterationCount;
@ -983,7 +980,7 @@ void Unifier::tryUnify(TypePackId subTp, TypePackId superTp, bool isFunctionCall
void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall)
{
RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit, "TypePackId tryUnify_");
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit);
++sharedState.counters.iterationCount;
@ -1316,12 +1313,9 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
tryUnify_(subFunction->retTypes, superFunction->retTypes);
}
if (FFlag::LuauTxnLogRefreshFunctionPointers)
{
// Updating the log may have invalidated the function pointers
superFunction = log.getMutable<FunctionTypeVar>(superTy);
subFunction = log.getMutable<FunctionTypeVar>(subTy);
}
// Updating the log may have invalidated the function pointers
superFunction = log.getMutable<FunctionTypeVar>(superTy);
subFunction = log.getMutable<FunctionTypeVar>(subTy);
ctx = context;
@ -1360,9 +1354,6 @@ struct Resetter
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
if (!FFlag::LuauTableSubtypingVariance2)
return DEPRECATED_tryUnifyTables(subTy, superTy, isIntersection);
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
@ -1379,8 +1370,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto subIter = subTable->props.find(propName);
if (subIter == subTable->props.end() && (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) &&
!isOptional(superProp.type))
if (subIter == subTable->props.end() && subTable->state == TableState::Unsealed && !isOptional(superProp.type))
missingProperties.push_back(propName);
}
@ -1398,7 +1388,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
auto superIter = superTable->props.find(propName);
if (superIter == superTable->props.end() && (FFlag::LuauSubtypingAddOptPropsToUnsealedTables || !isOptional(subProp.type)))
if (superIter == superTable->props.end())
extraProperties.push_back(propName);
}
@ -1443,7 +1433,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
if (innerState.errors.empty())
log.concat(std::move(innerState.log));
}
else if ((!FFlag::LuauSubtypingAddOptPropsToUnsealedTables || subTable->state == TableState::Unsealed) && isOptional(prop.type))
else if (subTable->state == TableState::Unsealed && isOptional(prop.type))
// This is sound because unsealed table types are precise, so `{ p : T } <: { p : T, q : U? }`
// since if `t : { p : T }` then we are guaranteed that `t.q` is `nil`.
// TODO: if the supertype is written to, the subtype may no longer be precise (alias analysis?)
@ -1512,9 +1502,6 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
else if (variance == Covariant)
{
}
else if (!FFlag::LuauSubtypingAddOptPropsToUnsealedTables && isOptional(prop.type))
{
}
else if (superTable->state == TableState::Free)
{
PendingType* pendingSuper = log.queue(superTy);
@ -1639,296 +1626,6 @@ TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> see
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, ty}});
}
void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
Resetter resetter{&variance};
variance = Invariant;
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
if (!superTable || !subTable)
ice("passed non-table types to unifyTables");
if (superTable->state == TableState::Sealed && subTable->state == TableState::Sealed)
return tryUnifySealedTables(subTy, superTy, isIntersection);
else if ((superTable->state == TableState::Sealed && subTable->state == TableState::Unsealed) ||
(superTable->state == TableState::Unsealed && subTable->state == TableState::Sealed))
return tryUnifySealedTables(subTy, superTy, isIntersection);
else if ((superTable->state == TableState::Sealed && subTable->state == TableState::Generic) ||
(superTable->state == TableState::Generic && subTable->state == TableState::Sealed))
reportError(TypeError{location, TypeMismatch{superTy, subTy}});
else if ((superTable->state == TableState::Free) != (subTable->state == TableState::Free)) // one table is free and the other is not
{
TypeId freeTypeId = subTable->state == TableState::Free ? subTy : superTy;
TypeId otherTypeId = subTable->state == TableState::Free ? superTy : subTy;
return tryUnifyFreeTable(otherTypeId, freeTypeId);
}
else if (superTable->state == TableState::Free && subTable->state == TableState::Free)
{
tryUnifyFreeTable(subTy, superTy);
// avoid creating a cycle when the types are already pointing at each other
if (follow(superTy) != follow(subTy))
{
log.bindTable(superTy, subTy);
}
return;
}
else if (superTable->state != TableState::Sealed && subTable->state != TableState::Sealed)
{
// All free tables are checked in one of the branches above
LUAU_ASSERT(superTable->state != TableState::Free);
LUAU_ASSERT(subTable->state != TableState::Free);
// Tables must have exactly the same props and their types must all unify
// I honestly have no idea if this is remotely close to reasonable.
for (const auto& [name, prop] : superTable->props)
{
const auto& r = subTable->props.find(name);
if (r == subTable->props.end())
reportError(TypeError{location, UnknownProperty{subTy, name}});
else
tryUnify_(r->second.type, prop.type);
}
if (superTable->indexer && subTable->indexer)
tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
else if (superTable->indexer)
{
// passing/assigning a table without an indexer to something that has one
// e.g. table.insert(t, 1) where t is a non-sealed table and doesn't have an indexer.
if (subTable->state == TableState::Unsealed)
{
log.changeIndexer(subTy, superTable->indexer);
}
else
reportError(TypeError{location, CannotExtendTable{subTy, CannotExtendTable::Indexer}});
}
}
else if (superTable->state == TableState::Sealed)
{
// lt is sealed and so it must be possible for rt to have precisely the same shape
// Verify that this is the case, then bind rt to lt.
ice("unsealed tables are not working yet", location);
}
else if (subTable->state == TableState::Sealed)
return tryUnifyTables(superTy, subTy, isIntersection);
else
ice("tryUnifyTables");
}
void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* freeTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
if (!freeTable || !subTable)
ice("passed non-table types to tryUnifyFreeTable");
// Any properties in freeTable must unify with those in otherTable.
// Then bind freeTable to otherTable.
for (const auto& [freeName, freeProp] : freeTable->props)
{
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
{
tryUnify_(*subProp, freeProp.type);
/*
* TypeVars are commonly cyclic, so it is entirely possible
* for unifying a property of a table to change the table itself!
* We need to check for this and start over if we notice this occurring.
*
* I believe this is guaranteed to terminate eventually because this will
* only happen when a free table is bound to another table.
*/
if (!log.getMutable<TableTypeVar>(superTy) || !log.getMutable<TableTypeVar>(subTy))
return tryUnify_(subTy, superTy);
if (TableTypeVar* pendingFreeTtv = log.getMutable<TableTypeVar>(superTy); pendingFreeTtv && pendingFreeTtv->boundTo)
return tryUnify_(subTy, superTy);
}
else
{
// If the other table is also free, then we are learning that it has more
// properties than we previously thought. Else, it is an error.
if (subTable->state == TableState::Free)
{
PendingType* pendingSub = log.queue(subTy);
TableTypeVar* pendingSubTtv = getMutable<TableTypeVar>(pendingSub);
LUAU_ASSERT(pendingSubTtv);
pendingSubTtv->props.insert({freeName, freeProp});
}
else
reportError(TypeError{location, UnknownProperty{subTy, freeName}});
}
}
if (freeTable->indexer && subTable->indexer)
{
Unifier innerState = makeChildUnifier();
innerState.tryUnifyIndexer(*subTable->indexer, *freeTable->indexer);
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
log.concat(std::move(innerState.log));
}
else if (subTable->state == TableState::Free && freeTable->indexer)
{
log.changeIndexer(superTy, subTable->indexer);
}
if (!freeTable->boundTo && subTable->state != TableState::Free)
{
log.bindTable(superTy, subTy);
}
}
void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
TableTypeVar* superTable = log.getMutable<TableTypeVar>(superTy);
TableTypeVar* subTable = log.getMutable<TableTypeVar>(subTy);
if (!superTable || !subTable)
ice("passed non-table types to unifySealedTables");
std::vector<std::string> missingPropertiesInSuper;
bool isUnnamedTable = subTable->name == std::nullopt && subTable->syntheticName == std::nullopt;
bool errorReported = false;
// Optimization: First test that the property sets are compatible without doing any recursive unification
if (!subTable->indexer)
{
for (const auto& [propName, superProp] : superTable->props)
{
auto subIter = subTable->props.find(propName);
if (subIter == subTable->props.end() && !isOptional(superProp.type))
missingPropertiesInSuper.push_back(propName);
}
if (!missingPropertiesInSuper.empty())
{
reportError(TypeError{location, MissingProperties{superTy, subTy, std::move(missingPropertiesInSuper)}});
return;
}
}
Unifier innerState = makeChildUnifier();
// Tables must have exactly the same props and their types must all unify
for (const auto& it : superTable->props)
{
const auto& r = subTable->props.find(it.first);
if (r == subTable->props.end())
{
if (isOptional(it.second.type))
continue;
missingPropertiesInSuper.push_back(it.first);
innerState.reportError(TypeError{location, TypeMismatch{superTy, subTy}});
}
else
{
if (isUnnamedTable && r->second.location)
{
size_t oldErrorSize = innerState.errors.size();
Location old = innerState.location;
innerState.location = *r->second.location;
innerState.tryUnify_(r->second.type, it.second.type);
innerState.location = old;
if (oldErrorSize != innerState.errors.size() && !errorReported)
{
errorReported = true;
reportError(innerState.errors.back());
}
}
else
{
innerState.tryUnify_(r->second.type, it.second.type);
}
}
}
if (superTable->indexer || subTable->indexer)
{
if (superTable->indexer && subTable->indexer)
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
else if (subTable->state == TableState::Unsealed)
{
if (superTable->indexer && !subTable->indexer)
{
log.changeIndexer(subTy, superTable->indexer);
}
}
else if (superTable->state == TableState::Unsealed)
{
if (subTable->indexer && !superTable->indexer)
{
log.changeIndexer(superTy, subTable->indexer);
}
}
else if (superTable->indexer)
{
innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType);
for (const auto& [name, type] : subTable->props)
{
const auto& it = superTable->props.find(name);
if (it == superTable->props.end())
innerState.tryUnify_(type.type, superTable->indexer->indexResultType);
}
}
else
innerState.reportError(TypeError{location, TypeMismatch{superTy, subTy}});
}
if (!errorReported)
log.concat(std::move(innerState.log));
else
return;
if (!missingPropertiesInSuper.empty())
{
reportError(TypeError{location, MissingProperties{superTy, subTy, std::move(missingPropertiesInSuper)}});
return;
}
// If the superTy is an immediate part of an intersection type, do not do extra-property check.
// Otherwise, we would falsely generate an extra-property-error for 's' in this code:
// local a: {n: number} & {s: string} = {n=1, s=""}
// When checking against the table '{n: number}'.
if (!isIntersection && superTable->state != TableState::Unsealed && !superTable->indexer)
{
// Check for extra properties in the subTy
std::vector<std::string> extraPropertiesInSub;
for (const auto& [subKey, subProp] : subTable->props)
{
const auto& superIt = superTable->props.find(subKey);
if (superIt == superTable->props.end())
{
if (isOptional(subProp.type))
continue;
extraPropertiesInSub.push_back(subKey);
}
}
if (!extraPropertiesInSub.empty())
{
reportError(TypeError{location, MissingProperties{superTy, subTy, std::move(extraPropertiesInSub), MissingProperties::Extra}});
return;
}
}
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
}
void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
{
const MetatableTypeVar* superMetatable = get<MetatableTypeVar>(superTy);
@ -2068,14 +1765,6 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
return fail();
}
void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
{
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
tryUnify_(subIndexer.indexType, superIndexer.indexType);
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
}
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
{
while (true)
@ -2435,7 +2124,7 @@ void Unifier::occursCheck(TypeId needle, TypeId haystack)
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{
RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit, "occursCheck for TypeId");
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit);
auto check = [&](TypeId tv) {
occursCheck(seen, needle, tv);
@ -2506,7 +2195,7 @@ void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
ice("Expected needle pack to be free");
RecursionLimiter _ra(&sharedState.counters.recursionCount,
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit, "occursCheck for TypePackId");
FFlag::LuauAutocompleteDynamicLimits ? sharedState.counters.recursionLimit : FInt::LuauTypeInferRecursionLimit);
while (!log.getMutable<ErrorTypeVar>(haystack))
{

View File

@ -9,6 +9,7 @@
#include "FileUtils.h"
LUAU_FASTFLAG(DebugLuauTimeTracing)
LUAU_FASTFLAG(LuauTypeMismatchModuleNameResolution)
enum class ReportFormat
{
@ -49,6 +50,9 @@ static void reportError(const Luau::Frontend& frontend, ReportFormat format, con
if (const Luau::SyntaxError* syntaxError = Luau::get_if<Luau::SyntaxError>(&error.data))
report(format, humanReadableName.c_str(), error.location, "SyntaxError", syntaxError->message.c_str());
else if (FFlag::LuauTypeMismatchModuleNameResolution)
report(format, humanReadableName.c_str(), error.location, "TypeError",
Luau::toString(error, Luau::TypeErrorToStringOptions{frontend.fileResolver}).c_str());
else
report(format, humanReadableName.c_str(), error.location, "TypeError", Luau::toString(error).c_str());
}

View File

@ -11,6 +11,7 @@ option(LUAU_BUILD_TESTS "Build tests" ON)
option(LUAU_BUILD_WEB "Build Web module" OFF)
option(LUAU_WERROR "Warnings as errors" OFF)
option(LUAU_STATIC_CRT "Link with the static CRT (/MT)" OFF)
option(LUAU_EXTERN_C "Use extern C for all APIs" OFF)
if(LUAU_STATIC_CRT)
cmake_minimum_required(VERSION 3.15)
@ -115,6 +116,14 @@ target_compile_options(Luau.CodeGen PRIVATE ${LUAU_OPTIONS})
target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS})
target_compile_options(isocline PRIVATE ${LUAU_OPTIONS} ${ISOCLINE_OPTIONS})
if(LUAU_EXTERN_C)
# enable extern "C" for VM (lua.h, lualib.h) and Compiler (luacode.h) to make Luau friendlier to use from non-C++ languages
# note that we enable LUA_USE_LONGJMP=1 as well; otherwise functions like luaL_error will throw C++ exceptions, which can't be done from extern "C" functions
target_compile_definitions(Luau.VM PUBLIC LUA_USE_LONGJMP=1)
target_compile_definitions(Luau.VM PUBLIC LUA_API=extern\"C\")
target_compile_definitions(Luau.Compiler PUBLIC LUACODE_API=extern\"C\")
endif()
if (MSVC AND MSVC_VERSION GREATER_EQUAL 1924)
# disable partial redundancy elimination which regresses interpreter codegen substantially in VS2022:
# https://developercommunity.visualstudio.com/t/performance-regression-on-a-complex-interpreter-lo/1631863

View File

@ -7,7 +7,7 @@
// Creating the bytecode is outside the scope of this file and is handled by bytecode builder (BytecodeBuilder.h) and bytecode compiler (Compiler.h)
// Note that ALL enums declared in this file are order-sensitive since the values are baked into bytecode that needs to be processed by legacy clients.
// Bytecode definitions
// # Bytecode definitions
// Bytecode instructions are using "word code" - each instruction is one or many 32-bit words.
// The first word in the instruction is always the instruction header, and *must* contain the opcode (enum below) in the least significant byte.
//
@ -19,7 +19,7 @@
// Instruction word is sometimes followed by one extra word, indicated as AUX - this is just a 32-bit word and is decoded according to the specification for each opcode.
// For each opcode the encoding is *static* - that is, based on the opcode you know a-priory how large the instruction is, with the exception of NEWCLOSURE
// Bytecode indices
// # Bytecode indices
// Bytecode instructions commonly refer to integer values that define offsets or indices for various entities. For each type, there's a maximum encodable value.
// Note that in some cases, the compiler will set a lower limit than the maximum encodable value is to prevent fragile code into bumping against the limits whenever we change the compilation details.
// Additionally, in some specific instructions such as ANDK, the limit on the encoded value is smaller; this means that if a value is larger, a different instruction must be selected.
@ -29,6 +29,15 @@
// Constants: 0-2^23-1. Constants are stored in a table allocated with each proto; to allow for future bytecode tweaks the encodable value is limited to 23 bits.
// Closures: 0-2^15-1. Closures are created from child protos via a child index; the limit is for the number of closures immediately referenced in each function.
// Jumps: -2^23..2^23. Jump offsets are specified in word increments, so jumping over an instruction may sometimes require an offset of 2 or more.
// # Bytecode versions
// Bytecode serialized format embeds a version number, that dictates both the serialized form as well as the allowed instructions. As long as the bytecode version falls into supported
// range (indicated by LBC_BYTECODE_MIN / LBC_BYTECODE_MAX) and was produced by Luau compiler, it should load and execute correctly.
//
// Note that Luau runtime doesn't provide indefinite bytecode compatibility: support for older versions gets removed over time. As such, bytecode isn't a durable storage format and it's expected
// that Luau users can recompile bytecode from source on Luau version upgrades if necessary.
// Bytecode opcode, part of the instruction header
enum LuauOpcode
{
// NOP: noop
@ -380,8 +389,10 @@ enum LuauOpcode
// Bytecode tags, used internally for bytecode encoded as a string
enum LuauBytecodeTag
{
// Bytecode version
LBC_VERSION = 2,
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
LBC_VERSION_MIN = 2,
LBC_VERSION_MAX = 2,
LBC_VERSION_TARGET = 2,
// Types of constant table entries
LBC_CONSTANT_NIL = 0,
LBC_CONSTANT_BOOLEAN,

View File

@ -119,6 +119,8 @@ public:
static std::string getError(const std::string& message);
static uint8_t getVersion();
private:
struct Constant
{

View File

@ -9,6 +9,9 @@
namespace Luau
{
static_assert(LBC_VERSION_TARGET >= LBC_VERSION_MIN && LBC_VERSION_TARGET <= LBC_VERSION_MAX, "Invalid bytecode version setup");
static_assert(LBC_VERSION_MAX <= 127, "Bytecode version should be 7-bit so that we can extend the serialization to use varint transparently");
static const uint32_t kMaxConstantCount = 1 << 23;
static const uint32_t kMaxClosureCount = 1 << 15;
@ -572,7 +575,10 @@ void BytecodeBuilder::finalize()
bytecode.reserve(capacity);
// assemble final bytecode blob
bytecode = char(LBC_VERSION);
uint8_t version = getVersion();
LUAU_ASSERT(version >= LBC_VERSION_MIN && version <= LBC_VERSION_MAX);
bytecode = char(version);
writeStringTable(bytecode);
@ -1040,7 +1046,7 @@ void BytecodeBuilder::expandJumps()
std::string BytecodeBuilder::getError(const std::string& message)
{
// 0 acts as a special marker for error bytecode (it's equal to LBC_VERSION for valid bytecode blobs)
// 0 acts as a special marker for error bytecode (it's equal to LBC_VERSION_TARGET for valid bytecode blobs)
std::string result;
result += char(0);
result += message;
@ -1048,6 +1054,12 @@ std::string BytecodeBuilder::getError(const std::string& message)
return result;
}
uint8_t BytecodeBuilder::getVersion()
{
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
return LBC_VERSION_TARGET;
}
#ifdef LUAU_ASSERTENABLED
void BytecodeBuilder::validate() const
{

View File

@ -16,8 +16,6 @@
#include <bitset>
#include <math.h>
LUAU_FASTFLAGVARIABLE(LuauCompileIterNoPairs, false)
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThresholdMaxBoost, 300)
@ -2672,7 +2670,7 @@ struct Compiler
else if (builtin.isGlobal("pairs")) // for .. in pairs(t)
{
skipOp = LOP_FORGPREP_NEXT;
loopOp = FFlag::LuauCompileIterNoPairs ? LOP_FORGLOOP : LOP_FORGLOOP_NEXT;
loopOp = LOP_FORGLOOP;
}
}
else if (stat->values.size == 2)
@ -2682,7 +2680,7 @@ struct Compiler
if (builtin.isGlobal("next")) // for .. in next,t
{
skipOp = LOP_FORGPREP_NEXT;
loopOp = FFlag::LuauCompileIterNoPairs ? LOP_FORGLOOP : LOP_FORGLOOP_NEXT;
loopOp = LOP_FORGLOOP;
}
}
}

View File

@ -26,6 +26,8 @@ void luaU_freeudata(lua_State* L, Udata* u, lua_Page* page)
{
void (*dtor)(lua_State*, void*) = nullptr;
dtor = L->global->udatagc[u->tag];
// TODO: access to L here is highly unsafe since this is called during internal GC traversal
// certain operations such as lua_getthreaddata are okay, but by and large this risks crashes on improper use
if (dtor)
dtor(L, u->data);
}

View File

@ -154,11 +154,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
return 1;
}
if (version != LBC_VERSION)
if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX)
{
char chunkid[LUA_IDSIZE];
luaO_chunkid(chunkid, chunkname, LUA_IDSIZE);
lua_pushfstring(L, "%s: bytecode version mismatch (expected %d, got %d)", chunkid, LBC_VERSION, version);
lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version);
return 1;
}

View File

@ -261,8 +261,6 @@ L1: RETURN R0 0
TEST_CASE("ForBytecode")
{
ScopedFastFlag sff2("LuauCompileIterNoPairs", false);
// basic for loop: variable directly refers to internal iteration index (R2)
CHECK_EQ("\n" + compileFunction0("for i=1,5 do print(i) end"), R"(
LOADN R2 1
@ -329,7 +327,7 @@ L0: GETIMPORT R5 3
MOVE R6 R3
MOVE R7 R4
CALL R5 2 0
L1: FORGLOOP_NEXT R0 L0
L1: FORGLOOP R0 L0 2
RETURN R0 0
)");
@ -342,7 +340,7 @@ L0: GETIMPORT R5 3
MOVE R6 R3
MOVE R7 R4
CALL R5 2 0
L1: FORGLOOP_NEXT R0 L0
L1: FORGLOOP R0 L0 2
RETURN R0 0
)");
}
@ -2262,8 +2260,6 @@ TEST_CASE("TypeAliasing")
TEST_CASE("DebugLineInfo")
{
ScopedFastFlag sff("LuauCompileIterNoPairs", false);
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
Luau::compileOrThrow(bcb, R"(
@ -2313,7 +2309,7 @@ return result
15: L0: MOVE R7 R1
15: MOVE R8 R5
15: CONCAT R1 R7 R8
14: L1: FORGLOOP_NEXT R2 L0
14: L1: FORGLOOP R2 L0 1
17: RETURN R1 1
)");
}
@ -2545,8 +2541,6 @@ a
TEST_CASE("DebugSource")
{
ScopedFastFlag sff("LuauCompileIterNoPairs", false);
const char* source = R"(
local kSelectedBiomes = {
['Mountains'] = true,
@ -2614,7 +2608,7 @@ L0: MOVE R7 R1
MOVE R8 R5
CONCAT R1 R7 R8
14: for k in pairs(kSelectedBiomes) do
L1: FORGLOOP_NEXT R2 L0
L1: FORGLOOP R2 L0 1
17: return result
RETURN R1 1
)");
@ -2622,8 +2616,6 @@ RETURN R1 1
TEST_CASE("DebugLocals")
{
ScopedFastFlag sff("LuauCompileIterNoPairs", false);
const char* source = R"(
function foo(e, f)
local a = 1
@ -2661,12 +2653,12 @@ end
local 0: reg 5, start pc 5 line 5, end pc 8 line 5
local 1: reg 6, start pc 14 line 8, end pc 18 line 8
local 2: reg 7, start pc 14 line 8, end pc 18 line 8
local 3: reg 3, start pc 21 line 12, end pc 24 line 12
local 4: reg 3, start pc 26 line 16, end pc 30 line 16
local 5: reg 0, start pc 0 line 3, end pc 34 line 21
local 6: reg 1, start pc 0 line 3, end pc 34 line 21
local 7: reg 2, start pc 1 line 4, end pc 34 line 21
local 8: reg 3, start pc 34 line 21, end pc 34 line 21
local 3: reg 3, start pc 22 line 12, end pc 25 line 12
local 4: reg 3, start pc 27 line 16, end pc 31 line 16
local 5: reg 0, start pc 0 line 3, end pc 35 line 21
local 6: reg 1, start pc 0 line 3, end pc 35 line 21
local 7: reg 2, start pc 1 line 4, end pc 35 line 21
local 8: reg 3, start pc 35 line 21, end pc 35 line 21
3: LOADN R2 1
4: LOADN R5 1
4: LOADN R3 3
@ -2683,7 +2675,7 @@ local 8: reg 3, start pc 34 line 21, end pc 34 line 21
8: MOVE R9 R6
8: MOVE R10 R7
8: CALL R8 2 0
7: L3: FORGLOOP_NEXT R3 L2
7: L3: FORGLOOP R3 L2 2
11: LOADN R3 2
12: GETIMPORT R4 1
12: LOADN R5 2
@ -3795,8 +3787,6 @@ RETURN R0 1
TEST_CASE("SharedClosure")
{
ScopedFastFlag sff("LuauCompileIterNoPairs", false);
// closures can be shared even if functions refer to upvalues, as long as upvalues are top-level
CHECK_EQ("\n" + compileFunction(R"(
local val = ...
@ -3939,7 +3929,7 @@ L2: GETIMPORT R5 1
NEWCLOSURE R6 P1
CAPTURE VAL R3
CALL R5 1 0
L3: FORGLOOP_NEXT R0 L2
L3: FORGLOOP R0 L2 2
LOADN R2 1
LOADN R0 10
LOADN R1 1

View File

@ -2,13 +2,13 @@
#pragma once
#include "Luau/Config.h"
#include "Luau/ConstraintGraphBuilder.h"
#include "Luau/FileResolver.h"
#include "Luau/Frontend.h"
#include "Luau/IostreamHelpers.h"
#include "Luau/Linter.h"
#include "Luau/Location.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Scope.h"
#include "Luau/ToString.h"
#include "Luau/TypeInfer.h"
#include "Luau/TypeVar.h"

View File

@ -279,7 +279,6 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
int limit = 400;
#endif
ScopedFastInt luauTypeCloneRecursionLimit{"LuauTypeCloneRecursionLimit", limit};
ScopedFastFlag sff{"LuauRecursionLimitException", true};
TypeArena src;

View File

@ -12,7 +12,6 @@ using namespace Luau;
struct NormalizeFixture : Fixture
{
ScopedFastFlag sff1{"LuauLowerBoundsCalculation", true};
ScopedFastFlag sff2{"LuauTableSubtypingVariance2", true};
};
void createSomeClasses(TypeChecker& typeChecker)

View File

@ -264,10 +264,13 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
}
)LUA";
CheckResult result = check(src);
CodeTooComplex ctc;
if (FFlag::LuauLowerBoundsCalculation)
(void)check(src);
LUAU_REQUIRE_ERRORS(result);
else
CHECK_THROWS_AS(check(src), std::exception);
CHECK(hasError(result, &ctc));
}
TEST_SUITE_END();

View File

@ -409,8 +409,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringDetailed")
TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
{
ScopedFastFlag sff{"LuauUnsealedTableLiteral", true};
CheckResult result = check(R"(
local base = {}
function base:one() return 1 end

View File

@ -7,8 +7,21 @@
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
TEST_SUITE_BEGIN("TypeAliases");
TEST_CASE_FIXTURE(Fixture, "basic_alias")
{
CheckResult result = check(R"(
type T = number
local x: T = 1
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number", toString(requireType("x")));
}
TEST_CASE_FIXTURE(Fixture, "cyclic_function_type_in_type_alias")
{
CheckResult result = check(R"(
@ -24,6 +37,63 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_function_type_in_type_alias")
CHECK_EQ("t1 where t1 = () -> t1?", toString(requireType("g")));
}
TEST_CASE_FIXTURE(Fixture, "names_are_ascribed")
{
CheckResult result = check(R"(
type T = { x: number }
local x: T
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("T", toString(requireType("x")));
}
TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
{
// This is a tricky case. In order to support recursive type aliases,
// we first walk the block and generate free types as placeholders.
// We then walk the AST as normal. If we declare a type alias as below,
// we generate a free type. We then begin our normal walk, examining
// local x: T = "foo", which establishes two constraints:
// a <: b
// string <: a
// We then visit the type alias, and establish that
// b <: number
// Then, when solving these constraints, we dispatch them in the order
// they appear above. This means that a ~ b, and a ~ string, thus
// b ~ string. This means the b <: number constraint has no effect.
// Essentially we've "stolen" the alias's type out from under it.
// This test ensures that we don't actually do this.
CheckResult result = check(R"(
local x: T = "foo"
type T = number
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK(result.errors[0] == TypeError{
Location{{1, 21}, {1, 26}},
getMainSourceModule()->name,
TypeMismatch{
getSingletonTypes().numberType,
getSingletonTypes().stringType,
},
});
}
else
{
CHECK(result.errors[0] == TypeError{
Location{{1, 8}, {1, 26}},
getMainSourceModule()->name,
TypeMismatch{
getSingletonTypes().numberType,
getSingletonTypes().stringType,
},
});
}
}
TEST_CASE_FIXTURE(Fixture, "cyclic_types_of_named_table_fields_do_not_expand_when_stringified")
{
CheckResult result = check(R"(
@ -41,7 +111,22 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_types_of_named_table_fields_do_not_expand_whe
CHECK_EQ(typeChecker.numberType, tm->givenType);
}
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_aliases")
{
CheckResult result = check(R"(
--!strict
type T = { f: number, g: U }
type U = { h: number, i: T? }
local x: T = { f = 37, g = { h = 5, i = nil } }
x.g.i = x
local y: T = { f = 3, g = { h = 5, i = nil } }
y.g.i = y
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{
CheckResult result = check(R"(
--!strict

View File

@ -30,11 +30,21 @@ TEST_CASE_FIXTURE(Fixture, "successful_check")
dumpErrors(result);
}
TEST_CASE_FIXTURE(Fixture, "variable_type_is_supertype")
{
CheckResult result = check(R"(
local x: number = 1
local y: number? = x
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "function_parameters_can_have_annotations")
{
CheckResult result = check(R"(
function double(x: number)
return x * 2
return 2
end
local four = double(2)
@ -47,7 +57,7 @@ TEST_CASE_FIXTURE(Fixture, "function_parameter_annotations_are_checked")
{
CheckResult result = check(R"(
function double(x: number)
return x * 2
return 2
end
local four = double("two")
@ -70,13 +80,13 @@ TEST_CASE_FIXTURE(Fixture, "function_return_annotations_are_checked")
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fiftyType);
REQUIRE(ftv != nullptr);
TypePackId retPack = ftv->retTypes;
TypePackId retPack = follow(ftv->retTypes);
const TypePack* tp = get<TypePack>(retPack);
REQUIRE(tp != nullptr);
REQUIRE_EQ(1, tp->head.size());
REQUIRE_EQ(typeChecker.anyType, tp->head[0]);
REQUIRE_EQ(typeChecker.anyType, follow(tp->head[0]));
}
TEST_CASE_FIXTURE(Fixture, "function_return_multret_annotations_are_checked")
@ -116,6 +126,23 @@ TEST_CASE_FIXTURE(Fixture, "function_return_annotation_should_continuously_parse
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(Fixture, "unknown_type_reference_generates_error")
{
CheckResult result = check(R"(
local x: IDoNotExist
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(result.errors[0] == TypeError{
Location{{1, 17}, {1, 28}},
getMainSourceModule()->name,
UnknownSymbol{
"IDoNotExist",
UnknownSymbol::Context::Type,
},
});
}
TEST_CASE_FIXTURE(Fixture, "typeof_variable_type_annotation_should_return_its_type")
{
CheckResult result = check(R"(
@ -632,7 +659,10 @@ int AssertionCatcher::tripped;
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
ScopedFastFlag sffs[] = {
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", false},
};
AssertionCatcher ac;
@ -646,9 +676,10 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice")
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_handler")
{
ScopedFastFlag sffs{"DebugLuauMagicTypes", true};
AssertionCatcher ac;
ScopedFastFlag sffs[] = {
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", false},
};
bool caught = false;
@ -662,8 +693,44 @@ TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_handler")
std::runtime_error);
CHECK_EQ(true, caught);
}
frontend.iceHandler.onInternalError = {};
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag")
{
ScopedFastFlag sffs[] = {
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", true},
};
AssertionCatcher ac;
CHECK_THROWS_AS(check(R"(
local a: _luau_ice = 55
)"),
InternalCompilerError);
LUAU_ASSERT(1 == AssertionCatcher::tripped);
}
TEST_CASE_FIXTURE(Fixture, "luau_ice_triggers_an_ice_exception_with_flag_handler")
{
ScopedFastFlag sffs[] = {
{"DebugLuauMagicTypes", true},
{"LuauUseInternalCompilerErrorException", true},
};
bool caught = false;
frontend.iceHandler.onInternalError = [&](const char*) {
caught = true;
};
CHECK_THROWS_AS(check(R"(
local a: _luau_ice = 55
)"),
InternalCompilerError);
CHECK_EQ(true, caught);
}
TEST_CASE_FIXTURE(Fixture, "luau_ice_is_not_special_without_the_flag")

View File

@ -700,11 +700,6 @@ end
TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe")
{
ScopedFastFlag sffs[] = {
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
--!strict
-- At one point this produced a UAF
@ -979,8 +974,6 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_generic_function_in_assignments2")
TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param")
{
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
// Mutability in type function application right now can create strange recursive types
CheckResult result = check(R"(
type Table = { a: number }
@ -1015,8 +1008,6 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
{
ScopedFastFlag sff{"LuauUnsealedTableLiteral", true};
CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a)
return f(x, y)
@ -1123,8 +1114,6 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1")
{
ScopedFastFlag sff{"LuauApplyTypeFunctionFix", true};
// https://github.com/Roblox/luau/issues/484
CheckResult result = check(R"(
--!strict
@ -1153,8 +1142,6 @@ local complex: ComplexObject<string> = {
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics2")
{
ScopedFastFlag sff{"LuauApplyTypeFunctionFix", true};
// https://github.com/Roblox/luau/issues/484
CheckResult result = check(R"(
--!strict

View File

@ -12,8 +12,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauTableSubtypingVariance2)
TEST_SUITE_BEGIN("TypeInferModules");
TEST_CASE_FIXTURE(BuiltinsFixture, "require")
@ -326,16 +324,9 @@ local b: B.T = a
CheckResult result = frontend.check("game/C");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauTableSubtypingVariance2)
{
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
caused by:
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
}
else
{
CHECK_EQ(toString(result.errors[0]), "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'");
}
}
TEST_CASE_FIXTURE(BuiltinsFixture, "module_type_conflict_instantiated")
@ -367,16 +358,9 @@ local b: B.T = a
CheckResult result = frontend.check("game/D");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauTableSubtypingVariance2)
{
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
CHECK_EQ(toString(result.errors[0]), R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
caused by:
Property 'x' is not compatible. Type 'number' could not be converted into 'string')");
}
else
{
CHECK_EQ(toString(result.errors[0]), "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'");
}
}
TEST_SUITE_END();

View File

@ -353,8 +353,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_non_binary_expressions_actually_resol
TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal")
{
ScopedFastFlag LuauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
local t: {x: number?} = {x = nil}

View File

@ -260,10 +260,6 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_alias_or_parens_is_indexer")
TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
{
ScopedFastFlag sffs[]{
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
--!strict
local x: { ["<>"] : number }

View File

@ -276,8 +276,6 @@ TEST_CASE_FIXTURE(Fixture, "open_table_unification")
TEST_CASE_FIXTURE(Fixture, "open_table_unification_2")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
local a = {}
a.x = 99
@ -347,8 +345,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_row_polymorphism_1")
TEST_CASE_FIXTURE(Fixture, "table_param_row_polymorphism_2")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
--!strict
function foo(o)
@ -370,8 +366,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_row_polymorphism_2")
TEST_CASE_FIXTURE(Fixture, "table_param_row_polymorphism_3")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
local T = {}
T.bar = 'hello'
@ -477,8 +471,6 @@ TEST_CASE_FIXTURE(Fixture, "ok_to_add_property_to_free_table")
TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_assignment")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
--!strict
local t = { u = {} }
@ -512,8 +504,6 @@ TEST_CASE_FIXTURE(Fixture, "okay_to_add_property_to_unsealed_tables_by_function_
TEST_CASE_FIXTURE(Fixture, "width_subtyping")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
--!strict
function f(x : { q : number })
@ -772,8 +762,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_for_left_unsealed_table_from_right_han
TEST_CASE_FIXTURE(Fixture, "sealed_table_value_can_infer_an_indexer")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
local t: { a: string, [number]: string } = { a = "foo" }
)");
@ -783,8 +771,6 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_value_can_infer_an_indexer")
TEST_CASE_FIXTURE(Fixture, "array_factory_function")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
function empty() return {} end
local array: {string} = empty()
@ -1175,8 +1161,6 @@ TEST_CASE_FIXTURE(Fixture, "defining_a_self_method_for_a_local_sealed_table_must
TEST_CASE_FIXTURE(Fixture, "defining_a_method_for_a_local_unsealed_table_is_ok")
{
ScopedFastFlag sff{"LuauUnsealedTableLiteral", true};
CheckResult result = check(R"(
local t = {x = 1}
function t.m() end
@ -1187,8 +1171,6 @@ TEST_CASE_FIXTURE(Fixture, "defining_a_method_for_a_local_unsealed_table_is_ok")
TEST_CASE_FIXTURE(Fixture, "defining_a_self_method_for_a_local_unsealed_table_is_ok")
{
ScopedFastFlag sff{"LuauUnsealedTableLiteral", true};
CheckResult result = check(R"(
local t = {x = 1}
function t:m() end
@ -1468,11 +1450,6 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer")
{
ScopedFastFlag sff[]{
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
type StringToStringMap = { [string]: string }
local rt: StringToStringMap = { ["foo"] = 1 }
@ -1518,11 +1495,6 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2")
TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer3")
{
ScopedFastFlag sff[]{
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
local function foo(a: {[string]: number, a: string}) end
foo({ a = 1 })
@ -1609,8 +1581,6 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multipl
TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_is_ok")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
local vec3 = {x = 1, y = 2, z = 3}
local vec1 = {x = 1}
@ -1998,8 +1968,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_properties_in_strict")
{
ScopedFastFlag sff{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
--!strict
local buttons = {}
@ -2013,8 +1981,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope
TEST_CASE_FIXTURE(Fixture, "error_detailed_prop")
{
ScopedFastFlag LuauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
CheckResult result = check(R"(
type A = { x: number, y: number }
type B = { x: number, y: string }
@ -2031,8 +1997,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_prop_nested")
{
ScopedFastFlag LuauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
CheckResult result = check(R"(
type AS = { x: number, y: number }
type BS = { x: number, y: string }
@ -2054,11 +2018,6 @@ caused by:
TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop")
{
ScopedFastFlag sff[]{
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end });
local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end });
@ -2085,8 +2044,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
{
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
CheckResult result = check(R"(
type A = { [number]: string }
type B = { [string]: string }
@ -2103,8 +2060,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
{
ScopedFastFlag luauTableSubtypingVariance2{"LuauTableSubtypingVariance2", true}; // Only for new path
CheckResult result = check(R"(
type A = { [number]: number }
type B = { [number]: string }
@ -2121,10 +2076,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table")
{
ScopedFastFlag sffs[]{
{"LuauTableSubtypingVariance2", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
@ -2140,11 +2091,6 @@ a.p = { x = 9 }
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error")
{
ScopedFastFlag sffs[]{
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
@ -2166,10 +2112,6 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
{
ScopedFastFlag sffs[]{
{"LuauTableSubtypingVariance2", true},
};
CheckResult result = check(R"(
--!strict
type Super = { x : number }
@ -2185,10 +2127,6 @@ a.p = { x = 9 }
TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call")
{
ScopedFastFlag sff[]{
{"LuauUnsealedTableLiteral", true},
};
CheckResult result = check(R"(
local b
b = setmetatable({}, {__call = b})
@ -2201,11 +2139,6 @@ b()
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
{
ScopedFastFlag sffs[] = {
{"LuauTableSubtypingVariance2", true},
{"LuauSubtypingAddOptPropsToUnsealedTables", true},
};
CheckResult result = check(R"(
--!strict
local function setNumber(t: { p: number? }, x:number) t.p = x end
@ -2706,8 +2639,6 @@ type t0<t32> = any
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_table_cloning_2")
{
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
CheckResult result = check(R"(
type X<T> = T
type K = X<typeof(math)>
@ -2725,8 +2656,6 @@ type K = X<typeof(math)>
TEST_CASE_FIXTURE(Fixture, "instantiate_table_cloning_3")
{
ScopedFastFlag sff{"LuauOnlyMutateInstantiatedTables", true};
CheckResult result = check(R"(
type X<T> = T
local a = {}
@ -2977,8 +2906,6 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra")
{
ScopedFastFlag luauSubtypingAddOptPropsToUnsealedTables{"LuauSubtypingAddOptPropsToUnsealedTables", true};
CheckResult result = check(R"(
type X = { { x: boolean?, y: boolean? } }

View File

@ -887,8 +887,6 @@ end
TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
{
ScopedFastFlag subtypingVariance{"LuauTableSubtypingVariance2", true};
CheckResult result = check(R"(
--!strict
--!nolint
@ -928,7 +926,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
{
ScopedFastInt sfi("LuauTypeInferRecursionLimit", 2);
ScopedFastFlag sff{"LuauRecursionLimitException", true};
CheckResult result = check(R"(
function complex()

View File

@ -428,12 +428,6 @@ y = x
TEST_CASE_FIXTURE(Fixture, "unify_sealed_table_union_check")
{
ScopedFastFlag sffs[] = {
{"LuauTableSubtypingVariance2", true},
{"LuauUnsealedTableLiteral", true},
{"LuauSubtypingAddOptPropsToUnsealedTables", true},
};
CheckResult result = check(R"(
-- the difference between this and unify_unsealed_table_union_check is the type annotation on x
local t = { x = 3, y = true }

View File

@ -10,14 +10,9 @@ using namespace Luau;
LUAU_FASTINT(LuauVisitRecursionLimit)
struct VisitTypeVarFixture : Fixture
{
ScopedFastFlag flag2 = {"LuauRecursionLimitException", true};
};
TEST_SUITE_BEGIN("VisitTypeVar");
TEST_CASE_FIXTURE(VisitTypeVarFixture, "throw_when_limit_is_exceeded")
TEST_CASE_FIXTURE(Fixture, "throw_when_limit_is_exceeded")
{
ScopedFastInt sfi{"LuauVisitRecursionLimit", 3};
@ -30,7 +25,7 @@ TEST_CASE_FIXTURE(VisitTypeVarFixture, "throw_when_limit_is_exceeded")
CHECK_THROWS_AS(toString(tType), RecursionLimitException);
}
TEST_CASE_FIXTURE(VisitTypeVarFixture, "dont_throw_when_limit_is_high_enough")
TEST_CASE_FIXTURE(Fixture, "dont_throw_when_limit_is_high_enough")
{
ScopedFastInt sfi{"LuauVisitRecursionLimit", 8};