From 5e0779fd57edf4669ab63ddc02b4c830bbdcd18e Mon Sep 17 00:00:00 2001 From: Junseo Yoo <59751754+joonyoo181@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:47:49 -0700 Subject: [PATCH] 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 Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: Dibri Nsofor Co-authored-by: Jeremy Yoo Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Aaron Weiss Co-authored-by: Alexander McCord Co-authored-by: Andy Friesen Co-authored-by: Vighnesh Co-authored-by: Aviral Goel Co-authored-by: David Cope Co-authored-by: Lily Brown Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/AnyTypeSummary.h | 131 ++++ Analysis/include/Luau/AstQuery.h | 16 + Analysis/include/Luau/Frontend.h | 2 + Analysis/include/Luau/Module.h | 6 + Analysis/include/Luau/Normalize.h | 1 + Analysis/src/AnyTypeSummary.cpp | 820 ++++++++++++++++++++ Analysis/src/AstQuery.cpp | 80 +- Analysis/src/ConstraintGenerator.cpp | 21 +- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 229 +----- Analysis/src/Frontend.cpp | 21 +- Analysis/src/Linter.cpp | 5 +- Analysis/src/Normalize.cpp | 9 + Analysis/src/TypeInfer.cpp | 60 +- Ast/src/Ast.cpp | 5 - Ast/src/Lexer.cpp | 9 +- Ast/src/Parser.cpp | 54 +- Common/include/Luau/ExperimentalFlags.h | 2 +- EqSat/src/UnionFind.cpp | 2 - Sources.cmake | 3 + VM/src/ltm.cpp | 78 +- tests/AnyTypeSummary.test.cpp | 745 ++++++++++++++++++ tests/AstQuery.test.cpp | 30 + tests/Conformance.test.cpp | 19 +- tests/Differ.test.cpp | 10 +- tests/Frontend.test.cpp | 28 +- tests/Linter.test.cpp | 3 +- tests/NonStrictTypeChecker.test.cpp | 8 - tests/Parser.test.cpp | 57 +- tests/ToString.test.cpp | 2 - tests/TypeInfer.builtins.test.cpp | 25 +- tests/TypeInfer.cfa.test.cpp | 86 -- tests/TypeInfer.functions.test.cpp | 13 + tests/TypeInfer.generics.test.cpp | 132 +++- tests/TypeInfer.modules.test.cpp | 3 - tests/TypeInfer.oop.test.cpp | 31 + tests/TypeInfer.operators.test.cpp | 4 +- tests/TypeInfer.tables.test.cpp | 160 +++- tests/TypeInfer.test.cpp | 6 - tests/main.cpp | 3 + tools/faillist.txt | 38 - 40 files changed, 2281 insertions(+), 676 deletions(-) create mode 100644 Analysis/include/Luau/AnyTypeSummary.h create mode 100644 Analysis/src/AnyTypeSummary.cpp create mode 100644 tests/AnyTypeSummary.test.cpp diff --git a/Analysis/include/Luau/AnyTypeSummary.h b/Analysis/include/Luau/AnyTypeSummary.h new file mode 100644 index 000000000..c1444db2e --- /dev/null +++ b/Analysis/include/Luau/AnyTypeSummary.h @@ -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 +#include +#include +#include + +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 seenTypeFamilyInstances{nullptr}; + + int recursionCount = 0; + + std::string root; + int strictCount = 0; + + DenseHashMap seen{nullptr}; + + AnyTypeSummary(); + + void traverse(Module* module, AstStat* src, NotNull builtinTypes); + + std::pair 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); + bool isAnyCall(Scope* scope, AstExpr* expr, Module* module, NotNull builtinTypes); + + bool hasVariadicAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); + bool hasArgAnys(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); + bool hasAnyReturns(Scope* scope, AstExprFunction* expr, Module* module, NotNull builtinTypes); + + TypeId checkForFamilyInhabitance(TypeId instance, Location location); + TypeId lookupType(AstExpr* expr, Module* module, NotNull builtinTypes); + TypePackId reconstructTypePack(AstArray exprs, Module* module, NotNull builtinTypes); + + DenseHashSet seenTypeFunctionInstances{nullptr}; + TypeId lookupAnnotation(AstType* annotation, Module* module, NotNull builtintypes); + std::optional 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; + + /** + * 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); + void visit(Scope* scope, AstStatBlock* block, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatIf* ifStatement, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatWhile* while_, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatLocal* local, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatFor* for_, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatCompoundAssign* assign, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatFunction* function, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatLocalFunction* function, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatTypeAlias* alias, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatExpr* expr, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatDeclareGlobal* declareGlobal, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull builtinTypes); + void visit(Scope* scope, AstStatError* error, Module* module, NotNull builtinTypes); +}; + +} // namespace Luau \ No newline at end of file diff --git a/Analysis/include/Luau/AstQuery.h b/Analysis/include/Luau/AstQuery.h index 7652a89f0..633d6faf5 100644 --- a/Analysis/include/Luau/AstQuery.h +++ b/Analysis/include/Luau/AstQuery.h @@ -61,6 +61,22 @@ struct ExprOrLocal AstLocal* local = nullptr; }; +struct FindFullAncestry final : public AstVisitor +{ + std::vector 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 findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos); std::vector findAncestryAtPositionForAutocomplete(AstStatBlock* root, Position pos); std::vector findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false); diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 27a67f40c..2a70b072e 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -9,6 +9,7 @@ #include "Luau/Scope.h" #include "Luau/TypeCheckLimits.h" #include "Luau/Variant.h" +#include "Luau/AnyTypeSummary.h" #include #include @@ -31,6 +32,7 @@ struct ParseResult; struct HotComment; struct BuildQueueItem; struct FrontendCancellationToken; +struct AnyTypeSummary; struct LoadDefinitionFileResult { diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 152d8c65c..f909deb8e 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -8,6 +8,7 @@ #include "Luau/ParseResult.h" #include "Luau/Scope.h" #include "Luau/TypeArena.h" +#include "Luau/AnyTypeSummary.h" #include #include @@ -18,6 +19,7 @@ namespace Luau { struct Module; +struct AnyTypeSummary; using ScopePtr = std::shared_ptr; using ModulePtr = std::shared_ptr; @@ -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; std::shared_ptr names; diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index b21e470cb..d844d211d 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -59,6 +59,7 @@ class TypeIds const_iterator begin() const; const_iterator end() const; iterator erase(const_iterator it); + void erase(TypeId ty); size_t size() const; bool empty() const; diff --git a/Analysis/src/AnyTypeSummary.cpp b/Analysis/src/AnyTypeSummary.cpp new file mode 100644 index 000000000..3bcb51445 --- /dev/null +++ b/Analysis/src/AnyTypeSummary.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +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 getInferredType(AstExpr* expr, Module* module) +{ + std::optional 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) +{ + Scope* scope = findInnerMostScope(src->location, module); + visit(scope, src, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStat* stat, Module* module, NotNull builtinTypes) +{ + RecursionLimiter limiter{&recursionCount, FInt::LuauAnySummaryRecursionLimit}; + + if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto i = stat->as()) + return visit(scope, i, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto r = stat->as()) + return visit(scope, r, module, builtinTypes); + else if (auto e = stat->as()) + return visit(scope, e, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto a = stat->as()) + return visit(scope, a, module, builtinTypes); + else if (auto a = stat->as()) + return visit(scope, a, module, builtinTypes); + else if (auto f = stat->as()) + return visit(scope, f, module, builtinTypes); + else if (auto f = stat->as()) + return visit(scope, f, module, builtinTypes); + else if (auto a = stat->as()) + return visit(scope, a, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); + else if (auto s = stat->as()) + return visit(scope, s, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStatBlock* block, Module* module, NotNull 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) +{ + 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) +{ + Scope* whileScope = findInnerMostScope(while_->location, module); + visit(whileScope, while_->body, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStatRepeat* repeat, Module* module, NotNull builtinTypes) +{ + Scope* repeatScope = findInnerMostScope(repeat->location, module); + visit(repeatScope, repeat->body, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStatReturn* ret, Module* module, NotNull 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()) + { + 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) +{ + 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) +{ + Scope* forScope = findInnerMostScope(for_->location, module); + visit(forScope, for_->body, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStatForIn* forIn, Module* module, NotNull builtinTypes) +{ + Scope* loopScope = findInnerMostScope(forIn->location, module); + visit(loopScope, forIn->body, module, builtinTypes); +} + +void AnyTypeSummary::visit(Scope* scope, AstStatAssign* assign, Module* module, NotNull 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()) + { + 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) +{ + 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()) + { + 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) +{ + 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) +{ + 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) +{ + + 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) +{ + 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) {} + +void AnyTypeSummary::visit(Scope* scope, AstStatDeclareClass* declareClass, Module* module, NotNull builtinTypes) {} + +void AnyTypeSummary::visit(Scope* scope, AstStatDeclareFunction* declareFunction, Module* module, NotNull builtinTypes) {} + +void AnyTypeSummary::visit(Scope* scope, AstStatError* error, Module* module, NotNull 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) +{ + 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 exprs, Module* module, NotNull builtinTypes) +{ + if (exprs.size == 0) + return arena.addTypePack(TypePack{{}, std::nullopt}); + + std::vector 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) +{ + if (auto call = expr->as()) + { + 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) +{ + 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) +{ + 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) +{ + 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) +{ + if (auto cast = expr->as()) + { + auto annot = lookupAnnotation(cast->annotation, module, builtinTypes); + if (containsAny(annot)) + { + return true; + } + } + return false; +} + +TypeId AnyTypeSummary::lookupAnnotation(AstType* annotation, Module* module, NotNull builtintypes) +{ + if (FFlag::DebugLuauMagicTypes) + { + if (auto ref = annotation->as(); 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 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(typ)) + { + found = true; + } + else if (auto ty = get(typ)) + { + found = true; + } + else if (auto ty = get(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(typ)) + { + for (auto part : ty->parts) + { + if (containsAny(part)) + { + found = true; + } + } + } + else if (auto ty = get(typ)) + { + for (auto option : ty->options) + { + if (containsAny(option)) + { + found = true; + } + } + } + else if (auto ty = get(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(tail)) + { + if (auto ty = get(follow(vtp->ty))) + { + found = true; + } + } + else if (auto tftp = get(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 \ No newline at end of file diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 928e5dfbc..be296f726 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -177,60 +177,52 @@ struct FindNode : public AstVisitor } }; -struct FindFullAncestry final : public AstVisitor +} // namespace + +FindFullAncestry::FindFullAncestry(Position pos, Position documentEnd, bool includeTypes) + : pos(pos) + , documentEnd(documentEnd) + , includeTypes(includeTypes) { - std::vector nodes; - Position pos; - Position documentEnd; - bool includeTypes = false; +} - explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false) - : pos(pos) - , documentEnd(documentEnd) - , includeTypes(includeTypes) - { - } +bool FindFullAncestry::visit(AstType* type) +{ + if (includeTypes) + return visit(static_cast(type)); + else + return false; +} - bool visit(AstType* type) override - { - if (includeTypes) - return visit(static_cast(type)); - else - return false; - } +bool FindFullAncestry::visit(AstStatFunction* node) +{ + visit(static_cast(node)); + if (node->name->location.contains(pos)) + node->name->visit(this); + else if (node->func->location.contains(pos)) + node->func->visit(this); + return false; +} - bool visit(AstStatFunction* node) override +bool FindFullAncestry::visit(AstNode* node) +{ + if (node->location.contains(pos)) { - visit(static_cast(node)); - if (node->name->location.contains(pos)) - node->name->visit(this); - else if (node->func->location.contains(pos)) - node->func->visit(this); - return false; + nodes.push_back(node); + return true; } - bool visit(AstNode* node) override - { - if (node->location.contains(pos)) - { - nodes.push_back(node); - return true; - } - - // Edge case: If we ask for the node at the position that is the very end of the document - // return the innermost AST element that ends at that position. + // Edge case: If we ask for the node at the position that is the very end of the document + // return the innermost AST element that ends at that position. - if (node->location.end == documentEnd && pos >= documentEnd) - { - nodes.push_back(node); - return true; - } - - return false; + if (node->location.end == documentEnd && pos >= documentEnd) + { + nodes.push_back(node); + return true; } -}; -} // namespace + return false; +} std::vector findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos) { diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 9f776b64c..d3e3f5962 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -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(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(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. diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 91d8006a4..e539661a4 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -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...) - -declare function type(value: T): string -declare function typeof(value: T): string - --- `assert` has a magic function attached that will give more detailed type information -declare function assert(value: T, errorMessage: string?): T -declare function error(message: T, level: number?): never - -declare function tostring(value: T): string -declare function tonumber(value: T, radix: number?): number? - -declare function rawequal(a: T1, b: T2): boolean -declare function rawget(tab: {[K]: V}, k: K): V -declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} -declare function rawlen(obj: {[K]: V} | string): number - -declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? - -declare function ipairs(tab: {V}): (({V}, number) -> (number?, V), {V}, number) - -declare function pcall(f: (A...) -> R..., ...: A...): (boolean, R...) - --- FIXME: The actual type of `xpcall` is: --- (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(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...) - --- `select` has a magic function attached to provide more detailed type information -declare function select(i: string | number, ...: A...): ...any - --- FIXME: This type is not entirely correct - `loadstring` returns a function or --- (nil, string). -declare function loadstring(src: string, chunkname: string?): (((A...) -> any)?, string?) - -declare function newproxy(mt: boolean?): any - -declare coroutine: { - create: (f: (A...) -> R...) -> thread, - resume: (co: thread, A...) -> (boolean, R...), - running: () -> thread, - status: (co: thread) -> "dead" | "running" | "normal" | "suspended", - wrap: (f: (A...) -> R...) -> ((A...) -> R...), - yield: (A...) -> R..., - isyieldable: () -> boolean, - close: (co: thread) -> (boolean, any) -} - -declare table: { - concat: (t: {V}, sep: string?, i: number?, j: number?) -> string, - insert: ((t: {V}, value: V) -> ()) & ((t: {V}, pos: number, value: V) -> ()), - maxn: (t: {V}) -> number, - remove: (t: {V}, number?) -> V?, - sort: (t: {V}, comp: ((V, V) -> boolean)?) -> (), - create: (count: number, value: V?) -> {V}, - find: (haystack: {V}, needle: V, init: number?) -> number?, - - unpack: (list: {V}, i: number?, j: number?) -> ...V, - pack: (...V) -> { n: number, [number]: V }, - - getn: (t: {V}) -> number, - foreach: (t: {[K]: V}, f: (K, V) -> ()) -> (), - foreachi: ({V}, (number, V) -> ()) -> (), - - move: (src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V}, - clear: (table: {[K]: V}) -> (), - - isfrozen: (t: {[K]: V}) -> boolean, -} - -declare debug: { - info: ((thread: thread, level: number, options: string) -> R...) & ((level: number, options: string) -> R...) & ((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(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; } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 9f59eeb8a..810a1786d 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -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::optionallintResult; + + 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; diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index e9d4ca53a..586ff7b00 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -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& ho static bool hasNativeCommentDirective(const std::vector& 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 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)) diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 9585cdc75..fda70953a 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -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(); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 00d683dd4..7df906289 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -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()) return check(scope, *repeat); else if (program.is()) - return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Breaks : ControlFlow::None; + return ControlFlow::Breaks; else if (program.is()) - return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Continues : ControlFlow::None; + return ControlFlow::Continues; else if (auto return_ = program.as()) return check(scope, *return_); else if (auto expr = program.as()) { checkExprPack(scope, *expr->expr); - if (FFlag::LuauTinyControlFlowAnalysis) - { - if (auto call = expr->expr->as(); call && doesCallError(call)) - return ControlFlow::Throws; - } + if (auto call = expr->expr->as(); call && doesCallError(call)) + return ControlFlow::Throws; return ControlFlow::None; } @@ -740,41 +736,25 @@ 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); + ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location); + resolve(result.predicates, elseScope, false); - ControlFlow thencf = check(thenScope, *statement.thenbody); - ControlFlow elsecf = ControlFlow::None; - if (statement.elsebody) - elsecf = check(elseScope, *statement.elsebody); + ControlFlow thencf = check(thenScope, *statement.thenbody); + ControlFlow elsecf = ControlFlow::None; + if (statement.elsebody) + elsecf = check(elseScope, *statement.elsebody); - if (thencf != ControlFlow::None && elsecf == ControlFlow::None) - scope->inheritRefinements(elseScope); - else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) - scope->inheritRefinements(thenScope); + if (thencf != ControlFlow::None && elsecf == ControlFlow::None) + scope->inheritRefinements(elseScope); + else if (thencf == ControlFlow::None && elsecf != ControlFlow::None) + scope->inheritRefinements(thenScope); - if (FFlag::LuauTinyControlFlowAnalysis && 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; - } + if (thencf == elsecf) + return thencf; + else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws)) + return ControlFlow::Returns; 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 @@ -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 diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index a3e53af5a..e48ae2a1f 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -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) diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 8e9b3be94..0ea052965 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -8,7 +8,6 @@ #include 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: @@ -1007,11 +1005,8 @@ Lexeme Lexer::readNext() } case '@': { - if (FFlag::LuauAttributeSyntax) - { - std::pair attribute = readName(); - return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); - } + std::pair attribute = readName(); + return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); } default: if (isDigit(peekch())) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index bfe52ddca..9701cbc54 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -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,8 +319,7 @@ AstStat* Parser::parseStat() case Lexeme::ReservedBreak: return parseBreak(); case Lexeme::Attribute: - if (FFlag::LuauAttributeSyntax) - return parseAttributeStat(); + return parseAttributeStat(); default:; } @@ -692,8 +689,6 @@ AstStat* Parser::parseFunctionStat(const AstArray& attributes) std::pair Parser::validateAttribute(const char* attributeName, const TempVector& attributes) { - LUAU_ASSERT(FFlag::LuauAttributeSyntax); - AstAttr::Type type; // check if the attribute name is valid @@ -739,8 +734,6 @@ std::pair Parser::validateAttribute(const char* attributeNa // attribute ::= '@' NAME void Parser::parseAttribute(TempVector& 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& attributes) // attributes ::= {attribute} AstArray Parser::parseAttributes() { - LUAU_ASSERT(FFlag::LuauAttributeSyntax); - Lexeme::Type type = lexer.current().type; LUAU_ASSERT(type == Lexeme::Attribute); @@ -777,8 +768,6 @@ AstArray Parser::parseAttributes() // declare Name '{' Name ':' attributes `(' [parlist] `)' [`:` Type] '}' AstStat* Parser::parseAttributeStat() { - LUAU_ASSERT(FFlag::LuauAttributeSyntax); - AstArray attributes = parseAttributes(); Lexeme::Type type = lexer.current().type; @@ -834,7 +823,7 @@ AstStat* Parser::parseLocal(const AstArray& 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 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,30 +1803,19 @@ AstType* Parser::parseType(bool inDeclarationContext) Location begin = lexer.current().location; - if (FFlag::LuauLeadingBarAndAmpersand2) - { - AstType* type = nullptr; + AstType* type = nullptr; - Lexeme::Type c = lexer.current().type; - if (c != '|' && c != '&') - { - type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type; - recursionCounter = oldRecursionCount; - } - - AstType* typeWithSuffix = parseTypeSuffix(type, begin); + Lexeme::Type c = lexer.current().type; + if (c != '|' && c != '&') + { + type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type; recursionCounter = oldRecursionCount; - - return typeWithSuffix; } - else - { - AstType* type = parseSimpleType(/* allowPack= */ false, /* in declaration context */ inDeclarationContext).type; - recursionCounter = oldRecursionCount; + AstType* typeWithSuffix = parseTypeSuffix(type, begin); + recursionCounter = oldRecursionCount; - return parseTypeSuffix(type, begin); - } + return typeWithSuffix; } // Type ::= nil | Name[`.' Name] [ `<' Type [`,' ...] `>' ] | `typeof' `(' expr `)' | `{' [PropList] `}' @@ -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 attributes{nullptr, 0}; - if (FFlag::LuauAttributeSyntax && FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute) + if (FFlag::LuauAttributeSyntaxFunExpr && lexer.current().type == Lexeme::Attribute) { attributes = parseAttributes(); diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index b02d37778..f83de70b2 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -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, }; diff --git a/EqSat/src/UnionFind.cpp b/EqSat/src/UnionFind.cpp index 4d7dd4b24..3c4825be7 100644 --- a/EqSat/src/UnionFind.cpp +++ b/EqSat/src/UnionFind.cpp @@ -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)]++; } diff --git a/Sources.cmake b/Sources.cmake index c6bbfdbc2..80bcd5b25 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -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 diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index 09c3d8242..16775f9b9 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -10,6 +10,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauPreserveLudataRenaming, false) + // clang-format off const char* const luaT_typenames[] = { // ORDER TYPE @@ -122,34 +124,74 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event) const TString* luaT_objtypenamestr(lua_State* L, const TValue* o) { - if (ttisuserdata(o) && uvalue(o)->tag != UTAG_PROXY && uvalue(o)->metatable) + if (FFlag::LuauPreserveLudataRenaming) { - const TValue* type = luaH_getstr(uvalue(o)->metatable, L->global->tmname[TM_TYPE]); + // 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); - } - else if (ttislightuserdata(o)) - { - int tag = lightuserdatatag(o); + if (ttisstring(type)) + return tsvalue(type); + + return L->global->ttname[ttype(o)]; + } - if (unsigned(tag) < LUA_LUTAG_LIMIT) + // Tagged lightuserdata can be named using lua_setlightuserdataname + if (ttislightuserdata(o)) { - const TString* name = L->global->lightuserdataname[tag]; + int tag = lightuserdatatag(o); - if (name) - return name; + 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 (Table* mt = L->global->mt[ttype(o)]) + else { - const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]); + 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); - } + if (ttisstring(type)) + return tsvalue(type); + } + else if (ttislightuserdata(o)) + { + int tag = lightuserdatatag(o); + + if (unsigned(tag) < LUA_LUTAG_LIMIT) + { + const TString* name = L->global->lightuserdataname[tag]; - return L->global->ttname[ttype(o)]; + if (name) + return name; + } + } + else 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)]; + } } const char* luaT_objtypename(lua_State* L, const TValue* o) diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp new file mode 100644 index 000000000..705e7e465 --- /dev/null +++ b/tests/AnyTypeSummary.test.cpp @@ -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 + +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 = 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 = {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 = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed? +type Pair = (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) -> 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 = { f: a, g: U } + type U = { h: a, i: T? } + local x: T = { f = 37, g = { h = 5, i = nil } } + x.g.i = x + local y: T = { 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...) -> () -- 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(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(); diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index c53fe7314..3c302c379 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -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 declBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {1, 26}); + // REQUIRE(declBinding); + + // CHECK(declBinding->location == Location{{1, 25}, {1, 28}}); + + std::optional innerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {2, 15}); + REQUIRE(innerCallBinding); + + CHECK(innerCallBinding->location == Location{{1, 23}, {1, 27}}); + + std::optional outerCallBinding = findBindingAtPosition(*getMainModule(), *getMainSourceModule(), {5, 8}); + REQUIRE(outerCallBinding); + + CHECK(outerCallBinding->location == Location{{1, 23}, {1, 27}}); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 65af4e4d0..c3cc70c93 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -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 diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp index b009cec0c..311ff5f3d 100644 --- a/tests/Differ.test.cpp +++ b/tests/Differ.test.cpp @@ -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 .x.y.Negation has type string, while the right type at .x.y.Negation has type number)"); + R"(DiffError: these two types are not equal because the left type at is a union containing type { x: { y: ~string } }, while the right type at 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 .x.y.Negation has type string, while the right type at .x.y.Negation has type number)"); } TEST_CASE_FIXTURE(DifferFixture, "union_missing_right") diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index a69994666..3df6c0e79 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -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 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"); - LUAU_REQUIRE_ERROR_COUNT(1, resultB); + // 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"( diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index d74c48557..73aa8905d 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -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 diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 81a84722b..b2be38a73 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -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, diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 627d674b8..481ca981c 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -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& 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."); } diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 17faa2e71..186977976 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -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"( diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index e79227566..f43537b22 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -698,8 +698,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash") )"); LUAU_REQUIRE_ERROR_COUNT(2, result); - 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])); + + 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,7 +952,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0])); + 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])); } TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type2") @@ -991,7 +1004,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types") )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f"))); + + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f"))); + else + CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2") diff --git a/tests/TypeInfer.cfa.test.cpp b/tests/TypeInfer.cfa.test.cpp index b701e960d..e097e18e4 100644 --- a/tests/TypeInfer.cfa.test.cpp +++ b/tests/TypeInfer.cfa.test.cpp @@ -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 = { tag: "ok", value: T } type Err = { 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 = { tag: "ok", value: T } type Err = { 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 = { tag: "ok", value: T } type Err = { 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 diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 94b53cc5e..ef02a4fc3 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -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(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index b98d5a876..33ab45c89 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -632,7 +632,12 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_pack_parentheses") function f(...: a...): any return (...) end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + // 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); } TEST_CASE_FIXTURE(Fixture, "better_mismatch_error_messages") @@ -647,13 +652,27 @@ TEST_CASE_FIXTURE(Fixture, "better_mismatch_error_messages") end )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - SwappedGenericTypeParameter* fErr = get(result.errors[0]); + 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(result.errors[1]); + gErr = get(result.errors[2]); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + fErr = get(result.errors[0]); + gErr = get(result.errors[1]); + } + REQUIRE(fErr); CHECK_EQ(fErr->name, "T"); CHECK_EQ(fErr->kind, SwappedGenericTypeParameter::Pack); - SwappedGenericTypeParameter* gErr = get(result.errors[1]); REQUIRE(gErr); CHECK_EQ(gErr->name, "T"); CHECK_EQ(gErr->kind, SwappedGenericTypeParameter::Type); @@ -1111,7 +1130,10 @@ local a: Self )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "Table"); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK_EQ(toString(requireType("a")), "Table
"); + else + CHECK_EQ(toString(requireType("a")), "Table"); } TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") @@ -1127,7 +1149,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") std::optional t0 = lookupType("t0"); REQUIRE(t0); - CHECK_EQ("*error-type*", toString(*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) { return get(err); @@ -1137,20 +1162,40 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") { - CheckResult result = check(R"( - local function sum(x: a, y: a, f: (a, a) -> a) - return f(x, y) - end - return sum(2, 3, function(a, b) return a + b end) - )"); - LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CheckResult result = check(R"( + local function sum(x: a, y: a, f: (a, a) -> add) + return f(x, y) + end + return sum(2, 3, function(a: T, b: T): add return a + b end) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + InternalError* ie = get(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(x: a, y: a, f: (a, a) -> a) + return f(x, y) + end + return sum(2, 3, function(a, b) return a + b end) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2") { CheckResult result = check(R"( - local function map(arr: {a}, f: (a) -> b) + local function map(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,11 +1221,14 @@ 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); - REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r"))); + 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"))); } TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded") @@ -1214,22 +1262,48 @@ 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"( -local function sum(x: a, y: a, f: (a, a) -> a) return f(x, y) end -local function sumrec(f: typeof(sum)) - return sum(2, 3, function(a, b) return a + b end) -end + CheckResult result; -local b = sumrec(sum) -- ok -local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred - )"); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + result = check(R"( + local function sum(x: a, y: a, f: (a, a) -> a) return f(x, y) end - LUAU_REQUIRE_NO_ERRORS(result); + local function sumrec(f: typeof(sum)) + return sum(2, 3, function(a: T, b: T): add 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(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(x: a, y: a, f: (a, a) -> a) return f(x, y) end + + local function sumrec(f: typeof(sum)) + return sum(2, 3, function(a, b) 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_NO_ERRORS(result); + } } + TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table") { CheckResult result = check(R"( @@ -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(cb: () -> U): U return cb() end, -} +} :: MyObject type ComplexObject = { id: T, diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index dd8093745..13a18c14b 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -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 )"; diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 8234a4fb2..e1d2e1b6f 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -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 clsBinding = b->getModuleScope()->linearSearchForBinding("tbl"); + REQUIRE(clsBinding); + + TypeId clsType = clsBinding->typeId; + + CHECK("{ @metatable cls, tbl }" == toString(clsType)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 0d1ccbbe4..98f5ec8bf 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -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"); - CHECK(toString(result.errors[0]) == "Type function instance Add is uninhabited"); + CHECK(toString(requireType("b")) == "add"); + 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") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 4788545c2..d6c93c237 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -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 } )"); - LUAU_REQUIRE_ERROR_COUNT(3, result); + 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])); + 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(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("(t1) -> string where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}", toString(requireType("f"))); + CHECK_EQ("(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,27 +3646,41 @@ 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(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); - // 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 |}' - // caused by: - // Property 'm' is not compatible. Type '(a) -> a' could not be converted into '(number) -> number'; different number of generic type - // 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`. -} + 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(result.errors[0])); + CHECK(get(result.errors[1])); + CHECK(get(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 |}' + // caused by: + // Property 'm' is not compatible. Type '(a) -> a' could not be converted into '(number) -> number'; different number of generic type + // 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`. + LUAU_REQUIRE_NO_ERRORS(result); + } +} TEST_CASE_FIXTURE(BuiltinsFixture, "generic_table_instantiation_potential_regression") { @@ -3657,11 +3696,22 @@ local g : ({ p : number, q : string }) -> ({ p : number, r : boolean }) = f LUAU_REQUIRE_ERROR_COUNT(1, result); - MissingProperties* error = get(result.errors[0]); - REQUIRE(error != nullptr); - REQUIRE(error->properties.size() == 1); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + const TypeMismatch* error = get(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(result.errors[0]); + REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << result.errors[0]); - CHECK_EQ("r", error->properties[0]); + REQUIRE(error->properties.size() == 1); + CHECK_EQ("r", error->properties[0]); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect") @@ -3804,7 +3854,10 @@ TEST_CASE_FIXTURE(Fixture, "when_augmenting_an_unsealed_table_with_an_indexer_ap CHECK(tt->props.empty()); REQUIRE(tt->indexer); - CHECK("string" == toString(tt->indexer->indexType)); + if (FFlag::DebugLuauDeferredConstraintResolution) + CHECK("unknown" == toString(tt->indexer->indexType)); + else + CHECK("string" == toString(tt->indexer->indexType)); LUAU_REQUIRE_NO_ERRORS(result); } @@ -3829,7 +3882,10 @@ TEST_CASE_FIXTURE(Fixture, "dont_extend_unsealed_tables_in_rvalue_position") CHECK(0 == ttv->props.count("")); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::DebugLuauDeferredConstraintResolution) + LUAU_REQUIRE_ERROR_COUNT(1, result); + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "extend_unsealed_table_with_metatable") @@ -3979,21 +4035,38 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") LUAU_REQUIRE_ERROR_COUNT(2, result); - TypeError& err1 = result.errors[0]; - MissingProperties* error1 = get(err1); - REQUIRE(error1); - REQUIRE(error1->properties.size() == 1); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + const TypeMismatch* err1 = get(result.errors[0]); + REQUIRE_MESSAGE(err1, "Expected TypeMismatch but got " << result.errors[0]); - CHECK_EQ("prop", error1->properties[0]); + CHECK("{Thing}" == toString(err1->wantedType)); + CHECK("{{ name: string }}" == toString(err1->givenType)); - TypeError& err2 = result.errors[1]; - TypeMismatch* mismatch = get(err2); - REQUIRE(mismatch); - MissingProperties* error2 = get(*mismatch->error); - REQUIRE(error2); - REQUIRE(error2->properties.size() == 1); + const TypeMismatch* err2 = get(result.errors[1]); + REQUIRE_MESSAGE(err2, "Expected TypeMismatch but got " << result.errors[1]); - CHECK_EQ("prop", error2->properties[0]); + CHECK("{ [string]: Thing }" == toString(err2->wantedType)); + CHECK("{ [string]: { name: string } }" == toString(err2->givenType)); + } + else + { + TypeError& err1 = result.errors[0]; + MissingProperties* error1 = get(err1); + REQUIRE(error1); + REQUIRE(error1->properties.size() == 1); + + CHECK_EQ("prop", error1->properties[0]); + + TypeError& err2 = result.errors[1]; + TypeMismatch* mismatch = get(err2); + REQUIRE(mismatch); + MissingProperties* error2 = get(*mismatch->error); + REQUIRE(error2); + REQUIRE(error2->properties.size() == 1); + + 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") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 1d1dd9998..fc61ecb36 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -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 = & )"); diff --git a/tests/main.cpp b/tests/main.cpp index 4de391b67..bda1e2fe5 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -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; } diff --git a/tools/faillist.txt b/tools/faillist.txt index 8a31b4301..918b761f7 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -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