Sync to upstream/release/572 (#899)
* Fixed exported types not being suggested in autocomplete * `T...` is now convertible to `...any` (Fixes https://github.com/Roblox/luau/issues/767) * Fixed issue with `T?` not being convertible to `T | T` or `T?` (sometimes when internal pointer identity is different) * Fixed potential crash in missing table key error suggestion to use a similar existing key * `lua_topointer` now returns a pointer for strings C++ API Changes: * `prepareModuleScope` callback has moved from TypeChecker to Frontend * For LSPs, AstQuery functions (and `isWithinComment`) can be used without full Frontend data A lot of changes in our two experimental components as well. In our work on the new type-solver, the following issues were fixed: * Fixed table union and intersection indexing * Correct custom type environments are now used * Fixed issue with values of `free & number` type not accepted in numeric operations And these are the changes in native code generation (JIT): * arm64 lowering is almost complete with support for 99% of IR commands and all fastcalls * Fixed x64 assembly encoding for extended byte registers * More external x64 calls are aware of register allocator * `math.min`/`math.max` with more than 2 arguments are now lowered to IR as well * Fixed correctness issues with `math` library calls with multiple results in variadic context and with x64 register conflicts * x64 register allocator learnt to restore values from VM memory instead of always using stack spills * x64 exception unwind information now supports multiple functions and fixes function start offset in Dwarf2 info
This commit is contained in:
parent
7345891f6b
commit
d141a5c48d
|
@ -64,8 +64,11 @@ private:
|
|||
};
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(AstStatBlock* root, Position pos, bool includeTypes = false);
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos);
|
||||
AstNode* findNodeAtPosition(AstStatBlock* root, Position pos);
|
||||
AstExpr* findExprAtPosition(const SourceModule& source, Position pos);
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos);
|
||||
std::optional<Binding> findBindingAtPosition(const Module& module, const SourceModule& source, Position pos);
|
||||
|
|
|
@ -165,7 +165,15 @@ struct Frontend
|
|||
bool captureComments, bool typeCheckForAutocomplete = false);
|
||||
|
||||
private:
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete = false, bool recordJsonLog = false);
|
||||
struct TypeCheckLimits
|
||||
{
|
||||
std::optional<double> finishTime;
|
||||
std::optional<int> instantiationChildLimit;
|
||||
std::optional<int> unifierIterationLimit;
|
||||
};
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, std::optional<ScopePtr> environmentScope,
|
||||
bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits);
|
||||
|
||||
std::pair<SourceNode*, SourceModule*> getSourceNode(const ModuleName& name);
|
||||
SourceModule parse(const ModuleName& name, std::string_view src, const ParseOptions& parseOptions);
|
||||
|
@ -185,15 +193,21 @@ public:
|
|||
const NotNull<BuiltinTypes> builtinTypes;
|
||||
|
||||
FileResolver* fileResolver;
|
||||
|
||||
FrontendModuleResolver moduleResolver;
|
||||
FrontendModuleResolver moduleResolverForAutocomplete;
|
||||
|
||||
GlobalTypes globals;
|
||||
GlobalTypes globalsForAutocomplete;
|
||||
TypeChecker typeChecker;
|
||||
TypeChecker typeCheckerForAutocomplete;
|
||||
|
||||
// TODO: remove with FFlagLuauOnDemandTypecheckers
|
||||
TypeChecker typeChecker_DEPRECATED;
|
||||
TypeChecker typeCheckerForAutocomplete_DEPRECATED;
|
||||
|
||||
ConfigResolver* configResolver;
|
||||
FrontendOptions options;
|
||||
InternalErrorReporter iceHandler;
|
||||
std::function<void(const ModuleName& name, const ScopePtr& scope, bool forAutocomplete)> prepareModuleScope;
|
||||
|
||||
std::unordered_map<ModuleName, SourceNode> sourceNodes;
|
||||
std::unordered_map<ModuleName, SourceModule> sourceModules;
|
||||
|
|
|
@ -51,6 +51,7 @@ struct SourceModule
|
|||
};
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos);
|
||||
bool isWithinComment(const ParseResult& result, Position pos);
|
||||
|
||||
struct RequireCycle
|
||||
{
|
||||
|
|
|
@ -738,6 +738,7 @@ const T* get(TypeId tv)
|
|||
return get_if<T>(&tv->ty);
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
T* getMutable(TypeId tv)
|
||||
{
|
||||
|
@ -897,6 +898,19 @@ bool hasTag(TypeId ty, const std::string& tagName);
|
|||
bool hasTag(const Property& prop, const std::string& tagName);
|
||||
bool hasTag(const Tags& tags, const std::string& tagName); // Do not use in new work.
|
||||
|
||||
template<typename T>
|
||||
bool hasTypeInIntersection(TypeId ty)
|
||||
{
|
||||
TypeId tf = follow(ty);
|
||||
if (get<T>(tf))
|
||||
return true;
|
||||
for (auto t : flattenIntersection(tf))
|
||||
if (get<T>(follow(t)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasPrimitiveTypeInIntersection(TypeId ty, PrimitiveType::Type primTy);
|
||||
/*
|
||||
* Use this to change the kind of a particular type.
|
||||
*
|
||||
|
|
|
@ -137,9 +137,9 @@ private:
|
|||
|
||||
public:
|
||||
// Returns true if the type "needle" already occurs within "haystack" and reports an "infinite type error"
|
||||
bool occursCheck(TypeId needle, TypeId haystack);
|
||||
bool occursCheck(TypeId needle, TypeId haystack, bool reversed);
|
||||
bool occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack);
|
||||
bool occursCheck(TypePackId needle, TypePackId haystack);
|
||||
bool occursCheck(TypePackId needle, TypePackId haystack, bool reversed);
|
||||
bool occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack);
|
||||
|
||||
Unifier makeChildUnifier();
|
||||
|
|
|
@ -211,33 +211,48 @@ struct FindFullAncestry final : public AstVisitor
|
|||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
|
||||
{
|
||||
AutocompleteNodeFinder finder{pos, source.root};
|
||||
source.root->visit(&finder);
|
||||
return findAncestryAtPositionForAutocomplete(source.root, pos);
|
||||
}
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos)
|
||||
{
|
||||
AutocompleteNodeFinder finder{pos, root};
|
||||
root->visit(&finder);
|
||||
return finder.ancestry;
|
||||
}
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes)
|
||||
{
|
||||
const Position end = source.root->location.end;
|
||||
return findAstAncestryOfPosition(source.root, pos, includeTypes);
|
||||
}
|
||||
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(AstStatBlock* root, Position pos, bool includeTypes)
|
||||
{
|
||||
const Position end = root->location.end;
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
FindFullAncestry finder(pos, end, includeTypes);
|
||||
source.root->visit(&finder);
|
||||
root->visit(&finder);
|
||||
return finder.nodes;
|
||||
}
|
||||
|
||||
AstNode* findNodeAtPosition(const SourceModule& source, Position pos)
|
||||
{
|
||||
const Position end = source.root->location.end;
|
||||
if (pos < source.root->location.begin)
|
||||
return source.root;
|
||||
return findNodeAtPosition(source.root, pos);
|
||||
}
|
||||
|
||||
AstNode* findNodeAtPosition(AstStatBlock* root, Position pos)
|
||||
{
|
||||
const Position end = root->location.end;
|
||||
if (pos < root->location.begin)
|
||||
return root;
|
||||
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
FindNode findNode{pos, end};
|
||||
findNode.visit(source.root);
|
||||
findNode.visit(root);
|
||||
return findNode.best;
|
||||
}
|
||||
|
||||
|
|
|
@ -595,6 +595,11 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
* make any sense to stop and wait for someone else to do it.
|
||||
*/
|
||||
|
||||
// If any is present, the expression must evaluate to any as well.
|
||||
bool leftAny = get<AnyType>(leftType) || get<ErrorType>(leftType);
|
||||
bool rightAny = get<AnyType>(rightType) || get<ErrorType>(rightType);
|
||||
bool anyPresent = leftAny || rightAny;
|
||||
|
||||
if (isBlocked(leftType) && leftType != resultType)
|
||||
return block(c.leftType, constraint);
|
||||
|
||||
|
@ -604,12 +609,12 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
if (!force)
|
||||
{
|
||||
// Logical expressions may proceed if the LHS is free.
|
||||
if (get<FreeType>(leftType) && !isLogical)
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && !isLogical)
|
||||
return block(leftType, constraint);
|
||||
}
|
||||
|
||||
// Logical expressions may proceed if the LHS is free.
|
||||
if (isBlocked(leftType) || (get<FreeType>(leftType) && !isLogical))
|
||||
if (isBlocked(leftType) || (hasTypeInIntersection<FreeType>(leftType) && !isLogical))
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(resultType);
|
||||
|
@ -696,11 +701,6 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// If there's no metamethod available, fall back to primitive behavior.
|
||||
}
|
||||
|
||||
// If any is present, the expression must evaluate to any as well.
|
||||
bool leftAny = get<AnyType>(leftType) || get<ErrorType>(leftType);
|
||||
bool rightAny = get<AnyType>(rightType) || get<ErrorType>(rightType);
|
||||
bool anyPresent = leftAny || rightAny;
|
||||
|
||||
switch (c.op)
|
||||
{
|
||||
// For arithmetic operators, if the LHS is a number, the RHS must be a
|
||||
|
@ -711,6 +711,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
case AstExprBinary::Op::Div:
|
||||
case AstExprBinary::Op::Pow:
|
||||
case AstExprBinary::Op::Mod:
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && force)
|
||||
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->numberType);
|
||||
if (isNumber(leftType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
|
@ -723,6 +725,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// For concatenation, if the LHS is a string, the RHS must be a string as
|
||||
// well. The result will also be a string.
|
||||
case AstExprBinary::Op::Concat:
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && force)
|
||||
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->stringType);
|
||||
if (isString(leftType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
|
|
|
@ -31,7 +31,8 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
|||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOnDemandTypecheckers, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -131,8 +132,8 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S
|
|||
LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
||||
const std::string& packageName, bool captureComments, bool typeCheckForAutocomplete)
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return Luau::loadDefinitionFileNoDCR(typeCheckForAutocomplete ? typeCheckerForAutocomplete : typeChecker,
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution && !FFlag::LuauOnDemandTypecheckers)
|
||||
return Luau::loadDefinitionFileNoDCR(typeCheckForAutocomplete ? typeCheckerForAutocomplete_DEPRECATED : typeChecker_DEPRECATED,
|
||||
typeCheckForAutocomplete ? globalsForAutocomplete : globals, targetScope, source, packageName, captureComments);
|
||||
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
@ -142,7 +143,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, Scop
|
|||
if (parseResult.errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, nullptr};
|
||||
|
||||
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {});
|
||||
ModulePtr checkedModule = check(sourceModule, Mode::Definition, {}, std::nullopt, /*forAutocomplete*/ false, /*recordJsonLog*/ false, {});
|
||||
|
||||
if (checkedModule->errors.size() > 0)
|
||||
return LoadDefinitionFileResult{false, parseResult, sourceModule, checkedModule};
|
||||
|
@ -155,6 +156,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(GlobalTypes& globals, Scop
|
|||
LoadDefinitionFileResult loadDefinitionFileNoDCR(TypeChecker& typeChecker, GlobalTypes& globals, ScopePtr targetScope, std::string_view source,
|
||||
const std::string& packageName, bool captureComments)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauOnDemandTypecheckers);
|
||||
LUAU_TIMETRACE_SCOPE("loadDefinitionFile", "Frontend");
|
||||
|
||||
Luau::SourceModule sourceModule;
|
||||
|
@ -406,8 +408,8 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
|
|||
, moduleResolverForAutocomplete(this)
|
||||
, globals(builtinTypes)
|
||||
, globalsForAutocomplete(builtinTypes)
|
||||
, typeChecker(globals.globalScope, &moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete(globalsForAutocomplete.globalScope, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, typeChecker_DEPRECATED(globals.globalScope, &moduleResolver, builtinTypes, &iceHandler)
|
||||
, typeCheckerForAutocomplete_DEPRECATED(globalsForAutocomplete.globalScope, &moduleResolverForAutocomplete, builtinTypes, &iceHandler)
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
{
|
||||
|
@ -491,35 +493,68 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
|
||||
if (frontendOptions.forAutocomplete)
|
||||
{
|
||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
||||
// to provide better type information for IDE features
|
||||
typeCheckerForAutocomplete.requireCycles = requireCycles;
|
||||
ModulePtr moduleForAutocomplete;
|
||||
|
||||
double autocompleteTimeLimit = FInt::LuauAutocompleteCheckTimeoutMs / 1000.0;
|
||||
|
||||
if (autocompleteTimeLimit != 0.0)
|
||||
typeCheckerForAutocomplete.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
||||
else
|
||||
typeCheckerForAutocomplete.finishTime = std::nullopt;
|
||||
if (!FFlag::LuauOnDemandTypecheckers)
|
||||
{
|
||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
||||
// to provide better type information for IDE features
|
||||
typeCheckerForAutocomplete_DEPRECATED.requireCycles = requireCycles;
|
||||
|
||||
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
||||
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
||||
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
||||
if (FInt::LuauTarjanChildLimit > 0)
|
||||
typeCheckerForAutocomplete.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckerForAutocomplete.instantiationChildLimit = std::nullopt;
|
||||
if (autocompleteTimeLimit != 0.0)
|
||||
typeCheckerForAutocomplete_DEPRECATED.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
||||
else
|
||||
typeCheckerForAutocomplete_DEPRECATED.finishTime = std::nullopt;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0)
|
||||
typeCheckerForAutocomplete.unifierIterationLimit =
|
||||
std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckerForAutocomplete.unifierIterationLimit = std::nullopt;
|
||||
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
||||
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
||||
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
||||
if (FInt::LuauTarjanChildLimit > 0)
|
||||
typeCheckerForAutocomplete_DEPRECATED.instantiationChildLimit =
|
||||
std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckerForAutocomplete_DEPRECATED.instantiationChildLimit = std::nullopt;
|
||||
|
||||
ModulePtr moduleForAutocomplete =
|
||||
FFlag::DebugLuauDeferredConstraintResolution
|
||||
? check(sourceModule, Mode::Strict, requireCycles, /*forAutocomplete*/ true, /*recordJsonLog*/ false)
|
||||
: typeCheckerForAutocomplete.check(sourceModule, Mode::Strict, environmentScope);
|
||||
if (FInt::LuauTypeInferIterationLimit > 0)
|
||||
typeCheckerForAutocomplete_DEPRECATED.unifierIterationLimit =
|
||||
std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckerForAutocomplete_DEPRECATED.unifierIterationLimit = std::nullopt;
|
||||
|
||||
moduleForAutocomplete =
|
||||
FFlag::DebugLuauDeferredConstraintResolution
|
||||
? check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true, /*recordJsonLog*/ false, {})
|
||||
: typeCheckerForAutocomplete_DEPRECATED.check(sourceModule, Mode::Strict, environmentScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The autocomplete typecheck is always in strict mode with DM awareness
|
||||
// to provide better type information for IDE features
|
||||
TypeCheckLimits typeCheckLimits;
|
||||
|
||||
if (autocompleteTimeLimit != 0.0)
|
||||
typeCheckLimits.finishTime = TimeTrace::getClock() + autocompleteTimeLimit;
|
||||
else
|
||||
typeCheckLimits.finishTime = std::nullopt;
|
||||
|
||||
// TODO: This is a dirty ad hoc solution for autocomplete timeouts
|
||||
// We are trying to dynamically adjust our existing limits to lower total typechecking time under the limit
|
||||
// so that we'll have type information for the whole file at lower quality instead of a full abort in the middle
|
||||
if (FInt::LuauTarjanChildLimit > 0)
|
||||
typeCheckLimits.instantiationChildLimit = std::max(1, int(FInt::LuauTarjanChildLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckLimits.instantiationChildLimit = std::nullopt;
|
||||
|
||||
if (FInt::LuauTypeInferIterationLimit > 0)
|
||||
typeCheckLimits.unifierIterationLimit = std::max(1, int(FInt::LuauTypeInferIterationLimit * sourceNode.autocompleteLimitsMult));
|
||||
else
|
||||
typeCheckLimits.unifierIterationLimit = std::nullopt;
|
||||
|
||||
moduleForAutocomplete = check(sourceModule, Mode::Strict, requireCycles, environmentScope, /*forAutocomplete*/ true,
|
||||
/*recordJsonLog*/ false, typeCheckLimits);
|
||||
}
|
||||
|
||||
moduleResolverForAutocomplete.modules[moduleName] = moduleForAutocomplete;
|
||||
|
||||
|
@ -543,13 +578,22 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
continue;
|
||||
}
|
||||
|
||||
typeChecker.requireCycles = requireCycles;
|
||||
|
||||
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson && moduleName == name;
|
||||
|
||||
ModulePtr module = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
||||
? check(sourceModule, mode, requireCycles, /*forAutocomplete*/ false, recordJsonLog)
|
||||
: typeChecker.check(sourceModule, mode, environmentScope);
|
||||
ModulePtr module;
|
||||
|
||||
if (!FFlag::LuauOnDemandTypecheckers)
|
||||
{
|
||||
typeChecker_DEPRECATED.requireCycles = requireCycles;
|
||||
|
||||
module = (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
||||
? check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, recordJsonLog, {})
|
||||
: typeChecker_DEPRECATED.check(sourceModule, mode, environmentScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, recordJsonLog, {});
|
||||
}
|
||||
|
||||
stats.timeCheck += getTimestamp() - timestamp;
|
||||
stats.filesStrict += mode == Mode::Strict;
|
||||
|
@ -752,7 +796,7 @@ ScopePtr Frontend::getModuleEnvironment(const SourceModule& module, const Config
|
|||
AstName name = module.names->get(global.c_str());
|
||||
|
||||
if (name.value)
|
||||
result->bindings[name].typeId = typeChecker.anyType;
|
||||
result->bindings[name].typeId = FFlag::LuauOnDemandTypecheckers ? builtinTypes->anyType : typeChecker_DEPRECATED.anyType;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -829,15 +873,15 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
|||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, FrontendOptions options)
|
||||
const ScopePtr& parentScope, FrontendOptions options)
|
||||
{
|
||||
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
||||
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, globalScope, options, recordJsonLog);
|
||||
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope, options, recordJsonLog);
|
||||
}
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, FrontendOptions options, bool recordJsonLog)
|
||||
const ScopePtr& parentScope, FrontendOptions options, bool recordJsonLog)
|
||||
{
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, iceHandler);
|
||||
|
@ -868,7 +912,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||
moduleResolver,
|
||||
builtinTypes,
|
||||
iceHandler,
|
||||
globalScope,
|
||||
parentScope,
|
||||
logger.get(),
|
||||
NotNull{&dfg},
|
||||
};
|
||||
|
@ -911,11 +955,35 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||
return result;
|
||||
}
|
||||
|
||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles, bool forAutocomplete, bool recordJsonLog)
|
||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles,
|
||||
std::optional<ScopePtr> environmentScope, bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits)
|
||||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope, options, recordJsonLog);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
||||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope, options, recordJsonLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauOnDemandTypecheckers);
|
||||
|
||||
TypeChecker typeChecker(globals.globalScope, forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler);
|
||||
|
||||
if (prepareModuleScope)
|
||||
{
|
||||
typeChecker.prepareModuleScope = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) {
|
||||
prepareModuleScope(name, scope, forAutocomplete);
|
||||
};
|
||||
}
|
||||
|
||||
typeChecker.requireCycles = requireCycles;
|
||||
typeChecker.finishTime = typeCheckLimits.finishTime;
|
||||
typeChecker.instantiationChildLimit = typeCheckLimits.instantiationChildLimit;
|
||||
typeChecker.unifierIterationLimit = typeCheckLimits.unifierIterationLimit;
|
||||
|
||||
return typeChecker.check(sourceModule, mode, environmentScope);
|
||||
}
|
||||
}
|
||||
|
||||
// Read AST into sourceModules if necessary. Trace require()s. Report parse errors.
|
||||
|
|
|
@ -20,6 +20,7 @@ LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess2, false);
|
|||
LUAU_FASTFLAG(LuauSubstitutionReentrant);
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution);
|
||||
LUAU_FASTFLAG(LuauSubstitutionFixMissingFields);
|
||||
LUAU_FASTFLAGVARIABLE(LuauCopyExportedTypes, false);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -37,14 +38,14 @@ static bool contains(Position pos, Comment comment)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
||||
static bool isWithinComment(const std::vector<Comment>& commentLocations, Position pos)
|
||||
{
|
||||
auto iter = std::lower_bound(sourceModule.commentLocations.begin(), sourceModule.commentLocations.end(),
|
||||
Comment{Lexeme::Comment, Location{pos, pos}}, [](const Comment& a, const Comment& b) {
|
||||
auto iter = std::lower_bound(
|
||||
commentLocations.begin(), commentLocations.end(), Comment{Lexeme::Comment, Location{pos, pos}}, [](const Comment& a, const Comment& b) {
|
||||
return a.location.end < b.location.end;
|
||||
});
|
||||
|
||||
if (iter == sourceModule.commentLocations.end())
|
||||
if (iter == commentLocations.end())
|
||||
return false;
|
||||
|
||||
if (contains(pos, *iter))
|
||||
|
@ -53,12 +54,22 @@ bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
|||
// Due to the nature of std::lower_bound, it is possible that iter points at a comment that ends
|
||||
// at pos. We'll try the next comment, if it exists.
|
||||
++iter;
|
||||
if (iter == sourceModule.commentLocations.end())
|
||||
if (iter == commentLocations.end())
|
||||
return false;
|
||||
|
||||
return contains(pos, *iter);
|
||||
}
|
||||
|
||||
bool isWithinComment(const SourceModule& sourceModule, Position pos)
|
||||
{
|
||||
return isWithinComment(sourceModule.commentLocations, pos);
|
||||
}
|
||||
|
||||
bool isWithinComment(const ParseResult& result, Position pos)
|
||||
{
|
||||
return isWithinComment(result.commentLocations, pos);
|
||||
}
|
||||
|
||||
struct ClonePublicInterface : Substitution
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
|
@ -227,7 +238,7 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
|||
|
||||
// Copy external stuff over to Module itself
|
||||
this->returnType = moduleScope->returnType;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauCopyExportedTypes)
|
||||
this->exportedTypeBindings = moduleScope->exportedTypeBindings;
|
||||
else
|
||||
this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings);
|
||||
|
|
|
@ -337,7 +337,16 @@ bool isSubset(const UnionType& super, const UnionType& sub)
|
|||
|
||||
return true;
|
||||
}
|
||||
bool hasPrimitiveTypeInIntersection(TypeId ty, PrimitiveType::Type primTy)
|
||||
{
|
||||
TypeId tf = follow(ty);
|
||||
if (isPrim(tf, primTy))
|
||||
return true;
|
||||
|
||||
for (auto t : flattenIntersection(tf))
|
||||
return isPrim(follow(t), primTy);
|
||||
return false;
|
||||
}
|
||||
// When typechecking an assignment `x = e`, we typecheck `x:T` and `e:U`,
|
||||
// then instantiate U if `isGeneric(U)` is true, and `maybeGeneric(T)` is false.
|
||||
bool isGeneric(TypeId ty)
|
||||
|
|
|
@ -1160,11 +1160,7 @@ struct TypeChecker2
|
|||
visit(expr, RValue);
|
||||
|
||||
TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
|
||||
const NormalizedType* norm = normalizer.normalize(leftType);
|
||||
if (!norm)
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
|
||||
checkIndexTypeFromType(leftType, *norm, propName, location, context);
|
||||
checkIndexTypeFromType(leftType, propName, location, context);
|
||||
}
|
||||
|
||||
void visit(AstExprIndexName* indexName, ValueContext context)
|
||||
|
@ -2033,8 +2029,16 @@ struct TypeChecker2
|
|||
reportError(std::move(e));
|
||||
}
|
||||
|
||||
void checkIndexTypeFromType(TypeId tableTy, const NormalizedType& norm, const std::string& prop, const Location& location, ValueContext context)
|
||||
// If the provided type does not have the named property, report an error.
|
||||
void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, const Location& location, ValueContext context)
|
||||
{
|
||||
const NormalizedType* norm = normalizer.normalize(tableTy);
|
||||
if (!norm)
|
||||
{
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
return;
|
||||
}
|
||||
|
||||
bool foundOneProp = false;
|
||||
std::vector<TypeId> typesMissingTheProp;
|
||||
|
||||
|
@ -2042,49 +2046,50 @@ struct TypeChecker2
|
|||
if (!normalizer.isInhabited(ty))
|
||||
return;
|
||||
|
||||
bool found = hasIndexTypeFromType(ty, prop, location);
|
||||
std::unordered_set<TypeId> seen;
|
||||
bool found = hasIndexTypeFromType(ty, prop, location, seen);
|
||||
foundOneProp |= found;
|
||||
if (!found)
|
||||
typesMissingTheProp.push_back(ty);
|
||||
};
|
||||
|
||||
fetch(norm.tops);
|
||||
fetch(norm.booleans);
|
||||
fetch(norm->tops);
|
||||
fetch(norm->booleans);
|
||||
|
||||
if (FFlag::LuauNegatedClassTypes)
|
||||
{
|
||||
for (const auto& [ty, _negations] : norm.classes.classes)
|
||||
for (const auto& [ty, _negations] : norm->classes.classes)
|
||||
{
|
||||
fetch(ty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId ty : norm.DEPRECATED_classes)
|
||||
for (TypeId ty : norm->DEPRECATED_classes)
|
||||
fetch(ty);
|
||||
}
|
||||
fetch(norm.errors);
|
||||
fetch(norm.nils);
|
||||
fetch(norm.numbers);
|
||||
if (!norm.strings.isNever())
|
||||
fetch(norm->errors);
|
||||
fetch(norm->nils);
|
||||
fetch(norm->numbers);
|
||||
if (!norm->strings.isNever())
|
||||
fetch(builtinTypes->stringType);
|
||||
fetch(norm.threads);
|
||||
for (TypeId ty : norm.tables)
|
||||
fetch(norm->threads);
|
||||
for (TypeId ty : norm->tables)
|
||||
fetch(ty);
|
||||
if (norm.functions.isTop)
|
||||
if (norm->functions.isTop)
|
||||
fetch(builtinTypes->functionType);
|
||||
else if (!norm.functions.isNever())
|
||||
else if (!norm->functions.isNever())
|
||||
{
|
||||
if (norm.functions.parts.size() == 1)
|
||||
fetch(norm.functions.parts.front());
|
||||
if (norm->functions.parts.size() == 1)
|
||||
fetch(norm->functions.parts.front());
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
parts.insert(parts.end(), norm.functions.parts.begin(), norm.functions.parts.end());
|
||||
parts.insert(parts.end(), norm->functions.parts.begin(), norm->functions.parts.end());
|
||||
fetch(testArena.addType(IntersectionType{std::move(parts)}));
|
||||
}
|
||||
}
|
||||
for (const auto& [tyvar, intersect] : norm.tyvars)
|
||||
for (const auto& [tyvar, intersect] : norm->tyvars)
|
||||
{
|
||||
if (get<NeverType>(intersect->tops))
|
||||
{
|
||||
|
@ -2110,8 +2115,15 @@ struct TypeChecker2
|
|||
}
|
||||
}
|
||||
|
||||
bool hasIndexTypeFromType(TypeId ty, const std::string& prop, const Location& location)
|
||||
bool hasIndexTypeFromType(TypeId ty, const std::string& prop, const Location& location, std::unordered_set<TypeId>& seen)
|
||||
{
|
||||
// If we have already encountered this type, we must assume that some
|
||||
// other codepath will do the right thing and signal false if the
|
||||
// property is not present.
|
||||
const bool isUnseen = seen.insert(ty).second;
|
||||
if (!isUnseen)
|
||||
return true;
|
||||
|
||||
if (get<ErrorType>(ty) || get<AnyType>(ty) || get<NeverType>(ty))
|
||||
return true;
|
||||
|
||||
|
@ -2136,10 +2148,12 @@ struct TypeChecker2
|
|||
else if (const ClassType* cls = get<ClassType>(ty))
|
||||
return bool(lookupClassProp(cls, prop));
|
||||
else if (const UnionType* utv = get<UnionType>(ty))
|
||||
ice.ice("getIndexTypeFromTypeHelper cannot take a UnionType");
|
||||
return std::all_of(begin(utv), end(utv), [&](TypeId part) {
|
||||
return hasIndexTypeFromType(part, prop, location, seen);
|
||||
});
|
||||
else if (const IntersectionType* itv = get<IntersectionType>(ty))
|
||||
return std::any_of(begin(itv), end(itv), [&](TypeId part) {
|
||||
return hasIndexTypeFromType(part, prop, location);
|
||||
return hasIndexTypeFromType(part, prop, location, seen);
|
||||
});
|
||||
else
|
||||
return false;
|
||||
|
|
|
@ -35,14 +35,13 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
|
||||
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
|
||||
LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReducingAndOr, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1623,9 +1622,28 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& ty
|
|||
|
||||
TypeId& bindingType = bindingsMap[name].type;
|
||||
|
||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||
bindingType = ty;
|
||||
if (!FFlag::LuauOccursIsntAlwaysFailure)
|
||||
{
|
||||
if (unify(ty, bindingType, aliasScope, typealias.location))
|
||||
bindingType = ty;
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
unify(ty, bindingType, aliasScope, typealias.location);
|
||||
|
||||
// It is possible for this unification to succeed but for
|
||||
// `bindingType` still to be free For example, in
|
||||
// `type T = T|T`, we generate a fresh free type `X`, and then
|
||||
// unify `X` with `X|X`, which succeeds without binding `X` to
|
||||
// anything, since `X <: X|X`
|
||||
if (bindingType->ty.get_if<FreeType>())
|
||||
{
|
||||
ty = errorRecoveryType(aliasScope);
|
||||
unify(ty, bindingType, aliasScope, typealias.location);
|
||||
reportError(TypeError{typealias.location, OccursCheckFailed{}});
|
||||
}
|
||||
|
||||
bindingType = ty;
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
|
@ -2848,7 +2866,7 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
{
|
||||
return lhsType;
|
||||
}
|
||||
else if (FFlag::LuauTryhardAnd)
|
||||
else
|
||||
{
|
||||
// If lhs is free, we can't tell which 'falsy' components it has, if any
|
||||
if (get<FreeType>(lhsType))
|
||||
|
@ -2860,14 +2878,11 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
{
|
||||
LUAU_ASSERT(oty);
|
||||
|
||||
if (FFlag::LuauReducingAndOr)
|
||||
{
|
||||
// Perform a limited form of type reduction for booleans
|
||||
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
|
||||
return booleanType;
|
||||
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
|
||||
return booleanType;
|
||||
}
|
||||
// Perform a limited form of type reduction for booleans
|
||||
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
|
||||
return booleanType;
|
||||
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
|
||||
return booleanType;
|
||||
|
||||
return unionOfTypes(*oty, rhsType, scope, expr.location, false);
|
||||
}
|
||||
|
@ -2876,16 +2891,12 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
return rhsType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return unionOfTypes(rhsType, booleanType, scope, expr.location, false);
|
||||
}
|
||||
case AstExprBinary::Or:
|
||||
if (lhsIsAny)
|
||||
{
|
||||
return lhsType;
|
||||
}
|
||||
else if (FFlag::LuauTryhardAnd)
|
||||
else
|
||||
{
|
||||
auto [oty, notNever] = pickTypesFromSense(lhsType, true, neverType); // Filter out truthy types
|
||||
|
||||
|
@ -2893,14 +2904,11 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
{
|
||||
LUAU_ASSERT(oty);
|
||||
|
||||
if (FFlag::LuauReducingAndOr)
|
||||
{
|
||||
// Perform a limited form of type reduction for booleans
|
||||
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
|
||||
return booleanType;
|
||||
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
|
||||
return booleanType;
|
||||
}
|
||||
// Perform a limited form of type reduction for booleans
|
||||
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
|
||||
return booleanType;
|
||||
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
|
||||
return booleanType;
|
||||
|
||||
return unionOfTypes(*oty, rhsType, scope, expr.location);
|
||||
}
|
||||
|
@ -2909,10 +2917,6 @@ TypeId TypeChecker::checkRelationalOperation(
|
|||
return rhsType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return unionOfTypes(lhsType, rhsType, scope, expr.location);
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(0);
|
||||
ice(format("checkRelationalOperation called with incorrect binary expression '%s'", toString(expr.op).c_str()), expr.location);
|
||||
|
|
|
@ -19,8 +19,10 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
|||
LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauVariadicAnyCanBeGeneric, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauNormalizeBlockedTypes)
|
||||
|
@ -431,14 +433,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
|
||||
if (superFree && subFree && subsumes(useScopes, superFree, subFree))
|
||||
{
|
||||
if (!occursCheck(subTy, superTy))
|
||||
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||
log.replace(subTy, BoundType(superTy));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (superFree && subFree)
|
||||
{
|
||||
if (!occursCheck(superTy, subTy))
|
||||
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||
{
|
||||
if (subsumes(useScopes, superFree, subFree))
|
||||
{
|
||||
|
@ -461,7 +463,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
return;
|
||||
}
|
||||
|
||||
if (!occursCheck(superTy, subTy))
|
||||
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||
{
|
||||
promoteTypeLevels(log, types, superFree->level, superFree->scope, useScopes, subTy);
|
||||
|
||||
|
@ -487,7 +489,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
|||
return;
|
||||
}
|
||||
|
||||
if (!occursCheck(subTy, superTy))
|
||||
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||
{
|
||||
promoteTypeLevels(log, types, subFree->level, subFree->scope, useScopes, superTy);
|
||||
log.replace(subTy, BoundType(superTy));
|
||||
|
@ -1593,7 +1595,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
|
||||
if (log.getMutable<FreeTypePack>(superTp))
|
||||
{
|
||||
if (!occursCheck(superTp, subTp))
|
||||
if (!occursCheck(superTp, subTp, /* reversed = */ true))
|
||||
{
|
||||
Widen widen{types, builtinTypes};
|
||||
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
|
||||
|
@ -1601,7 +1603,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
|||
}
|
||||
else if (log.getMutable<FreeTypePack>(subTp))
|
||||
{
|
||||
if (!occursCheck(subTp, superTp))
|
||||
if (!occursCheck(subTp, superTp, /* reversed = */ false))
|
||||
{
|
||||
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
||||
}
|
||||
|
@ -2585,13 +2587,14 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
|
|||
void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool reversed, int subOffset)
|
||||
{
|
||||
const VariadicTypePack* superVariadic = log.getMutable<VariadicTypePack>(superTp);
|
||||
const TypeId variadicTy = follow(superVariadic->ty);
|
||||
|
||||
if (!superVariadic)
|
||||
ice("passed non-variadic pack to tryUnifyVariadics");
|
||||
|
||||
if (const VariadicTypePack* subVariadic = log.get<VariadicTypePack>(subTp))
|
||||
{
|
||||
tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty);
|
||||
tryUnify_(reversed ? variadicTy : subVariadic->ty, reversed ? subVariadic->ty : variadicTy);
|
||||
}
|
||||
else if (log.get<TypePack>(subTp))
|
||||
{
|
||||
|
@ -2602,7 +2605,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
|||
|
||||
while (subIter != subEnd)
|
||||
{
|
||||
tryUnify_(reversed ? superVariadic->ty : *subIter, reversed ? *subIter : superVariadic->ty);
|
||||
tryUnify_(reversed ? variadicTy : *subIter, reversed ? *subIter : variadicTy);
|
||||
++subIter;
|
||||
}
|
||||
|
||||
|
@ -2615,7 +2618,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
|||
}
|
||||
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
tryUnify_(vtp->ty, superVariadic->ty);
|
||||
tryUnify_(vtp->ty, variadicTy);
|
||||
}
|
||||
else if (get<GenericTypePack>(tail))
|
||||
{
|
||||
|
@ -2631,6 +2634,10 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauVariadicAnyCanBeGeneric && get<AnyType>(variadicTy) && log.get<GenericTypePack>(subTp))
|
||||
{
|
||||
// Nothing to do. This is ok.
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(location, GenericError{"Failed to unify variadic packs"});
|
||||
|
@ -2751,11 +2758,42 @@ TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool Unifier::occursCheck(TypeId needle, TypeId haystack)
|
||||
bool Unifier::occursCheck(TypeId needle, TypeId haystack, bool reversed)
|
||||
{
|
||||
sharedState.tempSeenTy.clear();
|
||||
|
||||
return occursCheck(sharedState.tempSeenTy, needle, haystack);
|
||||
bool occurs = occursCheck(sharedState.tempSeenTy, needle, haystack);
|
||||
|
||||
if (occurs && FFlag::LuauOccursIsntAlwaysFailure)
|
||||
{
|
||||
Unifier innerState = makeChildUnifier();
|
||||
if (const UnionType* ut = get<UnionType>(haystack))
|
||||
{
|
||||
if (reversed)
|
||||
innerState.tryUnifyUnionWithType(haystack, ut, needle);
|
||||
else
|
||||
innerState.tryUnifyTypeWithUnion(needle, haystack, ut, /* cacheEnabled = */ false, /* isFunction = */ false);
|
||||
}
|
||||
else if (const IntersectionType* it = get<IntersectionType>(haystack))
|
||||
{
|
||||
if (reversed)
|
||||
innerState.tryUnifyIntersectionWithType(haystack, it, needle, /* cacheEnabled = */ false, /* isFunction = */ false);
|
||||
else
|
||||
innerState.tryUnifyTypeWithIntersection(needle, haystack, it);
|
||||
}
|
||||
else
|
||||
{
|
||||
innerState.failure = true;
|
||||
}
|
||||
|
||||
if (innerState.failure)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryType());
|
||||
}
|
||||
}
|
||||
|
||||
return occurs;
|
||||
}
|
||||
|
||||
bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
||||
|
@ -2785,8 +2823,11 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
|
|||
|
||||
if (needle == haystack)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryType());
|
||||
if (!FFlag::LuauOccursIsntAlwaysFailure)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2807,11 +2848,19 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
|
|||
return occurrence;
|
||||
}
|
||||
|
||||
bool Unifier::occursCheck(TypePackId needle, TypePackId haystack)
|
||||
bool Unifier::occursCheck(TypePackId needle, TypePackId haystack, bool reversed)
|
||||
{
|
||||
sharedState.tempSeenTp.clear();
|
||||
|
||||
return occursCheck(sharedState.tempSeenTp, needle, haystack);
|
||||
bool occurs = occursCheck(sharedState.tempSeenTp, needle, haystack);
|
||||
|
||||
if (occurs && FFlag::LuauOccursIsntAlwaysFailure)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryTypePack());
|
||||
}
|
||||
|
||||
return occurs;
|
||||
}
|
||||
|
||||
bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
||||
|
@ -2836,8 +2885,11 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
|||
{
|
||||
if (needle == haystack)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryTypePack());
|
||||
if (!FFlag::LuauOccursIsntAlwaysFailure)
|
||||
{
|
||||
reportError(location, OccursCheckFailed{});
|
||||
log.replace(needle, *builtinTypes->errorRecoveryTypePack());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -167,7 +167,9 @@ size_t editDistance(std::string_view a, std::string_view b)
|
|||
|
||||
for (size_t y = 1; y <= b.size(); ++y)
|
||||
{
|
||||
size_t x1 = seenCharToRow[b[y - 1]];
|
||||
// The value of b[N] can be negative with unicode characters
|
||||
unsigned char bSeenCharIndex = static_cast<unsigned char>(b[y - 1]);
|
||||
size_t x1 = seenCharToRow[bSeenCharIndex];
|
||||
size_t y1 = lastMatchedY;
|
||||
|
||||
size_t cost = 1;
|
||||
|
@ -187,7 +189,9 @@ size_t editDistance(std::string_view a, std::string_view b)
|
|||
distances[getPos(x + 1, y + 1)] = std::min(std::min(insertion, deletion), std::min(substitution, transposition));
|
||||
}
|
||||
|
||||
seenCharToRow[a[x - 1]] = x;
|
||||
// The value of a[N] can be negative with unicode characters
|
||||
unsigned char aSeenCharIndex = static_cast<unsigned char>(a[x - 1]);
|
||||
seenCharToRow[aSeenCharIndex] = x;
|
||||
}
|
||||
|
||||
return distances[getPos(a.size() + 1, b.size() + 1)];
|
||||
|
|
|
@ -29,7 +29,7 @@ struct AddressA64
|
|||
// For example, ldr x0, [reg+imm] is limited to 8 KB offsets assuming imm is divisible by 8, but loading into w0 reduces the range to 4 KB
|
||||
static constexpr size_t kMaxOffset = 1023;
|
||||
|
||||
AddressA64(RegisterA64 base, int off = 0)
|
||||
constexpr AddressA64(RegisterA64 base, int off = 0)
|
||||
: kind(AddressKindA64::imm)
|
||||
, base(base)
|
||||
, offset(xzr)
|
||||
|
@ -38,7 +38,7 @@ struct AddressA64
|
|||
LUAU_ASSERT(base.kind == KindA64::x || base == sp);
|
||||
}
|
||||
|
||||
AddressA64(RegisterA64 base, RegisterA64 offset)
|
||||
constexpr AddressA64(RegisterA64 base, RegisterA64 offset)
|
||||
: kind(AddressKindA64::reg)
|
||||
, base(base)
|
||||
, offset(offset)
|
||||
|
|
|
@ -49,17 +49,25 @@ public:
|
|||
void cmp(RegisterA64 src1, RegisterA64 src2);
|
||||
void cmp(RegisterA64 src1, uint16_t src2);
|
||||
void csel(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond);
|
||||
void cset(RegisterA64 dst, ConditionA64 cond);
|
||||
|
||||
// Bitwise
|
||||
// TODO: support immediate arguments (they have odd encoding and forbid many values)
|
||||
// TODO: support bic (andnot)
|
||||
// TODO: support shifts
|
||||
// TODO: support bitfield ops
|
||||
void and_(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void orr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void bic(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void tst(RegisterA64 src1, RegisterA64 src2);
|
||||
void mvn(RegisterA64 dst, RegisterA64 src);
|
||||
|
||||
// Bitwise with immediate
|
||||
// Note: immediate must have a single contiguous sequence of 1 bits set of length 1..31
|
||||
void and_(RegisterA64 dst, RegisterA64 src1, uint32_t src2);
|
||||
void orr(RegisterA64 dst, RegisterA64 src1, uint32_t src2);
|
||||
void eor(RegisterA64 dst, RegisterA64 src1, uint32_t src2);
|
||||
void tst(RegisterA64 src1, uint32_t src2);
|
||||
|
||||
// Shifts
|
||||
void lsl(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
void lsr(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2);
|
||||
|
@ -168,7 +176,7 @@ public:
|
|||
private:
|
||||
// Instruction archetypes
|
||||
void place0(const char* name, uint32_t word);
|
||||
void placeSR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift = 0);
|
||||
void placeSR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift = 0, int N = 0);
|
||||
void placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op, uint8_t op2 = 0);
|
||||
void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t op2);
|
||||
void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
|
||||
|
@ -181,8 +189,9 @@ private:
|
|||
void placeADR(const char* name, RegisterA64 src, uint8_t op);
|
||||
void placeADR(const char* name, RegisterA64 src, uint8_t op, Label& label);
|
||||
void placeP(const char* name, RegisterA64 dst1, RegisterA64 dst2, AddressA64 src, uint8_t op, uint8_t opc, int sizelog);
|
||||
void placeCS(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond, uint8_t op, uint8_t opc);
|
||||
void placeCS(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, ConditionA64 cond, uint8_t op, uint8_t opc, int invert = 0);
|
||||
void placeFCMP(const char* name, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t opc);
|
||||
void placeBM(const char* name, RegisterA64 dst, RegisterA64 src1, uint32_t src2, uint8_t op);
|
||||
|
||||
void place(uint32_t word);
|
||||
|
||||
|
|
|
@ -41,12 +41,14 @@ public:
|
|||
|
||||
void call(const OperandX64& func);
|
||||
|
||||
RegisterX64 suggestNextArgumentRegister(SizeX64 size) const;
|
||||
|
||||
IrRegAllocX64& regs;
|
||||
AssemblyBuilderX64& build;
|
||||
uint32_t instIdx = ~0u;
|
||||
|
||||
private:
|
||||
void assignTargetRegisters();
|
||||
OperandX64 getNextArgumentTarget(SizeX64 size) const;
|
||||
void countRegisterUses();
|
||||
CallArgument* findNonInterferingArgument();
|
||||
bool interferesWithOperand(const OperandX64& op, RegisterX64 reg) const;
|
||||
|
@ -67,6 +69,9 @@ private:
|
|||
std::array<CallArgument, kMaxCallArguments> args;
|
||||
int argCount = 0;
|
||||
|
||||
int gprPos = 0;
|
||||
int xmmPos = 0;
|
||||
|
||||
OperandX64 funcOp;
|
||||
|
||||
// Internal counters for remaining register use counts
|
||||
|
|
|
@ -155,7 +155,7 @@ enum class IrCmd : uint8_t
|
|||
|
||||
// Compute Luau 'not' operation on destructured TValue
|
||||
// A: tag
|
||||
// B: double
|
||||
// B: int (value)
|
||||
NOT_ANY, // TODO: boolean specialization will be useful
|
||||
|
||||
// Unconditional jump
|
||||
|
@ -233,7 +233,7 @@ enum class IrCmd : uint8_t
|
|||
|
||||
// Try to get pointer to tag method TValue inside the table's metatable or jump if there is no such value or metatable
|
||||
// A: table
|
||||
// B: int
|
||||
// B: int (TMS enum)
|
||||
// C: block
|
||||
TRY_CALL_FASTGETTM,
|
||||
|
||||
|
@ -256,8 +256,8 @@ enum class IrCmd : uint8_t
|
|||
// B: Rn (result start)
|
||||
// C: Rn (argument start)
|
||||
// D: Rn or Kn or a boolean that's false (optional second argument)
|
||||
// E: int (argument count or -1 to use all arguments up to stack top)
|
||||
// F: int (result count or -1 to preserve all results and adjust stack top)
|
||||
// E: int (argument count)
|
||||
// F: int (result count)
|
||||
FASTCALL,
|
||||
|
||||
// Call the fastcall builtin function
|
||||
|
@ -517,8 +517,10 @@ enum class IrCmd : uint8_t
|
|||
FALLBACK_FORGPREP,
|
||||
|
||||
// Instruction that passes value through, it is produced by constant folding and users substitute it with the value
|
||||
// When operand location is set, updates the tracked location of the value in memory
|
||||
SUBSTITUTE,
|
||||
// A: operand of any type
|
||||
// B: Rn/Kn/none (location of operand in memory; optional)
|
||||
};
|
||||
|
||||
enum class IrConstKind : uint8_t
|
||||
|
@ -694,6 +696,9 @@ struct IrFunction
|
|||
|
||||
std::vector<BytecodeMapping> bcMapping;
|
||||
|
||||
// For each instruction, an operand that can be used to recompute the calue
|
||||
std::vector<IrOp> valueRestoreOps;
|
||||