mirror of
https://github.com/luau-lang/luau.git
synced 2025-04-01 17:30:53 +01:00
Sync to upstream/release/667 (#1754)
After a very auspicious release last week, we have a new bevy of changes for you! ## What's Changed ### Deprecated Attribute This release includes an implementation of the `@deprecated` attribute proposed in [this RFC](https://rfcs.luau.org/syntax-attribute-functions-deprecated.html). It relies on the new type solver to propagate deprecation information from function and method AST nodes to the corresponding type objects. These objects are queried by a linter pass when it encounters local, global, or indexed variables, to issue deprecation warnings. Uses of deprecated functions and methods in recursion are ignored. To support deprecation of class methods, the parser has been extended to allow attribute declarations on class methods. The implementation does not support parameters, so it is not currently possible for users to customize deprecation messages. ### General - Add a limit for normalization of function types. ### New Type Solver - Fix type checker to accept numbers as concat operands (Fixes #1671). - Fix user-defined type functions failing when used inside type aliases/nested calls (Fixes #1738, Fixes #1679). - Improve constraint generation for overloaded functions (in part thanks to @vvatheus in #1694). - Improve type inference for indexers on table literals, especially when passing table literals directly as a function call argument. - Equate regular error type and intersection with a negation of an error type. - Avoid swapping types in 2-part union when RHS is optional. - Use simplification when doing `~nil` refinements. - `len<>` now works on metatables without `__len` function. ### AST - Retain source information for `AstTypeUnion` and `AstTypeIntersection`. ### Transpiler - Print attributes on functions. ### Parser - Allow types in indexers to begin with string literals by @jackdotink in #1750. ### Autocomplete - Evaluate user-defined type functions in ill-formed source code to provide autocomplete. - Fix the start location of functions that have attributes. - Implement better fragment selection. ### Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Sora Kanosue <skanosue@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> **Full Changelog**: https://github.com/luau-lang/luau/compare/0.666...0.667 --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
This commit is contained in:
parent
12dac2f1f4
commit
6b33251b89
59 changed files with 3962 additions and 2750 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement)
|
|||
LUAU_FASTFLAGVARIABLE(LuauExtraFollows)
|
||||
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
|
||||
|
||||
LUAU_FASTFLAG(LuauDeprecatedAttribute)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -548,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);
|
||||
}
|
||||
|
@ -1358,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
|
||||
|
@ -1395,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,
|
||||
|
@ -1418,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;
|
||||
}
|
||||
|
@ -1459,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;
|
||||
|
@ -1467,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,
|
||||
|
@ -1982,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)
|
||||
|
@ -2161,9 +2196,9 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
else
|
||||
{
|
||||
std::vector<std::optional<Luau::TypeId>> expectedTypes = {};
|
||||
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
|
||||
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size())
|
||||
{
|
||||
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + i, expectedTypesForCall.end());
|
||||
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end());
|
||||
}
|
||||
auto [tp, refis] = checkPack(scope, arg, expectedTypes);
|
||||
argTail = tp;
|
||||
|
@ -2425,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;
|
||||
|
||||
|
@ -2434,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);
|
||||
|
@ -2454,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.
|
||||
|
@ -3583,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]
|
||||
|
@ -229,7 +231,7 @@ private:
|
|||
};
|
||||
|
||||
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
// Remove with FFlagLuauStoreCSTData
|
||||
// Remove with FFlagLuauStoreCSTData2
|
||||
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
|
||||
|
||||
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
|
||||
|
|
|
@ -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,13 +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
|
||||
{
|
||||
|
@ -2542,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");
|
||||
|
@ -3820,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
|
||||
|
@ -3881,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
|
||||
|
@ -3930,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
|
||||
|
@ -3937,6 +3976,7 @@ 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")
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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