mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/636 (#1346)
# What's Changed? - Telemetry support for usage of any type in old/new solver - Bug fixes and flag removals with the new solver ## New Solver - Fixed constraint ordering bug to infer types more accurately - Improved inferring a call to `setmetatable()` ## VM - Restored global metatable lookup for `typeof` on lightuserdata to fix unintentional API change (Fixes #1335) --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Dibri Nsofor <dnsofor@roblox.com> Co-authored-by: Jeremy Yoo <jyoo@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
a80abdb9b3
commit
5e0779fd57
131
Analysis/include/Luau/AnyTypeSummary.h
Normal file
131
Analysis/include/Luau/AnyTypeSummary.h
Normal file
@ -0,0 +1,131 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
class AstStat;
|
||||
class ParseError;
|
||||
struct TypeError;
|
||||
struct LintWarning;
|
||||
struct GlobalTypes;
|
||||
struct ModuleResolver;
|
||||
struct ParseResult;
|
||||
struct DcrLogger;
|
||||
|
||||
struct TelemetryTypePair
|
||||
{
|
||||
std::string annotatedType;
|
||||
std::string inferredType;
|
||||
};
|
||||
|
||||
struct AnyTypeSummary
|
||||
{
|
||||
TypeArena arena;
|
||||
|
||||
DenseHashSet<TypeId> seenTypeFamilyInstances{nullptr};
|
||||
|
||||
int recursionCount = 0;
|
||||
|
||||
std::string root;
|
||||
int strictCount = 0;
|
||||
|
||||
DenseHashMap<const void*, bool> seen{nullptr};
|
||||
|
||||
AnyTypeSummary();
|
||||
|
||||
void traverse(Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
std::pair<bool, TypeId> checkForAnyCast(Scope* scope, AstExprTypeAssertion* expr);
|
||||
|
||||
// Todo: errors resolved by anys
|
||||
void reportError(Location location, TypeErrorData err);
|
||||
|
||||
bool containsAny(TypePackId typ);
|
||||
bool containsAny(TypeId typ);
|
||||
|
||||
bool isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
bool hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
bool hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
TypeId checkForFamilyInhabitance(TypeId instance, Location location);
|
||||
TypeId lookupType(AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
TypePackId reconstructTypePack(AstArray<AstExpr*> exprs, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
|
||||
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};
|
||||
TypeId lookupAnnotation(AstType* annotation, Module* module, NotNull<BuiltinTypes> builtintypes);
|
||||
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation, Module* module);
|
||||
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
|
||||
|
||||
enum Pattern : uint64_t
|
||||
{
|
||||
Casts,
|
||||
FuncArg,
|
||||
FuncRet,
|
||||
FuncApp,
|
||||
VarAnnot,
|
||||
VarAny,
|
||||
TableProp,
|
||||
Alias,
|
||||
Assign
|
||||
};
|
||||
|
||||
struct TypeInfo
|
||||
{
|
||||
Pattern code;
|
||||
std::string node;
|
||||
TelemetryTypePair type;
|
||||
std::string debug;
|
||||
|
||||
explicit TypeInfo(Pattern code, std::string node, TelemetryTypePair type);
|
||||
};
|
||||
|
||||
std::vector<TypeInfo> typeInfo;
|
||||
|
||||
/**
|
||||
* Fabricates a scope that is a child of another scope.
|
||||
* @param node the lexical node that the scope belongs to.
|
||||
* @param parent the parent scope of the new scope. Must not be null.
|
||||
*/
|
||||
Scope* childScope(AstNode* node, const Scope* parent);
|
||||
|
||||
Scope* findInnerMostScope(Location location, Module* module);
|
||||
|
||||
void visit(Scope* scope, AstStat* stat, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatBlock* block, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatLocal* local, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatFor* for_, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
void visit(Scope* scope, AstStatError* error, Module* module, NotNull<BuiltinTypes> builtinTypes);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
@ -61,6 +61,22 @@ private:
|
||||
AstLocal* local = nullptr;
|
||||
};
|
||||
|
||||
struct FindFullAncestry final : public AstVisitor
|
||||
{
|
||||
std::vector<AstNode*> nodes;
|
||||
Position pos;
|
||||
Position documentEnd;
|
||||
bool includeTypes = false;
|
||||
|
||||
explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false);
|
||||
|
||||
bool visit(AstType* type) override;
|
||||
|
||||
bool visit(AstStatFunction* node) override;
|
||||
|
||||
bool visit(AstNode* node) override;
|
||||
};
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos);
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos);
|
||||
std::vector<AstNode*> findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@ -31,6 +32,7 @@ struct ParseResult;
|
||||
struct HotComment;
|
||||
struct BuildQueueItem;
|
||||
struct FrontendCancellationToken;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
struct LoadDefinitionFileResult
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Luau/ParseResult.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -18,6 +19,7 @@ namespace Luau
|
||||
{
|
||||
|
||||
struct Module;
|
||||
struct AnyTypeSummary;
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
using ModulePtr = std::shared_ptr<Module>;
|
||||
@ -71,6 +73,10 @@ struct Module
|
||||
TypeArena interfaceTypes;
|
||||
TypeArena internalTypes;
|
||||
|
||||
// Summary of Ast Nodes that either contain
|
||||
// user annotated anys or typechecker inferred anys
|
||||
AnyTypeSummary ats{};
|
||||
|
||||
// Scopes and AST types refer to parse data, so we need to keep that alive
|
||||
std::shared_ptr<Allocator> allocator;
|
||||
std::shared_ptr<AstNameTable> names;
|
||||
|
@ -59,6 +59,7 @@ public:
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
iterator erase(const_iterator it);
|
||||
void erase(TypeId ty);
|
||||
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
|
820
Analysis/src/AnyTypeSummary.cpp
Normal file
820
Analysis/src/AnyTypeSummary.cpp
Normal file
@ -0,0 +1,820 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/ConstraintGenerator.h"
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Transpiler.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeOrPack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(StudioReportLuauAny, false);
|
||||
LUAU_FASTINTVARIABLE(LuauAnySummaryRecursionLimit, 300);
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
// TODO: instead of pair just type for solver? generated type
|
||||
// TODO: see lookupAnnotation in typechecker2. is cleaner than resolvetype
|
||||
// or delay containsAny() check and do not return pair.
|
||||
// quick flag in typeid saying was annotation or inferred, would be solid
|
||||
std::optional<TypeOrPack> getInferredType(AstExpr* expr, Module* module)
|
||||
{
|
||||
std::optional<TypeOrPack> inferredType;
|
||||
|
||||
if (module->astTypePacks.contains(expr))
|
||||
{
|
||||
inferredType = *module->astTypePacks.find(expr);
|
||||
}
|
||||
else if (module->astTypes.contains(expr))
|
||||
{
|
||||
inferredType = *module->astTypes.find(expr);
|
||||
}
|
||||
|
||||
return inferredType;
|
||||
}
|
||||
|
||||
void AnyTypeSummary::traverse(Module* module, AstStat* src, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
Scope* scope = findInnerMostScope(src->location, module);
|
||||
visit(scope, src, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStat* stat, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit};
|
||||
|
||||
if (auto s = stat->as<AstStatBlock>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto i = stat->as<AstStatIf>())
|
||||
return visit(scope, i, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatWhile>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatRepeat>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto r = stat->as<AstStatReturn>())
|
||||
return visit(scope, r, module, builtinTypes);
|
||||
else if (auto e = stat->as<AstStatExpr>())
|
||||
return visit(scope, e, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatLocal>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatFor>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatForIn>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatAssign>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatCompoundAssign>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto f = stat->as<AstStatFunction>())
|
||||
return visit(scope, f, module, builtinTypes);
|
||||
else if (auto f = stat->as<AstStatLocalFunction>())
|
||||
return visit(scope, f, module, builtinTypes);
|
||||
else if (auto a = stat->as<AstStatTypeAlias>())
|
||||
return visit(scope, a, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareGlobal>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareFunction>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatDeclareClass>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
else if (auto s = stat->as<AstStatError>())
|
||||
return visit(scope, s, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatBlock* block, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
|
||||
return; // don't report
|
||||
|
||||
for (AstStat* stat : block->body)
|
||||
visit(scope, stat, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (ifStatement->thenbody)
|
||||
{
|
||||
Scope* thenScope = findInnerMostScope(ifStatement->thenbody->location, module);
|
||||
visit(thenScope, ifStatement->thenbody, module, builtinTypes);
|
||||
}
|
||||
|
||||
if (ifStatement->elsebody)
|
||||
{
|
||||
Scope* elseScope = findInnerMostScope(ifStatement->elsebody->location, module);
|
||||
visit(elseScope, ifStatement->elsebody, module, builtinTypes);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
Scope* whileScope = findInnerMostScope(while_->location, module);
|
||||
visit(whileScope, while_->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
Scope* repeatScope = findInnerMostScope(repeat->location, module);
|
||||
visit(repeatScope, repeat->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
// Scope* outScope = findOuterScope(ret->location, module);
|
||||
Scope* retScope = findInnerMostScope(ret->location, module);
|
||||
|
||||
for (auto val : ret->list)
|
||||
{
|
||||
if (isAnyCall(retScope, val, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(val, module, builtinTypes));
|
||||
TypeInfo ti{Pattern::FuncApp, toString(ret), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(retScope, val, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = val->as<AstExprTypeAssertion>())
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
auto inf = getInferredType(cast->expr, module);
|
||||
if (inf)
|
||||
types.inferredType = toString(*inf);
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(ret), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatLocal* local, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TypePackId values = reconstructTypePack(local->values, module, builtinTypes);
|
||||
auto [head, tail] = flatten(values);
|
||||
|
||||
size_t posn = 0;
|
||||
for (AstLocal* loc : local->vars)
|
||||
{
|
||||
if (local->vars.data[0] == loc && posn < local->values.size)
|
||||
{
|
||||
if (loc->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
|
||||
auto inf = getInferredType(local->values.data[posn], module);
|
||||
if (inf)
|
||||
types.inferredType = toString(*inf);
|
||||
|
||||
TypeInfo ti{Pattern::VarAnnot, toString(local), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::min(local->values.size - 1, posn) < head.size())
|
||||
{
|
||||
if (loc->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(loc->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
types.inferredType = toString(head[std::min(local->values.size - 1, posn)]);
|
||||
|
||||
TypeInfo ti{Pattern::VarAnnot, toString(local), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tail)
|
||||
{
|
||||
if (containsAny(*tail))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(*tail);
|
||||
|
||||
TypeInfo ti{Pattern::VarAny, toString(local), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++posn;
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatFor* for_, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
Scope* forScope = findInnerMostScope(for_->location, module);
|
||||
visit(forScope, for_->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
Scope* loopScope = findInnerMostScope(forIn->location, module);
|
||||
visit(loopScope, forIn->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TypePackId values = reconstructTypePack(assign->values, module, builtinTypes);
|
||||
auto [head, tail] = flatten(values);
|
||||
|
||||
size_t posn = 0;
|
||||
for (AstExpr* var : assign->vars)
|
||||
{
|
||||
TypeId tp = lookupType(var, module, builtinTypes);
|
||||
if (containsAny(tp))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(tp);
|
||||
|
||||
auto loc = std::min(assign->vars.size - 1, posn);
|
||||
if (head.size() >= assign->vars.size)
|
||||
{
|
||||
types.inferredType = toString(head[posn]);
|
||||
}
|
||||
else if (loc < head.size())
|
||||
types.inferredType = toString(head[loc]);
|
||||
else
|
||||
types.inferredType = toString(builtinTypes->nilType);
|
||||
|
||||
TypeInfo ti{Pattern::Assign, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
++posn;
|
||||
}
|
||||
|
||||
for (AstExpr* val : assign->values)
|
||||
{
|
||||
if (isAnyCall(scope, val, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
auto inf = getInferredType(val, module);
|
||||
if (inf)
|
||||
types.inferredType = toString(*inf);
|
||||
|
||||
TypeInfo ti{Pattern::FuncApp, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(scope, val, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = val->as<AstExprTypeAssertion>())
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
auto inf = getInferredType(val, module);
|
||||
if (inf)
|
||||
types.inferredType = toString(*inf);
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (containsAny(*tail))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(*tail);
|
||||
|
||||
TypeInfo ti{Pattern::Assign, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(lookupType(assign->value, module, builtinTypes));
|
||||
types.annotatedType = toString(lookupType(assign->var, module, builtinTypes));
|
||||
|
||||
if (module->astTypes.contains(assign->var))
|
||||
{
|
||||
if (containsAny(*module->astTypes.find(assign->var)))
|
||||
{
|
||||
TypeInfo ti{Pattern::Assign, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
else if (module->astTypePacks.contains(assign->var))
|
||||
{
|
||||
if (containsAny(*module->astTypePacks.find(assign->var)))
|
||||
{
|
||||
TypeInfo ti{Pattern::Assign, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAnyCall(scope, assign->value, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncApp, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (isAnyCast(scope, assign->value, module, builtinTypes))
|
||||
{
|
||||
if (auto cast = assign->value->as<AstExprTypeAssertion>())
|
||||
{
|
||||
types.annotatedType = toString(lookupAnnotation(cast->annotation, module, builtinTypes));
|
||||
auto inf = getInferredType(cast->expr, module);
|
||||
if (inf)
|
||||
types.inferredType = toString(*inf);
|
||||
|
||||
TypeInfo ti{Pattern::Casts, toString(assign), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
|
||||
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::VarAny, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasArgAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncArg, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasAnyReturns(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncRet, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (function->func->body->body.size > 0)
|
||||
visit(scope, function->func->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
types.inferredType = toString(lookupType(function->func, module, builtinTypes));
|
||||
|
||||
if (hasVariadicAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::VarAny, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasArgAnys(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncArg, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (hasAnyReturns(scope, function->func, module, builtinTypes))
|
||||
{
|
||||
TypeInfo ti{Pattern::FuncRet, toString(function), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
|
||||
if (function->func->body->body.size > 0)
|
||||
visit(scope, function->func->body, module, builtinTypes);
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
|
||||
auto annot = lookupAnnotation(alias->type, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
// no expr => no inference for aliases
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.annotatedType = toString(annot);
|
||||
TypeInfo ti{Pattern::Alias, toString(alias), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (isAnyCall(scope, expr->expr, module, builtinTypes))
|
||||
{
|
||||
TelemetryTypePair types;
|
||||
|
||||
types.inferredType = toString(lookupType(expr->expr, module, builtinTypes));
|
||||
|
||||
TypeInfo ti{Pattern::FuncApp, toString(expr), types};
|
||||
typeInfo.push_back(ti);
|
||||
}
|
||||
}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
void AnyTypeSummary::visit(Scope* scope, AstStatError* error, Module* module, NotNull<BuiltinTypes> builtinTypes) {}
|
||||
|
||||
TypeId AnyTypeSummary::checkForFamilyInhabitance(TypeId instance, Location location)
|
||||
{
|
||||
if (seenTypeFamilyInstances.find(instance))
|
||||
return instance;
|
||||
|
||||
seenTypeFamilyInstances.insert(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::lookupType(AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
TypeId* ty = module->astTypes.find(expr);
|
||||
if (ty)
|
||||
return checkForFamilyInhabitance(follow(*ty), expr->location);
|
||||
|
||||
TypePackId* tp = module->astTypePacks.find(expr);
|
||||
if (tp)
|
||||
{
|
||||
if (auto fst = first(*tp, /*ignoreHiddenVariadics*/ false))
|
||||
return checkForFamilyInhabitance(*fst, expr->location);
|
||||
else if (finite(*tp) && size(*tp) == 0)
|
||||
return checkForFamilyInhabitance(builtinTypes->nilType, expr->location);
|
||||
}
|
||||
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
TypePackId AnyTypeSummary::reconstructTypePack(AstArray<AstExpr*> exprs, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (exprs.size == 0)
|
||||
return arena.addTypePack(TypePack{{}, std::nullopt});
|
||||
|
||||
std::vector<TypeId> head;
|
||||
|
||||
for (size_t i = 0; i < exprs.size - 1; ++i)
|
||||
{
|
||||
head.push_back(lookupType(exprs.data[i], module, builtinTypes));
|
||||
}
|
||||
|
||||
TypePackId* tail = module->astTypePacks.find(exprs.data[exprs.size - 1]);
|
||||
if (tail)
|
||||
return arena.addTypePack(TypePack{std::move(head), follow(*tail)});
|
||||
else
|
||||
return arena.addTypePack(TypePack{std::move(head), builtinTypes->errorRecoveryTypePack()});
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (auto call = expr->as<AstExprCall>())
|
||||
{
|
||||
TypePackId args = reconstructTypePack(call->args, module, builtinTypes);
|
||||
if (containsAny(args))
|
||||
return true;
|
||||
|
||||
TypeId func = lookupType(call->func, module, builtinTypes);
|
||||
if (containsAny(func))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (expr->vararg && expr->varargAnnotation)
|
||||
{
|
||||
auto annot = lookupPackAnnotation(expr->varargAnnotation, module);
|
||||
if (annot && containsAny(*annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (expr->args.size > 0)
|
||||
{
|
||||
for (const AstLocal* arg : expr->args)
|
||||
{
|
||||
if (arg->annotation)
|
||||
{
|
||||
auto annot = lookupAnnotation(arg->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (!expr->returnAnnotation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (AstType* ret : expr->returnAnnotation->types)
|
||||
{
|
||||
if (containsAny(lookupAnnotation(ret, module, builtinTypes)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (expr->returnAnnotation->tailType)
|
||||
{
|
||||
auto annot = lookupPackAnnotation(expr->returnAnnotation->tailType, module);
|
||||
if (annot && containsAny(*annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::isAnyCast(Scope* scope, AstExpr* expr, Module* module, NotNull<BuiltinTypes> builtinTypes)
|
||||
{
|
||||
if (auto cast = expr->as<AstExprTypeAssertion>())
|
||||
{
|
||||
auto annot = lookupAnnotation(cast->annotation, module, builtinTypes);
|
||||
if (containsAny(annot))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, Module* module, NotNull<BuiltinTypes> builtintypes)
|
||||
{
|
||||
if (FFlag::DebugLuauMagicTypes)
|
||||
{
|
||||
if (auto ref = annotation->as<AstTypeReference>(); ref && ref->parameters.size > 0)
|
||||
{
|
||||
if (auto ann = ref->parameters.data[0].type)
|
||||
{
|
||||
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type, module, builtintypes);
|
||||
return follow(argTy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeId* ty = module->astResolvedTypes.find(annotation);
|
||||
if (ty)
|
||||
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
|
||||
else
|
||||
return checkForTypeFunctionInhabitance(builtintypes->errorRecoveryType(), annotation->location);
|
||||
}
|
||||
|
||||
TypeId AnyTypeSummary::checkForTypeFunctionInhabitance(TypeId instance, Location location)
|
||||
{
|
||||
if (seenTypeFunctionInstances.find(instance))
|
||||
return instance;
|
||||
seenTypeFunctionInstances.insert(instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> AnyTypeSummary::lookupPackAnnotation(AstTypePack* annotation, Module* module)
|
||||
{
|
||||
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
|
||||
if (tp != nullptr)
|
||||
return {follow(*tp)};
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::containsAny(TypeId typ)
|
||||
{
|
||||
typ = follow(typ);
|
||||
|
||||
if (auto t = seen.find(typ); t && !*t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[typ] = false;
|
||||
|
||||
RecursionCounter counter{&recursionCount};
|
||||
if (recursionCount >= FInt::LuauAnySummaryRecursionLimit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (auto ty = get<AnyType>(typ))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else if (auto ty = get<UnknownType>(typ))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else if (auto ty = get<TableType>(typ))
|
||||
{
|
||||
for (auto& [_name, prop] : ty->props)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto newT = follow(prop.readTy))
|
||||
{
|
||||
if (containsAny(*newT))
|
||||
found = true;
|
||||
}
|
||||
else if (auto newT = follow(prop.writeTy))
|
||||
{
|
||||
if (containsAny(*newT))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (containsAny(prop.type()))
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<IntersectionType>(typ))
|
||||
{
|
||||
for (auto part : ty->parts)
|
||||
{
|
||||
if (containsAny(part))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<UnionType>(typ))
|
||||
{
|
||||
for (auto option : ty->options)
|
||||
{
|
||||
if (containsAny(option))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto ty = get<FunctionType>(typ))
|
||||
{
|
||||
if (containsAny(ty->argTypes))
|
||||
found = true;
|
||||
else if (containsAny(ty->retTypes))
|
||||
found = true;
|
||||
}
|
||||
|
||||
seen[typ] = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool AnyTypeSummary::containsAny(TypePackId typ)
|
||||
{
|
||||
typ = follow(typ);
|
||||
|
||||
if (auto t = seen.find(typ); t && !*t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
seen[typ] = false;
|
||||
|
||||
auto [head, tail] = flatten(typ);
|
||||
bool found = false;
|
||||
|
||||
for (auto tp : head)
|
||||
{
|
||||
if (containsAny(tp))
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (tail)
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(tail))
|
||||
{
|
||||
if (auto ty = get<AnyType>(follow(vtp->ty)))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
else if (auto tftp = get<TypeFunctionInstanceTypePack>(tail))
|
||||
{
|
||||
|
||||
for (TypePackId tp : tftp->packArguments)
|
||||
{
|
||||
if (containsAny(tp))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (TypeId t : tftp->typeArguments)
|
||||
{
|
||||
if (containsAny(t))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seen[typ] = found;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
Scope* AnyTypeSummary::findInnerMostScope(Location location, Module* module)
|
||||
{
|
||||
Scope* bestScope = module->getModuleScope().get();
|
||||
|
||||
bool didNarrow = false;
|
||||
do
|
||||
{
|
||||
didNarrow = false;
|
||||
for (auto scope : bestScope->children)
|
||||
{
|
||||
if (scope->location.encloses(location))
|
||||
{
|
||||
bestScope = scope.get();
|
||||
didNarrow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (didNarrow && bestScope->children.size() > 0);
|
||||
|
||||
return bestScope;
|
||||
}
|
||||
|
||||
AnyTypeSummary::TypeInfo::TypeInfo(Pattern code, std::string node, TelemetryTypePair type)
|
||||
: code(code)
|
||||
, node(node)
|
||||
, type(type)
|
||||
{
|
||||
}
|
||||
|
||||
AnyTypeSummary::AnyTypeSummary() {}
|
||||
|
||||
} // namespace Luau
|
@ -177,21 +177,16 @@ struct FindNode : public AstVisitor
|
||||
}
|
||||
};
|
||||
|
||||
struct FindFullAncestry final : public AstVisitor
|
||||
{
|
||||
std::vector<AstNode*> nodes;
|
||||
Position pos;
|
||||
Position documentEnd;
|
||||
bool includeTypes = false;
|
||||
} // namespace
|
||||
|
||||
explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false)
|
||||
FindFullAncestry::FindFullAncestry(Position pos, Position documentEnd, bool includeTypes)
|
||||
: pos(pos)
|
||||
, documentEnd(documentEnd)
|
||||
, includeTypes(includeTypes)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstType* type) override
|
||||
bool FindFullAncestry::visit(AstType* type)
|
||||
{
|
||||
if (includeTypes)
|
||||
return visit(static_cast<AstNode*>(type));
|
||||
@ -199,7 +194,7 @@ struct FindFullAncestry final : public AstVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstStatFunction* node) override
|
||||
bool FindFullAncestry::visit(AstStatFunction* node)
|
||||
{
|
||||
visit(static_cast<AstNode*>(node));
|
||||
if (node->name->location.contains(pos))
|
||||
@ -209,7 +204,7 @@ struct FindFullAncestry final : public AstVisitor
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(AstNode* node) override
|
||||
bool FindFullAncestry::visit(AstNode* node)
|
||||
{
|
||||
if (node->location.contains(pos))
|
||||
{
|
||||
@ -228,9 +223,6 @@ struct FindFullAncestry final : public AstVisitor
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<AstNode*> findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos)
|
||||
{
|
||||
|
@ -29,7 +29,6 @@
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
|
||||
|
||||
namespace Luau
|
||||
@ -757,7 +756,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
||||
scope->lvalueTypes[def] = assignee;
|
||||
}
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp;
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
if (hasAnnotation)
|
||||
{
|
||||
@ -791,6 +792,12 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
||||
|
||||
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack});
|
||||
|
||||
forEachConstraint(start, end, this,
|
||||
[&uc](const ConstraintPtr& runBefore)
|
||||
{
|
||||
uc->dependencies.push_back(NotNull{runBefore.get()});
|
||||
});
|
||||
|
||||
for (TypeId t : valueTypes)
|
||||
getMutable<BlockedType>(t)->setOwner(uc);
|
||||
}
|
||||
@ -981,7 +988,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
||||
scope->bindings[function->name] = Binding{functionType, function->name->location};
|
||||
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location};
|
||||
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
if (sigFullyDefined)
|
||||
@ -1516,7 +1523,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
|
||||
|
||||
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
|
||||
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
||||
ftv->isCheckedFunction = FFlag::LuauAttributeSyntax ? global->isCheckedFunction() : false;
|
||||
ftv->isCheckedFunction = global->isCheckedFunction();
|
||||
|
||||
ftv->argNames.reserve(global->paramNames.size);
|
||||
for (const auto& el : global->paramNames)
|
||||
@ -1759,6 +1766,12 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
||||
scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment
|
||||
scope->rvalueRefinements[def] = resultTy; // TODO: typestates: track this as an assignment
|
||||
|
||||
// HACK: If we have a targetLocal, it has already been added to the
|
||||
// inferredBindings table. We want to replace it so that we don't
|
||||
// infer a weird union like tbl | { @metatable something, tbl }
|
||||
if (InferredBinding* ib = inferredBindings.find(targetLocal->local))
|
||||
ib->types.erase(target);
|
||||
|
||||
recordInferredBinding(targetLocal->local, resultTy);
|
||||
}
|
||||
|
||||
@ -2932,7 +2945,7 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
// TODO: FunctionType needs a pointer to the scope so that we know
|
||||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, scope.get(), {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = FFlag::LuauAttributeSyntax ? fn->isCheckedFunction() : false;
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
|
@ -1,231 +1,9 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions2, false);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
|
||||
|
||||
declare buffer: {
|
||||
create: (size: number) -> buffer,
|
||||
fromstring: (str: string) -> buffer,
|
||||
tostring: (b: buffer) -> string,
|
||||
len: (b: buffer) -> number,
|
||||
copy: (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
|
||||
fill: (b: buffer, offset: number, value: number, count: number?) -> (),
|
||||
readi8: (b: buffer, offset: number) -> number,
|
||||
readu8: (b: buffer, offset: number) -> number,
|
||||
readi16: (b: buffer, offset: number) -> number,
|
||||
readu16: (b: buffer, offset: number) -> number,
|
||||
readi32: (b: buffer, offset: number) -> number,
|
||||
readu32: (b: buffer, offset: number) -> number,
|
||||
readf32: (b: buffer, offset: number) -> number,
|
||||
readf64: (b: buffer, offset: number) -> number,
|
||||
writei8: (b: buffer, offset: number, value: number) -> (),
|
||||
writeu8: (b: buffer, offset: number, value: number) -> (),
|
||||
writei16: (b: buffer, offset: number, value: number) -> (),
|
||||
writeu16: (b: buffer, offset: number, value: number) -> (),
|
||||
writei32: (b: buffer, offset: number, value: number) -> (),
|
||||
writeu32: (b: buffer, offset: number, value: number) -> (),
|
||||
writef32: (b: buffer, offset: number, value: number) -> (),
|
||||
writef64: (b: buffer, offset: number, value: number) -> (),
|
||||
readstring: (b: buffer, offset: number, count: number) -> string,
|
||||
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
|
||||
}
|
||||
|
||||
declare bit32: {
|
||||
band: (...number) -> number,
|
||||
bor: (...number) -> number,
|
||||
bxor: (...number) -> number,
|
||||
btest: (number, ...number) -> boolean,
|
||||
rrotate: (x: number, disp: number) -> number,
|
||||
lrotate: (x: number, disp: number) -> number,
|
||||
lshift: (x: number, disp: number) -> number,
|
||||
arshift: (x: number, disp: number) -> number,
|
||||
rshift: (x: number, disp: number) -> number,
|
||||
bnot: (x: number) -> number,
|
||||
extract: (n: number, field: number, width: number?) -> number,
|
||||
replace: (n: number, v: number, field: number, width: number?) -> number,
|
||||
countlz: (n: number) -> number,
|
||||
countrz: (n: number) -> number,
|
||||
byteswap: (n: number) -> number,
|
||||
}
|
||||
|
||||
declare math: {
|
||||
frexp: (n: number) -> (number, number),
|
||||
ldexp: (s: number, e: number) -> number,
|
||||
fmod: (x: number, y: number) -> number,
|
||||
modf: (n: number) -> (number, number),
|
||||
pow: (x: number, y: number) -> number,
|
||||
exp: (n: number) -> number,
|
||||
|
||||
ceil: (n: number) -> number,
|
||||
floor: (n: number) -> number,
|
||||
abs: (n: number) -> number,
|
||||
sqrt: (n: number) -> number,
|
||||
|
||||
log: (n: number, base: number?) -> number,
|
||||
log10: (n: number) -> number,
|
||||
|
||||
rad: (n: number) -> number,
|
||||
deg: (n: number) -> number,
|
||||
|
||||
sin: (n: number) -> number,
|
||||
cos: (n: number) -> number,
|
||||
tan: (n: number) -> number,
|
||||
sinh: (n: number) -> number,
|
||||
cosh: (n: number) -> number,
|
||||
tanh: (n: number) -> number,
|
||||
atan: (n: number) -> number,
|
||||
acos: (n: number) -> number,
|
||||
asin: (n: number) -> number,
|
||||
atan2: (y: number, x: number) -> number,
|
||||
|
||||
min: (number, ...number) -> number,
|
||||
max: (number, ...number) -> number,
|
||||
|
||||
pi: number,
|
||||
huge: number,
|
||||
|
||||
randomseed: (seed: number) -> (),
|
||||
random: (number?, number?) -> number,
|
||||
|
||||
sign: (n: number) -> number,
|
||||
clamp: (n: number, min: number, max: number) -> number,
|
||||
noise: (x: number, y: number?, z: number?) -> number,
|
||||
round: (n: number) -> number,
|
||||
}
|
||||
|
||||
type DateTypeArg = {
|
||||
year: number,
|
||||
month: number,
|
||||
day: number,
|
||||
hour: number?,
|
||||
min: number?,
|
||||
sec: number?,
|
||||
isdst: boolean?,
|
||||
}
|
||||
|
||||
type DateTypeResult = {
|
||||
year: number,
|
||||
month: number,
|
||||
wday: number,
|
||||
yday: number,
|
||||
day: number,
|
||||
hour: number,
|
||||
min: number,
|
||||
sec: number,
|
||||
isdst: boolean,
|
||||
}
|
||||
|
||||
declare os: {
|
||||
time: (time: DateTypeArg?) -> number,
|
||||
date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string),
|
||||
difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number,
|
||||
clock: () -> number,
|
||||
}
|
||||
|
||||
declare function require(target: any): any
|
||||
|
||||
declare function getfenv(target: any): { [string]: any }
|
||||
|
||||
declare _G: any
|
||||
declare _VERSION: string
|
||||
|
||||
declare function gcinfo(): number
|
||||
|
||||
declare function print<T...>(...: T...)
|
||||
|
||||
declare function type<T>(value: T): string
|
||||
declare function typeof<T>(value: T): string
|
||||
|
||||
-- `assert` has a magic function attached that will give more detailed type information
|
||||
declare function assert<T>(value: T, errorMessage: string?): T
|
||||
declare function error<T>(message: T, level: number?): never
|
||||
|
||||
declare function tostring<T>(value: T): string
|
||||
declare function tonumber<T>(value: T, radix: number?): number?
|
||||
|
||||
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
|
||||
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
|
||||
declare function rawset<K, V>(tab: {[K]: V}, k: K, v: V): {[K]: V}
|
||||
declare function rawlen<K, V>(obj: {[K]: V} | string): number
|
||||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
-- FIXME: The actual type of `xpcall` is:
|
||||
-- <E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...)
|
||||
-- Since we can't represent the return value, we use (boolean, R1...).
|
||||
declare function xpcall<E, A..., R1..., R2...>(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...)
|
||||
|
||||
-- `select` has a magic function attached to provide more detailed type information
|
||||
declare function select<A...>(i: string | number, ...: A...): ...any
|
||||
|
||||
-- FIXME: This type is not entirely correct - `loadstring` returns a function or
|
||||
-- (nil, string).
|
||||
declare function loadstring<A...>(src: string, chunkname: string?): (((A...) -> any)?, string?)
|
||||
|
||||
declare function newproxy(mt: boolean?): any
|
||||
|
||||
declare coroutine: {
|
||||
create: <A..., R...>(f: (A...) -> R...) -> thread,
|
||||
resume: <A..., R...>(co: thread, A...) -> (boolean, R...),
|
||||
running: () -> thread,
|
||||
status: (co: thread) -> "dead" | "running" | "normal" | "suspended",
|
||||
wrap: <A..., R...>(f: (A...) -> R...) -> ((A...) -> R...),
|
||||
yield: <A..., R...>(A...) -> R...,
|
||||
isyieldable: () -> boolean,
|
||||
close: (co: thread) -> (boolean, any)
|
||||
}
|
||||
|
||||
declare table: {
|
||||
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
|
||||
insert: (<V>(t: {V}, value: V) -> ()) & (<V>(t: {V}, pos: number, value: V) -> ()),
|
||||
maxn: <V>(t: {V}) -> number,
|
||||
remove: <V>(t: {V}, number?) -> V?,
|
||||
sort: <V>(t: {V}, comp: ((V, V) -> boolean)?) -> (),
|
||||
create: <V>(count: number, value: V?) -> {V},
|
||||
find: <V>(haystack: {V}, needle: V, init: number?) -> number?,
|
||||
|
||||
unpack: <V>(list: {V}, i: number?, j: number?) -> ...V,
|
||||
pack: <V>(...V) -> { n: number, [number]: V },
|
||||
|
||||
getn: <V>(t: {V}) -> number,
|
||||
foreach: <K, V>(t: {[K]: V}, f: (K, V) -> ()) -> (),
|
||||
foreachi: <V>({V}, (number, V) -> ()) -> (),
|
||||
|
||||
move: <V>(src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V},
|
||||
clear: <K, V>(table: {[K]: V}) -> (),
|
||||
|
||||
isfrozen: <K, V>(t: {[K]: V}) -> boolean,
|
||||
}
|
||||
|
||||
declare debug: {
|
||||
info: (<R...>(thread: thread, level: number, options: string) -> R...) & (<R...>(level: number, options: string) -> R...) & (<A..., R1..., R2...>(func: (A...) -> R1..., options: string) -> R2...),
|
||||
traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string),
|
||||
}
|
||||
|
||||
declare utf8: {
|
||||
char: (...number) -> string,
|
||||
charpattern: string,
|
||||
codes: (str: string) -> ((string, number) -> (number, number), string, number),
|
||||
codepoint: (str: string, i: number?, j: number?) -> ...number,
|
||||
len: (s: string, i: number?, j: number?) -> (number?, number?),
|
||||
offset: (s: string, n: number?, i: number?) -> number,
|
||||
}
|
||||
|
||||
-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype.
|
||||
declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
|
||||
|
||||
)BUILTIN_SRC";
|
||||
|
||||
static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC(
|
||||
|
||||
declare bit32: {
|
||||
@ -449,12 +227,7 @@ declare buffer: {
|
||||
|
||||
std::string getBuiltinDefinitionSource()
|
||||
{
|
||||
std::string result = kBuiltinDefinitionLuaSrc;
|
||||
|
||||
// Annotates each non generic function as checked
|
||||
if (FFlag::LuauCheckedEmbeddedDefinitions2 && FFlag::LuauAttributeSyntax)
|
||||
result = kBuiltinDefinitionLuaSrcChecked;
|
||||
|
||||
std::string result = kBuiltinDefinitionLuaSrcChecked;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Frontend.h"
|
||||
|
||||
#include "Luau/AnyTypeSummary.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
@ -10,13 +11,15 @@
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Transpiler.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/VisitType.h"
|
||||
@ -44,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSourceModuleUpdatedWithSelectedMode, false)
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -488,6 +493,20 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
|
||||
if (item.name == name)
|
||||
checkResult.lintResult = item.module->lintResult;
|
||||
|
||||
if (FFlag::StudioReportLuauAny && item.options.retainFullTypeGraphs)
|
||||
{
|
||||
if (item.module)
|
||||
{
|
||||
const SourceModule& sourceModule = *item.sourceModule;
|
||||
if (sourceModule.mode == Luau::Mode::Strict)
|
||||
{
|
||||
item.module->ats.root = toString(sourceModule.root);
|
||||
}
|
||||
|
||||
item.module->ats.traverse(item.module.get(), sourceModule.root, NotNull{&builtinTypes_});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
|
@ -16,7 +16,6 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax)
|
||||
LUAU_FASTFLAG(LuauAttribute)
|
||||
LUAU_FASTFLAG(LuauNativeAttribute)
|
||||
LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute, false)
|
||||
@ -2929,7 +2928,6 @@ static void lintComments(LintContext& context, const std::vector<HotComment>& ho
|
||||
|
||||
static bool hasNativeCommentDirective(const std::vector<HotComment>& hotcomments)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
LUAU_ASSERT(FFlag::LuauNativeAttribute);
|
||||
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
||||
|
||||
@ -2956,7 +2954,6 @@ struct LintRedundantNativeAttribute : AstVisitor
|
||||
public:
|
||||
LUAU_NOINLINE static void process(LintContext& context)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
LUAU_ASSERT(FFlag::LuauNativeAttribute);
|
||||
LUAU_ASSERT(FFlag::LintRedundantNativeAttribute);
|
||||
|
||||
@ -3071,7 +3068,7 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
|
||||
if (context.warningEnabled(LintWarning::Code_ComparisonPrecedence))
|
||||
LintComparisonPrecedence::process(context);
|
||||
|
||||
if (FFlag::LuauAttributeSyntax && FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute &&
|
||||
if (FFlag::LuauNativeAttribute && FFlag::LintRedundantNativeAttribute &&
|
||||
context.warningEnabled(LintWarning::Code_RedundantNativeAttribute))
|
||||
{
|
||||
if (hasNativeCommentDirective(hotcomments))
|
||||
|
@ -115,6 +115,15 @@ TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it)
|
||||
return order.erase(it);
|
||||
}
|
||||
|
||||
void TypeIds::erase(TypeId ty)
|
||||
{
|
||||
const_iterator it = std::find(order.begin(), order.end(), ty);
|
||||
if (it == order.end())
|
||||
return;
|
||||
|
||||
erase(it);
|
||||
}
|
||||
|
||||
size_t TypeIds::size() const
|
||||
{
|
||||
return order.size();
|
||||
|
@ -33,7 +33,6 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
|
||||
@ -350,20 +349,17 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
|
||||
else if (auto repeat = program.as<AstStatRepeat>())
|
||||
return check(scope, *repeat);
|
||||
else if (program.is<AstStatBreak>())
|
||||
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Breaks : ControlFlow::None;
|
||||
return ControlFlow::Breaks;
|
||||
else if (program.is<AstStatContinue>())
|
||||
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Continues : ControlFlow::None;
|
||||
return ControlFlow::Continues;
|
||||
else if (auto return_ = program.as<AstStatReturn>())
|
||||
return check(scope, *return_);
|
||||
else if (auto expr = program.as<AstStatExpr>())
|
||||
{
|
||||
checkExprPack(scope, *expr->expr);
|
||||
|
||||
if (FFlag::LuauTinyControlFlowAnalysis)
|
||||
{
|
||||
if (auto call = expr->expr->as<AstExprCall>(); call && doesCallError(call))
|
||||
return ControlFlow::Throws;
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -740,8 +736,6 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
|
||||
ScopePtr thenScope = childScope(scope, statement.thenbody->location);
|
||||
resolve(result.predicates, thenScope, true);
|
||||
|
||||
if (FFlag::LuauTinyControlFlowAnalysis)
|
||||
{
|
||||
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
|
||||
resolve(result.predicates, elseScope, false);
|
||||
|
||||
@ -755,27 +749,13 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement
|
||||
else if (thencf == ControlFlow::None && elsecf != ControlFlow::None)
|
||||
scope->inheritRefinements(thenScope);
|
||||
|
||||
if (FFlag::LuauTinyControlFlowAnalysis && thencf == elsecf)
|
||||
if (thencf == elsecf)
|
||||
return thencf;
|
||||
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
|
||||
return ControlFlow::Returns;
|
||||
else
|
||||
return ControlFlow::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
check(thenScope, *statement.thenbody);
|
||||
|
||||
if (statement.elsebody)
|
||||
{
|
||||
ScopePtr elseScope = childScope(scope, statement.elsebody->location);
|
||||
resolve(result.predicates, elseScope, false);
|
||||
check(elseScope, *statement.elsebody);
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Id>
|
||||
ErrorVec TypeChecker::canUnify_(Id subTy, Id superTy, const ScopePtr& scope, const Location& location)
|
||||
@ -906,12 +886,12 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatReturn& retur
|
||||
if (!errors.empty())
|
||||
currentModule->getModuleScope()->returnType = addTypePack({anyType});
|
||||
|
||||
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
|
||||
return ControlFlow::Returns;
|
||||
}
|
||||
|
||||
unify(retPack, scope->returnType, scope, return_.location, CountMismatch::Context::Return);
|
||||
|
||||
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
|
||||
return ControlFlow::Returns;
|
||||
}
|
||||
|
||||
template<typename Id>
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauNativeAttribute);
|
||||
|
||||
namespace Luau
|
||||
@ -764,8 +763,6 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
|
||||
|
||||
bool AstStatDeclareFunction::isCheckedFunction() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
for (const AstAttr* attr : attributes)
|
||||
{
|
||||
if (attr->type == AstAttr::Type::Checked)
|
||||
@ -901,8 +898,6 @@ void AstTypeFunction::visit(AstVisitor* visitor)
|
||||
|
||||
bool AstTypeFunction::isCheckedFunction() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
for (const AstAttr* attr : attributes)
|
||||
{
|
||||
if (attr->type == AstAttr::Type::Checked)
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLexerLookaheadRemembersBraceType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntax, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -201,7 +200,6 @@ std::string Lexeme::toString() const
|
||||
return "comment";
|
||||
|
||||
case Attribute:
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
return name ? format("'%s'", name) : "attribute";
|
||||
|
||||
case BrokenString:
|
||||
@ -1006,13 +1004,10 @@ Lexeme Lexer::readNext()
|
||||
return Lexeme(Location(start, 1), ch);
|
||||
}
|
||||
case '@':
|
||||
{
|
||||
if (FFlag::LuauAttributeSyntax)
|
||||
{
|
||||
std::pair<AstName, Lexeme::Type> attribute = readName();
|
||||
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
|
||||
}
|
||||
}
|
||||
default:
|
||||
if (isDigit(peekch()))
|
||||
{
|
||||
|
@ -17,8 +17,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
// flag so that we don't break production games by reverting syntax changes.
|
||||
// See docs/SyntaxChanges.md for an explanation.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLeadingBarAndAmpersand2, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeclarationExtraPropData, false)
|
||||
@ -321,7 +319,6 @@ AstStat* Parser::parseStat()
|
||||
case Lexeme::ReservedBreak:
|
||||
return parseBreak();
|
||||
case Lexeme::Attribute:
|
||||
if (FFlag::LuauAttributeSyntax)
|
||||
return parseAttributeStat();
|
||||
default:;
|
||||
}
|
||||
@ -692,8 +689,6 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
|
||||
|
||||
std::pair<bool, AstAttr::Type> Parser::validateAttribute(const char* attributeName, const TempVector<AstAttr*>& attributes)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
AstAttr::Type type;
|
||||
|
||||
// check if the attribute name is valid
|
||||
@ -739,8 +734,6 @@ std::pair<bool, AstAttr::Type> Parser::validateAttribute(const char* attributeNa
|
||||
// attribute ::= '@' NAME
|
||||
void Parser::parseAttribute(TempVector<AstAttr*>& attributes)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute);
|
||||
|
||||
Location loc = lexer.current().location;
|
||||
@ -757,8 +750,6 @@ void Parser::parseAttribute(TempVector<AstAttr*>& attributes)
|
||||
// attributes ::= {attribute}
|
||||
AstArray<AstAttr*> Parser::parseAttributes()
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
Lexeme::Type type = lexer.current().type;
|
||||
|
||||
LUAU_ASSERT(type == Lexeme::Attribute);
|
||||
@ -777,8 +768,6 @@ AstArray<AstAttr*> Parser::parseAttributes()
|
||||
// declare Name '{' Name ':' attributes `(' [parlist] `)' [`:` Type] '}'
|
||||
AstStat* Parser::parseAttributeStat()
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauAttributeSyntax);
|
||||
|
||||
AstArray<AstAttr*> attributes = parseAttributes();
|
||||
|
||||
Lexeme::Type type = lexer.current().type;
|
||||
@ -834,7 +823,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauAttributeSyntax && attributes.size != 0)
|
||||
if (attributes.size != 0)
|
||||
{
|
||||
return reportStatError(lexer.current().location, {}, {}, "Expected 'function' after local declaration with attribute, but got %s instead",
|
||||
lexer.current().toString().c_str());
|
||||
@ -980,7 +969,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
|
||||
{
|
||||
// `declare` token is already parsed at this point
|
||||
|
||||
if (FFlag::LuauAttributeSyntax && (attributes.size != 0) && (lexer.current().type != Lexeme::ReservedFunction))
|
||||
if ((attributes.size != 0) && (lexer.current().type != Lexeme::ReservedFunction))
|
||||
return reportStatError(lexer.current().location, {}, {}, "Expected a function type declaration after attribute, but got %s instead",
|
||||
lexer.current().toString().c_str());
|
||||
|
||||
@ -1707,10 +1696,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
||||
{
|
||||
TempVector<AstType*> parts(scratchType);
|
||||
|
||||
if (!FFlag::LuauLeadingBarAndAmpersand2 || type != nullptr)
|
||||
{
|
||||
if (type != nullptr)
|
||||
parts.push_back(type);
|
||||
}
|
||||
|
||||
incrementRecursionCounter("type annotation");
|
||||
|
||||
@ -1769,7 +1756,7 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin)
|
||||
}
|
||||
|
||||
if (parts.size() == 1)
|
||||
return FFlag::LuauLeadingBarAndAmpersand2 ? parts[0] : type;
|
||||
return parts[0];
|
||||
|
||||
if (isUnion && isIntersection)
|
||||
{
|
||||
@ -1816,8 +1803,6 @@ AstType* Parser::parseType(bool inDeclarationContext)
|
||||
|
||||
Location begin = lexer.current().location;
|
||||
|
||||
if (FFlag::LuauLeadingBarAndAmpersand2)
|
||||
{
|
||||
AstType* type = nullptr;
|
||||
|
||||
Lexeme::Type c = lexer.current().type;
|
||||
@ -1832,15 +1817,6 @@ AstType* Parser::parseType(bool inDeclarationContext)
|
||||
|
||||
return typeWithSuffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
AstType* type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type;
|
||||
|
||||
recursionCounter = oldRecursionCount;
|
||||
|
||||
return parseTypeSuffix(type, begin);
|
||||
}
|
||||
}
|
||||
|
||||
// Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}'
|
||||
// | [`<' varlist `>'] `(' [TypeList] `)' `->` ReturnType
|
||||
@ -1854,7 +1830,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext)
|
||||
|
||||
if (lexer.current().type == Lexeme::Attribute)
|
||||
{
|
||||
if (!inDeclarationContext || !FFlag::LuauAttributeSyntax)
|
||||
if (!inDeclarationContext)
|
||||
{
|
||||
return {reportTypeError(start, {}, "attributes are not allowed in declaration context")};
|
||||
}
|
||||
@ -2431,7 +2407,7 @@ AstExpr* Parser::parseSimpleExpr()
|
||||
|
||||
AstArray<AstAttr*> attributes{nullptr, 0};
|
||||
|
||||
if (FFlag::LuauAttributeSyntax && FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute)
|
||||
if (FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute)
|
||||
{
|
||||
attributes = parseAttributes();
|
||||
|
||||
|
@ -12,8 +12,8 @@ inline bool isFlagExperimental(const char* flag)
|
||||
// or critical bugs that are found after the code has been submitted.
|
||||
static const char* const kList[] = {
|
||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||
"StudioReportLuauAny", // takes telemetry data for usage of any types
|
||||
// makes sure we always have at least one entry
|
||||
nullptr,
|
||||
};
|
||||
|
@ -32,7 +32,6 @@ Id UnionFind::find(Id id)
|
||||
parents[size_t(id)] = set;
|
||||
id = parent;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@ -48,7 +47,6 @@ void UnionFind::merge(Id a, Id b)
|
||||
std::swap(aSet, bSet);
|
||||
|
||||
parents[size_t(bSet)] = aSet;
|
||||
|
||||
if (ranks[size_t(aSet)] == ranks[size_t(bSet)])
|
||||
ranks[size_t(aSet)]++;
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ target_sources(Luau.CodeGen PRIVATE
|
||||
# Luau.Analysis Sources
|
||||
target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Anyification.h
|
||||
Analysis/include/Luau/AnyTypeSummary.h
|
||||
Analysis/include/Luau/ApplyTypeFunction.h
|
||||
Analysis/include/Luau/AstJsonEncoder.h
|
||||
Analysis/include/Luau/AstQuery.h
|
||||
@ -236,6 +237,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/VisitType.h
|
||||
|
||||
Analysis/src/Anyification.cpp
|
||||
Analysis/src/AnyTypeSummary.cpp
|
||||
Analysis/src/ApplyTypeFunction.cpp
|
||||
Analysis/src/AstJsonEncoder.cpp
|
||||
Analysis/src/AstQuery.cpp
|
||||
@ -408,6 +410,7 @@ endif()
|
||||
if(TARGET Luau.UnitTest)
|
||||
# Luau.UnitTest Sources
|
||||
target_sources(Luau.UnitTest PRIVATE
|
||||
tests/AnyTypeSummary.test.cpp
|
||||
tests/AssemblyBuilderA64.test.cpp
|
||||
tests/AssemblyBuilderX64.test.cpp
|
||||
tests/AstJsonEncoder.test.cpp
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreserveLudataRenaming, false)
|
||||
|
||||
// clang-format off
|
||||
const char* const luaT_typenames[] = {
|
||||
// ORDER TYPE
|
||||
@ -121,6 +123,45 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event)
|
||||
}
|
||||
|
||||
const TString* luaT_objtypenamestr(lua_State* L, const TValue* o)
|
||||
{
|
||||
if (FFlag::LuauPreserveLudataRenaming)
|
||||
{
|
||||
// Userdata created by the environment can have a custom type name set in the individual metatable
|
||||
// If there is no custom name, 'userdata' is returned
|
||||
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
|
||||
{
|
||||
const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]);
|
||||
|
||||
if (ttisstring(type))
|
||||
return tsvalue(type);
|
||||
|
||||
return L->global->ttname[ttype(o)];
|
||||
}
|
||||
|
||||
// Tagged lightuserdata can be named using lua_setlightuserdataname
|
||||
if (ttislightuserdata(o))
|
||||
{
|
||||
int tag = lightuserdatatag(o);
|
||||
|
||||
if (unsigned(tag) < LUA_LUTAG_LIMIT)
|
||||
{
|
||||
if (const TString* name = L->global->lightuserdataname[tag])
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// For all types except userdata and table, a global metatable can be set with a global name override
|
||||
if (Table* mt = L->global->mt[ttype(o)])
|
||||
{
|
||||
const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]);
|
||||
|
||||
if (ttisstring(type))
|
||||
return tsvalue(type);
|
||||
}
|
||||
|
||||
return L->global->ttname[ttype(o)];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable)
|
||||
{
|
||||
@ -151,6 +192,7 @@ const TString* luaT_objtypenamestr(lua_State* L, const TValue* o)
|
||||
|
||||
return L->global->ttname[ttype(o)];
|
||||
}
|
||||
}
|
||||
|
||||
const char* luaT_objtypename(lua_State* L, const TValue* o)
|
||||
{
|
||||
|
745
tests/AnyTypeSummary.test.cpp
Normal file
745
tests/AnyTypeSummary.test.cpp
Normal file
@ -0,0 +1,745 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/AstQuery.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
|
||||
LUAU_FASTFLAG(StudioReportLuauAny);
|
||||
|
||||
struct ATSFixture: BuiltinsFixture
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
ATSFixture()
|
||||
{
|
||||
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
|
||||
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("AnyTypeSummaryTest");
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
type A = (number, string) -> ...any
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
type Pair<T> = {first: T, second: any}
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
local function greetings(name: string)
|
||||
return "Hello, " .. name, nil
|
||||
end
|
||||
|
||||
local x, y = greetings("Dibri")
|
||||
local x, y = greetings("Dibri"), nil
|
||||
local x, y, z = greetings("Dibri") -- mismatch
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
-- type Pair<T> = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed?
|
||||
type Pair<T> = (boolean, T) -> ...any
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function foo<A>(a: (...A) -> any, ...: A)
|
||||
return a(...)
|
||||
end
|
||||
|
||||
local function addNumbers(num1, num2)
|
||||
local result = num1 + num2
|
||||
return result
|
||||
end
|
||||
|
||||
foo(addNumbers)
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 3); // FuncApp
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local character = script.Parent
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "if_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
function f(x: any)
|
||||
if not x then
|
||||
x = {
|
||||
y = math.random(0, 2^31-1),
|
||||
left = nil,
|
||||
right = nil
|
||||
}
|
||||
else
|
||||
local expected = x * 5
|
||||
end
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function f(): (number, ...any) -- double count "any" ret if varags
|
||||
return 1, 5
|
||||
end
|
||||
|
||||
local x, y, z = f() -- not catching this any because no annot
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1); // do we need this to be the var arg pattern?
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Func Ret
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
type XCoord = {x: number}
|
||||
type YCoord = {y: any}
|
||||
type Vector2 = XCoord & YCoord -- table type intersections do not get normalized
|
||||
local vec2: Vector2 = {x = 1, y = 2}
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[2].code == 4); // Variable Annotation
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function f(...: any)
|
||||
end
|
||||
local function f(x: number?, y, z: any)
|
||||
end
|
||||
function f(x: number?, y, z: any)
|
||||
end
|
||||
function f(...: any)
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function f(...: any)
|
||||
end
|
||||
f("string", 123)
|
||||
f("string")
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 5); // Variadic Any
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local CAR_COLLISION_GROUP = "Car"
|
||||
|
||||
-- Set the car collision group
|
||||
for _, descendant in carTemplate:GetDescendants() do
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.CollisionGroup = CAR_COLLISION_GROUP
|
||||
end
|
||||
end
|
||||
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local function manageRace(raceContainer: Model)
|
||||
RaceManager.new(raceContainer)
|
||||
end
|
||||
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
|
||||
local RaceManager = require(script.RaceManager)
|
||||
|
||||
local RACE_TAG = "Race"
|
||||
|
||||
local function manageRace(raceContainer: Model)
|
||||
RaceManager.new(raceContainer)
|
||||
end
|
||||
|
||||
local function initialize()
|
||||
CollectionService:GetInstanceAddedSignal(RACE_TAG):Connect(manageRace)
|
||||
|
||||
for _, raceContainer in CollectionService:GetTagged(RACE_TAG) do
|
||||
manageRace(raceContainer)
|
||||
end
|
||||
end
|
||||
|
||||
initialize()
|
||||
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 5); //unknown Model -- why are we inferring so many anys here --prbably something with the data model. worth looking into
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local PhysicsService = game:GetService("PhysicsService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local safePlayerAdded = require(script.safePlayerAdded)
|
||||
|
||||
local CAR_COLLISION_GROUP = "Car"
|
||||
local CHARACTER_COLLISION_GROUP = "Character"
|
||||
|
||||
local carTemplate = ReplicatedStorage.Car
|
||||
|
||||
local function onCharacterAdded(character: Model)
|
||||
-- Set the collision group for any parts that are added to the character
|
||||
character.DescendantAdded:Connect(function(descendant)
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
|
||||
end
|
||||
end)
|
||||
|
||||
-- Set the collision group for any parts currently in the character
|
||||
for _, descendant in character:GetDescendants() do
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onPlayerAdded(player: Player)
|
||||
player.CharacterAdded:Connect(onCharacterAdded)
|
||||
|
||||
if player.Character then
|
||||
onCharacterAdded(player.Character)
|
||||
end
|
||||
end
|
||||
|
||||
local function initialize()
|
||||
-- Setup collision groups
|
||||
PhysicsService:RegisterCollisionGroup(CAR_COLLISION_GROUP)
|
||||
PhysicsService:RegisterCollisionGroup(CHARACTER_COLLISION_GROUP)
|
||||
|
||||
-- Stop the collision groups from colliding with each other
|
||||
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CAR_COLLISION_GROUP, false)
|
||||
PhysicsService:CollisionGroupSetCollidable(CHARACTER_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
|
||||
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
|
||||
|
||||
-- Set the car collision group
|
||||
for _, descendant in carTemplate:GetDescendants() do
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.CollisionGroup = CAR_COLLISION_GROUP
|
||||
end
|
||||
end
|
||||
|
||||
-- Set character collision groups for all players
|
||||
safePlayerAdded(onPlayerAdded)
|
||||
end
|
||||
|
||||
initialize()
|
||||
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(5, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local spawnCar = require(script.spawnCar)
|
||||
local destroyPlayerCars = require(script.destroyPlayerCars)
|
||||
|
||||
local spawnPromptTemplate = script.SpawnPrompt
|
||||
|
||||
local KIOSK_TAG = "CarSpawnKiosk"
|
||||
|
||||
local function setupKiosk(kiosk: Model)
|
||||
local spawnLocation = kiosk:FindFirstChild("SpawnLocation")
|
||||
assert(spawnLocation, `{kiosk:GetFullName()} has no SpawnLocation part`)
|
||||
local promptPart = kiosk:FindFirstChild("Prompt")
|
||||
assert(promptPart, `{kiosk:GetFullName()} has no Prompt part`)
|
||||
|
||||
-- Hide the car spawn location
|
||||
spawnLocation.Transparency = 1
|
||||
|
||||
-- Create a new prompt to spawn the car
|
||||
local spawnPrompt = spawnPromptTemplate:Clone()
|
||||
spawnPrompt.Parent = promptPart
|
||||
|
||||
spawnPrompt.Triggered:Connect(function(player: Player)
|
||||
-- Remove any existing cars the player has spawned
|
||||
destroyPlayerCars(player)
|
||||
-- Spawn a new car at the spawnLocation, owned by the player
|
||||
spawnCar(spawnLocation.CFrame, player)
|
||||
end)
|
||||
end
|
||||
|
||||
local function initialize()
|
||||
-- Remove cars owned by players whenever they leave
|
||||
Players.PlayerRemoving:Connect(destroyPlayerCars)
|
||||
|
||||
-- Setup all car spawning kiosks
|
||||
CollectionService:GetInstanceAddedSignal(KIOSK_TAG):Connect(setupKiosk)
|
||||
|
||||
for _, kiosk in CollectionService:GetTagged(KIOSK_TAG) do
|
||||
setupKiosk(kiosk)
|
||||
end
|
||||
end
|
||||
|
||||
initialize()
|
||||
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(5, result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Func Arg
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!strict
|
||||
type T<a> = { f: a, g: U<a> }
|
||||
type U<a> = { h: a, i: T<a>? }
|
||||
local x: T<number> = { f = 37, g = { h = 5, i = nil } }
|
||||
x.g.i = x
|
||||
local y: T<string> = { f = "hi", g = { h = "lo", i = nil } }
|
||||
y.g.i = y
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
type Foo<T...> = (T...) -> () -- also want to see how these are used.
|
||||
type Bar = Foo<(number, any)>
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local x = 2
|
||||
local x: any = 2, 3
|
||||
local x: any, y = 1, 2
|
||||
local x: number, y: any, z, h: nil = 1, nil
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local x: any = 0
|
||||
local y: number
|
||||
local z = {x=x, y=y} -- not catching this
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local x: any = 0
|
||||
function some1(x: typeof(x))
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 1); // Function Arguments
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local x: { x: any?} = {x = 1}
|
||||
local z: { x : any, y : number? } -- not catching this
|
||||
z.x = "bigfatlongstring"
|
||||
z.y = nil
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2); // double counting variable annot again
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[1].code == 8); // Assign
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
function some(x: any)
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
|
||||
{
|
||||
// TODO: should only return 1
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
function other(y: number): any
|
||||
return "gotcha!"
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 2); // Function Return
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
function cool(y: number): number
|
||||
local g: any = "gratatataaa"
|
||||
return y
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1); // should be one, double counitng annot
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 4); // Variable Annotation
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "generic_func")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
function reverse<T>(a: {T}, b: any): {T}
|
||||
return a
|
||||
end
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 1); // Function Arguments
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
|
||||
{
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
type Clear = any
|
||||
local z: Clear = "zip"
|
||||
)";
|
||||
|
||||
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
||||
LUAU_REQUIRE_NO_ERRORS(result1);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
||||
|
||||
if (FFlag::StudioReportLuauAny)
|
||||
{
|
||||
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
||||
LUAU_ASSERT((int)module->ats.typeInfo[0].code == 7); // Alias
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type MyFunction = (number, string) -> (any)
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local MyFunc = require(script.Parent.A)
|
||||
type Clear = any
|
||||
local z: Clear = "zip"
|
||||
)";
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local B = require(Modules.B)
|
||||
return {hello = B.hello}
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -345,4 +345,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_binding_at_position_global_start_of_fil
|
||||
CHECK_EQ(binding->location, Location{Position{0, 0}, Position{0, 0}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "interior_binding_location_is_consistent_with_exterior_binding")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function abcd(arg)
|
||||
abcd(arg)
|
||||
end
|
||||
|
||||
abcd(0)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// FIXME CLI-114385: findBindingByPosition does not properly handle AstStatLocalFunction.
|
||||
|
||||
// std::optional<Binding> declBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {1, 26});
|
||||
// REQUIRE(declBinding);
|
||||
|
||||
// CHECK(declBinding->location == Location{{1, 25}, {1, 28}});
|
||||
|
||||
std::optional<Binding> innerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {2, 15});
|
||||
REQUIRE(innerCallBinding);
|
||||
|
||||
CHECK(innerCallBinding->location == Location{{1, 23}, {1, 27}});
|
||||
|
||||
std::optional<Binding> outerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {5, 8});
|
||||
REQUIRE(outerCallBinding);
|
||||
|
||||
CHECK(outerCallBinding->location == Location{{1, 23}, {1, 27}});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -33,8 +33,8 @@ void luaC_validate(lua_State* L);
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax)
|
||||
LUAU_FASTFLAG(LuauNativeAttribute)
|
||||
LUAU_FASTFLAG(LuauPreserveLudataRenaming)
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
@ -2114,6 +2114,21 @@ TEST_CASE("LightuserdataApi")
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (FFlag::LuauPreserveLudataRenaming)
|
||||
{
|
||||
// Still possible to rename the global lightuserdata name using a metatable
|
||||
lua_pushlightuserdata(L, value);
|
||||
CHECK(strcmp(luaL_typename(L, -1), "userdata") == 0);
|
||||
|
||||
lua_createtable(L, 0, 1);
|
||||
lua_pushstring(L, "luserdata");
|
||||
lua_setfield(L, -2, "__type");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
CHECK(strcmp(luaL_typename(L, -1), "luserdata") == 0);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
globalState.reset();
|
||||
}
|
||||
|
||||
@ -2711,7 +2726,7 @@ TEST_CASE("NativeAttribute")
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauNativeAttribute, true}};
|
||||
ScopedFastFlag sffs[] = {{FFlag::LuauNativeAttribute, true}};
|
||||
|
||||
std::string source = R"R(
|
||||
@native
|
||||
|
@ -776,12 +776,18 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation")
|
||||
if typeof(almostBar.x.y) ~= "number" then
|
||||
almostFoo = almostBar
|
||||
end
|
||||
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
compareTypesNe("foo", "almostFoo",
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.x.y.Negation has type string, while the right type at <unlabeled-symbol>.x.y.Negation has type number)");
|
||||
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { x: { y: ~string } }, while the right type at <unlabeled-symbol> is a union missing type { x: { y: ~string } })");
|
||||
|
||||
// TODO: a more desirable expected error here is as below, but `Differ` requires improvements to
|
||||
// dealing with unions to get something like this (recognizing that the union is identical
|
||||
// except in one component where they differ).
|
||||
//
|
||||
// compareTypesNe("foo", "almostFoo",
|
||||
// R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.x.y.Negation has type string, while the right type at <unlabeled-symbol>.x.y.Negation has type number)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "union_missing_right")
|
||||
|
@ -295,6 +295,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
|
||||
return {hello = A.hello}
|
||||
)";
|
||||
fileResolver.source["game/Gui/Modules/C"] = R"(
|
||||
--!strict
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
local A = require(Modules.A)
|
||||
local B = require(Modules.B)
|
||||
@ -911,7 +912,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
|
||||
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
REQUIRE_EQ(
|
||||
"Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||
R"(Type
|
||||
'{ count: string }'
|
||||
could not be converted into
|
||||
'{ Count: number }')", toString(result.errors[0]));
|
||||
else
|
||||
REQUIRE_EQ(
|
||||
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
|
||||
@ -919,6 +923,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode")
|
||||
{
|
||||
// The new non-strict mode is not currently expected to signal any errors here.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
--!nonstrict
|
||||
local module = {}
|
||||
@ -968,13 +976,25 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments")
|
||||
local foo: Foo = 1
|
||||
)";
|
||||
|
||||
fileResolver.source["C"] = R"(
|
||||
--!strict
|
||||
local foo: Foo = 1
|
||||
)";
|
||||
|
||||
fileResolver.environments["A"] = "test";
|
||||
|
||||
CheckResult resultA = frontend.check("A");
|
||||
LUAU_REQUIRE_NO_ERRORS(resultA);
|
||||
|
||||
CheckResult resultB = frontend.check("B");
|
||||
// In the new non-strict mode, we do not currently support error reporting for unknown symbols in type positions.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(resultB);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, resultB);
|
||||
|
||||
CheckResult resultC = frontend.check("C");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, resultC);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "ast_node_at_position")
|
||||
@ -1075,6 +1095,10 @@ TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "imported_table_modification_2")
|
||||
{
|
||||
// This test describes non-strict mode behavior that is just not currently present in the new non-strict mode.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
frontend.options.retainFullTypeGraphs = false;
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauNativeAttribute);
|
||||
LUAU_FASTFLAG(LintRedundantNativeAttribute);
|
||||
|
||||
@ -1960,7 +1959,7 @@ local _ = a <= (b == 0)
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "RedundantNativeAttribute")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
|
||||
|
||||
LintResult result = lint(R"(
|
||||
--!native
|
||||
|
@ -15,8 +15,6 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
|
||||
#define NONSTRICT_REQUIRE_ERR_AT_POS(pos, result, idx) \
|
||||
do \
|
||||
{ \
|
||||
@ -70,7 +68,6 @@ struct NonStrictTypeCheckerFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
{FFlag::LuauAttributeSyntax, true},
|
||||
};
|
||||
LoadDefinitionFileResult res = loadDefinition(definitions);
|
||||
LUAU_ASSERT(res.success);
|
||||
@ -81,7 +78,6 @@ struct NonStrictTypeCheckerFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
{FFlag::LuauAttributeSyntax, true},
|
||||
};
|
||||
LoadDefinitionFileResult res = loadDefinition(definitions);
|
||||
LUAU_ASSERT(res.success);
|
||||
@ -562,10 +558,6 @@ local E = require(script.Parent.A)
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_shouldnt_warn_on_valid_buffer_use")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauAttributeSyntax, true},
|
||||
};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare buffer: {
|
||||
create: @checked (size: number) -> buffer,
|
||||
|
@ -16,8 +16,6 @@ LUAU_FASTINT(LuauRecursionLimit);
|
||||
LUAU_FASTINT(LuauTypeLengthLimit);
|
||||
LUAU_FASTINT(LuauParseErrorLimit);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
LUAU_FASTFLAG(LuauLeadingBarAndAmpersand2);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr);
|
||||
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
|
||||
|
||||
@ -3070,7 +3068,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_top_level_checked_fn")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
std::string src = R"BUILTIN_SRC(
|
||||
@checked declare function abs(n: number): number
|
||||
@ -3090,7 +3087,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_declared_table_checked_member")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
const std::string src = R"BUILTIN_SRC(
|
||||
declare math : {
|
||||
@ -3118,7 +3114,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseResult pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
@ -3132,7 +3127,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
@ -3148,7 +3142,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
@checked function(x: number) : number
|
||||
@ -3162,7 +3155,6 @@ TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @blah = 3
|
||||
@ -3193,28 +3185,6 @@ TEST_CASE_FIXTURE(Fixture, "read_write_table_properties")
|
||||
LUAU_ASSERT(pr.errors.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_bar_unions_successfully")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
parse(R"(type A = | "Hello" | "World")");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_ampersand_intersections_successfully")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
parse(R"(type A = & { string } & { number })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "mixed_leading_intersection_and_union_not_allowed")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
matchParseError("type A = & number | string | boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
|
||||
matchParseError("type A = | number & string & boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
|
||||
}
|
||||
|
||||
void checkAttribute(const AstAttr* attr, const AstAttr::Type type, const Location& location)
|
||||
{
|
||||
CHECK_EQ(attr->type, type);
|
||||
@ -3232,7 +3202,6 @@ void checkFirstErrorForAttributes(const std::vector<ParseError>& errors, const s
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_stat")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
AstStatBlock* stat = parse(R"(
|
||||
@checked
|
||||
@ -3254,7 +3223,7 @@ end)");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attribute_for_function_expression")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauAttributeSyntaxFunExpr, true}};
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntaxFunExpr, true}};
|
||||
|
||||
AstStatBlock* stat1 = parse(R"(
|
||||
local function invoker(f)
|
||||
@ -3293,8 +3262,6 @@ local f = @checked function(x) return (x + 2) end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_local_function_stat")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
AstStatBlock* stat = parse(R"(
|
||||
@checked
|
||||
local function hello(x, y)
|
||||
@ -3315,8 +3282,6 @@ end)");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "empty_attribute_name_is_not_allowed")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
@
|
||||
function hello(x, y)
|
||||
@ -3328,8 +3293,6 @@ end)");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_stat")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseResult pr1 = tryParse(R"(
|
||||
@checked
|
||||
if a<0 then a = 0 end)");
|
||||
@ -3401,7 +3364,7 @@ function foo1 () @checked return 'a' end
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_parse_attribute_on_argument_non_function")
|
||||
{
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntax, true}, {FFlag::LuauAttributeSyntaxFunExpr, true}};
|
||||
ScopedFastFlag sff[] = {{FFlag::LuauAttributeSyntaxFunExpr, true}};
|
||||
|
||||
ParseResult pr = tryParse(R"(
|
||||
local function invoker(f, y)
|
||||
@ -3417,8 +3380,6 @@ invoker(function(x) return (x + 2) end, @checked 1)
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_type_declaration")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
|
||||
@ -3445,8 +3406,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_attribute_on_function_type_declaration")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attributes_on_function_type_declaration_in_table")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
|
||||
@ -3482,8 +3441,6 @@ declare bit32: {
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "dont_parse_attributes_on_non_function_type_declarations")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
|
||||
@ -3517,8 +3474,6 @@ declare bit32: {
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "attributes_cannot_be_duplicated")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
@checked
|
||||
@checked
|
||||
@ -3531,8 +3486,6 @@ end)");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unsupported_attributes_are_not_allowed")
|
||||
{
|
||||
ScopedFastFlag luauAttributeSyntax{FFlag::LuauAttributeSyntax, true};
|
||||
|
||||
ParseResult result = tryParse(R"(
|
||||
@checked
|
||||
@cool_attribute
|
||||
@ -3545,22 +3498,16 @@ end)");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_bar_unions_successfully")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
parse(R"(type A = | "Hello" | "World")");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "can_parse_leading_ampersand_intersections_successfully")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
parse(R"(type A = & { string } & { number })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "mixed_leading_intersection_and_union_not_allowed")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
|
||||
matchParseError("type A = & number | string | boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
|
||||
matchParseError("type A = | number & string & boolean", "Mixing union and intersection types is not allowed; consider wrapping in parentheses.");
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ using namespace Luau;
|
||||
LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||
LUAU_FASTFLAG(LuauAttributeSyntax);
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
@ -977,7 +976,6 @@ TEST_CASE_FIXTURE(Fixture, "checked_fn_toString")
|
||||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true},
|
||||
{FFlag::LuauAttributeSyntax, true},
|
||||
};
|
||||
|
||||
auto _result = loadDefinition(R"(
|
||||
|
@ -698,9 +698,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// The argument count is the same, but the errors are currently cyclic type family instance ones.
|
||||
// This isn't great, but the desired behavior here was that it didn't cause a crash and that is still true.
|
||||
// The larger fix for this behavior will likely be integration of egraph-based normalization throughout the new solver.
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Argument count mismatch. Function '_' expects at least 1 argument, but none are specified", toString(result.errors[0]));
|
||||
CHECK_EQ("Argument count mismatch. Function 'select' expects 1 argument, but none are specified", toString(result.errors[1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
|
||||
{
|
||||
@ -942,6 +952,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
@ -991,6 +1004,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
|
||||
else
|
||||
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,10 @@
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
|
||||
|
||||
TEST_SUITE_BEGIN("ControlFlowAnalysis");
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
if not x then
|
||||
@ -28,8 +24,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -48,8 +42,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -68,8 +60,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
if not x then
|
||||
@ -90,8 +80,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -115,8 +103,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -140,8 +126,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -165,8 +149,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -190,8 +172,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
if not x then
|
||||
@ -214,8 +194,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_rand_return_elif_not_y_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -241,8 +219,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_br
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -268,8 +244,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
if not x then
|
||||
@ -292,8 +266,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_rand_return_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -319,8 +291,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_rand_break_elif_not_y_fa
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_not_y_fallthrough")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -346,8 +316,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_rand_continue_elif_no
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?, z: string?)
|
||||
if not x then
|
||||
@ -372,8 +340,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_not_z_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -402,8 +368,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_elif_not_y_fallthrough_elif_n
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_elif_not_z_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -432,8 +396,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_fallthrough_eli
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_z_fallthrough")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -462,8 +424,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_elif_not_y_throw_elif_not_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_not_z_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}}, z: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -492,8 +452,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_elif_not_y_fallthrough_elif_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
do
|
||||
@ -512,8 +470,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_if_not_x_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -534,8 +490,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -556,8 +510,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_record_do_if_not_x_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed_to_run_first")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
while math.random() > 0.5 do
|
||||
@ -579,8 +531,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_isnt_guaranteed
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
repeat
|
||||
@ -602,8 +552,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_to_run_first_2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
for i = 1, 10 do
|
||||
@ -625,8 +573,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "early_return_in_a_loop_which_is_guaranteed_t
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
if not x then
|
||||
@ -643,8 +589,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_error")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
if not x then
|
||||
@ -661,8 +605,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_then_assert_false")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?, y: string?)
|
||||
if not x then
|
||||
@ -685,8 +627,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_return_if_not_y_return")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -712,8 +652,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_break")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -739,8 +677,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -766,8 +702,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_continue_if_not_y_throw")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}}, y: {{value: string?}})
|
||||
for i, recordX in x do
|
||||
@ -793,8 +727,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "if_not_x_break_if_not_y_continue")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
if typeof(x) == "string" then
|
||||
@ -816,8 +748,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -841,8 +771,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_breaking")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -866,8 +794,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_does_not_leak_out_continuing")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
// In CG, we walk the block to prototype aliases. We then visit the block in-order, which will resolve the prototype to a real type.
|
||||
// That second walk assumes that the name occurs in the same `Scope` that the prototype walk had. If we arbitrarily change scope midway
|
||||
// through, we'd invoke UB.
|
||||
@ -892,8 +818,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_breaking")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -917,8 +841,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_scope_continuing")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: {{value: string?}})
|
||||
for _, record in x do
|
||||
@ -942,8 +864,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyping_and_visiting_alias_has_the_same_
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
type Err<E> = { tag: "err", error: E }
|
||||
@ -977,8 +897,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
type Err<E> = { tag: "err", error: E }
|
||||
@ -1010,8 +928,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_breaking")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Ok<T> = { tag: "ok", value: T }
|
||||
type Err<E> = { tag: "err", error: E }
|
||||
@ -1043,8 +959,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tagged_unions_continuing")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_assert_x")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
do
|
||||
|
@ -2763,5 +2763,18 @@ local function captureDependencies(
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unpack_depends_on_rhs_pack_to_be_fully_resolved")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function id(x)
|
||||
return x
|
||||
end
|
||||
local u,v = id(3), id(id(44))
|
||||
)");
|
||||
|
||||
CHECK_EQ(builtinTypes->numberType, requireType("v"));
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -632,6 +632,11 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_pack_parentheses")
|
||||
function f<a...>(...: a...): any return (...) end
|
||||
)");
|
||||
|
||||
// This should really error, but the error from the old solver is wrong.
|
||||
// `a...` is a generic type pack, and we don't know that it will be non-empty, thus this code may not work.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
@ -647,13 +652,27 @@ TEST_CASE_FIXTURE(Fixture, "better_mismatch_error_messages")
|
||||
end
|
||||
)");
|
||||
|
||||
SwappedGenericTypeParameter* fErr;
|
||||
SwappedGenericTypeParameter* gErr;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
// The first error here is an unknown symbol that is redundant with the `fErr`.
|
||||
fErr = get<SwappedGenericTypeParameter>(result.errors[1]);
|
||||
gErr = get<SwappedGenericTypeParameter>(result.errors[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
SwappedGenericTypeParameter* fErr = get<SwappedGenericTypeParameter>(result.errors[0]);
|
||||
fErr = get<SwappedGenericTypeParameter>(result.errors[0]);
|
||||
gErr = get<SwappedGenericTypeParameter>(result.errors[1]);
|
||||
}
|
||||
|
||||
REQUIRE(fErr);
|
||||
CHECK_EQ(fErr->name, "T");
|
||||
CHECK_EQ(fErr->kind, SwappedGenericTypeParameter::Pack);
|
||||
|
||||
SwappedGenericTypeParameter* gErr = get<SwappedGenericTypeParameter>(result.errors[1]);
|
||||
REQUIRE(gErr);
|
||||
CHECK_EQ(gErr->name, "T");
|
||||
CHECK_EQ(gErr->kind, SwappedGenericTypeParameter::Type);
|
||||
@ -1111,6 +1130,9 @@ local a: Self<Table>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireType("a")), "Table<Table>");
|
||||
else
|
||||
CHECK_EQ(toString(requireType("a")), "Table");
|
||||
}
|
||||
|
||||
@ -1127,6 +1149,9 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
||||
|
||||
std::optional<TypeId> t0 = lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("any", toString(*t0));
|
||||
else
|
||||
CHECK_EQ("*error-type*", toString(*t0));
|
||||
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||
@ -1136,6 +1161,25 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
|
||||
{
|
||||
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function sum<a>(x: a, y: a, f: (a, a) -> add<a>)
|
||||
return f(x, y)
|
||||
end
|
||||
return sum(2, 3, function<T>(a: T, b: T): add<T> return a + b end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
InternalError* ie = get<InternalError>(result.errors[0]);
|
||||
REQUIRE(ie);
|
||||
CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function sum<a>(x: a, y: a, f: (a, a) -> a)
|
||||
@ -1146,11 +1190,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function map<a, b>(arr: {a}, f: (a) -> b)
|
||||
local function map<a, b>(arr: {a}, f: (a) -> b): {b}
|
||||
local r = {}
|
||||
for i,v in ipairs(arr) do
|
||||
table.insert(r, f(v))
|
||||
@ -1158,7 +1203,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2")
|
||||
return r
|
||||
end
|
||||
local a = {1, 2, 3}
|
||||
local r = map(a, function(a) return a + a > 100 end)
|
||||
local r = map(a, function(a: number) return a + a > 100 end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -1176,10 +1221,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_3")
|
||||
return r
|
||||
end
|
||||
local a = {1, 2, 3}
|
||||
local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} end)
|
||||
local r = foldl(a, {s=0,c=0}, function(a: {s: number, c: number}, b: number) return {s = a.s + b, c = a.c + 1} end)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
REQUIRE_EQ("{ c: number, s: number } | { c: number, s: number }", toString(requireType("r")));
|
||||
else
|
||||
REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r")));
|
||||
}
|
||||
|
||||
@ -1214,9 +1262,33 @@ table.sort(a, function(x, y) return x.x < y.x end)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_infer_generic_functions")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
||||
CheckResult result;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
result = check(R"(
|
||||
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
|
||||
|
||||
local function sumrec(f: typeof(sum))
|
||||
return sum(2, 3, function<T>(a: T, b: T): add<T> return a + b end)
|
||||
end
|
||||
|
||||
local b = sumrec(sum) -- ok
|
||||
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
InternalError* ie = get<InternalError>(result.errors[0]);
|
||||
REQUIRE(ie);
|
||||
CHECK_EQ("Type inference failed to complete, you may see some confusing types and type errors.", ie->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = check(R"(
|
||||
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
|
||||
|
||||
local function sumrec(f: typeof(sum))
|
||||
@ -1229,6 +1301,8 @@ local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not i
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
{
|
||||
@ -1246,6 +1320,8 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1")
|
||||
{
|
||||
// CLI-114507: temporarily changed to have a cast for `object` to silence false positive error
|
||||
|
||||
// https://github.com/luau-lang/luau/issues/484
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
@ -1256,7 +1332,7 @@ local object: MyObject = {
|
||||
getReturnValue = function<U>(cb: () -> U): U
|
||||
return cb()
|
||||
end,
|
||||
}
|
||||
} :: MyObject
|
||||
|
||||
type ComplexObject<T> = {
|
||||
id: T,
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauTinyControlFlowAnalysis);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -492,8 +491,6 @@ return unpack(l0[_])
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "check_imported_module_names")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauTinyControlFlowAnalysis, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
return function(...) end
|
||||
)";
|
||||
|
@ -515,4 +515,35 @@ TEST_CASE_FIXTURE(Fixture, "method_should_not_create_cyclic_type")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_metatable")
|
||||
{
|
||||
fileResolver.source["game/A"] = R"(
|
||||
--!strict
|
||||
local cls = {}
|
||||
cls.__index = cls
|
||||
function cls:abc() return 4 end
|
||||
return cls
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
--!strict
|
||||
local cls = require(game.A)
|
||||
local tbl = {}
|
||||
setmetatable(tbl, cls)
|
||||
)";
|
||||
|
||||
CheckResult result = frontend.check("game/B");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ModulePtr b = frontend.moduleResolver.getModule("game/B");
|
||||
REQUIRE(b);
|
||||
|
||||
std::optional<Binding> clsBinding = b->getModuleScope()->linearSearchForBinding("tbl");
|
||||
REQUIRE(clsBinding);
|
||||
|
||||
TypeId clsType = clsBinding->typeId;
|
||||
|
||||
CHECK("{ @metatable cls, tbl }" == toString(clsType));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1472,8 +1472,8 @@ TEST_CASE_FIXTURE(Fixture, "add_type_function_works")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(toString(requireType("a")) == "number");
|
||||
CHECK(toString(requireType("b")) == "Add<string, string>");
|
||||
CHECK(toString(result.errors[0]) == "Type function instance Add<string, string> is uninhabited");
|
||||
CHECK(toString(requireType("b")) == "add<string, string>");
|
||||
CHECK(toString(result.errors[0]) == "Operator '+' could not be applied to operands of types string and string; there is no corresponding overload for __add");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "normalize_strings_comparison")
|
||||
|
@ -2059,6 +2059,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantifying_a_bound_var_works")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "less_exponential_blowup_please")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
@ -3275,6 +3276,7 @@ TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
check(R"(
|
||||
@ -3304,16 +3306,25 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys")
|
||||
local t: { [string]: number } = { 5, 6, 7 }
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK("Type '{number}' could not be converted into '{ [string]: number }'; at indexer(), number is not exactly string" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
||||
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[0]));
|
||||
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[1]));
|
||||
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "shared_selfs")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
@ -3335,6 +3346,7 @@ TEST_CASE_FIXTURE(Fixture, "shared_selfs")
|
||||
TEST_CASE_FIXTURE(Fixture, "shared_selfs_from_free_param")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t)
|
||||
@ -3351,6 +3363,7 @@ TEST_CASE_FIXTURE(Fixture, "shared_selfs_from_free_param")
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "shared_selfs_through_metatables")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = {}
|
||||
@ -3430,6 +3443,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
|
||||
{
|
||||
ScopedFastFlag sff[]{
|
||||
{FFlag::DebugLuauSharedSelf, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
@ -3460,6 +3474,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "quantify_metatables_of_metatables_of_table")
|
||||
TEST_CASE_FIXTURE(Fixture, "quantify_even_that_table_was_never_exported_at_all")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauSharedSelf, true};
|
||||
ScopedFastFlag sff2{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
@ -3568,7 +3583,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
|
||||
{
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
local foo = s:absolutely_no_scalar_has_this_method()
|
||||
@ -3576,27 +3590,38 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) ==
|
||||
"Type pack 't1 where t1 = { absolutely_no_scalar_has_this_method: (t1) -> (unknown, a...) }' could not be converted into 'string'; at "
|
||||
"[0], t1 where t1 = { absolutely_no_scalar_has_this_method: (t1) -> (unknown, a...) } is not a subtype of string");
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
|
||||
CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value.");
|
||||
// FIXME: These free types should have been generalized by now.
|
||||
CHECK(toString(result.errors[1]) == "Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: ('a <: (never) -> ('b, c...)) -}' here.");
|
||||
CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here.");
|
||||
CHECK(get<CannotCallNonFunction>(result.errors[3]));
|
||||
|
||||
CHECK_EQ("(never) -> string", toString(requireType("f")));
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
const std::string expected =
|
||||
R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
||||
CHECK_EQ("<a, b...>(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f")));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly")
|
||||
{
|
||||
// We need egraphs to simplify the type of `out` here. CLI-114134
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function stringByteList(str)
|
||||
local out = {}
|
||||
@ -3621,17 +3646,29 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = {}
|
||||
function t.m(x) return x end
|
||||
function t.m<T>(x: T) return x end
|
||||
local a : string = t.m("hi")
|
||||
local b : number = t.m(5)
|
||||
function f(x : { m : (number)->number })
|
||||
x.m = function(x) return 1+x end
|
||||
x.m = function(x: number) return 1+x end
|
||||
end
|
||||
|
||||
f(t) -- This shouldn't typecheck
|
||||
|
||||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// FIXME. We really should be reporting just one error in this case. CLI-114509
|
||||
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
||||
|
||||
CHECK(get<TypePackMismatch>(result.errors[0]));
|
||||
CHECK(get<TypeMismatch>(result.errors[1]));
|
||||
CHECK(get<TypeMismatch>(result.errors[2]));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: test behavior is wrong until we can re-enable the covariant requirement for instantiation in subtyping
|
||||
// LUAU_REQUIRE_ERRORS(result);
|
||||
// CHECK_EQ(toString(result.errors[0]), R"(Type 't' could not be converted into '{| m: (number) -> number |}'
|
||||
@ -3640,8 +3677,10 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
||||
// parameters)");
|
||||
// // this error message is not great since the underlying issue is that the context is invariant,
|
||||
// and `(number) -> number` cannot be a subtype of `<a>(a) -> a`.
|
||||
}
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_table_instantiation_potential_regression")
|
||||
{
|
||||
@ -3657,12 +3696,23 @@ local g : ({ p : number, q : string }) -> ({ p : number, r : boolean }) = f
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingProperties* error = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE(error != nullptr);
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
const TypeMismatch* error = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(error, "Expected TypeMismatch but got " << result.errors[0]);
|
||||
|
||||
CHECK("({ p: number, q: string }) -> { p: number, r: boolean }" == toString(error->wantedType));
|
||||
CHECK("({ p: number }) -> { p: number }" == toString(error->givenType));
|
||||
}
|
||||
else
|
||||
{
|
||||
const MissingProperties* error = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << result.errors[0]);
|
||||
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
CHECK_EQ("r", error->properties[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect")
|
||||
{
|
||||
@ -3804,6 +3854,9 @@ TEST_CASE_FIXTURE(Fixture, "when_augmenting_an_unsealed_table_with_an_indexer_ap
|
||||
CHECK(tt->props.empty());
|
||||
REQUIRE(tt->indexer);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("unknown" == toString(tt->indexer->indexType));
|
||||
else
|
||||
CHECK("string" == toString(tt->indexer->indexType));
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
@ -3829,6 +3882,9 @@ TEST_CASE_FIXTURE(Fixture, "dont_extend_unsealed_tables_in_rvalue_position")
|
||||
|
||||
CHECK(0 == ttv->props.count(""));
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
@ -3979,6 +4035,22 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
const TypeMismatch* err1 = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(err1, "Expected TypeMismatch but got " << result.errors[0]);
|
||||
|
||||
CHECK("{Thing}" == toString(err1->wantedType));
|
||||
CHECK("{{ name: string }}" == toString(err1->givenType));
|
||||
|
||||
const TypeMismatch* err2 = get<TypeMismatch>(result.errors[1]);
|
||||
REQUIRE_MESSAGE(err2, "Expected TypeMismatch but got " << result.errors[1]);
|
||||
|
||||
CHECK("{ [string]: Thing }" == toString(err2->wantedType));
|
||||
CHECK("{ [string]: { name: string } }" == toString(err2->givenType));
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeError& err1 = result.errors[0];
|
||||
MissingProperties* error1 = get<MissingProperties>(err1);
|
||||
REQUIRE(error1);
|
||||
@ -3995,6 +4067,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
|
||||
|
||||
CHECK_EQ("prop", error2->properties[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "simple_method_definition")
|
||||
{
|
||||
@ -4314,7 +4387,8 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("({number}, unknown) -> ()", toString(requireType("f")));
|
||||
// FIXME CLI-114134. We need to simplify types more consistently.
|
||||
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "write_to_union_property_not_all_present")
|
||||
|
@ -19,7 +19,6 @@
|
||||
LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping);
|
||||
LUAU_FASTFLAG(LuauLeadingBarAndAmpersand2)
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTINT(LuauNormalizeCacheLimit);
|
||||
LUAU_FASTINT(LuauRecursionLimit);
|
||||
@ -1575,7 +1574,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_iter_metamethod")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "leading_bar")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
CheckResult result = check(R"(
|
||||
type Bar = | number
|
||||
)");
|
||||
@ -1586,7 +1584,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "leading_bar_question_mark")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
CheckResult result = check(R"(
|
||||
type Bar = |?
|
||||
)");
|
||||
@ -1598,7 +1595,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar_question_mark")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "leading_ampersand")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
CheckResult result = check(R"(
|
||||
type Amp = & string
|
||||
)");
|
||||
@ -1609,7 +1605,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_ampersand")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "leading_bar_no_type")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
CheckResult result = check(R"(
|
||||
type Bar = |
|
||||
)");
|
||||
@ -1621,7 +1616,6 @@ TEST_CASE_FIXTURE(Fixture, "leading_bar_no_type")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "leading_ampersand_no_type")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauLeadingBarAndAmpersand2, true};
|
||||
CheckResult result = check(R"(
|
||||
type Amp = &
|
||||
)");
|
||||
|
@ -51,6 +51,9 @@ static bool skipFastFlag(const char* flagName)
|
||||
if (strncmp(flagName, "Debug", 5) == 0)
|
||||
return true;
|
||||
|
||||
if (strcmp(flagName, "StudioReportLuauAny") == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,8 @@ AutocompleteTest.suggest_table_keys
|
||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_table
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
|
||||
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
BuiltinTests.coroutine_resume_anything_goes
|
||||
BuiltinTests.gmatch_capture_types
|
||||
BuiltinTests.gmatch_capture_types2
|
||||
@ -31,20 +29,8 @@ BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
BuiltinTests.string_format_use_correct_argument2
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
BuiltinTests.tonumber_returns_optional_number_type
|
||||
Differ.metatable_metamissing_left
|
||||
Differ.metatable_metamissing_right
|
||||
Differ.metatable_metanormal
|
||||
Differ.negation
|
||||
FrontendTest.environments
|
||||
FrontendTest.imported_table_modification_2
|
||||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||
FrontendTest.trace_requires_in_nonstrict_mode
|
||||
GenericsTests.apply_type_function_nested_generics1
|
||||
GenericsTests.better_mismatch_error_messages
|
||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
||||
GenericsTests.do_not_infer_generic_functions
|
||||
GenericsTests.dont_substitute_bound_types
|
||||
GenericsTests.error_detailed_function_mismatch_generic_pack
|
||||
GenericsTests.error_detailed_function_mismatch_generic_types
|
||||
@ -54,19 +40,13 @@ GenericsTests.generic_argument_count_too_many
|
||||
GenericsTests.generic_factories
|
||||
GenericsTests.generic_functions_in_types
|
||||
GenericsTests.generic_type_functions_work_in_subtyping
|
||||
GenericsTests.generic_type_pack_parentheses
|
||||
GenericsTests.generic_type_pack_unification1
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.generic_type_pack_unification3
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
GenericsTests.infer_generic_function_function_argument_2
|
||||
GenericsTests.infer_generic_function_function_argument_3
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
GenericsTests.properties_can_be_instantiated_polytypes
|
||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
IntersectionTypes.error_detailed_intersection_all
|
||||
IntersectionTypes.error_detailed_intersection_part
|
||||
IntersectionTypes.intersect_bool_and_false
|
||||
@ -145,22 +125,18 @@ RefinementTest.typeguard_cast_free_table_to_vector
|
||||
RefinementTest.typeguard_in_assert_position
|
||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||
RefinementTest.x_is_not_instance_or_else_not_part
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||
TableTests.array_factory_function
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.checked_prop_too_early
|
||||
TableTests.cli_84607_missing_prop_in_array_or_dict
|
||||
TableTests.common_table_element_general
|
||||
TableTests.common_table_element_union_in_call_tail
|
||||
TableTests.confusing_indexing
|
||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_indexer_key
|
||||
@ -170,20 +146,15 @@ TableTests.explicitly_typed_table
|
||||
TableTests.explicitly_typed_table_error
|
||||
TableTests.explicitly_typed_table_with_indexer
|
||||
TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.infer_indexer_from_array_like_table
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add
|
||||
TableTests.meta_add_inferred
|
||||
TableTests.metatable_mismatch_should_fail
|
||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||
TableTests.nil_assign_doesnt_hit_indexer
|
||||
TableTests.ok_to_provide_a_subtype_during_construction
|
||||
TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr
|
||||
@ -191,22 +162,15 @@ TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
|
||||
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
|
||||
TableTests.only_ascribe_synthetic_names_at_module_scope
|
||||
TableTests.open_table_unification_2
|
||||
TableTests.parameter_was_set_an_indexer_and_bounded_by_another_parameter
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||
TableTests.persistent_sealed_table_is_immutable
|
||||
TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||
TableTests.quantify_metatables_of_metatables_of_table
|
||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||
TableTests.recursive_metatable_type_call
|
||||
TableTests.right_table_missing_key2
|
||||
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
TableTests.sealed_table_indexers_must_unify
|
||||
TableTests.setmetatable_has_a_side_effect
|
||||
TableTests.shared_selfs
|
||||
TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_call_metamethod_must_be_callable
|
||||
TableTests.table_param_width_subtyping_2
|
||||
@ -219,7 +183,6 @@ TableTests.table_unifies_into_map
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
|
||||
ToDot.function
|
||||
ToString.exhaustive_toString_of_cyclic_table
|
||||
ToString.free_types
|
||||
@ -363,7 +326,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.promise_type_error_too_complex
|
||||
TypeInferOperators.add_type_function_works
|
||||
TypeInferOperators.cli_38355_recursive_union
|
||||
TypeInferOperators.compound_assign_result_must_be_compatible_with_var
|
||||
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
|
||||
|
Loading…
Reference in New Issue
Block a user