mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-01 17:30:53 +01:00
Sync to upstream/release/667
This commit is contained in:
parent
2eefa3f8e0
commit
d4c2c64dcd
60 changed files with 4327 additions and 2863 deletions
|
@ -1,148 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class ParseError;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct GlobalTypes;
|
||||
struct ModuleResolver;
|
||||
struct ParseResult;
|
||||
struct DcrLogger;
|
||||
|
||||
struct TelemetryTypePair
|
||||
{
|
||||
std::string annotatedType;
|
||||
std::string inferredType;
|
||||
};
|
||||
|
||||
struct AnyTypeSummary
|
||||
{
|
||||
TypeArena arena;
|
||||
|
||||
AstStatBlock* rootSrc = nullptr;
|
||||
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
|
||||
|
||||
int recursionCount = 0;
|
||||
|
||||
std::string root;
|
||||
int strictCount = 0;
|
||||
|
||||
DenseHashMap<const void*, bool> seen{nullptr};
|
||||
|
||||
AnyTypeSummary();
|
||||
|
||||
void traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
std::pair<bool, TypeId> checkForAnyCast(const Scope* scope, AstExprTypeAssertion* expr);
|
||||
|
||||
bool containsAny(TypePackId typ);
|
||||
bool containsAny(TypeId typ);
|
||||
|
||||
bool isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
bool hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
TypeId checkForFamilyInhabitance(const TypeId instance, Location location);
|
||||
TypeId lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
TypePackId reconstructTypePack(const AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
|
||||
TypeId lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes);
|
||||
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, const Module* module);
|
||||
TypeId checkForTypeFunctionInhabitance(const TypeId instance, const Location location);
|
||||
|
||||
enum Pattern : uint64_t
|
||||
{
|
||||
Casts,
|
||||
FuncArg,
|
||||
FuncRet,
|
||||
FuncApp,
|
||||
VarAnnot,
|
||||
VarAny,
|
||||
TableProp,
|
||||
Alias,
|
||||
Assign,
|
||||
TypePk
|
||||
};
|
||||
|
||||
struct TypeInfo
|
||||
{
|
||||
Pattern code;
|
||||
std::string node;
|
||||
TelemetryTypePair type;
|
||||
|
||||
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
|
||||
};
|
||||
|
||||
struct FindReturnAncestry final : public AstVisitor
|
||||
{
|
||||
AstNode* currNode{nullptr};
|
||||
AstNode* stat{nullptr};
|
||||
Position rootEnd;
|
||||
bool found = false;
|
||||
|
||||
explicit FindReturnAncestry(AstNode* stat, Position rootEnd);
|
||||
|
||||
bool visit(AstType* node) override;
|
||||
bool visit(AstNode* node) override;
|
||||
bool visit(AstStatFunction* node) override;
|
||||
bool visit(AstStatLocalFunction* node) override;
|
||||
};
|
||||
|
||||
std::vector<TypeInfo> typeInfo;
|
||||
|
||||
/**
|
||||
* Fabricates a scope that is a child of another scope.
|
||||
* @param node the lexical node that the scope belongs to.
|
||||
* @param parent the parent scope of the new scope. Must not be null.
|
||||
*/
|
||||
const Scope* childScope(const AstNode* node, const Scope* parent);
|
||||
|
||||
std::optional<AstExpr*> matchRequire(const AstExprCall& call);
|
||||
AstNode* getNode(AstStatBlock* root, AstNode* node);
|
||||
const Scope* findInnerMostScope(const Location location, const Module* module);
|
||||
const AstNode* findAstAncestryAtLocation(const AstStatBlock* root, AstNode* node);
|
||||
|
||||
void visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -50,6 +50,7 @@ struct GeneralizationConstraint
|
|||
TypeId sourceType;
|
||||
|
||||
std::vector<TypeId> interiorTypes;
|
||||
bool hasDeprecatedAttribute = false;
|
||||
};
|
||||
|
||||
// variables ~ iterate iterator
|
||||
|
|
|
@ -365,7 +365,7 @@ public:
|
|||
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
|
||||
* does not exist
|
||||
*/
|
||||
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables = false);
|
||||
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);
|
||||
|
||||
/**
|
||||
* Checks the existing set of constraints to see if there exist any that contain
|
||||
|
|
|
@ -38,8 +38,6 @@ struct DataFlowGraph
|
|||
DefId getDef(const AstExpr* expr) const;
|
||||
// Look up the definition optionally, knowing it may not be present.
|
||||
std::optional<DefId> getDefOptional(const AstExpr* expr) const;
|
||||
// Look up for the rvalue def for a compound assignment.
|
||||
std::optional<DefId> getRValueDefForCompoundAssign(const AstExpr* expr) const;
|
||||
|
||||
DefId getDef(const AstLocal* local) const;
|
||||
|
||||
|
@ -66,10 +64,6 @@ private:
|
|||
// All keys in this maps are really only statements that ambiently declares a symbol.
|
||||
DenseHashMap<const AstStat*, const Def*> declaredDefs{nullptr};
|
||||
|
||||
// Compound assignments are in a weird situation where the local being assigned to is also being used at its
|
||||
// previous type implicitly in an rvalue position. This map provides the previous binding.
|
||||
DenseHashMap<const AstExpr*, const Def*> compoundAssignDefs{nullptr};
|
||||
|
||||
DenseHashMap<const AstExpr*, const RefinementKey*> astRefinementKeys{nullptr};
|
||||
friend struct DataFlowGraphBuilder;
|
||||
};
|
||||
|
|
|
@ -49,6 +49,8 @@ struct FragmentAutocompleteAncestryResult
|
|||
std::vector<AstLocal*> localStack;
|
||||
std::vector<AstNode*> ancestry;
|
||||
AstStat* nearestStatement = nullptr;
|
||||
AstStatBlock* parentBlock = nullptr;
|
||||
Location fragmentSelectionRegion;
|
||||
};
|
||||
|
||||
struct FragmentParseResult
|
||||
|
@ -59,6 +61,7 @@ struct FragmentParseResult
|
|||
AstStat* nearestStatement = nullptr;
|
||||
std::vector<Comment> commentLocations;
|
||||
std::unique_ptr<Allocator> alloc = std::make_unique<Allocator>();
|
||||
Position scopePos{0, 0};
|
||||
};
|
||||
|
||||
struct FragmentTypeCheckResult
|
||||
|
@ -76,10 +79,28 @@ struct FragmentAutocompleteResult
|
|||
AutocompleteResult acResults;
|
||||
};
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos);
|
||||
struct FragmentRegion
|
||||
{
|
||||
Location fragmentLocation;
|
||||
AstStat* nearestStatement = nullptr; // used for tests
|
||||
AstStatBlock* parentBlock = nullptr; // used for scope detection
|
||||
};
|
||||
|
||||
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse);
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos);
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
|
||||
AstStatBlock* root,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
);
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
AstStatBlock* root,
|
||||
AstStatBlock* stale,
|
||||
AstStatBlock* mostRecentParse,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
|
@ -93,6 +114,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
std::optional<FrontendOptions> opts,
|
||||
std::string_view src,
|
||||
std::optional<Position> fragmentEndPosition,
|
||||
AstStatBlock* recentParse = nullptr,
|
||||
IFragmentAutocompleteReporter* reporter = nullptr
|
||||
);
|
||||
|
||||
|
@ -104,6 +126,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
std::optional<FrontendOptions> opts,
|
||||
StringCompletionCallback callback,
|
||||
std::optional<Position> fragmentEndPosition = std::nullopt,
|
||||
AstStatBlock* recentParse = nullptr,
|
||||
IFragmentAutocompleteReporter* reporter = nullptr
|
||||
);
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "Luau/Set.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
@ -34,7 +33,6 @@ struct HotComment;
|
|||
struct BuildQueueItem;
|
||||
struct BuildQueueWorkState;
|
||||
struct FrontendCancellationToken;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
|
|
|
@ -13,8 +13,7 @@ std::optional<TypeId> generalize(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
TypeId ty,
|
||||
/* avoid sealing tables*/ bool avoidSealingTables = false
|
||||
TypeId ty
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -21,14 +20,13 @@ LUAU_FASTFLAG(LuauIncrementalAutocompleteCommentDetection)
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
using LogLuauProc = void (*)(std::string_view);
|
||||
using LogLuauProc = void (*)(std::string_view, std::string_view);
|
||||
extern LogLuauProc logLuau;
|
||||
|
||||
void setLogLuau(LogLuauProc ll);
|
||||
void resetLogLuauProc();
|
||||
|
||||
struct Module;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
|
@ -86,10 +84,6 @@ struct Module
|
|||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
// Summary of Ast Nodes that either contain
|
||||
// user annotated anys or typechecker inferred anys
|
||||
AnyTypeSummary ats{};
|
||||
|
||||
// Scopes and AST types refer to parse data, so we need to keep that alive
|
||||
std::shared_ptr<Allocator> allocator;
|
||||
std::shared_ptr<AstNameTable> names;
|
||||
|
|
|
@ -1,9 +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/DataFlowGraph.h"
|
||||
#include "Luau/EqSatSimplification.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||
|
@ -38,6 +37,15 @@ struct Constraint;
|
|||
struct Subtyping;
|
||||
struct TypeChecker2;
|
||||
|
||||
enum struct Polarity : uint8_t
|
||||
{
|
||||
None = 0b000,
|
||||
Positive = 0b001,
|
||||
Negative = 0b010,
|
||||
Mixed = 0b011,
|
||||
Unknown = 0b100,
|
||||
};
|
||||
|
||||
/**
|
||||
* There are three kinds of type variables:
|
||||
* - `Free` variables are metavariables, which stand for unconstrained types.
|
||||
|
@ -396,6 +404,7 @@ struct FunctionType
|
|||
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
||||
bool hasNoFreeOrGenericTypes = false;
|
||||
bool isCheckedFunction = false;
|
||||
bool isDeprecatedFunction = false;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
|
|
|
@ -177,6 +177,7 @@ struct FunctionGraphReductionResult
|
|||
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
||||
DenseHashSet<TypeId> reducedTypes{nullptr};
|
||||
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
||||
DenseHashSet<TypeId> irreducibleTypes{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,902 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Transpiler.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeOrPack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny2);
|
||||
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
void AnyTypeSummary::traverse(const Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
visit(findInnerMostScope(src->location, module), src, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStat* stat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
|
||||
|
||||
if (auto s = stat->as<AstStatBlock>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto i = stat->as<AstStatIf>())
|
||||
return visit(scope, i, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatWhile>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatRepeat>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto r = stat->as<AstStatReturn>())
|
||||
return visit(scope, r, module, builtinTypes);
|
||||
else if (auto e = stat->as<AstStatExpr>())
|
||||
return visit(scope, e, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatLocal>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatFor>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatForIn>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatAssign>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatCompoundAssign>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto f = stat->as<AstStatFunction>())
|
||||
return visit(scope, f, module, builtinTypes);
|
||||
else if (auto f = stat->as<AstStatLocalFunction>())
|
||||
return visit(scope, f, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatTypeAlias>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareGlobal>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareFunction>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareClass>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatError>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatBlock* block, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
|
||||
return; // don't report
|
||||
|
||||
for (AstStat* stat : block->body)
|
||||
visit(scope, stat, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatIf* ifStatement, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (ifStatement->thenbody)
|
||||
{
|
||||
const Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
|
||||
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
|
||||
}
|
||||
|
||||
if (ifStatement->elsebody)
|
||||
{
|
||||
const Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
|
||||
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatWhile* while_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const Scope* whileScope = findInnerMostScope(while_->location, module);
|
||||
visit(whileScope, while_->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatRepeat* repeat, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const Scope* repeatScope = findInnerMostScope(repeat->location, module);
|
||||
visit(repeatScope, repeat->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatReturn* ret, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const Scope* retScope = findInnerMostScope(ret->location, module);
|
||||
|
||||
auto ctxNode = getNode(rootSrc, ret);
|
||||
bool seenTP = false;
|
||||
|
||||
for (auto val : ret->list)
|
||||
{
|
||||
if (isAnyCall(retScope, val, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(val, module, builtinTypes));
|
||||
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(retScope, val, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = val->as<AstExprTypeAssertion>())
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret->list.size > 1 && !seenTP)
|
||||
{
|
||||
if (containsAny(retScope->returnType))
|
||||
{
|
||||
seenTP = true;
|
||||
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(retScope->returnType);
|
||||
|
||||
TypeInfo ti{Pattern::TypePk, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatLocal* local, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
auto ctxNode = getNode(rootSrc, local);
|
||||
|
||||
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
|
||||
auto [head, tail] = flatten(values);
|
||||
|
||||
size_t posn = 0;
|
||||
for (AstLocal* loc : local->vars)
|
||||
{
|
||||
if (local->vars.data[0] == loc && posn < local->values.size)
|
||||
{
|
||||
if (loc->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
types.inferredType = toString(lookupType(local->values.data[posn], module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
const AstExprTypeAssertion* maybeRequire = local->values.data[posn]->as<AstExprTypeAssertion>();
|
||||
if (!maybeRequire)
|
||||
continue;
|
||||
|
||||
if (std::min(local->values.size - 1, posn) < head.size())
|
||||
{
|
||||
if (isAnyCast(scope, local->values.data[posn], module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (std::min(local->values.size - 1, posn) < head.size())
|
||||
{
|
||||
if (loc->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
|
||||
|
||||
TypeInfo ti{Pattern::VarAnnot, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tail)
|
||||
{
|
||||
if (containsAny(*tail))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(*tail);
|
||||
|
||||
TypeInfo ti{Pattern::VarAny, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++posn;
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatFor* for_, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const Scope* forScope = findInnerMostScope(for_->location, module);
|
||||
visit(forScope, for_->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatForIn* forIn, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const Scope* loopScope = findInnerMostScope(forIn->location, module);
|
||||
visit(loopScope, forIn->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
auto ctxNode = getNode(rootSrc, assign);
|
||||
|
||||
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
|
||||
auto [head, tail] = flatten(values);
|
||||
|
||||
size_t posn = 0;
|
||||
for (AstExpr* var : assign->vars)
|
||||
{
|
||||
TypeId tp = lookupType(var, module, builtinTypes);
|
||||
if (containsAny(tp))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(tp);
|
||||
|
||||
auto loc = std::min(assign->vars.size - 1, posn);
|
||||
if (head.size() >= assign->vars.size && posn < head.size())
|
||||
{
|
||||
types.inferredType = toString(head[posn]);
|
||||
}
|
||||
else if (loc < head.size())
|
||||
types.inferredType = toString(head[loc]);
|
||||
else
|
||||
types.inferredType = toString(builtinTypes->nilType);
|
||||
|
||||
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
++posn;
|
||||
}
|
||||
|
||||
for (AstExpr* val : assign->values)
|
||||
{
|
||||
if (isAnyCall(scope, val, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(lookupType(val, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(scope, val, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = val->as<AstExprTypeAssertion>())
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
types.inferredType = toString(lookupType(val, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (containsAny(*tail))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(*tail);
|
||||
|
||||
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatCompoundAssign* assign, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
auto ctxNode = getNode(rootSrc, assign);
|
||||
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
|
||||
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
|
||||
|
||||
if (module->astTypes.contains(assign->var))
|
||||
{
|
||||
if (containsAny(*module->astTypes.find(assign->var)))
|
||||
{
|
||||
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
else if (module->astTypePacks.contains(assign->var))
|
||||
{
|
||||
if (containsAny(*module->astTypePacks.find(assign->var)))
|
||||
{
|
||||
TypeInfo ti{Pattern::Assign, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAnyCall(scope, assign->value, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(scope, assign->value, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = assign->value->as<AstExprTypeAssertion>())
|
||||
{
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
types.inferredType = toString(lookupType(cast->expr, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
|
||||
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::VarAny, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasArgAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncArg, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasAnyReturns(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncRet, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (function->func->body->body.size > 0)
|
||||
visit(scope, function->func->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatLocalFunction* function, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
TypeInfo ti{Pattern::VarAny, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasArgAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
TypeInfo ti{Pattern::FuncArg, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasAnyReturns(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
TypeInfo ti{Pattern::FuncRet, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (function->func->body->body.size > 0)
|
||||
visit(scope, function->func->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatTypeAlias* alias, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
auto ctxNode = getNode(rootSrc, alias);
|
||||
|
||||
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
// no expr => no inference for aliases
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
TypeInfo ti{Pattern::Alias, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
auto ctxNode = getNode(rootSrc, expr);
|
||||
|
||||
if (isAnyCall(scope, expr->expr, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::FuncApp, toString(ctxNode), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareGlobal* declareGlobal, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareClass* declareClass, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatDeclareFunction* declareFunction, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(const Scope* scope, AstStatError* error, const Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
TypeId AnyTypeSummary::checkForFamilyInhabitance(const TypeId instance, const Location location)
|
||||
{
|
||||
if (seenTypeFamilyInstances.find(instance))
|
||||
return instance;
|
||||
|
||||
seenTypeFamilyInstances.insert(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::lookupType(const AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
const TypeId* ty = module->astTypes.find(expr);
|
||||
if (ty)
|
||||
return checkForFamilyInhabitance(follow(*ty), expr->location);
|
||||
|
||||
const TypePackId* tp = module->astTypePacks.find(expr);
|
||||
if (tp)
|
||||
{
|
||||
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
|
||||
return checkForFamilyInhabitance(*fst, expr->location);
|
||||
else if (finite(*tp) && size(*tp) == 0)
|
||||
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
|
||||
}
|
||||
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (exprs.size == 0)
|
||||
return arena.addTypePack(TypePack{{}, std::nullopt});
|
||||
|
||||
std::vector<TypeId> head;
|
||||
|
||||
for (size_t i = 0; i < exprs.size - 1; ++i)
|
||||
{
|
||||
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
|
||||
}
|
||||
|
||||
const TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
|
||||
if (tail)
|
||||
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
|
||||
else
|
||||
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::isAnyCall(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (auto call = expr->as<AstExprCall>())
|
||||
{
|
||||
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
|
||||
if (containsAny(args))
|
||||
return true;
|
||||
|
||||
TypeId func = lookupType(call->func, module, builtinTypes);
|
||||
if (containsAny(func))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasVariadicAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (expr->vararg && expr->varargAnnotation)
|
||||
{
|
||||
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
|
||||
if (annot && containsAny(*annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasArgAnys(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (expr->args.size > 0)
|
||||
{
|
||||
for (const AstLocal* arg : expr->args)
|
||||
{
|
||||
if (arg->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasAnyReturns(const Scope* scope, AstExprFunction* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (!expr->returnAnnotation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (AstType* ret : expr->returnAnnotation->types)
|
||||
{
|
||||
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (expr->returnAnnotation->tailType)
|
||||
{
|
||||
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
|
||||
if (annot && containsAny(*annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::isAnyCast(const Scope* scope, AstExpr* expr, const Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (auto cast = expr->as<AstExprTypeAssertion>())
|
||||
{
|
||||
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, const Module* module, NotNull<BuiltinTypes> builtintypes)
|
||||
{
|
||||
if (FFlag::DebugLuauMagicTypes)
|
||||
{
|
||||
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
|
||||
{
|
||||
if (auto ann = ref->parameters.data[0].type)
|
||||
{
|
||||
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
|
||||
return follow(argTy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TypeId* ty = module->astResolvedTypes.find(annotation);
|
||||
if (ty)
|
||||
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
|
||||
else
|
||||
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(const TypeId instance, const Location location)
|
||||
{
|
||||
if (seenTypeFunctionInstances.find(instance))
|
||||
return instance;
|
||||
seenTypeFunctionInstances.insert(instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, const Module* module)
|
||||
{
|
||||
const TypePackId* tp = module->astResolvedTypePacks.find(annotation);
|
||||
if (tp != nullptr)
|
||||
return {follow(*tp)};
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::containsAny(TypeId typ)
|
||||
{
|
||||
typ = follow(typ);
|
||||
|
||||
if (auto t = seen.find(typ); t && !*t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[typ] = false;
|
||||
|
||||
RecursionCounter counter{&recursionCount};
|
||||
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (auto ty = get<AnyType>(typ))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else if (auto ty = get<UnknownType>(typ))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else if (auto ty = get<TableType>(typ))
|
||||
{
|
||||
for (auto& [_name, prop] : ty->props)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
if (auto newT = follow(prop.readTy))
|
||||
{
|
||||
if (containsAny(*newT))
|
||||
found = true;
|
||||
}
|
||||
else if (auto newT = follow(prop.writeTy))
|
||||
{
|
||||
if (containsAny(*newT))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (containsAny(prop.type()))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<IntersectionType>(typ))
|
||||
{
|
||||
for (auto part : ty->parts)
|
||||
{
|
||||
if (containsAny(part))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<UnionType>(typ))
|
||||
{
|
||||
for (auto option : ty->options)
|
||||
{
|
||||
if (containsAny(option))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<FunctionType>(typ))
|
||||
{
|
||||
if (containsAny(ty->argTypes))
|
||||
found = true;
|
||||
else if (containsAny(ty->retTypes))
|
||||
found = true;
|
||||
}
|
||||
|
||||
seen[typ] = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::containsAny(TypePackId typ)
|
||||
{
|
||||
typ = follow(typ);
|
||||
|
||||
if (auto t = seen.find(typ); t && !*t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[typ] = false;
|
||||
|
||||
auto [head, tail] = flatten(typ);
|
||||
bool found = false;
|
||||
|
||||
for (auto tp : head)
|
||||
{
|
||||
if (containsAny(tp))
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
if (auto ty = get<AnyType>(follow(vtp->ty)))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
|
||||
{
|
||||
|
||||
for (TypePackId tp : tftp->packArguments)
|
||||
{
|
||||
if (containsAny(tp))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (TypeId t : tftp->typeArguments)
|
||||
{
|
||||
if (containsAny(t))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seen[typ] = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
const Scope* AnyTypeSummary::findInnerMostScope(const Location location, const Module* module)
|
||||
{
|
||||
const Scope* bestScope = module->getModuleScope().get();
|
||||
|
||||
bool didNarrow = false;
|
||||
do
|
||||
{
|
||||
didNarrow = false;
|
||||
for (auto scope : bestScope->children)
|
||||
{
|
||||
if (scope->location.encloses(location))
|
||||
{
|
||||
bestScope = scope.get();
|
||||
didNarrow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (didNarrow && bestScope->children.size() > 0);
|
||||
|
||||
return bestScope;
|
||||
}
|
||||
|
||||
std::optional<AstExpr*> AnyTypeSummary::matchRequire(const AstExprCall& call)
|
||||
{
|
||||
const char* require = "require";
|
||||
|
||||
if (call.args.size != 1)
|
||||
return std::nullopt;
|
||||
|
||||
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
|
||||
if (!funcAsGlobal || funcAsGlobal->name != require)
|
||||
return std::nullopt;
|
||||
|
||||
if (call.args.size != 1)
|
||||
return std::nullopt;
|
||||
|
||||
return call.args.data[0];
|
||||
}
|
||||
|
||||
AstNode* AnyTypeSummary::getNode(AstStatBlock* root, AstNode* node)
|
||||
{
|
||||
FindReturnAncestry finder(node, root->location.end);
|
||||
root->visit(&finder);
|
||||
|
||||
if (!finder.currNode)
|
||||
finder.currNode = node;
|
||||
|
||||
LUAU_ASSERT(finder.found && finder.currNode);
|
||||
return finder.currNode;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatLocalFunction* node)
|
||||
{
|
||||
currNode = node;
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::FindReturnAncestry::visit(AstStatFunction* node)
|
||||
{
|
||||
currNode = node;
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::FindReturnAncestry::visit(AstType* node)
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::FindReturnAncestry::visit(AstNode* node)
|
||||
{
|
||||
if (node == stat)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (node->location.end == rootEnd && stat->location.end >= rootEnd)
|
||||
{
|
||||
currNode = node;
|
||||
found = true;
|
||||
}
|
||||
|
||||
return !found;
|
||||
}
|
||||
|
||||
|
||||
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
|
||||
: code(code)
|
||||
, node(node)
|
||||
, type(type)
|
||||
{
|
||||
}
|
||||
|
||||
AnyTypeSummary::FindReturnAncestry::FindReturnAncestry(AstNode* stat, Position rootEnd)
|
||||
: stat(stat)
|
||||
, rootEnd(rootEnd)
|
||||
{
|
||||
}
|
||||
|
||||
AnyTypeSummary::AnyTypeSummary() {}
|
||||
|
||||
} // namespace Luau
|
|
@ -1151,6 +1151,8 @@ struct AstJsonEncoder : public AstVisitor
|
|||
return writeString("checked");
|
||||
case AstAttr::Type::Native:
|
||||
return writeString("native");
|
||||
case AstAttr::Type::Deprecated:
|
||||
return writeString("deprecated");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
|
|||
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
|
||||
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
|
||||
|
@ -46,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
|||
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -547,7 +550,7 @@ void ConstraintGenerator::computeRefinement(
|
|||
refis->get(proposition->key->def)->shouldAppendNilType =
|
||||
(sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def);
|
||||
}
|
||||
|
@ -1357,6 +1360,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
|
|||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstExprFunction* func)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>())
|
||||
{
|
||||
genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
}
|
||||
|
||||
static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
FunctionType* fty = getMutable<FunctionType>(signature);
|
||||
LUAU_ASSERT(fty);
|
||||
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
|
||||
{
|
||||
// Local
|
||||
|
@ -1394,6 +1414,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
|||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature});
|
||||
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
propagateDeprecatedAttributeToConstraint(c->c, function->func);
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(
|
||||
start,
|
||||
|
@ -1417,7 +1440,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
|||
module->astTypes[function->func] = functionType;
|
||||
}
|
||||
else
|
||||
{
|
||||
module->astTypes[function->func] = sig.signature;
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
propagateDeprecatedAttributeToType(sig.signature, function->func);
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
@ -1458,7 +1485,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
|
||||
TypeId generalizedType = arena->addType(BlockedType{});
|
||||
if (sigFullyDefined)
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
propagateDeprecatedAttributeToType(sig.signature, function->func);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope;
|
||||
|
@ -1466,6 +1497,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
|
||||
getMutable<BlockedType>(generalizedType)->setOwner(c);
|
||||
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
propagateDeprecatedAttributeToConstraint(c->c, function->func);
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(
|
||||
start,
|
||||
|
@ -1981,6 +2015,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
|
|||
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
|
||||
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
||||
ftv->isCheckedFunction = global->isCheckedFunction();
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
|
||||
|
||||
ftv->argNames.reserve(global->paramNames.size);
|
||||
for (const auto& el : global->paramNames)
|
||||
|
@ -2148,13 +2184,23 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
}
|
||||
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
|
||||
{
|
||||
auto [ty, refinement] = check(scope, arg, /*expectedType*/ std::nullopt, /*forceSingleton*/ false, /*generalize*/ false);
|
||||
std::optional<TypeId> expectedType = std::nullopt;
|
||||
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
|
||||
{
|
||||
expectedType = expectedTypesForCall[i];
|
||||
}
|
||||
auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false);
|
||||
args.push_back(ty);
|
||||
argumentRefinements.push_back(refinement);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto [tp, refis] = checkPack(scope, arg, {});
|
||||
std::vector<std::optional<Luau::TypeId>> expectedTypes = {};
|
||||
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
|
||||
{
|
||||
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end());
|
||||
}
|
||||
auto [tp, refis] = checkPack(scope, arg, expectedTypes);
|
||||
argTail = tp;
|
||||
argumentRefinements.insert(argumentRefinements.end(), refis.begin(), refis.end());
|
||||
}
|
||||
|
@ -2414,8 +2460,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(local);
|
||||
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(local);
|
||||
LUAU_ASSERT(key || rvalueDef);
|
||||
LUAU_ASSERT(key);
|
||||
|
||||
std::optional<TypeId> maybeTy;
|
||||
|
||||
|
@ -2423,11 +2468,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
|
|||
if (key)
|
||||
maybeTy = lookup(scope, local->location, key->def);
|
||||
|
||||
// if the current def doesn't have a type, we might be doing a compound assignment
|
||||
// and therefore might need to look at the rvalue def instead.
|
||||
if (!maybeTy && rvalueDef)
|
||||
maybeTy = lookup(scope, local->location, *rvalueDef);
|
||||
|
||||
if (maybeTy)
|
||||
{
|
||||
TypeId ty = follow(*maybeTy);
|
||||
|
@ -2443,11 +2483,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
{
|
||||
const RefinementKey* key = dfg->getRefinementKey(global);
|
||||
std::optional<DefId> rvalueDef = dfg->getRValueDefForCompoundAssign(global);
|
||||
LUAU_ASSERT(key || rvalueDef);
|
||||
LUAU_ASSERT(key);
|
||||
|
||||
// we'll use whichever of the two definitions we have here.
|
||||
DefId def = key ? key->def : *rvalueDef;
|
||||
DefId def = key->def;
|
||||
|
||||
/* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any
|
||||
* global that is not already in-scope is definitely an unknown symbol.
|
||||
|
@ -3572,6 +3610,8 @@ TypeId ConstraintGenerator::resolveFunctionType(
|
|||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
|
||||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
|
|
|
@ -38,6 +38,9 @@ LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
|
|||
LUAU_FASTFLAGVARIABLE(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -625,14 +628,6 @@ bool ConstraintSolver::isDone() const
|
|||
|
||||
struct TypeSearcher : TypeVisitor
|
||||
{
|
||||
enum struct Polarity: uint8_t
|
||||
{
|
||||
None = 0b00,
|
||||
Positive = 0b01,
|
||||
Negative = 0b10,
|
||||
Mixed = 0b11,
|
||||
};
|
||||
|
||||
TypeId needle;
|
||||
Polarity current = Polarity::Positive;
|
||||
|
||||
|
@ -748,12 +743,12 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
|
||||
switch (ts.result)
|
||||
{
|
||||
case TypeSearcher::Polarity::None:
|
||||
case Polarity::None:
|
||||
asMutable(ty)->reassign(Type{BoundType{upperBound}});
|
||||
break;
|
||||
|
||||
case TypeSearcher::Polarity::Negative:
|
||||
case TypeSearcher::Polarity::Mixed:
|
||||
case Polarity::Negative:
|
||||
case Polarity::Mixed:
|
||||
if (get<UnknownType>(upperBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
|
@ -763,15 +758,17 @@ void ConstraintSolver::generalizeOneType(TypeId ty)
|
|||
asMutable(ty)->reassign(Type{BoundType{upperBound}});
|
||||
break;
|
||||
|
||||
case TypeSearcher::Polarity::Positive:
|
||||
if (get<UnknownType>(lowerBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
}
|
||||
else
|
||||
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
|
||||
break;
|
||||
case Polarity::Positive:
|
||||
if (get<UnknownType>(lowerBound) && ts.count > 1)
|
||||
{
|
||||
asMutable(ty)->reassign(Type{GenericType{tyScope}});
|
||||
function->generics.emplace_back(ty);
|
||||
}
|
||||
else
|
||||
asMutable(ty)->reassign(Type{BoundType{lowerBound}});
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -918,6 +915,15 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
bind(constraint, generalizedType, *generalizedTy);
|
||||
else
|
||||
unify(constraint, generalizedType, *generalizedTy);
|
||||
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
|
||||
{
|
||||
if (c.hasDeprecatedAttribute)
|
||||
fty->isDeprecatedFunction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -931,12 +937,12 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
// clang-tidy doesn't understand this is safe.
|
||||
if (constraint->scope->interiorFreeTypes)
|
||||
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId ty : c.interiorTypes)
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty, /* avoidSealingTables */ false);
|
||||
generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1352,7 +1358,7 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
|
|||
if (isBlocked(*ty))
|
||||
// We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored.
|
||||
emplaceType<BoundType>(asMutable(follow(*ty)), builtinTypes->noRefineType);
|
||||
|
||||
|
||||
// We also need to unconditionally unblock these types, otherwise
|
||||
// you end up with funky looking "Blocked on *no-refine*."
|
||||
unblock(*ty, constraint->location);
|
||||
|
@ -1558,6 +1564,43 @@ static AstExpr* unwrapGroup(AstExpr* expr)
|
|||
return expr;
|
||||
}
|
||||
|
||||
struct ContainsGenerics : public TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<const void*> generics{nullptr};
|
||||
|
||||
bool found = false;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const GenericType&) override
|
||||
{
|
||||
found |= generics.contains(ty);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack&) override
|
||||
{
|
||||
found |= generics.contains(tp);
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool hasGeneric(TypeId ty)
|
||||
{
|
||||
traverse(ty);
|
||||
auto ret = found;
|
||||
found = false;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
TypeId fn = follow(c.fn);
|
||||
|
@ -1600,36 +1643,49 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
DenseHashMap<TypeId, TypeId> replacements{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
|
||||
|
||||
ContainsGenerics containsGenerics;
|
||||
|
||||
for (auto generic : ftv->generics)
|
||||
{
|
||||
replacements[generic] = builtinTypes->unknownType;
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
containsGenerics.generics.insert(generic);
|
||||
}
|
||||
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
{
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
containsGenerics.generics.insert(genericPack);
|
||||
}
|
||||
|
||||
// If the type of the function has generics, we don't actually want to push any of the generics themselves
|
||||
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
|
||||
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
|
||||
if (!replacements.empty() || !replacementPacks.empty())
|
||||
if (!FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
std::optional<TypeId> res = replacer.substitute(fn);
|
||||
if (res)
|
||||
if (!replacements.empty() || !replacementPacks.empty())
|
||||
{
|
||||
if (*res != fn)
|
||||
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
std::optional<TypeId> res = replacer.substitute(fn);
|
||||
if (res)
|
||||
{
|
||||
FunctionType* ftvMut = getMutable<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftvMut);
|
||||
ftvMut->generics.clear();
|
||||
ftvMut->genericPacks.clear();
|
||||
if (*res != fn)
|
||||
{
|
||||
FunctionType* ftvMut = getMutable<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftvMut);
|
||||
ftvMut->generics.clear();
|
||||
ftvMut->genericPacks.clear();
|
||||
}
|
||||
|
||||
fn = *res;
|
||||
ftv = get<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
|
||||
reproduceConstraints(constraint->scope, constraint->location, replacer);
|
||||
}
|
||||
|
||||
fn = *res;
|
||||
ftv = get<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
// we've potentially copied type functions here, so we need to reproduce their reduce constraint.
|
||||
reproduceConstraints(constraint->scope, constraint->location, replacer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1648,6 +1704,10 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
|
||||
(*c.astExpectedTypes)[expr] = expectedArgTy;
|
||||
|
||||
// Generic types are skipped over entirely, for now.
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && containsGenerics.hasGeneric(expectedArgTy))
|
||||
continue;
|
||||
|
||||
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
|
||||
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
|
||||
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
|
||||
|
@ -2359,11 +2419,18 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||
for (TypePackId r : result.reducedPacks)
|
||||
unblock(r, constraint->location);
|
||||
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2)
|
||||
{
|
||||
for (TypeId ity : result.irreducibleTypes)
|
||||
uninhabitedTypeFunctions.insert(ity);
|
||||
}
|
||||
|
||||
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||
|
||||
ty = follow(ty);
|
||||
|
||||
// If we couldn't reduce this type function, stick it in the set!
|
||||
if (get<TypeFunctionInstanceType>(ty))
|
||||
if (get<TypeFunctionInstanceType>(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty)))
|
||||
typeFunctionsToFinalize[ty] = constraint;
|
||||
|
||||
if (force || reductionFinished)
|
||||
|
@ -3283,7 +3350,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type, bool avoidSealingTables)
|
||||
std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope, TypeId type)
|
||||
{
|
||||
TypeId t = follow(type);
|
||||
if (get<FreeType>(t))
|
||||
|
@ -3298,7 +3365,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
|
|||
// that until all constraint generation is complete.
|
||||
}
|
||||
|
||||
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type, avoidSealingTables);
|
||||
return generalize(NotNull{arena}, builtinTypes, scope, generalizedTypes, type);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)
|
||||
|
|
|
@ -82,12 +82,6 @@ std::optional<DefId> DataFlowGraph::getDefOptional(const AstExpr* expr) const
|
|||
return NotNull{*def};
|
||||
}
|
||||
|
||||
std::optional<DefId> DataFlowGraph::getRValueDefForCompoundAssign(const AstExpr* expr) const
|
||||
{
|
||||
auto def = compoundAssignDefs.find(expr);
|
||||
return def ? std::optional<DefId>(*def) : std::nullopt;
|
||||
}
|
||||
|
||||
DefId DataFlowGraph::getDef(const AstLocal* local) const
|
||||
{
|
||||
auto def = localDefs.find(local);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauDebugInfoDefn)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -215,15 +213,6 @@ declare debug: {
|
|||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionDebugSrc_DEPRECATED = R"BUILTIN_SRC(
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
|
||||
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
|
||||
}
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
|
||||
|
||||
declare utf8: {
|
||||
|
@ -309,7 +298,7 @@ std::string getBuiltinDefinitionSource()
|
|||
result += kBuiltinDefinitionOsSrc;
|
||||
result += kBuiltinDefinitionCoroutineSrc;
|
||||
result += kBuiltinDefinitionTableSrc;
|
||||
result += FFlag::LuauDebugInfoDefn ? kBuiltinDefinitionDebugSrc : kBuiltinDefinitionDebugSrc_DEPRECATED;
|
||||
result += kBuiltinDefinitionDebugSrc;
|
||||
result += kBuiltinDefinitionUtf8Src;
|
||||
result += kBuiltinDefinitionBufferSrc;
|
||||
result += kBuiltinDefinitionVectorSrc;
|
||||
|
|
|
@ -28,10 +28,9 @@ LUAU_FASTINT(LuauTypeInferIterationLimit);
|
|||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteBugfixes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule)
|
||||
LUAU_FASTFLAGVARIABLE(LogFragmentsFromAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentAcSupportsReporter)
|
||||
|
@ -42,6 +41,7 @@ LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack)
|
|||
LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -87,6 +87,333 @@ void cloneModuleMap(
|
|||
}
|
||||
}
|
||||
|
||||
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
|
||||
|
||||
// when typing a function partially, get the span of the first line
|
||||
// e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
|
||||
// editing type annotations in this range
|
||||
Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprName = nullptr, AstLocal* localName = nullptr)
|
||||
{
|
||||
auto fnBegin = exprFn->location.begin;
|
||||
auto fnEnd = exprFn->location.end;
|
||||
if (auto returnAnnot = exprFn->returnAnnotation)
|
||||
{
|
||||
if (returnAnnot->tailType)
|
||||
fnEnd = returnAnnot->tailType->location.end;
|
||||
else if (returnAnnot->types.size != 0)
|
||||
fnEnd = returnAnnot->types.data[returnAnnot->types.size - 1]->location.end;
|
||||
}
|
||||
else if (exprFn->args.size != 0)
|
||||
{
|
||||
auto last = exprFn->args.data[exprFn->args.size - 1];
|
||||
if (last->annotation)
|
||||
fnEnd = last->annotation->location.end;
|
||||
else
|
||||
fnEnd = last->location.end;
|
||||
}
|
||||
else if (exprFn->genericPacks.size != 0)
|
||||
fnEnd = exprFn->genericPacks.data[exprFn->genericPacks.size - 1]->location.end;
|
||||
else if (exprFn->generics.size != 0)
|
||||
fnEnd = exprFn->generics.data[exprFn->generics.size - 1]->location.end;
|
||||
else if (exprName)
|
||||
fnEnd = exprName->location.end;
|
||||
else if (localName)
|
||||
fnEnd = localName->location.end;
|
||||
return Location{fnBegin, fnEnd};
|
||||
};
|
||||
|
||||
Location getAstStatForExtents(AstStatFor* forStat)
|
||||
{
|
||||
auto begin = forStat->location.begin;
|
||||
auto end = forStat->location.end;
|
||||
if (forStat->step)
|
||||
end = forStat->step->location.end;
|
||||
else if (forStat->to)
|
||||
end = forStat->to->location.end;
|
||||
else if (forStat->from)
|
||||
end = forStat->from->location.end;
|
||||
else if (forStat->var)
|
||||
end = forStat->var->location.end;
|
||||
|
||||
return Location{begin, end};
|
||||
}
|
||||
|
||||
Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPosition)
|
||||
{
|
||||
Location empty{cursorPosition, cursorPosition};
|
||||
if (nearestStatement)
|
||||
{
|
||||
Location nonEmpty{nearestStatement->location.begin, cursorPosition};
|
||||
// If your sibling is a do block, do nothing
|
||||
if (auto doEnd = nearestStatement->as<AstStatBlock>())
|
||||
return empty;
|
||||
|
||||
// If you're inside the body of the function and this is your sibling, empty fragment
|
||||
// If you're outside the body (e.g. you're typing stuff out, non-empty)
|
||||
if (auto fn = nearestStatement->as<AstStatFunction>())
|
||||
{
|
||||
auto loc = getFunctionDeclarationExtents(fn->func, fn->name, /* local */ nullptr);
|
||||
if (loc.containsClosed(cursorPosition))
|
||||
return nonEmpty;
|
||||
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
|
||||
return empty;
|
||||
else if (fn->func->location.contains(cursorPosition))
|
||||
return nonEmpty;
|
||||
}
|
||||
|
||||
if (auto fn = nearestStatement->as<AstStatLocalFunction>())
|
||||
{
|
||||
auto loc = getFunctionDeclarationExtents(fn->func, /* global func */ nullptr, fn->name);
|
||||
if (loc.containsClosed(cursorPosition))
|
||||
return nonEmpty;
|
||||
else if (fn->func->body->location.containsClosed(cursorPosition) || fn->location.end <= cursorPosition)
|
||||
return empty;
|
||||
else if (fn->func->location.contains(cursorPosition))
|
||||
return nonEmpty;
|
||||
}
|
||||
|
||||
if (auto wh = nearestStatement->as<AstStatWhile>())
|
||||
{
|
||||
if (!wh->hasDo)
|
||||
return nonEmpty;
|
||||
else
|
||||
return empty;
|
||||
}
|
||||
|
||||
if (auto forStat = nearestStatement->as<AstStatFor>())
|
||||
{
|
||||
if (!forStat->hasDo)
|
||||
return nonEmpty;
|
||||
else
|
||||
return empty;
|
||||
}
|
||||
|
||||
if (auto forIn = nearestStatement->as<AstStatForIn>())
|
||||
{
|
||||
// If we don't have a do statement
|
||||
if (!forIn->hasDo)
|
||||
return nonEmpty;
|
||||
else
|
||||
return empty;
|
||||
}
|
||||
|
||||
if (auto ifS = nearestStatement->as<AstStatIf>())
|
||||
{
|
||||
auto conditionExtents = Location{ifS->location.begin, ifS->condition->location.end};
|
||||
if (conditionExtents.containsClosed(cursorPosition))
|
||||
return nonEmpty;
|
||||
else if (ifS->thenbody->location.containsClosed(cursorPosition))
|
||||
return empty;
|
||||
else if (auto elseS = ifS->elsebody)
|
||||
{
|
||||
if (auto elseIf = ifS->elsebody->as<AstStatIf>())
|
||||
{
|
||||
|
||||
if (elseIf->thenbody->hasEnd)
|
||||
return empty;
|
||||
else
|
||||
return {elseS->location.begin, cursorPosition};
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
return nonEmpty;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
struct NearestStatementFinder : public AstVisitor
|
||||
{
|
||||
explicit NearestStatementFinder(const Position& cursorPosition)
|
||||
: cursor(cursorPosition)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstStatBlock* block) override
|
||||
{
|
||||
if (block->location.containsClosed(cursor))
|
||||
{
|
||||
parent = block;
|
||||
for (auto v : block->body)
|
||||
{
|
||||
if (v->location.begin <= cursor)
|
||||
{
|
||||
nearest = v;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const Position& cursor;
|
||||
AstStat* nearest = nullptr;
|
||||
AstStatBlock* parent = nullptr;
|
||||
};
|
||||
|
||||
FragmentRegion getFragmentRegion(AstStatBlock* root, const Position& cursorPosition)
|
||||
{
|
||||
NearestStatementFinder nsf{cursorPosition};
|
||||
root->visit(&nsf);
|
||||
AstStatBlock* parent = root;
|
||||
if (nsf.parent)
|
||||
parent = nsf.parent;
|
||||
return FragmentRegion{getFragmentLocation(nsf.nearest, cursorPosition), nsf.nearest, parent};
|
||||
};
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* stale, const Position& cursorPos, AstStatBlock* lastGoodParse)
|
||||
{
|
||||
// the freshest ast can sometimes be null if the parse was bad.
|
||||
if (lastGoodParse == nullptr)
|
||||
return {};
|
||||
FragmentRegion region = getFragmentRegion(lastGoodParse, cursorPos);
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(stale, cursorPos);
|
||||
LUAU_ASSERT(ancestry.size() >= 1);
|
||||
// We should only pick up locals that are before the region
|
||||
DenseHashMap<AstName, AstLocal*> localMap{AstName()};
|
||||
std::vector<AstLocal*> localStack;
|
||||
|
||||
for (AstNode* node : ancestry)
|
||||
{
|
||||
if (auto block = node->as<AstStatBlock>())
|
||||
{
|
||||
for (auto stat : block->body)
|
||||
{
|
||||
if (stat->location.begin < region.fragmentLocation.begin)
|
||||
{
|
||||
// This statement precedes the current one
|
||||
if (auto statLoc = stat->as<AstStatLocal>())
|
||||
{
|
||||
for (auto v : statLoc->vars)
|
||||
{
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
}
|
||||
}
|
||||
else if (auto locFun = stat->as<AstStatLocalFunction>())
|
||||
{
|
||||
localStack.push_back(locFun->name);
|
||||
localMap[locFun->name->name] = locFun->name;
|
||||
if (locFun->location.contains(cursorPos))
|
||||
{
|
||||
for (AstLocal* loc : locFun->func->args)
|
||||
{
|
||||
localStack.push_back(loc);
|
||||
localMap[loc->name] = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto globFun = stat->as<AstStatFunction>())
|
||||
{
|
||||
if (globFun->location.contains(cursorPos))
|
||||
{
|
||||
for (AstLocal* loc : globFun->func->args)
|
||||
{
|
||||
localStack.push_back(loc);
|
||||
localMap[loc->name] = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun)
|
||||
{
|
||||
if (typeFun->location.contains(cursorPos))
|
||||
{
|
||||
for (AstLocal* loc : typeFun->body->args)
|
||||
{
|
||||
localStack.push_back(loc);
|
||||
localMap[loc->name] = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto forL = stat->as<AstStatFor>())
|
||||
{
|
||||
if (forL->var && forL->var->location.begin < region.fragmentLocation.begin)
|
||||
{
|
||||
localStack.push_back(forL->var);
|
||||
localMap[forL->var->name] = forL->var;
|
||||
}
|
||||
}
|
||||
else if (auto forIn = stat->as<AstStatForIn>())
|
||||
{
|
||||
for (auto var : forIn->vars)
|
||||
{
|
||||
if (var->location.begin < region.fragmentLocation.begin)
|
||||
{
|
||||
localStack.push_back(var);
|
||||
localMap[var->name] = var;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto exprFunc = node->as<AstExprFunction>())
|
||||
{
|
||||
if (exprFunc->location.contains(cursorPos))
|
||||
{
|
||||
for (auto v : exprFunc->args)
|
||||
{
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {localMap, localStack, ancestry, region.nearestStatement, region.parentBlock, region.fragmentLocation};
|
||||
}
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
AstStatBlock* stale,
|
||||
AstStatBlock* mostRecentParse,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
const Position& cursorPos,
|
||||
std::optional<Position> fragmentEndPosition
|
||||
)
|
||||
{
|
||||
if (mostRecentParse == nullptr)
|
||||
return std::nullopt;
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(stale, cursorPos, mostRecentParse);
|
||||
AstStat* nearestStatement = result.nearestStatement;
|
||||
|
||||
Position startPos = result.fragmentSelectionRegion.begin;
|
||||
Position endPos = fragmentEndPosition.value_or(result.fragmentSelectionRegion.end);
|
||||
|
||||
auto [offsetStart, parseLength] = getDocumentOffsets(src, startPos, endPos);
|
||||
const char* srcStart = src.data() + offsetStart;
|
||||
std::string_view dbg = src.substr(offsetStart, parseLength);
|
||||
FragmentParseResult fragmentResult;
|
||||
fragmentResult.fragmentToParse = std::string(dbg);
|
||||
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
|
||||
if (FFlag::DebugLogFragmentsFromAutocomplete)
|
||||
logLuau("Fragment Selected", dbg);
|
||||
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = false;
|
||||
opts.captureComments = true;
|
||||
opts.parseFragment = FragmentParseResumeSettings{std::move(result.localMap), std::move(result.localStack), startPos};
|
||||
ParseResult p = Luau::Parser::parse(srcStart, parseLength, *names, *fragmentResult.alloc, opts);
|
||||
// This means we threw a ParseError and we should decline to offer autocomplete here.
|
||||
if (p.root == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<AstNode*> fabricatedAncestry = std::move(result.ancestry);
|
||||
std::vector<AstNode*> fragmentAncestry = findAncestryAtPositionForAutocomplete(p.root, cursorPos);
|
||||
fabricatedAncestry.insert(fabricatedAncestry.end(), fragmentAncestry.begin(), fragmentAncestry.end());
|
||||
if (nearestStatement == nullptr)
|
||||
nearestStatement = p.root;
|
||||
fragmentResult.root = p.root;
|
||||
fragmentResult.ancestry = std::move(fabricatedAncestry);
|
||||
fragmentResult.nearestStatement = nearestStatement;
|
||||
fragmentResult.commentLocations = std::move(p.commentLocations);
|
||||
fragmentResult.scopePos = result.parentBlock->location.begin;
|
||||
return fragmentResult;
|
||||
}
|
||||
|
||||
struct UsageFinder : public AstVisitor
|
||||
{
|
||||
|
||||
|
@ -158,6 +485,7 @@ void cloneTypesFromFragment(
|
|||
const ModulePtr& staleModule,
|
||||
NotNull<TypeArena> destArena,
|
||||
NotNull<DataFlowGraph> dfg,
|
||||
NotNull<BuiltinTypes> builtins,
|
||||
AstStatBlock* program,
|
||||
Scope* destScope
|
||||
)
|
||||
|
@ -188,6 +516,13 @@ void cloneTypesFromFragment(
|
|||
destScope->lvalueTypes[d] = Luau::cloneIncremental(pair->second.typeId, *destArena, cloneState, destScope);
|
||||
destScope->bindings[pair->first] = Luau::cloneIncremental(pair->second, *destArena, cloneState, destScope);
|
||||
}
|
||||
else if (FFlag::LuauBetterScopeSelection)
|
||||
{
|
||||
destScope->lvalueTypes[d] = builtins->unknownType;
|
||||
Binding b;
|
||||
b.typeId = builtins->unknownType;
|
||||
destScope->bindings[Symbol(loc)] = b;
|
||||
}
|
||||
}
|
||||
|
||||
// Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved.
|
||||
|
@ -400,15 +735,10 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
|
|||
|
||||
bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
|
||||
{
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
{
|
||||
return (stat->location.begin < cursorPos);
|
||||
}
|
||||
|
||||
return stat->location.begin < cursorPos && stat->location.begin.line < cursorPos.line;
|
||||
return (stat->location.begin < cursorPos);
|
||||
}
|
||||
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* root, const Position& cursorPos)
|
||||
FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstStatBlock* root, const Position& cursorPos)
|
||||
{
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(root, cursorPos);
|
||||
// Should always contain the root AstStat
|
||||
|
@ -437,7 +767,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
{
|
||||
for (auto stat : block->body)
|
||||
{
|
||||
if (statIsBeforePos(stat, FFlag::LuauIncrementalAutocompleteBugfixes ? nearestStatement->location.begin : cursorPos))
|
||||
if (statIsBeforePos(stat, nearestStatement->location.begin))
|
||||
{
|
||||
// This statement precedes the current one
|
||||
if (auto statLoc = stat->as<AstStatLocal>())
|
||||
|
@ -486,17 +816,14 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
}
|
||||
}
|
||||
}
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
if (auto exprFunc = node->as<AstExprFunction>())
|
||||
{
|
||||
if (auto exprFunc = node->as<AstExprFunction>())
|
||||
if (exprFunc->location.contains(cursorPos))
|
||||
{
|
||||
if (exprFunc->location.contains(cursorPos))
|
||||
for (auto v : exprFunc->args)
|
||||
{
|
||||
for (auto v : exprFunc->args)
|
||||
{
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
}
|
||||
localStack.push_back(v);
|
||||
localMap[v->name] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -513,7 +840,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* ro
|
|||
* Example - your document is "foo bar baz" and getDocumentOffsets is passed (0, 4), (0, 8). This function returns the pair {3, 5}
|
||||
* which corresponds to the string " bar "
|
||||
*/
|
||||
std::pair<size_t, size_t> getDocumentOffsets(const std::string_view& src, const Position& startPos, const Position& endPos)
|
||||
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos)
|
||||
{
|
||||
size_t lineCount = 0;
|
||||
size_t colCount = 0;
|
||||
|
@ -570,14 +897,14 @@ std::pair<size_t, size_t> getDocumentOffsets(const std::string_view& src, const
|
|||
return {min, len};
|
||||
}
|
||||
|
||||
ScopePtr findClosestScope(const ModulePtr& module, const AstStat* nearestStatement)
|
||||
ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nearestStatement)
|
||||
{
|
||||
LUAU_ASSERT(module->hasModuleScope());
|
||||
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
|
||||
// find the scope the nearest statement belonged to.
|
||||
for (auto [loc, sc] : module->scopes)
|
||||
for (const auto& [loc, sc] : module->scopes)
|
||||
{
|
||||
if (loc.encloses(nearestStatement->location) && closest->location.begin <= loc.begin)
|
||||
closest = sc;
|
||||
|
@ -586,7 +913,23 @@ ScopePtr findClosestScope(const ModulePtr& module, const AstStat* nearestStateme
|
|||
return closest;
|
||||
}
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment(
|
||||
ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos)
|
||||
{
|
||||
LUAU_ASSERT(module->hasModuleScope());
|
||||
|
||||
ScopePtr closest = module->getModuleScope();
|
||||
|
||||
// find the scope the nearest statement belonged to.
|
||||
for (const auto& [loc, sc] : module->scopes)
|
||||
{
|
||||
if (sc->location.contains(scopePos) && closest->location.begin < sc->location.begin)
|
||||
closest = sc;
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
std::optional<FragmentParseResult> parseFragment_DEPRECATED(
|
||||
AstStatBlock* root,
|
||||
AstNameTable* names,
|
||||
std::string_view src,
|
||||
|
@ -594,7 +937,7 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
std::optional<Position> fragmentEndPosition
|
||||
)
|
||||
{
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse(root, cursorPos);
|
||||
FragmentAutocompleteAncestryResult result = findAncestryForFragmentParse_DEPRECATED(root, cursorPos);
|
||||
AstStat* nearestStatement = result.nearestStatement;
|
||||
|
||||
const Location& rootSpan = root->location;
|
||||
|
@ -625,8 +968,8 @@ std::optional<FragmentParseResult> parseFragment(
|
|||
FragmentParseResult fragmentResult;
|
||||
fragmentResult.fragmentToParse = std::string(dbg.data(), parseLength);
|
||||
// For the duration of the incremental parse, we want to allow the name table to re-use duplicate names
|
||||
if (FFlag::LogFragmentsFromAutocomplete)
|
||||
logLuau(dbg);
|
||||
if (FFlag::DebugLogFragmentsFromAutocomplete)
|
||||
logLuau("Fragment Selected", dbg);
|
||||
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = false;
|
||||
|
@ -1025,7 +1368,14 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
|
||||
cloneTypesFromFragment(
|
||||
cloneState, closestScope.get(), stale, NotNull{&incrementalModule->internalTypes}, NotNull{&dfg}, root, freshChildOfNearestScope.get()
|
||||
cloneState,
|
||||
closestScope.get(),
|
||||
stale,
|
||||
NotNull{&incrementalModule->internalTypes},
|
||||
NotNull{&dfg},
|
||||
frontend.builtinTypes,
|
||||
root,
|
||||
freshChildOfNearestScope.get()
|
||||
);
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd);
|
||||
|
||||
|
@ -1086,6 +1436,7 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
std::optional<FrontendOptions> opts,
|
||||
std::string_view src,
|
||||
std::optional<Position> fragmentEndPosition,
|
||||
AstStatBlock* recentParse,
|
||||
IFragmentAutocompleteReporter* reporter
|
||||
)
|
||||
{
|
||||
|
@ -1106,7 +1457,9 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
std::optional<FragmentParseResult> tryParse;
|
||||
if (FFlag::LuauModuleHoldsAstRoot)
|
||||
{
|
||||
tryParse = parseFragment(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
tryParse = FFlag::LuauBetterScopeSelection
|
||||
? parseFragment(module->root, recentParse, module->names.get(), src, cursorPos, fragmentEndPosition)
|
||||
: parseFragment_DEPRECATED(module->root, module->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1117,15 +1470,12 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
return {};
|
||||
}
|
||||
|
||||
if (FFlag::LuauIncrementalAutocompleteBugfixes)
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
if (sourceModule->allocator.get() != module->allocator.get())
|
||||
{
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
}
|
||||
|
||||
tryParse = parseFragment(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
tryParse = parseFragment_DEPRECATED(sourceModule->root, sourceModule->names.get(), src, cursorPos, fragmentEndPosition);
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ParseFragmentEnd);
|
||||
}
|
||||
|
||||
|
@ -1138,7 +1488,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
|
||||
FrontendOptions frontendOptions = opts.value_or(frontend.options);
|
||||
const ScopePtr& closestScope = findClosestScope(module, parseResult.nearestStatement);
|
||||
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
|
||||
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
|
||||
FragmentTypeCheckResult result =
|
||||
FFlag::LuauIncrementalAutocompleteDemandBasedCloning
|
||||
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
|
||||
|
@ -1174,14 +1525,15 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete(
|
|||
context.opts,
|
||||
std::move(stringCompletionCB),
|
||||
context.DEPRECATED_fragmentEndPosition,
|
||||
context.freshParse.root,
|
||||
FFlag::LuauFragmentAcSupportsReporter ? context.reporter : nullptr
|
||||
);
|
||||
return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)};
|
||||
}
|
||||
catch (const Luau::InternalCompilerError& e)
|
||||
{
|
||||
if (FFlag::LogFragmentsFromAutocomplete)
|
||||
logLuau(e.what());
|
||||
if (FFlag::DebugLogFragmentsFromAutocomplete)
|
||||
logLuau("tryFragmentAutocomplete exception", e.what());
|
||||
return {FragmentAutocompleteStatus::InternalIce, std::nullopt};
|
||||
}
|
||||
}
|
||||
|
@ -1194,6 +1546,7 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
std::optional<FrontendOptions> opts,
|
||||
StringCompletionCallback callback,
|
||||
std::optional<Position> fragmentEndPosition,
|
||||
AstStatBlock* recentParse,
|
||||
IFragmentAutocompleteReporter* reporter
|
||||
)
|
||||
{
|
||||
|
@ -1215,14 +1568,14 @@ FragmentAutocompleteResult fragmentAutocomplete(
|
|||
return {};
|
||||
}
|
||||
|
||||
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, reporter);
|
||||
auto [tcStatus, tcResult] = typecheckFragment(frontend, moduleName, cursorPosition, opts, src, fragmentEndPosition, recentParse, reporter);
|
||||
if (tcStatus == FragmentTypeCheckStatus::SkipAutocomplete)
|
||||
return {};
|
||||
|
||||
reportWaypoint(reporter, FragmentAutocompleteWaypoint::TypecheckFragmentEnd);
|
||||
auto globalScope = (opts && opts->forAutocomplete) ? frontend.globalsForAutocomplete.globalScope.get() : frontend.globals.globalScope.get();
|
||||
if (FFlag::LogFragmentsFromAutocomplete)
|
||||
logLuau(src);
|
||||
if (FFlag::DebugLogFragmentsFromAutocomplete)
|
||||
logLuau("Fragment Autocomplete Source Script", src);
|
||||
TypeArena arenaForFragmentAutocomplete;
|
||||
auto result = Luau::autocomplete_(
|
||||
tcResult.incrementalModule,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
|
@ -51,9 +50,8 @@ LUAU_FASTFLAGVARIABLE(LuauModuleHoldsAstRoot)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixMultithreadTypecheck)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny2)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -461,20 +459,6 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
|
||||
if (item.name == name)
|
||||
checkResult.lintResult = item.module->lintResult;
|
||||
|
||||
if (FFlag::StudioReportLuauAny2 && item.options.retainFullTypeGraphs)
|
||||
{
|
||||
if (item.module)
|
||||
{
|
||||
const SourceModule& sourceModule = *item.sourceModule;
|
||||
if (sourceModule.mode == Luau::Mode::Strict)
|
||||
{
|
||||
item.module->ats.root = toString(sourceModule.root);
|
||||
}
|
||||
item.module->ats.rootSrc = sourceModule.root;
|
||||
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
|
@ -1658,7 +1642,7 @@ ModulePtr check(
|
|||
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
|
||||
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
||||
|
||||
typeFunctionRuntime.allowEvaluation = sourceModule.parseErrors.empty();
|
||||
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty();
|
||||
|
||||
ConstraintGenerator cg{
|
||||
result,
|
||||
|
|
|
@ -30,7 +30,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
std::vector<TypePackId> genericPacks;
|
||||
|
||||
bool isWithinFunction = false;
|
||||
bool avoidSealingTables = false;
|
||||
|
||||
MutatingGeneralizer(
|
||||
NotNull<TypeArena> arena,
|
||||
|
@ -38,8 +37,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
DenseHashMap<const void*, size_t> positiveTypes,
|
||||
DenseHashMap<const void*, size_t> negativeTypes,
|
||||
bool avoidSealingTables
|
||||
DenseHashMap<const void*, size_t> negativeTypes
|
||||
)
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
, arena(arena)
|
||||
|
@ -48,7 +46,6 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
, cachedTypes(cachedTypes)
|
||||
, positiveTypes(std::move(positiveTypes))
|
||||
, negativeTypes(std::move(negativeTypes))
|
||||
, avoidSealingTables(avoidSealingTables)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -145,7 +142,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
TypeId onlyType = it->parts[0];
|
||||
LUAU_ASSERT(onlyType != needle);
|
||||
emplaceType<BoundType>(asMutable(needle), onlyType);
|
||||
}
|
||||
}
|
||||
else if (FFlag::LuauGeneralizationRemoveRecursiveUpperBound2 && it->parts.empty())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(needle), builtinTypes->unknownType);
|
||||
|
@ -292,8 +289,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
|||
TableType* tt = getMutable<TableType>(ty);
|
||||
LUAU_ASSERT(tt);
|
||||
|
||||
if (!avoidSealingTables)
|
||||
tt->state = TableState::Sealed;
|
||||
tt->state = TableState::Sealed;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -332,26 +328,19 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
}
|
||||
|
||||
enum Polarity
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Both,
|
||||
};
|
||||
|
||||
Polarity polarity = Positive;
|
||||
Polarity polarity = Polarity::Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
polarity = Negative;
|
||||
case Polarity::Positive:
|
||||
polarity = Polarity::Negative;
|
||||
break;
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
case Polarity::Negative:
|
||||
polarity = Polarity::Positive;
|
||||
break;
|
||||
case Both:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +352,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
{
|
||||
if (seenPositive.contains(ty))
|
||||
return true;
|
||||
|
@ -371,7 +360,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenPositive.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
{
|
||||
if (seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -379,7 +368,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
{
|
||||
if (seenPositive.contains(ty) && seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -388,6 +377,8 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -418,16 +409,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -442,16 +435,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,7 +459,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete);
|
||||
|
||||
Polarity p = polarity;
|
||||
polarity = Both;
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(prop.type());
|
||||
polarity = p;
|
||||
}
|
||||
|
@ -508,16 +503,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[tp]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[tp]++;
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -972,8 +969,7 @@ std::optional<TypeId> generalize(
|
|||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Scope> scope,
|
||||
NotNull<DenseHashSet<TypeId>> cachedTypes,
|
||||
TypeId ty,
|
||||
bool avoidSealingTables
|
||||
TypeId ty
|
||||
)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
@ -984,7 +980,7 @@ std::optional<TypeId> generalize(
|
|||
FreeTypeSearcher fts{scope, cachedTypes};
|
||||
fts.traverse(ty);
|
||||
|
||||
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes), avoidSealingTables};
|
||||
MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
|
||||
|
||||
gen.traverse(ty);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauAttribute)
|
||||
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute)
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -2280,6 +2282,57 @@ private:
|
|||
{
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
|
||||
const FunctionType* fty = getFunctionType(node);
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->local->name.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprGlobal* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
const FunctionType* fty = getFunctionType(node);
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->name.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatLocalFunction* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
check(node->func);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstStatFunction* node) override
|
||||
{
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
check(node->func);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprIndexName* node) override
|
||||
{
|
||||
if (std::optional<TypeId> ty = context->getType(node->expr))
|
||||
|
@ -2325,18 +2378,59 @@ private:
|
|||
|
||||
if (prop && prop->deprecated)
|
||||
report(node->location, *prop, cty->name.c_str(), node->index.value);
|
||||
else if (FFlag::LuauDeprecatedAttribute && prop)
|
||||
{
|
||||
if (std::optional<TypeId> ty = prop->readTy)
|
||||
{
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
{
|
||||
const char* className = nullptr;
|
||||
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||
className = global->name.value;
|
||||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const TableType* tty = get<TableType>(ty))
|
||||
{
|
||||
auto prop = tty->props.find(node->index.value);
|
||||
|
||||
if (prop != tty->props.end() && prop->second.deprecated)
|
||||
if (prop != tty->props.end())
|
||||
{
|
||||
// strip synthetic typeof() for builtin tables
|
||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||
else
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
if (prop->second.deprecated)
|
||||
{
|
||||
// strip synthetic typeof() for builtin tables
|
||||
if (tty->name && tty->name->compare(0, 7, "typeof(") == 0 && tty->name->back() == ')')
|
||||
report(node->location, prop->second, tty->name->substr(7, tty->name->length() - 8).c_str(), node->index.value);
|
||||
else
|
||||
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
|
||||
}
|
||||
else if (FFlag::LuauDeprecatedAttribute)
|
||||
{
|
||||
if (std::optional<TypeId> ty = prop->second.readTy)
|
||||
{
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
{
|
||||
const char* className = nullptr;
|
||||
if (AstExprGlobal* global = node->expr->as<AstExprGlobal>())
|
||||
className = global->name.value;
|
||||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2355,6 +2449,26 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void check(AstExprFunction* func)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(func);
|
||||
|
||||
const FunctionType* fty = getFunctionType(func);
|
||||
bool isDeprecated = fty && fty->isDeprecatedFunction;
|
||||
|
||||
// If a function is deprecated, we don't want to flag its recursive uses.
|
||||
// So we push it on a stack while its body is being analyzed.
|
||||
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
|
||||
if (isDeprecated)
|
||||
pushScope(fty);
|
||||
|
||||
func->visit(this);
|
||||
|
||||
if (isDeprecated)
|
||||
popScope(fty);
|
||||
}
|
||||
|
||||
void report(const Location& location, const Property& prop, const char* container, const char* field)
|
||||
{
|
||||
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
|
||||
|
@ -2364,6 +2478,63 @@ private:
|
|||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* tableName, const char* functionName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
if (tableName)
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated", tableName, functionName);
|
||||
else
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* functionName)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
std::vector<const FunctionType*> functionTypeScopeStack;
|
||||
|
||||
void pushScope(const FunctionType* fty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
functionTypeScopeStack.push_back(fty);
|
||||
}
|
||||
|
||||
void popScope(const FunctionType* fty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
LUAU_ASSERT(fty == functionTypeScopeStack.back());
|
||||
functionTypeScopeStack.pop_back();
|
||||
}
|
||||
|
||||
bool inScope(const FunctionType* fty) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
LUAU_ASSERT(fty);
|
||||
|
||||
return std::find(functionTypeScopeStack.begin(), functionTypeScopeStack.end(), fty) != functionTypeScopeStack.end();
|
||||
}
|
||||
|
||||
const FunctionType* getFunctionType(AstExpr* node)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
std::optional<TypeId> ty = context->getType(node);
|
||||
if (!ty)
|
||||
return nullptr;
|
||||
|
||||
const FunctionType* fty = get<FunctionType>(follow(ty));
|
||||
|
||||
return fty;
|
||||
}
|
||||
};
|
||||
|
||||
class LintTableOperations : AstVisitor
|
||||
|
|
|
@ -20,7 +20,7 @@ LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteCommentDetection)
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
static void defaultLogLuau(std::string_view input)
|
||||
static void defaultLogLuau(std::string_view context, std::string_view input)
|
||||
{
|
||||
// The default is to do nothing because we don't want to mess with
|
||||
// the xml parsing done by the dcr script.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Luau/NonStrictTypeChecker.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Type.h"
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeNegationFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -581,7 +584,7 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ
|
|||
{
|
||||
left = follow(left);
|
||||
right = follow(right);
|
||||
// We're asking if intersection is inahbited between left and right but we've already seen them ....
|
||||
// We're asking if intersection is inhabited between left and right but we've already seen them ....
|
||||
|
||||
if (cacheInhabitance)
|
||||
{
|
||||
|
@ -1687,6 +1690,13 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
|||
return res;
|
||||
}
|
||||
|
||||
if (FFlag::LuauNormalizeLimitFunctionSet)
|
||||
{
|
||||
// Limit based on worst-case expansion of the function unions
|
||||
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
}
|
||||
|
||||
here.booleans = unionOfBools(here.booleans, there.booleans);
|
||||
unionClasses(here.classes, there.classes);
|
||||
|
||||
|
@ -1698,6 +1708,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
|
|||
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
|
||||
unionFunctions(here.functions, there.functions);
|
||||
unionTables(here.tables, there.tables);
|
||||
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
|
@ -1737,7 +1748,7 @@ NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, N
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
||||
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||
NormalizationResult Normalizer::unionNormalWithTy(
|
||||
NormalizedType& here,
|
||||
TypeId there,
|
||||
|
@ -3052,7 +3063,7 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
|
|||
return NormalizationResult::True;
|
||||
}
|
||||
|
||||
// See above for an explaination of `ignoreSmallerTyvars`.
|
||||
// See above for an explanation of `ignoreSmallerTyvars`.
|
||||
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
|
||||
{
|
||||
RecursionCounter _rc(&sharedState->counters.recursionCount);
|
||||
|
@ -3070,11 +3081,17 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor
|
|||
return unionNormals(here, there, ignoreSmallerTyvars);
|
||||
}
|
||||
|
||||
// Limit based on worst-case expansion of the table intersection
|
||||
// Limit based on worst-case expansion of the table/function intersections
|
||||
// This restriction can be relaxed when table intersection simplification is improved
|
||||
if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
|
||||
if (FFlag::LuauNormalizeLimitFunctionSet)
|
||||
{
|
||||
if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit))
|
||||
return NormalizationResult::HitLimits;
|
||||
}
|
||||
|
||||
here.booleans = intersectionOfBools(here.booleans, there.booleans);
|
||||
|
||||
intersectClasses(here.classes, there.classes);
|
||||
|
@ -3210,7 +3227,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
{
|
||||
TypeId errors = here.errors;
|
||||
clearNormal(here);
|
||||
here.errors = errors;
|
||||
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : there;
|
||||
}
|
||||
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
|
||||
{
|
||||
|
@ -3307,11 +3324,16 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
clearNormal(here);
|
||||
return NormalizationResult::True;
|
||||
}
|
||||
else if (FFlag::LuauNormalizeNegatedErrorToAnError && get<ErrorType>(t))
|
||||
{
|
||||
// ~error is still an error, so intersecting with the negation is the same as intersecting with a type
|
||||
TypeId errors = here.errors;
|
||||
clearNormal(here);
|
||||
here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get<ErrorType>(errors) ? errors : t;
|
||||
}
|
||||
else if (auto nt = get<NegationType>(t))
|
||||
{
|
||||
if (FFlag::LuauNormalizeNegationFix)
|
||||
here.tyvars = std::move(tyvars);
|
||||
|
||||
here.tyvars = std::move(tyvars);
|
||||
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -13,6 +13,7 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
|
||||
LUAU_FASTFLAG(LuauSyntheticErrors)
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -102,6 +103,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
|||
clone.tags = a.tags;
|
||||
clone.argNames = a.argNames;
|
||||
clone.isCheckedFunction = a.isCheckedFunction;
|
||||
if (FFlag::LuauDeprecatedAttribute)
|
||||
clone.isDeprecatedFunction = a.isDeprecatedFunction;
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TableType>)
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -415,6 +416,14 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && result.normalizationTooComplex)
|
||||
{
|
||||
if (result.isCacheable)
|
||||
resultCache[{subTy, superTy}] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
||||
{
|
||||
const auto& lb = bounds.lowerBound;
|
||||
|
@ -592,7 +601,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||
if (semantic.isSubtype)
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
|
||||
{
|
||||
result = semantic;
|
||||
}
|
||||
else if (semantic.isSubtype)
|
||||
{
|
||||
semantic.reasoning.clear();
|
||||
result = semantic;
|
||||
|
@ -607,7 +621,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
if (!result.isSubtype && !result.normalizationTooComplex)
|
||||
{
|
||||
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
|
||||
if (semantic.isSubtype)
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex)
|
||||
{
|
||||
result = semantic;
|
||||
}
|
||||
else if (semantic.isSubtype)
|
||||
{
|
||||
// Clear the semantic reasoning, as any reasonings within
|
||||
// potentially contain invalid paths.
|
||||
|
@ -1082,6 +1101,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
for (TypeId ty : superUnion)
|
||||
{
|
||||
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
|
||||
if (next.isSubtype)
|
||||
return SubtypingResult{true};
|
||||
}
|
||||
|
@ -1100,7 +1123,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subUnion)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1110,7 +1139,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : superIntersection)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1120,7 +1155,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte
|
|||
std::vector<SubtypingResult> subtypings;
|
||||
size_t i = 0;
|
||||
for (TypeId ty : subIntersection)
|
||||
{
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
|
||||
|
@ -1410,7 +1451,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta
|
|||
// of the supertype table.
|
||||
//
|
||||
// There's a flaw here in that if the __index metamethod contributes a new
|
||||
// field that would satisfy the subtyping relationship, we'll erronously say
|
||||
// field that would satisfy the subtyping relationship, we'll erroneously say
|
||||
// that the metatable isn't a subtype of the table, even though they have
|
||||
// compatible properties/shapes. We'll revisit this later when we have a
|
||||
// better understanding of how important this is.
|
||||
|
@ -1760,7 +1801,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
|||
{
|
||||
results.emplace_back();
|
||||
for (TypeId superTy : superTypes)
|
||||
{
|
||||
results.back().orElse(isCovariantWith(env, subTy, superTy, scope));
|
||||
|
||||
if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex)
|
||||
return SubtypingResult{false, /* normalizationTooComplex */ true};
|
||||
}
|
||||
}
|
||||
|
||||
return SubtypingResult::all(results);
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -136,14 +138,13 @@ TypeId matchLiteralType(
|
|||
* things like replace explicit named properties with indexers as required
|
||||
* by the expected type.
|
||||
*/
|
||||
|
||||
if (!isLiteral(expr))
|
||||
{
|
||||
if (FFlag::LuauBidirectionalInferenceUpcast)
|
||||
{
|
||||
auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope);
|
||||
return result.isSubtype
|
||||
? expectedType
|
||||
: exprType;
|
||||
return result.isSubtype ? expectedType : exprType;
|
||||
}
|
||||
else
|
||||
return exprType;
|
||||
|
@ -152,11 +153,23 @@ TypeId matchLiteralType(
|
|||
expectedType = follow(expectedType);
|
||||
exprType = follow(exprType);
|
||||
|
||||
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
// "Narrowing" to unknown or any is not going to do anything useful.
|
||||
return exprType;
|
||||
// The intent of `matchLiteralType` is to upcast values when it's safe
|
||||
// to do so. it's always safe to upcast to `any` or `unknown`, so we
|
||||
// can unconditionally do so here.
|
||||
if (is<AnyType, UnknownType>(expectedType))
|
||||
return expectedType;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<AnyType>(expectedType) || get<UnknownType>(expectedType))
|
||||
{
|
||||
// "Narrowing" to unknown or any is not going to do anything useful.
|
||||
return exprType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (expr->is<AstExprConstantString>())
|
||||
{
|
||||
|
@ -238,6 +251,15 @@ TypeId matchLiteralType(
|
|||
if (auto exprTable = expr->as<AstExprTable>())
|
||||
{
|
||||
TableType* const tableTy = getMutable<TableType>(exprType);
|
||||
|
||||
// This can occur if we have an expression like:
|
||||
//
|
||||
// { x = {}, x = 42 }
|
||||
//
|
||||
// The type of this will be `{ x: number }`
|
||||
if (FFlag::LuauBidirectionalFailsafe && !tableTy)
|
||||
return exprType;
|
||||
|
||||
LUAU_ASSERT(tableTy);
|
||||
|
||||
const TableType* expectedTableTy = get<TableType>(expectedType);
|
||||
|
@ -264,6 +286,9 @@ TypeId matchLiteralType(
|
|||
|
||||
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
|
||||
|
||||
DenseHashSet<TypeId> indexerKeyTypes{nullptr};
|
||||
DenseHashSet<TypeId> indexerValueTypes{nullptr};
|
||||
|
||||
for (const AstExprTable::Item& item : exprTable->items)
|
||||
{
|
||||
if (isRecord(item))
|
||||
|
@ -271,6 +296,11 @@ TypeId matchLiteralType(
|
|||
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
|
||||
std::string keyStr{s.data, s.data + s.size};
|
||||
auto it = tableTy->props.find(keyStr);
|
||||
|
||||
// This can occur, potentially, if we are re-entrant.
|
||||
if (FFlag::LuauBidirectionalFailsafe && it == tableTy->props.end())
|
||||
continue;
|
||||
|
||||
LUAU_ASSERT(it != tableTy->props.end());
|
||||
|
||||
Property& prop = it->second;
|
||||
|
@ -307,10 +337,18 @@ TypeId matchLiteralType(
|
|||
toBlock
|
||||
);
|
||||
|
||||
if (tableTy->indexer)
|
||||
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}}));
|
||||
indexerValueTypes.insert(matchedType);
|
||||
}
|
||||
else
|
||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||
{
|
||||
if (tableTy->indexer)
|
||||
unifier->unify(matchedType, tableTy->indexer->indexResultType);
|
||||
else
|
||||
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
|
||||
}
|
||||
|
||||
keysToDelete.insert(item.key->as<AstExprConstantString>());
|
||||
|
||||
|
@ -368,6 +406,11 @@ TypeId matchLiteralType(
|
|||
LUAU_ASSERT(matchedType);
|
||||
|
||||
(*astExpectedTypes)[item.value] = matchedType;
|
||||
// NOTE: We do *not* add to the potential indexer types here.
|
||||
// I think this is correct to support something like:
|
||||
//
|
||||
// { [string]: number, foo: boolean }
|
||||
//
|
||||
}
|
||||
else if (item.kind == AstExprTable::Item::List)
|
||||
{
|
||||
|
@ -392,9 +435,18 @@ TypeId matchLiteralType(
|
|||
toBlock
|
||||
);
|
||||
|
||||
// if the index result type is the prop type, we can replace it with the matched type here.
|
||||
if (tableTy->indexer->indexResultType == *propTy)
|
||||
tableTy->indexer->indexResultType = matchedType;
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(builtinTypes->numberType);
|
||||
indexerValueTypes.insert(matchedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the index result type is the prop type, we can replace it with the matched type here.
|
||||
if (tableTy->indexer->indexResultType == *propTy)
|
||||
tableTy->indexer->indexResultType = matchedType;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else if (item.kind == AstExprTable::Item::General)
|
||||
|
@ -416,6 +468,13 @@ TypeId matchLiteralType(
|
|||
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
|
||||
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
|
||||
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
|
||||
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
{
|
||||
indexerKeyTypes.insert(tKey);
|
||||
indexerValueTypes.insert(tProp);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unexpected");
|
||||
|
@ -477,9 +536,39 @@ TypeId matchLiteralType(
|
|||
// have one too.
|
||||
// TODO: If the expected table also has an indexer, we might want to
|
||||
// push the expected indexer's types into it.
|
||||
if (expectedTableTy->indexer && !tableTy->indexer)
|
||||
if (FFlag::LuauBidirectionalInferenceCollectIndexerTypes && expectedTableTy->indexer)
|
||||
{
|
||||
tableTy->indexer = expectedTableTy->indexer;
|
||||
if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0)
|
||||
{
|
||||
TypeId inferredKeyType = builtinTypes->neverType;
|
||||
TypeId inferredValueType = builtinTypes->neverType;
|
||||
for (auto kt: indexerKeyTypes)
|
||||
{
|
||||
auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt);
|
||||
inferredKeyType = simplified.result;
|
||||
}
|
||||
for (auto vt: indexerValueTypes)
|
||||
{
|
||||
auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt);
|
||||
inferredValueType = simplified.result;
|
||||
}
|
||||
tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType};
|
||||
auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope);
|
||||
if (keyCheck.isSubtype)
|
||||
tableTy->indexer->indexType = expectedTableTy->indexer->indexType;
|
||||
auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope);
|
||||
if (valueCheck.isSubtype)
|
||||
tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType;
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expectedTableTy->indexer && !tableTy->indexer)
|
||||
{
|
||||
tableTy->indexer = expectedTableTy->indexer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -167,7 +168,7 @@ struct StringWriter : Writer
|
|||
|
||||
void symbol(std::string_view s) override
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
write(s);
|
||||
}
|
||||
|
@ -257,7 +258,7 @@ public:
|
|||
first = !first;
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData && commaPosition)
|
||||
if (FFlag::LuauStoreCSTData2 && commaPosition)
|
||||
{
|
||||
writer.advance(*commaPosition);
|
||||
commaPosition++;
|
||||
|
@ -1229,9 +1230,18 @@ struct Printer_DEPRECATED
|
|||
AstType* l = a->types.data[0];
|
||||
AstType* r = a->types.data[1];
|
||||
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
|
||||
std::swap(l, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
}
|
||||
|
||||
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
|
||||
auto rta = r->as<AstTypeReference>();
|
||||
|
@ -1254,7 +1264,7 @@ struct Printer_DEPRECATED
|
|||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
if (a->types.data[i]->is<AstTypeOptional>())
|
||||
{
|
||||
|
@ -1489,7 +1499,8 @@ struct Printer
|
|||
|
||||
void visualize(AstExpr& expr)
|
||||
{
|
||||
advance(expr.location.begin);
|
||||
if (!expr.is<AstExprFunction>() || FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
advance(expr.location.begin);
|
||||
|
||||
if (const auto& a = expr.as<AstExprGroup>())
|
||||
{
|
||||
|
@ -1623,6 +1634,17 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = expr.as<AstExprFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (const auto cstNode = lookupCstNode<CstExprFunction>(a))
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
writer.keyword("function");
|
||||
visualizeFunctionBody(*a);
|
||||
}
|
||||
|
@ -1874,7 +1896,8 @@ struct Printer
|
|||
|
||||
void visualize(AstStat& program)
|
||||
{
|
||||
advance(program.location.begin);
|
||||
if ((!program.is<AstStatLocalFunction>() && !program.is<AstStatFunction>()) || FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
advance(program.location.begin);
|
||||
|
||||
if (const auto& block = program.as<AstStatBlock>())
|
||||
{
|
||||
|
@ -2111,13 +2134,36 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = program.as<AstStatFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->func->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (const auto cstNode = lookupCstNode<CstStatFunction>(a))
|
||||
advance(cstNode->functionKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
writer.keyword("function");
|
||||
visualize(*a->name);
|
||||
visualizeFunctionBody(*a->func);
|
||||
}
|
||||
else if (const auto& a = program.as<AstStatLocalFunction>())
|
||||
{
|
||||
for (const auto& attribute : a->func->attributes)
|
||||
visualizeAttribute(*attribute);
|
||||
|
||||
const auto cstNode = lookupCstNode<CstStatLocalFunction>(a);
|
||||
if (FFlag::LuauFixFunctionWithAttributesStartLocation)
|
||||
{
|
||||
if (cstNode)
|
||||
advance(cstNode->localKeywordPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(a->location.begin);
|
||||
}
|
||||
|
||||
writer.keyword("local");
|
||||
|
||||
|
@ -2261,7 +2307,7 @@ struct Printer
|
|||
|
||||
if (program.hasSemicolon)
|
||||
{
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
advanceBefore(program.location.end, 1);
|
||||
writer.symbol(";");
|
||||
}
|
||||
|
@ -2271,7 +2317,7 @@ struct Printer
|
|||
{
|
||||
const auto cstNode = lookupCstNode<CstExprFunction>(&func);
|
||||
|
||||
// TODO(CLI-139347): need to handle attributes, argument types, and return type (incl. parentheses of return type)
|
||||
// TODO(CLI-139347): need to handle return type (incl. parentheses of return type)
|
||||
|
||||
if (func.generics.size > 0 || func.genericPacks.size > 0)
|
||||
{
|
||||
|
@ -2427,6 +2473,23 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
void visualizeAttribute(AstAttr& attribute)
|
||||
{
|
||||
advance(attribute.location.begin);
|
||||
switch (attribute.type)
|
||||
{
|
||||
case AstAttr::Checked:
|
||||
writer.keyword("@checked");
|
||||
break;
|
||||
case AstAttr::Native:
|
||||
writer.keyword("@native");
|
||||
break;
|
||||
case AstAttr::Deprecated:
|
||||
writer.keyword("@deprecated");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void visualizeTypeAnnotation(AstType& typeAnnotation)
|
||||
{
|
||||
advance(typeAnnotation.location.begin);
|
||||
|
@ -2671,14 +2734,25 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeUnion>())
|
||||
{
|
||||
if (a->types.size == 2)
|
||||
const auto cstNode = lookupCstNode<CstTypeUnion>(a);
|
||||
|
||||
if (!cstNode && a->types.size == 2)
|
||||
{
|
||||
AstType* l = a->types.data[0];
|
||||
AstType* r = a->types.data[1];
|
||||
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil" && !r->is<AstTypeOptional>())
|
||||
std::swap(l, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lta = l->as<AstTypeReference>();
|
||||
if (lta && lta->name == "nil")
|
||||
std::swap(l, r);
|
||||
}
|
||||
|
||||
// it's still possible that we had a (T | U) or (T | nil) and not (nil | T)
|
||||
auto rta = r->as<AstTypeReference>();
|
||||
|
@ -2699,12 +2773,20 @@ struct Printer
|
|||
}
|
||||
}
|
||||
|
||||
if (cstNode && cstNode->leadingPosition)
|
||||
{
|
||||
advance(*cstNode->leadingPosition);
|
||||
writer.symbol("|");
|
||||
}
|
||||
|
||||
size_t separatorIndex = 0;
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
if (a->types.data[i]->is<AstTypeOptional>())
|
||||
if (const auto optional = a->types.data[i]->as<AstTypeOptional>())
|
||||
{
|
||||
advance(optional->location.begin);
|
||||
writer.symbol("?");
|
||||
continue;
|
||||
}
|
||||
|
@ -2712,11 +2794,18 @@ struct Printer
|
|||
|
||||
if (i > 0)
|
||||
{
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
if (cstNode && FFlag::LuauParseOptionalAsNode2)
|
||||
{
|
||||
// separatorIndex is only valid if `?` is handled as an AstTypeOptional
|
||||
advance(cstNode->separatorPositions.data[separatorIndex]);
|
||||
separatorIndex++;
|
||||
}
|
||||
else
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.symbol("|");
|
||||
}
|
||||
|
||||
bool wrap = a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>();
|
||||
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeIntersection>() || a->types.data[i]->as<AstTypeFunction>());
|
||||
|
||||
if (wrap)
|
||||
writer.symbol("(");
|
||||
|
@ -2729,15 +2818,27 @@ struct Printer
|
|||
}
|
||||
else if (const auto& a = typeAnnotation.as<AstTypeIntersection>())
|
||||
{
|
||||
const auto cstNode = lookupCstNode<CstTypeIntersection>(a);
|
||||
|
||||
// If the sizes are equal, we know there is a leading & token
|
||||
if (cstNode && cstNode->leadingPosition)
|
||||
{
|
||||
advance(*cstNode->leadingPosition);
|
||||
writer.symbol("&");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < a->types.size; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
if (cstNode)
|
||||
advance(cstNode->separatorPositions.data[i - 1]);
|
||||
else
|
||||
writer.maybeSpace(a->types.data[i]->location.begin, 2);
|
||||
writer.symbol("&");
|
||||
}
|
||||
|
||||
bool wrap = a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>();
|
||||
bool wrap = !cstNode && (a->types.data[i]->as<AstTypeUnion>() || a->types.data[i]->as<AstTypeFunction>());
|
||||
|
||||
if (wrap)
|
||||
writer.symbol("(");
|
||||
|
@ -2786,7 +2887,7 @@ std::string toString(AstNode* node)
|
|||
StringWriter writer;
|
||||
writer.pos = node->location.begin;
|
||||
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
Printer printer(writer, CstNodeMap{nullptr});
|
||||
printer.writeTypes = true;
|
||||
|
@ -2822,7 +2923,7 @@ void dump(AstNode* node)
|
|||
std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
||||
{
|
||||
StringWriter writer;
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
Printer(writer, cstNodeMap).visualizeBlock(block);
|
||||
}
|
||||
|
@ -2836,7 +2937,7 @@ std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
|||
std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap)
|
||||
{
|
||||
StringWriter writer;
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
Printer printer(writer, cstNodeMap);
|
||||
printer.writeTypes = true;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
|
@ -308,7 +308,7 @@ public:
|
|||
|
||||
if (el)
|
||||
new (arg)
|
||||
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData ? Location() : el->location));
|
||||
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
|
||||
else
|
||||
new (arg) std::optional<AstArgumentName>();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|||
LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds)
|
||||
LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2229,10 +2230,21 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
|||
|
||||
return builtinTypes->numberType;
|
||||
case AstExprBinary::Op::Concat:
|
||||
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
|
||||
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||
{
|
||||
if (FFlag::LuauTypeCheckerAcceptNumberConcats)
|
||||
{
|
||||
const TypeId numberOrString = module->internalTypes.addType(UnionType{{builtinTypes->numberType, builtinTypes->stringType}});
|
||||
testIsSubtype(leftType, numberOrString, expr->left->location);
|
||||
testIsSubtype(rightType, numberOrString, expr->right->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
testIsSubtype(leftType, builtinTypes->stringType, expr->left->location);
|
||||
testIsSubtype(rightType, builtinTypes->stringType, expr->right->location);
|
||||
}
|
||||
|
||||
return builtinTypes->stringType;
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
|
|
|
@ -48,22 +48,28 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipNestedAndRecursiveUnion)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using TypeOrTypePackIdSet = DenseHashSet<const void*>;
|
||||
|
||||
struct InstanceCollector : TypeOnceVisitor
|
||||
struct InstanceCollector_DEPRECATED : TypeOnceVisitor
|
||||
{
|
||||
VecDeque<TypeId> tys;
|
||||
VecDeque<TypePackId> tps;
|
||||
|
@ -118,6 +124,153 @@ struct InstanceCollector : TypeOnceVisitor
|
|||
}
|
||||
};
|
||||
|
||||
struct InstanceCollector : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId> recordedTys{nullptr};
|
||||
VecDeque<TypeId> tys;
|
||||
DenseHashSet<TypePackId> recordedTps{nullptr};
|
||||
VecDeque<TypePackId> tps;
|
||||
TypeOrTypePackIdSet shouldGuess{nullptr};
|
||||
std::vector<const void*> typeFunctionInstanceStack;
|
||||
std::vector<TypeId> cyclicInstance;
|
||||
|
||||
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
|
||||
{
|
||||
// TypeVisitor performs a depth-first traversal in the absence of
|
||||
// cycles. This means that by pushing to the front of the queue, we will
|
||||
// try to reduce deeper instances first if we start with the first thing
|
||||
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
||||
// we want to reduce the innermost Add<number, number> instantiation
|
||||
// first.
|
||||
|
||||
typeFunctionInstanceStack.push_back(ty);
|
||||
|
||||
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && int(typeFunctionInstanceStack.size()) > DFInt::LuauTypeFamilyUseGuesserDepth)
|
||||
shouldGuess.insert(ty);
|
||||
|
||||
if (!recordedTys.contains(ty))
|
||||
{
|
||||
recordedTys.insert(ty);
|
||||
tys.push_front(ty);
|
||||
}
|
||||
|
||||
for (TypeId p : tfit.typeArguments)
|
||||
traverse(p);
|
||||
|
||||
for (TypePackId p : tfit.packArguments)
|
||||
traverse(p);
|
||||
|
||||
typeFunctionInstanceStack.pop_back();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void cycle(TypeId ty) override
|
||||
{
|
||||
TypeId t = follow(ty);
|
||||
|
||||
if (get<TypeFunctionInstanceType>(t))
|
||||
{
|
||||
// If we see a type a second time and it's in the type function stack, it's a real cycle
|
||||
if (std::find(typeFunctionInstanceStack.begin(), typeFunctionInstanceStack.end(), t) != typeFunctionInstanceStack.end())
|
||||
cyclicInstance.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack& tfitp) override
|
||||
{
|
||||
// TypeVisitor performs a depth-first traversal in the absence of
|
||||
// cycles. This means that by pushing to the front of the queue, we will
|
||||
// try to reduce deeper instances first if we start with the first thing
|
||||
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
||||
// we want to reduce the innermost Add<number, number> instantiation
|
||||
// first.
|
||||
|
||||
typeFunctionInstanceStack.push_back(tp);
|
||||
|
||||
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && int(typeFunctionInstanceStack.size()) > DFInt::LuauTypeFamilyUseGuesserDepth)
|
||||
shouldGuess.insert(tp);
|
||||
|
||||
if (!recordedTps.contains(tp))
|
||||
{
|
||||
recordedTps.insert(tp);
|
||||
tps.push_front(tp);
|
||||
}
|
||||
|
||||
for (TypeId p : tfitp.typeArguments)
|
||||
traverse(p);
|
||||
|
||||
for (TypePackId p : tfitp.packArguments)
|
||||
traverse(p);
|
||||
|
||||
typeFunctionInstanceStack.pop_back();
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct UnscopedGenericFinder : TypeOnceVisitor
|
||||
{
|
||||
std::vector<TypeId> scopeGenTys;
|
||||
std::vector<TypePackId> scopeGenTps;
|
||||
bool foundUnscoped = false;
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
// Once we have found an unscoped generic, we will stop the traversal
|
||||
return !foundUnscoped;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
// Once we have found an unscoped generic, we will stop the traversal
|
||||
return !foundUnscoped;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const GenericType&) override
|
||||
{
|
||||
if (std::find(scopeGenTys.begin(), scopeGenTys.end(), ty) == scopeGenTys.end())
|
||||
foundUnscoped = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp, const GenericTypePack&) override
|
||||
{
|
||||
if (std::find(scopeGenTps.begin(), scopeGenTps.end(), tp) == scopeGenTps.end())
|
||||
foundUnscoped = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ftv) override
|
||||
{
|
||||
size_t startTyCount = scopeGenTys.size();
|
||||
size_t startTpCount = scopeGenTps.size();
|
||||
|
||||
scopeGenTys.insert(scopeGenTys.end(), ftv.generics.begin(), ftv.generics.end());
|
||||
scopeGenTps.insert(scopeGenTps.end(), ftv.genericPacks.begin(), ftv.genericPacks.end());
|
||||
|
||||
traverse(ftv.argTypes);
|
||||
traverse(ftv.retTypes);
|
||||
|
||||
scopeGenTys.resize(startTyCount);
|
||||
scopeGenTps.resize(startTpCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeFunctionReducer
|
||||
{
|
||||
TypeFunctionContext ctx;
|
||||
|
@ -358,7 +511,6 @@ struct TypeFunctionReducer
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
void stepType()
|
||||
{
|
||||
TypeId subject = follow(queuedTys.front());
|
||||
|
@ -372,6 +524,26 @@ struct TypeFunctionReducer
|
|||
|
||||
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject))
|
||||
{
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2 && tfit->function->name == "user")
|
||||
{
|
||||
UnscopedGenericFinder finder;
|
||||
finder.traverse(subject);
|
||||
|
||||
if (finder.foundUnscoped)
|
||||
{
|
||||
// Do not step into this type again
|
||||
irreducible.insert(subject);
|
||||
|
||||
// Let the caller know this type will not become reducible
|
||||
result.irreducibleTypes.insert(subject);
|
||||
|
||||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf("Irreducible due to an unscoped generic type\n");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SkipTestResult testCyclic = testForSkippability(subject);
|
||||
|
||||
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction)
|
||||
|
@ -480,56 +652,114 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
|
|||
|
||||
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
||||
{
|
||||
InstanceCollector collector;
|
||||
|
||||
try
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2)
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
InstanceCollector collector;
|
||||
|
||||
try
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
else
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
InstanceCollector_DEPRECATED collector;
|
||||
|
||||
try
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
|
||||
FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
||||
{
|
||||
InstanceCollector collector;
|
||||
|
||||
try
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2)
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
InstanceCollector collector;
|
||||
|
||||
try
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
else
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
InstanceCollector_DEPRECATED collector;
|
||||
|
||||
try
|
||||
{
|
||||
collector.traverse(entrypoint);
|
||||
}
|
||||
catch (RecursionLimitException&)
|
||||
{
|
||||
return FunctionGraphReductionResult{};
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
|
||||
if (collector.tys.empty() && collector.tps.empty())
|
||||
return {};
|
||||
|
||||
return reduceFunctionsInternal(
|
||||
std::move(collector.tys),
|
||||
std::move(collector.tps),
|
||||
std::move(collector.shouldGuess),
|
||||
std::move(collector.cyclicInstance),
|
||||
location,
|
||||
ctx,
|
||||
force
|
||||
);
|
||||
}
|
||||
|
||||
bool isPending(TypeId ty, ConstraintSolver* solver)
|
||||
|
@ -624,6 +854,42 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct FindUserTypeFunctionBlockers : TypeOnceVisitor
|
||||
{
|
||||
NotNull<TypeFunctionContext> ctx;
|
||||
DenseHashSet<TypeId> blockingTypeMap{nullptr};
|
||||
std::vector<TypeId> blockingTypes;
|
||||
|
||||
explicit FindUserTypeFunctionBlockers(NotNull<TypeFunctionContext> ctx)
|
||||
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
||||
, ctx(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
if (isPending(ty, ctx->solver))
|
||||
{
|
||||
if (!blockingTypeMap.contains(ty))
|
||||
{
|
||||
blockingTypeMap.insert(ty);
|
||||
blockingTypes.push_back(ty);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypePackId tp) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
||||
TypeId instance,
|
||||
const std::vector<TypeId>& typeParams,
|
||||
|
@ -646,21 +912,38 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
|||
}
|
||||
|
||||
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
|
||||
if (!ctx->typeFunctionRuntime->allowEvaluation)
|
||||
if (!ctx->typeFunctionRuntime->allowEvaluation || (FFlag::LuauTypeFunResultInAutocomplete && typeFunction->userFuncData.definition->hasErrors))
|
||||
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
for (auto typeParam : typeParams)
|
||||
if (FFlag::LuauNewTypeFunReductionChecks2)
|
||||
{
|
||||
TypeId ty = follow(typeParam);
|
||||
FindUserTypeFunctionBlockers check{ctx};
|
||||
|
||||
// block if we need to
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
|
||||
for (auto typeParam : typeParams)
|
||||
check.traverse(follow(typeParam));
|
||||
|
||||
if (!check.blockingTypes.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto typeParam : typeParams)
|
||||
{
|
||||
TypeId ty = follow(typeParam);
|
||||
|
||||
// block if we need to
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that whole type function environment is registered
|
||||
for (auto& [name, definition] : typeFunction->userFuncData.environment)
|
||||
{
|
||||
// Cannot evaluate if a potential dependency couldn't be parsed
|
||||
if (FFlag::LuauTypeFunResultInAutocomplete && definition.first->hasErrors)
|
||||
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
|
||||
{
|
||||
// Failure to register at this point means that original definition had to error out and should not have been present in the
|
||||
|
@ -874,7 +1157,16 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
|||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
|
||||
if (!mmType)
|
||||
{
|
||||
if (FFlag::LuauMetatablesHaveLength)
|
||||
{
|
||||
// If we have a metatable type with no __len, this means we still have a table with default length function
|
||||
if (get<MetatableType>(normalizedOperand))
|
||||
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||
}
|
||||
|
||||
mmType = follow(*mmType);
|
||||
if (isPending(*mmType, ctx->solver))
|
||||
|
@ -1004,6 +1296,10 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
|
|||
if (!allowEvaluation)
|
||||
return std::nullopt;
|
||||
|
||||
// Do not evaluate type functions with parse errors inside
|
||||
if (FFlag::LuauTypeFunResultInAutocomplete && function->hasErrors)
|
||||
return std::nullopt;
|
||||
|
||||
prepareState();
|
||||
|
||||
lua_State* global = state.get();
|
||||
|
@ -1046,7 +1342,6 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
|
|||
|
||||
std::string bytecode = builder.getBytecode();
|
||||
|
||||
|
||||
// Separate sandboxed thread for individual execution and private globals
|
||||
lua_State* L = lua_newthread(global);
|
||||
LuauTempThreadPopper popper(global);
|
||||
|
@ -1923,6 +2218,18 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauSimplyRefineNotNil)
|
||||
{
|
||||
if (auto negation = get<NegationType>(discriminant))
|
||||
{
|
||||
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
|
||||
{
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
||||
return {result.result, {}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
|
||||
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
|
||||
if (get<TableType>(target))
|
||||
|
@ -2030,6 +2337,29 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const UnionType& ut) override
|
||||
{
|
||||
if (FFlag::LuauReduceUnionFollowUnionType)
|
||||
{
|
||||
// If we have something like:
|
||||
//
|
||||
// union<A | B, C | D>
|
||||
//
|
||||
// We probably just want to consider this to be the same as
|
||||
//
|
||||
// union<A, B, C, D>
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy of the default visit method.
|
||||
options.insert(ty);
|
||||
if (isPending(ty, ctx->solver))
|
||||
blockingTypes.insert(ty);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
|
||||
{
|
||||
if (tfit.function->name != builtinTypeFunctions().unionFunc.name)
|
||||
|
@ -2618,6 +2948,10 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
|||
)
|
||||
{
|
||||
TypeId indexeeTy = follow(typeParams.at(0));
|
||||
|
||||
if (FFlag::LuauIndexDeferPendingIndexee && isPending(indexeeTy, ctx->solver))
|
||||
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
|
||||
|
||||
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
|
||||
|
||||
// if the indexee failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <vector>
|
||||
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunPrintFix)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
|
||||
|
||||
namespace Luau
|
||||
|
@ -1656,10 +1655,7 @@ static int print(lua_State* L)
|
|||
const char* s = luaL_tolstring(L, i, &l); // convert to string using __tostring et al
|
||||
if (i > 1)
|
||||
{
|
||||
if (FFlag::LuauTypeFunPrintFix)
|
||||
result.append(1, '\t');
|
||||
else
|
||||
result.append('\t', 1);
|
||||
result.append(1, '\t');
|
||||
}
|
||||
result.append(s, l);
|
||||
lua_pop(L, 1);
|
||||
|
|
|
@ -650,27 +650,22 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
}
|
||||
|
||||
enum Polarity
|
||||
{
|
||||
Positive,
|
||||
Negative,
|
||||
Both,
|
||||
};
|
||||
|
||||
Polarity polarity = Positive;
|
||||
Polarity polarity = Polarity::Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
polarity = Negative;
|
||||
case Polarity::Positive:
|
||||
polarity = Polarity::Negative;
|
||||
break;
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
case Polarity::Negative:
|
||||
polarity = Polarity::Positive;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -681,7 +676,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
{
|
||||
if (seenPositive.contains(ty))
|
||||
return true;
|
||||
|
@ -689,7 +684,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenPositive.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
{
|
||||
if (seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -697,7 +692,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
{
|
||||
if (seenPositive.contains(ty) && seenNegative.contains(ty))
|
||||
return true;
|
||||
|
@ -706,6 +701,8 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
seenNegative.insert(ty);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -736,16 +733,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -760,16 +759,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
{
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[ty]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -782,7 +783,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
LUAU_ASSERT(prop.isShared());
|
||||
|
||||
Polarity p = polarity;
|
||||
polarity = Both;
|
||||
polarity = Polarity::Mixed;
|
||||
traverse(prop.type());
|
||||
polarity = p;
|
||||
}
|
||||
|
@ -826,16 +827,18 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
|
||||
switch (polarity)
|
||||
{
|
||||
case Positive:
|
||||
case Polarity::Positive:
|
||||
positiveTypes[tp]++;
|
||||
break;
|
||||
case Negative:
|
||||
case Polarity::Negative:
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
case Both:
|
||||
case Polarity::Mixed:
|
||||
positiveTypes[tp]++;
|
||||
negativeTypes[tp]++;
|
||||
break;
|
||||
default:
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -194,6 +194,7 @@ public:
|
|||
{
|
||||
Checked,
|
||||
Native,
|
||||
Deprecated,
|
||||
};
|
||||
|
||||
AstAttr(const Location& location, Type type);
|
||||
|
@ -453,6 +454,7 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool hasNativeAttribute() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -890,14 +892,22 @@ class AstStatTypeFunction : public AstStat
|
|||
public:
|
||||
LUAU_RTTI(AstStatTypeFunction);
|
||||
|
||||
AstStatTypeFunction(const Location& location, const AstName& name, const Location& nameLocation, AstExprFunction* body, bool exported);
|
||||
AstStatTypeFunction(
|
||||
const Location& location,
|
||||
const AstName& name,
|
||||
const Location& nameLocation,
|
||||
AstExprFunction* body,
|
||||
bool exported,
|
||||
bool hasErrors
|
||||
);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
AstName name;
|
||||
Location nameLocation;
|
||||
AstExprFunction* body;
|
||||
bool exported;
|
||||
AstExprFunction* body = nullptr;
|
||||
bool exported = false;
|
||||
bool hasErrors = false;
|
||||
};
|
||||
|
||||
class AstStatDeclareGlobal : public AstStat
|
||||
|
@ -950,6 +960,7 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstName name;
|
||||
|
@ -1106,6 +1117,7 @@ public:
|
|||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -1459,6 +1471,10 @@ public:
|
|||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
}
|
||||
virtual bool visit(class AstStatTypeFunction* node)
|
||||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
}
|
||||
virtual bool visit(class AstStatDeclareFunction* node)
|
||||
{
|
||||
return visit(static_cast<AstStat*>(node));
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
|
||||
CstExprFunction();
|
||||
|
||||
Position functionKeywordPosition{0, 0};
|
||||
Position openGenericsPosition{0,0};
|
||||
AstArray<Position> genericsCommaPositions;
|
||||
Position closeGenericsPosition{0,0};
|
||||
|
@ -274,13 +275,24 @@ public:
|
|||
Position opPosition;
|
||||
};
|
||||
|
||||
class CstStatFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatFunction)
|
||||
|
||||
explicit CstStatFunction(Position functionKeywordPosition);
|
||||
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
class CstStatLocalFunction : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstStatLocalFunction)
|
||||
|
||||
explicit CstStatLocalFunction(Position functionKeywordPosition);
|
||||
explicit CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition);
|
||||
|
||||
Position localKeywordPosition;
|
||||
Position functionKeywordPosition;
|
||||
};
|
||||
|
||||
|
@ -421,6 +433,28 @@ public:
|
|||
Position closePosition;
|
||||
};
|
||||
|
||||
class CstTypeUnion : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeUnion)
|
||||
|
||||
CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
|
||||
|
||||
std::optional<Position> leadingPosition;
|
||||
AstArray<Position> separatorPositions;
|
||||
};
|
||||
|
||||
class CstTypeIntersection : public CstNode
|
||||
{
|
||||
public:
|
||||
LUAU_CST_RTTI(CstTypeIntersection)
|
||||
|
||||
explicit CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions);
|
||||
|
||||
std::optional<Position> leadingPosition;
|
||||
AstArray<Position> separatorPositions;
|
||||
};
|
||||
|
||||
class CstTypeSingletonString : public CstNode
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -125,7 +125,7 @@ private:
|
|||
AstStat* parseFor();
|
||||
|
||||
// funcname ::= Name {`.' Name} [`:' Name]
|
||||
AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname);
|
||||
AstExpr* parseFunctionName(bool& hasself, AstName& debugname);
|
||||
|
||||
// function funcname funcbody
|
||||
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
|
||||
|
@ -157,7 +157,9 @@ private:
|
|||
// type function Name ... end
|
||||
AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition);
|
||||
|
||||
AstDeclaredClassProp parseDeclaredClassMethod();
|
||||
AstDeclaredClassProp parseDeclaredClassMethod(const AstArray<AstAttr*>& attributes);
|
||||
AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED();
|
||||
|
||||
|
||||
// `declare global' Name: Type |
|
||||
// `declare function' Name`(' [parlist] `)' [`:` Type]
|
||||
|
@ -228,9 +230,9 @@ private:
|
|||
Position colonPosition;
|
||||
};
|
||||
|
||||
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation);
|
||||
// Remove with FFlagLuauStoreCSTData
|
||||
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation);
|
||||
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
// Remove with FFlagLuauStoreCSTData2
|
||||
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
|
||||
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
|
||||
AstType* parseFunctionTypeTail(
|
||||
|
|
|
@ -3,9 +3,24 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
for (const auto attribute : attributes)
|
||||
{
|
||||
if (attribute->type == attributeType)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
||||
{
|
||||
for (AstType* ty : list.types)
|
||||
|
@ -277,6 +292,13 @@ bool AstExprFunction::hasNativeAttribute() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, items(items)
|
||||
|
@ -791,13 +813,15 @@ AstStatTypeFunction::AstStatTypeFunction(
|
|||
const AstName& name,
|
||||
const Location& nameLocation,
|
||||
AstExprFunction* body,
|
||||
bool exported
|
||||
bool exported,
|
||||
bool hasErrors
|
||||
)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, nameLocation(nameLocation)
|
||||
, body(body)
|
||||
, exported(exported)
|
||||
, hasErrors(hasErrors)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -894,6 +918,13 @@ bool AstStatDeclareFunction::isCheckedFunction() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstStatDeclareClass::AstStatDeclareClass(
|
||||
const Location& location,
|
||||
const AstName& name,
|
||||
|
@ -1057,6 +1088,13 @@ bool AstTypeFunction::isCheckedFunction() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauDeprecatedAttribute);
|
||||
|
||||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
|
||||
: AstType(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
|
|
|
@ -129,12 +129,19 @@ CstStatCompoundAssign::CstStatCompoundAssign(Position opPosition)
|
|||
{
|
||||
}
|
||||
|
||||
CstStatLocalFunction::CstStatLocalFunction(Position functionKeywordPosition)
|
||||
CstStatFunction::CstStatFunction(Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstStatLocalFunction::CstStatLocalFunction(Position localKeywordPosition, Position functionKeywordPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, localKeywordPosition(localKeywordPosition)
|
||||
, functionKeywordPosition(functionKeywordPosition)
|
||||
{
|
||||
}
|
||||
|
||||
CstGenericType::CstGenericType(std::optional<Position> defaultEqualsPosition)
|
||||
: CstNode(CstClassIndex())
|
||||
, defaultEqualsPosition(defaultEqualsPosition)
|
||||
|
@ -221,6 +228,20 @@ CstTypeTypeof::CstTypeTypeof(Position openPosition, Position closePosition)
|
|||
{
|
||||
}
|
||||
|
||||
CstTypeUnion::CstTypeUnion(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, leadingPosition(leadingPosition)
|
||||
, separatorPositions(separatorPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeIntersection::CstTypeIntersection(std::optional<Position> leadingPosition, AstArray<Position> separatorPositions)
|
||||
: CstNode(CstClassIndex())
|
||||
, leadingPosition(leadingPosition)
|
||||
, separatorPositions(separatorPositions)
|
||||
{
|
||||
}
|
||||
|
||||
CstTypeSingletonString::CstTypeSingletonString(AstArray<char> sourceString, CstExprConstantString::QuoteStyle quoteStyle, unsigned int blockDepth)
|
||||
: CstNode(CstClassIndex())
|
||||
, sourceString(sourceString)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -169,7 +169,6 @@ target_sources(Luau.CodeGen PRIVATE
|
|||
# Luau.Analysis Sources
|
||||
target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Anyification.h
|
||||
Analysis/include/Luau/AnyTypeSummary.h
|
||||
Analysis/include/Luau/ApplyTypeFunction.h
|
||||
Analysis/include/Luau/AstJsonEncoder.h
|
||||
Analysis/include/Luau/AstQuery.h
|
||||
|
@ -248,7 +247,6 @@ target_sources(Luau.Analysis PRIVATE
|
|||
Analysis/include/Luau/VisitType.h
|
||||
|
||||
Analysis/src/Anyification.cpp
|
||||
Analysis/src/AnyTypeSummary.cpp
|
||||
Analysis/src/ApplyTypeFunction.cpp
|
||||
Analysis/src/AstJsonEncoder.cpp
|
||||
Analysis/src/AstQuery.cpp
|
||||
|
@ -433,7 +431,6 @@ endif()
|
|||
if(TARGET Luau.UnitTest)
|
||||
# Luau.UnitTest Sources
|
||||
target_sources(Luau.UnitTest PRIVATE
|
||||
tests/AnyTypeSummary.test.cpp
|
||||
tests/AssemblyBuilderA64.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/AstJsonEncoder.test.cpp
|
||||
|
|
|
@ -134,6 +134,7 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for
|
|||
getMutable<TableType>(vector3MetaType)->props = {
|
||||
{"__add", {makeFunction(arena, nullopt, {vector3InstanceType, vector3InstanceType}, {vector3InstanceType})}},
|
||||
};
|
||||
getMutable<TableType>(vector3MetaType)->state = TableState::Sealed;
|
||||
|
||||
globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
|
||||
|
||||
struct JsonEncoderFixture
|
||||
{
|
||||
|
@ -440,7 +441,9 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
|
|||
AstStat* expr = expectParseStatement("@checked function a(b) return c end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
|
||||
FFlag::LuauFixFunctionWithAttributesStartLocation
|
||||
? R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})"
|
||||
: R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete)
|
||||
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -4496,4 +4497,27 @@ this@2
|
|||
CHECK_EQ(ac.entryMap.count("thisShouldBeThere"), 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_eval_in_autocomplete")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauTypeFunResultInAutocomplete{FFlag::LuauTypeFunResultInAutocomplete, true};
|
||||
|
||||
check(R"(
|
||||
type function foo(x)
|
||||
local tbl = types.newtable(nil, nil, nil)
|
||||
tbl:setproperty(types.singleton("boolean"), x)
|
||||
tbl:setproperty(types.singleton("number"), types.number)
|
||||
return tbl
|
||||
end
|
||||
|
||||
local function test(a: foo<string>)
|
||||
return a.@1
|
||||
end
|
||||
)");
|
||||
|
||||
auto ac = autocomplete('1');
|
||||
CHECK_EQ(ac.entryMap.count("boolean"), 1);
|
||||
CHECK_EQ(ac.entryMap.count("number"), 1);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -372,7 +372,7 @@ LintResult Fixture::lint(const std::string& source, const std::optional<LintOpti
|
|||
fileResolver.source[mm] = std::move(source);
|
||||
frontend.markDirty(mm);
|
||||
|
||||
return lintModule(mm);
|
||||
return lintModule(mm, lintOptions);
|
||||
}
|
||||
|
||||
LintResult Fixture::lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LintRedundantNativeAttribute);
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1600,6 +1601,326 @@ setfenv(h :: any, {})
|
|||
CHECK_EQ(result.warnings[3].location.begin.line + 1, 11);
|
||||
}
|
||||
|
||||
static void checkDeprecatedWarning(const Luau::LintWarning& warning, const Luau::Position& begin, const Luau::Position& end, const char* msg)
|
||||
{
|
||||
CHECK_EQ(warning.code, LintWarning::Code_DeprecatedApi);
|
||||
CHECK_EQ(warning.location, Location(begin, end));
|
||||
CHECK_EQ(warning.text, msg);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttribute")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
|
||||
|
||||
// @deprecated works on local functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
local function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works on globals functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works on fully typed functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
local function testfun(x:number):number
|
||||
return x + 1
|
||||
end
|
||||
|
||||
if math.random(2) == 2 then
|
||||
testfun(1)
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(7, 4), Position(7, 11), "Function 'testfun' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works on functions without an explicit return type
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
local function testfun(x:number)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
g(testfun)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 2), Position(6, 9), "Function 'testfun' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works on functions without an explicit argument type
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
local function testfun(x):number
|
||||
if x == 1 then
|
||||
return x
|
||||
else
|
||||
return 1 + testfun(x - 1)
|
||||
end
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(10, 0), Position(10, 7), "Function 'testfun' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works on inner functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
function flipFlop()
|
||||
local state = false
|
||||
|
||||
@deprecated
|
||||
local function invert()
|
||||
state = !state
|
||||
return state
|
||||
end
|
||||
|
||||
return invert
|
||||
end
|
||||
|
||||
f = flipFlop()
|
||||
assert(f() == true)
|
||||
)");
|
||||
|
||||
REQUIRE(2 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(10, 11), Position(10, 17), "Function 'invert' is deprecated");
|
||||
checkDeprecatedWarning(result.warnings[1], Position(14, 7), Position(14, 8), "Function 'f' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated does not automatically apply to inner functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
function flipFlop()
|
||||
local state = false
|
||||
|
||||
local function invert()
|
||||
state = !state
|
||||
return state
|
||||
end
|
||||
|
||||
return invert
|
||||
end
|
||||
|
||||
f = flipFlop()
|
||||
assert(f() == true)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(13, 4), Position(13, 12), "Function 'flipFlop' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works correctly if deprecated function is shadowed
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
local function doTheThing()
|
||||
print("doing")
|
||||
end
|
||||
|
||||
doTheThing()
|
||||
|
||||
local function shadow()
|
||||
local function doTheThing()
|
||||
print("doing!")
|
||||
end
|
||||
|
||||
doTheThing()
|
||||
end
|
||||
|
||||
shadow()
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 10), "Function 'doTheThing' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated does not issue warnings if a deprecated function uses itself
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
function fibonacci(n)
|
||||
if n == 0 then
|
||||
return 0
|
||||
elseif n == 1 then
|
||||
return 1
|
||||
else
|
||||
return fibonacci(n - 1) + fibonacci(n - 2)
|
||||
end
|
||||
end
|
||||
|
||||
fibonacci(5)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 9), "Function 'fibonacci' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works for mutually recursive functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@deprecated
|
||||
function odd(x)
|
||||
if x == 0 then
|
||||
return false
|
||||
else
|
||||
return even(x - 1)
|
||||
end
|
||||
end
|
||||
|
||||
@deprecated
|
||||
function even(x)
|
||||
if x == 0 then
|
||||
return true
|
||||
else
|
||||
return odd(x - 1)
|
||||
end
|
||||
end
|
||||
|
||||
assert(odd(1) == true)
|
||||
assert(even(0) == true)
|
||||
)");
|
||||
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 15), Position(6, 19), "Function 'even' is deprecated");
|
||||
checkDeprecatedWarning(result.warnings[1], Position(15, 15), Position(15, 18), "Function 'odd' is deprecated");
|
||||
checkDeprecatedWarning(result.warnings[2], Position(19, 7), Position(19, 10), "Function 'odd' is deprecated");
|
||||
checkDeprecatedWarning(result.warnings[3], Position(20, 7), Position(20, 11), "Function 'even' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works for methods with a literal class name
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
Account = { balance=0 }
|
||||
|
||||
@deprecated
|
||||
function Account:deposit(v)
|
||||
self.balance = self.balance + v
|
||||
end
|
||||
|
||||
Account:deposit(200.00)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated");
|
||||
}
|
||||
|
||||
// @deprecated works for methods with a compound expression class name
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
Account = { balance=0 }
|
||||
|
||||
function getAccount()
|
||||
return Account
|
||||
end
|
||||
|
||||
@deprecated
|
||||
function Account:deposit (v)
|
||||
self.balance = self.balance + v
|
||||
end
|
||||
|
||||
(getAccount()):deposit(200.00)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
|
||||
|
||||
// @deprecated works on function type declarations
|
||||
|
||||
loadDefinition(R"(
|
||||
@deprecated declare function bar(x: number): string
|
||||
)");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
bar(2)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(1, 0), Position(1, 3), "Function 'bar' is deprecated");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeTableDeclaration")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
|
||||
|
||||
// @deprecated works on table type declarations
|
||||
|
||||
loadDefinition(R"(
|
||||
declare Hooty : {
|
||||
tooty : @deprecated @checked (number) -> number
|
||||
}
|
||||
)");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
print(Hooty:tooty(2.0))
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(1, 6), Position(1, 17), "Member 'Hooty.tooty' is deprecated");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeMethodDeclaration")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauDeprecatedAttribute, true}, {FFlag::LuauSolverV2, true}};
|
||||
|
||||
// @deprecated works on table type declarations
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
@deprecated
|
||||
function bar(self, value: number) : number
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local foo = Foo.new()
|
||||
print(foo:bar(2.0))
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(2, 6), Position(2, 13), "Member 'bar' is deprecated");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
|
|
@ -10,9 +10,15 @@
|
|||
#include "Luau/Normalize.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauNormalizeNegatedErrorToAnError)
|
||||
LUAU_FASTFLAG(LuauNormalizeIntersectErrorToAnError)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauNormalizeNegationFix)
|
||||
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeUnionLimit)
|
||||
LUAU_FASTFLAG(LuauNormalizeLimitFunctionSet)
|
||||
LUAU_FASTFLAG(LuauSubtypingStopAtNormFail)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
|
@ -593,6 +599,25 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy_expressed_as_intersection"
|
|||
)")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error")
|
||||
{
|
||||
ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true};
|
||||
|
||||
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & AAA)", 1);
|
||||
REQUIRE(norm);
|
||||
CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_not_error")
|
||||
{
|
||||
ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true};
|
||||
ScopedFastFlag luauNormalizeNegatedErrorToAnError{FFlag::LuauNormalizeNegatedErrorToAnError, true};
|
||||
|
||||
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & Not<)", 1);
|
||||
REQUIRE(norm);
|
||||
CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union")
|
||||
{
|
||||
CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"(
|
||||
|
@ -1032,7 +1057,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types
|
||||
{FFlag::LuauNormalizeNegationFix, true},
|
||||
};
|
||||
|
||||
TypeId freeTy = arena.freshType(builtinTypes, &globalScope);
|
||||
|
@ -1153,4 +1177,38 @@ end
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity")
|
||||
{
|
||||
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
|
||||
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
|
||||
ScopedFastFlag luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true};
|
||||
ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(_).readu32(l0)
|
||||
return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil
|
||||
end
|
||||
_(_)[_(n32)] %= _(_(_))
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures")
|
||||
{
|
||||
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
|
||||
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
|
||||
ScopedFastFlag luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true};
|
||||
ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function _(_,"").readu32(l0)
|
||||
return ({[_(_(_))]=_,[_(if _ then _,_())]=_,[""]=_,})[_],nil
|
||||
end
|
||||
_().readu32 %= _(_(_(_),_))
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,12 +18,17 @@ LUAU_FASTINT(LuauParseErrorLimit)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams)
|
||||
LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes)
|
||||
LUAU_FASTFLAG(LuauFixFunctionNameStartPosition)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3)
|
||||
LUAU_FASTFLAG(LuauFixDoBlockEndLocation)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
LUAU_FASTFLAG(LuauParseStringIndexer)
|
||||
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
|
||||
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
|
||||
|
||||
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
|
||||
extern bool luau_telemetry_parsed_return_type_variadic_with_type_suffix;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -2541,6 +2546,40 @@ TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token")
|
|||
CHECK_EQ(block->location, Location{{1, 8}, {3, 11}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFixFunctionWithAttributesStartLocation, true};
|
||||
|
||||
AstStatBlock* stat = parse(R"(
|
||||
@native
|
||||
function globalFunction()
|
||||
end
|
||||
|
||||
@native
|
||||
local function localFunction()
|
||||
end
|
||||
|
||||
local _ = @native function()
|
||||
end
|
||||
)");
|
||||
REQUIRE(stat);
|
||||
REQUIRE_EQ(3, stat->body.size);
|
||||
|
||||
auto globalFunction = stat->body.data[0]->as<AstStatFunction>();
|
||||
REQUIRE(globalFunction);
|
||||
CHECK_EQ(globalFunction->location, Location({1, 8}, {3, 11}));
|
||||
|
||||
auto localFunction = stat->body.data[1]->as<AstStatLocalFunction>();
|
||||
REQUIRE(localFunction);
|
||||
CHECK_EQ(localFunction->location, Location({5, 8}, {7, 11}));
|
||||
|
||||
auto localVariable = stat->body.data[2]->as<AstStatLocal>();
|
||||
REQUIRE(localVariable);
|
||||
REQUIRE_EQ(localVariable->values.size, 1);
|
||||
auto anonymousFunction = localVariable->values.data[0]->as<AstExprFunction>();
|
||||
CHECK_EQ(anonymousFunction->location, Location({9, 18}, {10, 11}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("ParseErrorRecovery");
|
||||
|
@ -3819,7 +3858,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
|
|||
}
|
||||
else
|
||||
CHECK(unionTy->types.data[0]->is<AstTypeFunction>()); // () -> ()
|
||||
if (FFlag::LuauParseOptionalAsNode)
|
||||
if (FFlag::LuauParseOptionalAsNode2)
|
||||
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
|
||||
else
|
||||
CHECK(unionTy->types.data[1]->is<AstTypeReference>()); // nil
|
||||
|
@ -3880,7 +3919,6 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true};
|
||||
AstStatBlock* block = parse(R"(
|
||||
function simple()
|
||||
end
|
||||
|
@ -3929,6 +3967,8 @@ TEST_CASE_FIXTURE(Fixture, "stat_end_includes_semicolon_position")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
|
||||
{
|
||||
ScopedFastFlag sff{DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
function foo(): (string, ...number) | boolean
|
||||
end
|
||||
|
@ -3936,7 +3976,13 @@ TEST_CASE_FIXTURE(Fixture, "parsing_type_suffix_for_return_type_with_variadic")
|
|||
|
||||
// TODO(CLI-140667): this should produce a ParseError in future when we fix the invalid syntax
|
||||
CHECK(result.errors.size() == 0);
|
||||
CHECK_EQ(luau_telemetry_parsed_return_type_variadic_with_type_suffix, true);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parsing_string_union_indexers")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauParseStringIndexer, true};
|
||||
parse(R"(type foo = { ["bar" | "baz"]: number })");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
template<typename T>
|
||||
struct [[nodiscard]] ScopedFValue
|
||||
|
@ -49,4 +49,4 @@ public:
|
|||
};
|
||||
|
||||
using ScopedFastFlag = ScopedFValue<bool>;
|
||||
using ScopedFastInt = ScopedFValue<int>;
|
||||
using ScopedFastInt = ScopedFValue<int>;
|
|
@ -12,9 +12,11 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
LUAU_FASTFLAG(LuauExtendStatEndPosWithSemicolon)
|
||||
LUAU_FASTFLAG(LuauAstTypeGroup3);
|
||||
LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType)
|
||||
LUAU_FASTFLAG(LuauParseOptionalAsNode2)
|
||||
|
||||
TEST_SUITE_BEGIN("TranspilerTests");
|
||||
|
||||
|
@ -48,7 +50,7 @@ TEST_CASE("string_literals_containing_utf8")
|
|||
|
||||
TEST_CASE("if_stmt_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( if This then Once() end)";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -97,7 +99,7 @@ TEST_CASE("elseif_chains_indent_sensibly")
|
|||
TEST_CASE("strips_type_annotations")
|
||||
{
|
||||
const std::string code = R"( local s: string= 'hello there' )";
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
const std::string expected = R"( local s = 'hello there' )";
|
||||
CHECK_EQ(expected, transpile(code).code);
|
||||
|
@ -112,7 +114,7 @@ TEST_CASE("strips_type_annotations")
|
|||
TEST_CASE("strips_type_assertion_expressions")
|
||||
{
|
||||
const std::string code = R"( local s= some_function() :: any+ something_else() :: number )";
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
const std::string expected = R"( local s= some_function() + something_else() )";
|
||||
CHECK_EQ(expected, transpile(code).code);
|
||||
|
@ -148,7 +150,7 @@ TEST_CASE("for_loop")
|
|||
|
||||
TEST_CASE("for_loop_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( for index = 1, 10 do call(index) end )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -173,7 +175,7 @@ TEST_CASE("for_in_loop")
|
|||
|
||||
TEST_CASE("for_in_loop_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( for k, v in ipairs(x) do end )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -198,7 +200,7 @@ TEST_CASE("while_loop")
|
|||
|
||||
TEST_CASE("while_loop_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( while f(x) do print() end )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -220,7 +222,7 @@ TEST_CASE("repeat_until_loop")
|
|||
|
||||
TEST_CASE("repeat_until_loop_condition_on_new_line")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
repeat
|
||||
print()
|
||||
|
@ -252,7 +254,7 @@ TEST_CASE("local_assignment")
|
|||
|
||||
TEST_CASE("local_assignment_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( local x = 1 )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -286,7 +288,7 @@ TEST_CASE("local_function")
|
|||
|
||||
TEST_CASE("local_function_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( local function p(o, m, ...) end )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -305,7 +307,7 @@ TEST_CASE("function")
|
|||
|
||||
TEST_CASE("function_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string two = R"( function p(o, m, ...) end )";
|
||||
CHECK_EQ(two, transpile(two).code);
|
||||
|
||||
|
@ -333,7 +335,7 @@ TEST_CASE("function_spaces_around_tokens")
|
|||
|
||||
TEST_CASE("function_with_types_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -394,7 +396,7 @@ TEST_CASE("function_with_types_spaces_around_tokens")
|
|||
|
||||
TEST_CASE("returns_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string one = R"( return 1 )";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -407,7 +409,7 @@ TEST_CASE("returns_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo = string )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -456,7 +458,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo<X = string, Z... = ...any> = string )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -517,7 +519,7 @@ TEST_CASE("table_literal_closing_brace_at_correct_position")
|
|||
|
||||
TEST_CASE("table_literal_with_semicolon_separators")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
local t = { x = 1; y = 2 }
|
||||
)";
|
||||
|
@ -527,7 +529,7 @@ TEST_CASE("table_literal_with_semicolon_separators")
|
|||
|
||||
TEST_CASE("table_literal_with_trailing_separators")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
local t = { x = 1, y = 2, }
|
||||
)";
|
||||
|
@ -537,7 +539,7 @@ TEST_CASE("table_literal_with_trailing_separators")
|
|||
|
||||
TEST_CASE("table_literal_with_spaces_around_separator")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
local t = { x = 1 , y = 2 }
|
||||
)";
|
||||
|
@ -547,7 +549,7 @@ TEST_CASE("table_literal_with_spaces_around_separator")
|
|||
|
||||
TEST_CASE("table_literal_with_spaces_around_equals")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
local t = { x = 1 }
|
||||
)";
|
||||
|
@ -557,7 +559,7 @@ TEST_CASE("table_literal_with_spaces_around_equals")
|
|||
|
||||
TEST_CASE("table_literal_multiline_with_indexers")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
local t = {
|
||||
["my first value"] = "x";
|
||||
|
@ -585,7 +587,7 @@ TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off")
|
|||
// Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves
|
||||
// falling out of sync with the original code. We need to push keywords out so that there's at least one space between them.
|
||||
const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )";
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -605,7 +607,7 @@ TEST_CASE("numbers")
|
|||
TEST_CASE("infinity")
|
||||
{
|
||||
const std::string code = R"( local a = 1e500 local b = 1e400 )";
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -618,21 +620,21 @@ TEST_CASE("infinity")
|
|||
|
||||
TEST_CASE("numbers_with_separators")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = 123_456_789 )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("hexadecimal_numbers")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = 0xFFFF )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("binary_numbers")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = 0b0101 )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -645,28 +647,28 @@ TEST_CASE("single_quoted_strings")
|
|||
|
||||
TEST_CASE("double_quoted_strings")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = "hello world" )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("simple_interp_string")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = `hello world` )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("raw_strings")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = [[ hello world ]] )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("raw_strings_with_blocks")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local a = [==[ hello world ]==] )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -685,7 +687,7 @@ TEST_CASE("escaped_strings_2")
|
|||
|
||||
TEST_CASE("escaped_strings_newline")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
print("foo \
|
||||
bar")
|
||||
|
@ -695,14 +697,14 @@ TEST_CASE("escaped_strings_newline")
|
|||
|
||||
TEST_CASE("escaped_strings_raw")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("position_correctly_updated_when_writing_multiline_string")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
call([[
|
||||
testing
|
||||
|
@ -748,56 +750,56 @@ TEST_CASE("function_call_parentheses_multiple_args_no_space")
|
|||
|
||||
TEST_CASE("function_call_parentheses_multiple_args_space_before_commas")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call(arg1 ,arg3 ,arg3) )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_spaces_before_parentheses")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call () )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_spaces_within_parentheses")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call( ) )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_string_double_quotes")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call "string" )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_string_single_quotes")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call 'string' )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_string_no_space")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call'string' )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_table_literal")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call { x = 1 } )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
||||
TEST_CASE("function_call_table_literal_no_space")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( call{x=1} )";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -842,7 +844,7 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "parentheses_multiline")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
local test = (
|
||||
x
|
||||
|
@ -855,7 +857,7 @@ local test = (
|
|||
TEST_CASE_FIXTURE(Fixture, "stmt_semicolon")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"( local test = 1; )";
|
||||
|
@ -878,7 +880,7 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon")
|
|||
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
|
@ -892,7 +894,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon")
|
|||
TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
|
@ -904,7 +906,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2")
|
|||
TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
|
@ -917,7 +919,7 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon")
|
|||
TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
|
@ -930,7 +932,7 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon")
|
|||
TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauExtendStatEndPosWithSemicolon, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
|
@ -1011,7 +1013,7 @@ TEST_CASE("always_emit_a_space_after_local_keyword")
|
|||
{
|
||||
std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end";
|
||||
|
||||
if (FFlag::LuauStoreCSTData)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
{
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
}
|
||||
|
@ -1056,7 +1058,7 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly")
|
|||
end
|
||||
)";
|
||||
|
||||
std::string expected = FFlag::LuauStoreCSTData ? R"(
|
||||
std::string expected = FFlag::LuauStoreCSTData2 ? R"(
|
||||
local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number)
|
||||
end
|
||||
|
||||
|
@ -1112,7 +1114,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = "local a = 5 :: number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1129,7 +1131,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5";
|
||||
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
|
@ -1137,7 +1139,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
local x = if yes
|
||||
then nil
|
||||
|
@ -1153,7 +1155,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = "local a = if 1 then 2 else 3";
|
||||
CHECK_EQ(code, transpile(code).code);
|
||||
|
||||
|
@ -1190,7 +1192,7 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
return
|
||||
if a then "was a" else
|
||||
|
@ -1218,7 +1220,7 @@ local a: Import.Type
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( local _: Foo.Type )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1257,7 +1259,7 @@ local b: Packed<(number, string)>
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type _ = Packed< T...> )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1316,7 +1318,9 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3")
|
|||
{
|
||||
std::string code = "local a: nil | (string & number)";
|
||||
|
||||
if (FFlag::LuauAstTypeGroup3)
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
else if (FFlag::LuauAstTypeGroup3)
|
||||
CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code);
|
||||
else
|
||||
CHECK_EQ("local a: ( string & number)?", transpile(code, {}, true).code);
|
||||
|
@ -1336,6 +1340,117 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2")
|
|||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
|
||||
{FFlag::LuauParseOptionalAsNode2, true},
|
||||
};
|
||||
std::string code = "local a: | string | number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: | string";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
|
||||
{FFlag::LuauParseOptionalAsNode2, true},
|
||||
};
|
||||
std::string code = "local a: string | number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
|
||||
};
|
||||
std::string code = "local a: & string & number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: & string";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true},
|
||||
};
|
||||
std::string code = "local a: string & number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string & number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauAstTypeGroup3, true},
|
||||
{FFlag::LuauParseOptionalAsNode2, true},
|
||||
};
|
||||
std::string code = "local a: string | (Foo & Bar)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | (Foo & Bar)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | ( Foo & Bar)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | (Foo & Bar )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string & (Foo | Bar)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string & ( Foo | Bar)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string & (Foo | Bar )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauParseOptionalAsNode2, true},
|
||||
};
|
||||
std::string code = "local a: string | nil";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string?";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string???";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string? | nil";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | nil | number";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string | nil | number?";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = "local a: string? | number?";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
|
||||
{
|
||||
std::string code = "local function f(...) return ... end";
|
||||
|
@ -1345,7 +1460,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_varargs")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string one = "local _ = a.name";
|
||||
CHECK_EQ(one, transpile(one, {}, true).code);
|
||||
|
||||
|
@ -1358,7 +1473,7 @@ TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = "sparkles.Color = Color3.new()";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
@ -1372,7 +1487,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_index_expr")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string one = "local _ = a[2]";
|
||||
CHECK_EQ(one, transpile(one, {}, true).code);
|
||||
|
||||
|
@ -1416,7 +1531,7 @@ local _ = # e
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
local _ = 1+1
|
||||
local _ = 1 +1
|
||||
|
@ -1458,7 +1573,7 @@ a ..= ' - result'
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string one = R"( a += 1 )";
|
||||
CHECK_EQ(one, transpile(one, {}, true).code);
|
||||
|
||||
|
@ -1475,7 +1590,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string one = "a = 1";
|
||||
CHECK_EQ(one, transpile(one).code);
|
||||
|
||||
|
@ -1512,7 +1627,10 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse")
|
|||
{
|
||||
std::string code = "local a: nil | number";
|
||||
|
||||
CHECK_EQ("local a: number?", transpile(code, {}, true).code);
|
||||
if (FFlag::LuauStoreCSTData2)
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
else
|
||||
CHECK_EQ("local a: number?", transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple")
|
||||
|
@ -1628,7 +1746,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types")
|
|||
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
|
||||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
};
|
||||
std::string code = R"( local _ = `hello {name}` )";
|
||||
|
||||
|
@ -1638,7 +1756,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp")
|
|||
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
|
||||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
};
|
||||
std::string code = R"( local _ = `hello {
|
||||
name
|
||||
|
@ -1650,7 +1768,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline")
|
|||
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
|
||||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
};
|
||||
std::string code = R"(
|
||||
error(
|
||||
|
@ -1663,7 +1781,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( local _ = `hello \
|
||||
world!` )";
|
||||
|
||||
|
@ -1673,7 +1791,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape")
|
|||
TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape")
|
||||
{
|
||||
ScopedFastFlag fflags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
};
|
||||
std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )";
|
||||
|
||||
|
@ -1689,7 +1807,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type function foo() end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1705,7 +1823,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type X = typeof(x) )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1730,14 +1848,14 @@ TEST_CASE("transpile_single_quoted_string_types")
|
|||
|
||||
TEST_CASE("transpile_double_quoted_string_types")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( type a = "hello world" )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_raw_string_types")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type a = [[ hello world ]] )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1747,14 +1865,14 @@ TEST_CASE("transpile_raw_string_types")
|
|||
|
||||
TEST_CASE("transpile_escaped_string_types")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"( type a = "\\b\\t\\n\\\\" )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_type_table_semicolon_separators")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
const std::string code = R"(
|
||||
type Foo = {
|
||||
bar: number;
|
||||
|
@ -1766,7 +1884,7 @@ TEST_CASE("transpile_type_table_semicolon_separators")
|
|||
|
||||
TEST_CASE("transpile_type_table_access_modifiers")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
type Foo = {
|
||||
read bar: number,
|
||||
|
@ -1787,7 +1905,7 @@ TEST_CASE("transpile_type_table_access_modifiers")
|
|||
|
||||
TEST_CASE("transpile_type_table_spaces_between_tokens")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo = { bar: number, } )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1830,7 +1948,7 @@ TEST_CASE("transpile_type_table_spaces_between_tokens")
|
|||
|
||||
TEST_CASE("transpile_type_table_preserve_original_indexer_style")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
type Foo = {
|
||||
[number]: string
|
||||
|
@ -1846,7 +1964,7 @@ TEST_CASE("transpile_type_table_preserve_original_indexer_style")
|
|||
|
||||
TEST_CASE("transpile_type_table_preserve_indexer_location")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
type Foo = {
|
||||
[number]: string,
|
||||
|
@ -1875,7 +1993,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location")
|
|||
|
||||
TEST_CASE("transpile_type_table_preserve_property_definition_style")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
type Foo = {
|
||||
["$$typeof1"]: string,
|
||||
|
@ -1888,7 +2006,7 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style")
|
|||
TEST_CASE("transpile_types_preserve_parentheses_style")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauStoreCSTData, true},
|
||||
{FFlag::LuauStoreCSTData2, true},
|
||||
{FFlag::LuauAstTypeGroup3, true},
|
||||
};
|
||||
|
||||
|
@ -1929,7 +2047,7 @@ end
|
|||
|
||||
TEST_CASE("transpile_type_function_unnamed_arguments")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo = () -> () )";
|
||||
CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1963,7 +2081,7 @@ TEST_CASE("transpile_type_function_unnamed_arguments")
|
|||
|
||||
TEST_CASE("transpile_type_function_named_arguments")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo = (x: string) -> () )";
|
||||
CHECK_EQ(R"( type Foo = (x: string) ->() )", transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1991,7 +2109,7 @@ TEST_CASE("transpile_type_function_named_arguments")
|
|||
|
||||
TEST_CASE("transpile_type_function_generics")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData, true};
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"( type Foo = <X, Y, Z...>() -> () )";
|
||||
CHECK_EQ(R"( type Foo = <X, Y, Z...>() ->() )", transpile(code, {}, true).code);
|
||||
|
||||
|
@ -2023,4 +2141,55 @@ TEST_CASE("transpile_type_function_generics")
|
|||
CHECK_EQ(R"( type Foo = <X, Y, Z...> () ->() )", transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("fuzzer_nil_optional")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauParseOptionalAsNode2, true};
|
||||
|
||||
const std::string code = R"( local x: nil? )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_CASE("transpile_function_attributes")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauStoreCSTData2, true};
|
||||
std::string code = R"(
|
||||
@native
|
||||
function foo()
|
||||
end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"(
|
||||
@native
|
||||
local function foo()
|
||||
end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"(
|
||||
@checked local function foo()
|
||||
end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"(
|
||||
local foo = @native function() end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"(
|
||||
@native
|
||||
function foo:bar()
|
||||
end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
code = R"(
|
||||
@native @checked
|
||||
function foo:bar()
|
||||
end
|
||||
)";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -17,7 +17,9 @@ LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements)
|
|||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods)
|
||||
LUAU_FASTFLAG(LuauMetatableTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauMetatablesHaveLength)
|
||||
LUAU_FASTFLAG(LuauIndexAnyIsAny)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
{
|
||||
|
@ -145,19 +147,17 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
|
|||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local impossible: <T>(Swap<T>) -> Swap<Swap<T>>
|
||||
local a = impossible(123)
|
||||
local b = impossible(true)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(6, result);
|
||||
CHECK(toString(result.errors[0]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
||||
CHECK(toString(result.errors[1]) == "Type function instance Swap<T> is uninhabited");
|
||||
CHECK(toString(result.errors[2]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
||||
CHECK(toString(result.errors[3]) == "Type function instance Swap<T> is uninhabited");
|
||||
CHECK(toString(result.errors[4]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
||||
CHECK(toString(result.errors[5]) == "Type function instance Swap<T> is uninhabited");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'never'");
|
||||
CHECK(toString(result.errors[1]) == "Type 'boolean' could not be converted into 'never'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
|
||||
|
@ -1552,4 +1552,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_respects_metatable_metamethod")
|
|||
CHECK_EQ(toString(requireTypeAlias("Metatable")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_function_correct_cycle_check")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type foo<T> = { a: add<T, T>, b : add<T, T> }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauMetatablesHaveLength{FFlag::LuauMetatablesHaveLength, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = setmetatable({}, { __mode = "v" })
|
||||
|
||||
local function f()
|
||||
table.insert(t, {})
|
||||
print(#t * 100)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -10,9 +10,9 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
|
||||
LUAU_FASTFLAG(LuauTypeFunPrintFix)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
|
@ -2030,7 +2030,7 @@ local _:test<number>
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypeFunPrintFix, true}};
|
||||
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function test(t)
|
||||
|
@ -2103,4 +2103,105 @@ end
|
|||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "outer_generics_irreducible")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function func(t)
|
||||
return t
|
||||
end
|
||||
|
||||
type wrap<T> = { a: func<T?> }
|
||||
|
||||
local x: wrap<string> = nil :: any
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: string? }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "inner_generics_reducible")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function func(t)
|
||||
return t
|
||||
end
|
||||
|
||||
type wrap<T> = { a: func<<T>(T) -> number>, b: T }
|
||||
|
||||
local x: wrap<string> = nil :: any
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: <T>(T) -> number, b: string }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "blocking_nested_pending_expansions")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function func(t)
|
||||
return t
|
||||
end
|
||||
|
||||
type test<T> = { x: T, y: T? }
|
||||
type wrap<T> = { a: func<(string, keyof<test<T>>) -> number>, b: T }
|
||||
local x: wrap<string>
|
||||
local y: keyof<typeof(x)>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("x"), ToStringOptions{true}) == R"({ a: (string, "x" | "y") -> number, b: string })");
|
||||
CHECK(toString(requireType("y"), ToStringOptions{true}) == R"("a" | "b")");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "blocking_nested_pending_expansions_2")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo(t)
|
||||
return types.unionof(t, types.singleton(nil))
|
||||
end
|
||||
|
||||
local x: foo<{a: foo<string>, b: foo<number>}> = nil
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: string?, b: number? }?");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "irreducible_pending_expansions")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type function foo(t)
|
||||
return types.unionof(t, types.singleton(nil))
|
||||
end
|
||||
|
||||
type table<T> = { a: index<T, "a"> }
|
||||
type wrap<T> = foo<table<T>>
|
||||
|
||||
local x: wrap<{a: number}> = { a = 2 }
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: number }?");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -12,6 +12,10 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauPrecalculateMutatedFreeTypes2)
|
||||
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
|
@ -253,8 +257,12 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
|
||||
{
|
||||
// CLI-116108
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTINT(LuauTarjanChildLimit)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauReduceUnionFollowUnionType)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -3191,4 +3192,30 @@ TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generali
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauReduceUnionFollowUnionType, true}
|
||||
};
|
||||
|
||||
// This block ends up minting a type like:
|
||||
//
|
||||
// t2 where t1 = union<t2, t1> | union<t2, t1> | union<t2, t1> ; t2 = union<t2, t1>
|
||||
//
|
||||
CheckResult result = check(R"(
|
||||
local _ = ...
|
||||
function _()
|
||||
_ = _
|
||||
end
|
||||
_[function(...) repeat until _(_[l100]) _ = _ end] += _
|
||||
)");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
auto err0 = get<UnknownSymbol>(result.errors[0]);
|
||||
CHECK(err0);
|
||||
CHECK_EQ(err0->name, "l100");
|
||||
auto err1 = get<NotATable>(result.errors[1]);
|
||||
CHECK(err1);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -13,7 +13,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData)
|
||||
LUAU_FASTFLAG(LuauStoreCSTData2)
|
||||
LUAU_FASTINT(LuauNormalizeCacheLimit)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
|
@ -49,7 +49,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
end
|
||||
)";
|
||||
|
||||
const std::string expected = FFlag::LuauStoreCSTData ? R"(
|
||||
const std::string expected = FFlag::LuauStoreCSTData2 ? R"(
|
||||
function f(a:{fn:()->(a,b...)}): ()
|
||||
if type(a) == 'boolean' then
|
||||
local a1:boolean=a
|
||||
|
@ -68,7 +68,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
end
|
||||
)";
|
||||
|
||||
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData ? R"(
|
||||
const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData2 ? R"(
|
||||
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||
if type(a) == 'boolean' then
|
||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||
|
@ -87,7 +87,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete")
|
|||
end
|
||||
)";
|
||||
|
||||
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData ? R"(
|
||||
const std::string expectedWithEqSat = FFlag::LuauStoreCSTData2 ? R"(
|
||||
function f(a:{fn:()->(unknown,...unknown)}): ()
|
||||
if type(a) == 'boolean' then
|
||||
local a1:{fn:()->(unknown,...unknown)}&boolean=a
|
||||
|
|
|
@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauIntersectNotNil)
|
|||
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement)
|
||||
LUAU_FASTFLAG(LuauSimplyRefineNotNil)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -756,14 +757,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
|
|||
{
|
||||
// CLI-115281 Types produced by refinements do not consistently get simplified
|
||||
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
|
||||
CHECK_EQ(
|
||||
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))
|
||||
); // type(v) ~= "nil"
|
||||
|
||||
if (FFlag::LuauSimplyRefineNotNil)
|
||||
CHECK_EQ(
|
||||
"string & ~nil", toString(requireTypeAtPosition({6, 24}))
|
||||
); // type(v) ~= "nil"
|
||||
else
|
||||
CHECK_EQ(
|
||||
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({6, 24}))
|
||||
); // type(v) ~= "nil"
|
||||
|
||||
CHECK_EQ("(nil & string)?", toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
|
||||
CHECK_EQ(
|
||||
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
|
||||
); // equivalent to type(v) ~= "nil"
|
||||
|
||||
if (FFlag::LuauSimplyRefineNotNil)
|
||||
CHECK_EQ("string & ~nil", toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
|
||||
else
|
||||
CHECK_EQ(
|
||||
"(boolean | buffer | class | function | number | string | table | thread) & string", toString(requireTypeAtPosition({12, 24}))
|
||||
); // equivalent to type(v) ~= "nil"
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeSingletons");
|
||||
|
@ -152,6 +153,26 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_function_resolution_singleton_parameters")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauPropagateExpectedTypesForCalls, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = ("A") -> string
|
||||
type B = ("B") -> number
|
||||
|
||||
local function foo(f: A & B)
|
||||
return f("A"), f("B")
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
TypeId t = requireType("foo");
|
||||
const FunctionType* fooType = get<FunctionType>(requireType("foo"));
|
||||
REQUIRE(fooType != nullptr);
|
||||
|
||||
CHECK(toString(t) == "(((\"A\") -> string) & ((\"B\") -> number)) -> (string, number)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
|
|
@ -30,6 +30,8 @@ LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast)
|
|||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauSearchForRefineableType)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes)
|
||||
LUAU_FASTFLAG(LuauBidirectionalFailsafe)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -5168,34 +5170,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_union_type")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
|
||||
{
|
||||
// NOTE: All of these examples should have no errors, but
|
||||
// bidirectional inference is known to be broken.
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
CheckResult result = check(R"(
|
||||
local function doTheThing(_: { [string]: unknown }) end
|
||||
doTheThing({
|
||||
['foo'] = 5,
|
||||
['bar'] = 'heyo',
|
||||
})
|
||||
)");
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, check(R"(
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
result = check(R"(
|
||||
type Input = { [string]: unknown }
|
||||
|
||||
local i : Input = {
|
||||
[('%s'):format('3.14')]=5,
|
||||
['stringField']='Heyo'
|
||||
}
|
||||
)"));
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
|
||||
// This example previously asserted due to eagerly mutating the underlying
|
||||
// table type.
|
||||
result = check(R"(
|
||||
type Input = { [string]: unknown }
|
||||
|
||||
|
@ -5206,8 +5209,45 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager")
|
|||
['stringField']='Heyo'
|
||||
})
|
||||
)");
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function getStuff(): (string, number, string)
|
||||
return "hello", 42, "world"
|
||||
end
|
||||
local t: { [string]: number } = {
|
||||
[select(1, getStuff())] = select(2, getStuff()),
|
||||
[select(3, getStuff())] = select(2, getStuff())
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
result = check(R"(
|
||||
local function getStuff(): (string, number, string)
|
||||
return "hello", 42, "world"
|
||||
end
|
||||
local t: { [string]: number } = {
|
||||
[select(1, getStuff())] = select(2, getStuff()),
|
||||
[select(3, getStuff())] = select(3, getStuff())
|
||||
}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK_EQ("{ [string]: number | string }", toString(err->givenType));
|
||||
CHECK_EQ("{ [string]: number }", toString(err->wantedType));
|
||||
}
|
||||
|
||||
|
||||
|
@ -5471,6 +5511,8 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
@ -5478,7 +5520,8 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
|
|||
local b: Book = { title = "The Odyssey" }
|
||||
local t: { Book } = {
|
||||
{ title = "The Illiad", author = "Homer" },
|
||||
{ author = "Virgil" }
|
||||
{ title = "Inferno", author = "Virgil" },
|
||||
{ author = "Virgil" },
|
||||
}
|
||||
)");
|
||||
|
||||
|
@ -5490,12 +5533,49 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference")
|
|||
CHECK_EQ(result.errors[0].location, Location{{2, 24}, {2, 49}});
|
||||
err = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE(err);
|
||||
CHECK_EQ(toString(err->givenType), "{{ author: string } | { author: string, title: string }}");
|
||||
// CLI-144203: This could be better.
|
||||
CHECK_EQ(toString(err->givenType), "{{ author: string }}");
|
||||
CHECK_EQ(toString(err->wantedType), "{Book}");
|
||||
CHECK_EQ(result.errors[1].location, Location{{3, 28}, {6, 9}});
|
||||
CHECK_EQ(result.errors[1].location, Location{{3, 28}, {7, 9}});
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauPrecalculateMutatedFreeTypes2, true},
|
||||
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true},
|
||||
{FFlag::LuauBidirectionalInferenceUpcast, true},
|
||||
{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true},
|
||||
};
|
||||
|
||||
auto result = check((R"(
|
||||
local function getStatus(): string
|
||||
return "Yeah can you look in returned books?"
|
||||
end
|
||||
local function getPratchettStatus()
|
||||
return { isLate = true }
|
||||
end
|
||||
type Status = { isLate: boolean, daysLate: number? }
|
||||
local key1 = "Great Expecations"
|
||||
local key2 = "The Outsiders"
|
||||
local key3 = "Guards! Guards!"
|
||||
local books: { [string]: Status } = {
|
||||
[key1] = { isLate = true, daysLate = "coconut" },
|
||||
[key2] = getStatus(),
|
||||
[key3] = getPratchettStatus()
|
||||
}
|
||||
)"));
|
||||
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(err);
|
||||
// NOTE: This is because the inferred keys of `books` are all primitive types.
|
||||
CHECK_EQ(toString(err->givenType), "{ [string | string | string]: string | { daysLate: string, isLate: boolean } | { isLate: boolean } }");
|
||||
CHECK_EQ(toString(err->wantedType), "{ [string]: Status }");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
|
@ -5572,5 +5652,43 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
|
|||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
|
||||
// It's kind of suspect that we allow multiple definitions of keys in
|
||||
// a single table.
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
type F = {
|
||||
_G: () -> ()
|
||||
}
|
||||
function _()
|
||||
return
|
||||
end
|
||||
local function h(f: F) end
|
||||
h({
|
||||
_G = {},
|
||||
_G = _,
|
||||
})
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assign")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauBidirectionalFailsafe, true};
|
||||
// This has a bunch of errors, we really just need it to not crash / assert.
|
||||
std::ignore = check(R"(
|
||||
--!strict
|
||||
local _ = 7143424
|
||||
_[
|
||||
setfenv(
|
||||
...,
|
||||
{
|
||||
n0 = _,
|
||||
}
|
||||
)
|
||||
] *= _
|
||||
)");
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -30,6 +30,7 @@ LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments)
|
|||
LUAU_FASTFLAG(LuauUnifyMetatableWithAny)
|
||||
LUAU_FASTFLAG(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauImproveTypePathsInErrors)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1862,7 +1863,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any")
|
|||
end,
|
||||
}
|
||||
)"));
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret")
|
||||
|
@ -1937,14 +1937,14 @@ end
|
|||
TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
ScopedFastFlag fixNumberConcats{FFlag::LuauTypeCheckerAcceptNumberConcats, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local function foo(n : number): string return "" end
|
||||
local function bar(n: number, m: string) end
|
||||
local function concat_stuff(x, y)
|
||||
local z = foo(x)
|
||||
bar(y, z)
|
||||
local function concat_stuff(x: string, y : string | number)
|
||||
return x .. y
|
||||
end
|
||||
)"));
|
||||
}
|
||||
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
Loading…
Add table
Reference in a new issue