From e00dbbeaf2a18e027fa3d59d5e5d063fa02db33d Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 7 Jul 2023 10:14:35 -0700 Subject: [PATCH] Sync to upstream/release/583 --- Analysis/include/Luau/Constraint.h | 10 +- Analysis/include/Luau/Differ.h | 138 +++++++++++ Analysis/include/Luau/Frontend.h | 4 + Analysis/include/Luau/Normalize.h | 9 +- Analysis/include/Luau/Type.h | 2 - Analysis/include/Luau/VisitType.h | 9 +- Analysis/src/Clone.cpp | 20 +- Analysis/src/ConstraintGraphBuilder.cpp | 44 +++- Analysis/src/ConstraintSolver.cpp | 26 +- Analysis/src/Differ.cpp | 273 ++++++++++++++++++++ Analysis/src/Frontend.cpp | 12 + Analysis/src/Normalize.cpp | 56 +++-- Analysis/src/Substitution.cpp | 30 +-- Analysis/src/ToDot.cpp | 9 +- Analysis/src/TypeChecker2.cpp | 5 + Analysis/src/TypeInfer.cpp | 108 +++----- Ast/include/Luau/Location.h | 6 + Ast/include/Luau/TimeTrace.h | 13 +- Ast/src/Location.cpp | 9 + Ast/src/TimeTrace.cpp | 11 +- CodeGen/include/Luau/IrData.h | 15 +- CodeGen/include/Luau/IrRegAllocX64.h | 2 + CodeGen/include/Luau/IrUtils.h | 4 + CodeGen/src/CodeGenA64.cpp | 29 +++ CodeGen/src/CodeGenLower.h | 2 + CodeGen/src/CodeGenX64.cpp | 10 + CodeGen/src/EmitCommon.h | 2 + CodeGen/src/EmitCommonX64.cpp | 17 ++ CodeGen/src/EmitCommonX64.h | 2 + CodeGen/src/EmitInstructionX64.cpp | 5 + CodeGen/src/IrBuilder.cpp | 79 ++++++ CodeGen/src/IrCallWrapperX64.cpp | 8 +- CodeGen/src/IrDump.cpp | 2 + CodeGen/src/IrLoweringA64.cpp | 66 +++-- CodeGen/src/IrLoweringA64.h | 7 + CodeGen/src/IrLoweringX64.cpp | 51 +++- CodeGen/src/IrLoweringX64.h | 9 +- CodeGen/src/IrRegAllocA64.cpp | 21 +- CodeGen/src/IrRegAllocA64.h | 2 + CodeGen/src/IrRegAllocX64.cpp | 8 + CodeGen/src/IrTranslateBuiltins.cpp | 19 ++ CodeGen/src/IrTranslation.cpp | 12 +- CodeGen/src/IrUtils.cpp | 29 ++- CodeGen/src/OptimizeConstProp.cpp | 13 +- Compiler/src/BytecodeBuilder.cpp | 4 +- Compiler/src/Compiler.cpp | 19 +- Compiler/src/Types.cpp | 156 ++++++++++-- Compiler/src/Types.h | 6 +- Makefile | 6 +- Sources.cmake | 3 + VM/src/lfunc.cpp | 4 + VM/src/lobject.h | 2 + VM/src/lvmload.cpp | 17 +- fuzz/proto.cpp | 21 ++ tests/ClassFixture.cpp | 1 - tests/Compiler.test.cpp | 105 +++++++- tests/Differ.test.cpp | 316 ++++++++++++++++++++++++ tests/Frontend.test.cpp | 31 +++ tests/IrBuilder.test.cpp | 56 +++-- tests/IrCallWrapperX64.test.cpp | 44 +++- tests/Normalize.test.cpp | 31 +++ tests/TypeInfer.classes.test.cpp | 1 - tests/TypeInfer.definitions.test.cpp | 1 - tests/TypeInfer.functions.test.cpp | 42 ++++ tests/TypeInfer.refinements.test.cpp | 71 +++++- tests/TypeInfer.singletons.test.cpp | 14 ++ tests/conformance/native.lua | 31 +++ tools/faillist.txt | 4 +- 68 files changed, 1897 insertions(+), 297 deletions(-) create mode 100644 Analysis/include/Luau/Differ.h create mode 100644 Analysis/src/Differ.cpp create mode 100644 tests/Differ.test.cpp diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index aa1d1c0e..67f9470e 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -34,6 +34,11 @@ struct PackSubtypeConstraint { TypePackId subPack; TypePackId superPack; + + // HACK!! TODO clip. + // We need to know which of `PackSubtypeConstraint` are emitted from `AstStatReturn` vs any others. + // Then we force these specific `PackSubtypeConstraint` to only dispatch in the order of the `return`s. + bool returns = false; }; // generalizedType ~ gen sourceType @@ -108,13 +113,12 @@ struct FunctionCallConstraint TypeId fn; TypePackId argsPack; TypePackId result; - class AstExprCall* callSite; + class AstExprCall* callSite = nullptr; std::vector> discriminantTypes; // When we dispatch this constraint, we update the key at this map to record // the overload that we selected. - DenseHashMap* astOriginalCallTypes; - DenseHashMap* astOverloadResolvedTypes; + DenseHashMap* astOverloadResolvedTypes = nullptr; }; // result ~ prim ExpectedType SomeSingletonType MultitonType diff --git a/Analysis/include/Luau/Differ.h b/Analysis/include/Luau/Differ.h new file mode 100644 index 00000000..ad276b4f --- /dev/null +++ b/Analysis/include/Luau/Differ.h @@ -0,0 +1,138 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Type.h" +#include +#include + +namespace Luau +{ +struct DiffPathNode +{ + // TODO: consider using Variants to simplify toString implementation + enum Kind + { + TableProperty, + FunctionArgument, + FunctionReturn, + Union, + Intersection, + }; + Kind kind; + // non-null when TableProperty + std::optional tableProperty; + // non-null when FunctionArgument, FunctionReturn, Union, or Intersection (i.e. anonymous fields) + std::optional index; + + /** + * Do not use for leaf nodes + */ + DiffPathNode(Kind kind) + : kind(kind) + { + } + + DiffPathNode(Kind kind, std::optional tableProperty, std::optional index) + : kind(kind) + , tableProperty(tableProperty) + , index(index) + { + } + + std::string toString() const; + + static DiffPathNode constructWithTableProperty(Name tableProperty); +}; +struct DiffPathNodeLeaf +{ + std::optional ty; + std::optional tableProperty; + DiffPathNodeLeaf(std::optional ty, std::optional tableProperty) + : ty(ty) + , tableProperty(tableProperty) + { + } + + static DiffPathNodeLeaf nullopts(); +}; +struct DiffPath +{ + std::vector path; + + std::string toString(bool prependDot) const; +}; +struct DiffError +{ + enum Kind + { + Normal, + MissingProperty, + LengthMismatchInFnArgs, + LengthMismatchInFnRets, + LengthMismatchInUnion, + LengthMismatchInIntersection, + }; + Kind kind; + + DiffPath diffPath; + DiffPathNodeLeaf left; + DiffPathNodeLeaf right; + + std::string leftRootName; + std::string rightRootName; + + DiffError(Kind kind, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName) + : kind(kind) + , left(left) + , right(right) + , leftRootName(leftRootName) + , rightRootName(rightRootName) + { + checkValidInitialization(left, right); + } + DiffError(Kind kind, DiffPath diffPath, DiffPathNodeLeaf left, DiffPathNodeLeaf right, std::string leftRootName, std::string rightRootName) + : kind(kind) + , diffPath(diffPath) + , left(left) + , right(right) + , leftRootName(leftRootName) + , rightRootName(rightRootName) + { + checkValidInitialization(left, right); + } + + std::string toString() const; + +private: + std::string toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const; + void checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right); + void checkNonMissingPropertyLeavesHaveNulloptTableProperty() const; +}; + +struct DifferResult +{ + std::optional diffError; + + DifferResult() {} + DifferResult(DiffError diffError) + : diffError(diffError) + { + } + + void wrapDiffPath(DiffPathNode node); +}; +struct DifferEnvironment +{ + TypeId rootLeft; + TypeId rootRight; +}; +DifferResult diff(TypeId ty1, TypeId ty2); + +/** + * True if ty is a "simple" type, i.e. cannot contain types. + * string, number, boolean are simple types. + * function and table are not simple types. + */ +bool isSimple(TypeId ty); + +} // namespace Luau diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 1306ad2c..7b1eb207 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -144,6 +144,10 @@ struct Frontend Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {}); + // Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking + void parse(const ModuleName& name); + + // Parse and typecheck module graph CheckResult check(const ModuleName& name, std::optional optionOverride = {}); // new shininess bool isDirty(const ModuleName& name, bool forAutocomplete = false) const; diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 1a252a88..75c07a7b 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -274,6 +274,9 @@ struct NormalizedType /// Returns true if the type is a subtype of string(it could be a singleton). Behaves like Type::isString() bool isSubtypeOfString() const; + /// Returns true if this type should result in error suppressing behavior. + bool shouldSuppressErrors() const; + // Helpers that improve readability of the above (they just say if the component is present) bool hasTops() const; bool hasBooleans() const; @@ -343,7 +346,7 @@ public: void unionTablesWithTable(TypeIds& heres, TypeId there); void unionTables(TypeIds& heres, const TypeIds& theres); bool unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1); - bool unionNormalWithTy(NormalizedType& here, TypeId there, int ignoreSmallerTyvars = -1); + bool unionNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set& seenSetTypes, int ignoreSmallerTyvars = -1); // ------- Negations std::optional negateNormal(const NormalizedType& here); @@ -365,9 +368,9 @@ public: std::optional intersectionOfFunctions(TypeId here, TypeId there); void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there); void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress); - bool intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there); + bool intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there, std::unordered_set& seenSetTypes); bool intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1); - bool intersectNormalWithTy(NormalizedType& here, TypeId there); + bool intersectNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set& seenSetTypes); bool normalizeIntersections(const std::vector& intersections, NormalizedType& outType); // Check for inhabitance diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 9a35a1d6..e9420922 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -23,7 +23,6 @@ LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTypeMaximumStringifierLength) -LUAU_FASTFLAG(LuauTypecheckClassTypeIndexers) namespace Luau { @@ -527,7 +526,6 @@ struct ClassType , definitionModuleName(definitionModuleName) , indexer(indexer) { - LUAU_ASSERT(FFlag::LuauTypecheckClassTypeIndexers); } }; diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 1464aa1b..a84fb48c 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -304,13 +304,10 @@ struct GenericTypeVisitor if (ctv->metatable) traverse(*ctv->metatable); - if (FFlag::LuauTypecheckClassTypeIndexers) + if (ctv->indexer) { - if (ctv->indexer) - { - traverse(ctv->indexer->indexType); - traverse(ctv->indexer->indexResultType); - } + traverse(ctv->indexer->indexType); + traverse(ctv->indexer->indexResultType); } } } diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 1eb78540..bdb510a3 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -55,7 +55,6 @@ Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState) static TableIndexer clone(const TableIndexer& indexer, TypeArena& dest, CloneState& cloneState) { - LUAU_ASSERT(FFlag::LuauTypecheckClassTypeIndexers); return TableIndexer{clone(indexer.indexType, dest, cloneState), clone(indexer.indexResultType, dest, cloneState)}; } @@ -312,16 +311,8 @@ void TypeCloner::operator()(const TableType& t) for (const auto& [name, prop] : t.props) ttv->props[name] = clone(prop, dest, cloneState); - if (FFlag::LuauTypecheckClassTypeIndexers) - { - if (t.indexer) - ttv->indexer = clone(*t.indexer, dest, cloneState); - } - else - { - if (t.indexer) - ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, cloneState), clone(t.indexer->indexResultType, dest, cloneState)}; - } + if (t.indexer) + ttv->indexer = clone(*t.indexer, dest, cloneState); for (TypeId& arg : ttv->instantiatedTypeParams) arg = clone(arg, dest, cloneState); @@ -360,11 +351,8 @@ void TypeCloner::operator()(const ClassType& t) if (t.metatable) ctv->metatable = clone(*t.metatable, dest, cloneState); - if (FFlag::LuauTypecheckClassTypeIndexers) - { - if (t.indexer) - ctv->indexer = clone(*t.indexer, dest, cloneState); - } + if (t.indexer) + ctv->indexer = clone(*t.indexer, dest, cloneState); } void TypeCloner::operator()(const AnyType& t) diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 429f1a4d..c62c214c 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -776,9 +776,10 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatWhile* while_) { - check(scope, while_->condition); + RefinementId refinement = check(scope, while_->condition).refinement; ScopePtr whileScope = childScope(while_, scope); + applyRefinements(whileScope, while_->condition->location, refinement); visit(whileScope, while_->body); @@ -825,8 +826,17 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun std::unique_ptr c = std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); - forEachConstraint(start, end, this, [&c](const ConstraintPtr& constraint) { + Constraint* previous = nullptr; + forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) { c->dependencies.push_back(NotNull{constraint.get()}); + + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } }); addConstraint(scope, std::move(c)); @@ -915,9 +925,18 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction std::unique_ptr c = std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); - forEachConstraint(start, end, this, [&c, &excludeList](const ConstraintPtr& constraint) { + Constraint* previous = nullptr; + forEachConstraint(start, end, this, [&c, &excludeList, &previous](const ConstraintPtr& constraint) { if (!excludeList.count(constraint.get())) c->dependencies.push_back(NotNull{constraint.get()}); + + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } }); addConstraint(scope, std::move(c)); @@ -936,7 +955,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* expectedTypes.push_back(ty); TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes).tp; - addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType}); + addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType, /*returns*/ true}); return ControlFlow::Returns; } @@ -1408,6 +1427,7 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa std::vector> expectedTypesForCall = getExpectedCallTypesForFunctionOverloads(fnType); module->astOriginalCallTypes[call->func] = fnType; + module->astOriginalCallTypes[call] = fnType; TypePackId expectedArgPack = arena->freshTypePack(scope.get()); TypePackId expectedRetPack = arena->freshTypePack(scope.get()); @@ -1547,7 +1567,6 @@ InferencePack ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExprCa rets, call, std::move(discriminantTypes), - &module->astOriginalCallTypes, &module->astOverloadResolvedTypes, }); @@ -1642,7 +1661,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprConstantSt if (expectedType) { const TypeId expectedTy = follow(*expectedType); - if (get(expectedTy) || get(expectedTy)) + if (get(expectedTy) || get(expectedTy) || get(expectedTy)) { TypeId ty = arena->addType(BlockedType{}); TypeId singletonType = arena->addType(SingletonType(StringSingleton{std::string(string->value.data, string->value.size)})); @@ -1774,8 +1793,17 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* TypeId generalizedTy = arena->addType(BlockedType{}); NotNull gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature}); - forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) { + Constraint* previous = nullptr; + forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) { gc->dependencies.emplace_back(constraint.get()); + + if (auto psc = get(*constraint); psc && psc->returns) + { + if (previous) + constraint->dependencies.push_back(NotNull{previous}); + + previous = constraint.get(); + } }); return Inference{generalizedTy}; @@ -2412,7 +2440,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun if (nullptr != getFallthrough(fn->body)) { - TypePackId empty = arena->addTypePack({}); // TODO we could have CSG retain one of these forever + TypePackId empty = arena->addTypePack({}); // TODO we could have CGB retain one of these forever addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty}); } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 81e6574a..c9b584fd 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1259,18 +1259,13 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(fn)) fn = collapse(it).value_or(fn); - if (c.callSite) - (*c.astOriginalCallTypes)[c.callSite] = fn; - // We don't support magic __call metamethods. if (std::optional callMm = findMetatableEntry(builtinTypes, errors, fn, "__call", constraint->location)) { - std::vector args{fn}; + auto [head, tail] = flatten(c.argsPack); + head.insert(head.begin(), fn); - for (TypeId arg : c.argsPack) - args.push_back(arg); - - argsPack = arena->addTypePack(TypePack{args, {}}); + argsPack = arena->addTypePack(TypePack{std::move(head), tail}); fn = *callMm; asMutable(c.result)->ty.emplace(constraint->scope); } @@ -1890,7 +1885,20 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNullty.emplace(result); + const NormalizedType* normType = normalizer->normalize(c.type); + + if (!normType) + reportError(NormalizationTooComplex{}, constraint->location); + + if (normType && normType->shouldSuppressErrors()) + { + auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result; + asMutable(c.resultType)->ty.emplace(resultOrError); + } + else + { + asMutable(c.resultType)->ty.emplace(result); + } unblock(c.resultType, constraint->location); diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp new file mode 100644 index 00000000..91b6f4b6 --- /dev/null +++ b/Analysis/src/Differ.cpp @@ -0,0 +1,273 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Differ.h" +#include "Luau/Error.h" +#include "Luau/ToString.h" +#include "Luau/Type.h" +#include + +namespace Luau +{ +std::string DiffPathNode::toString() const +{ + switch (kind) + { + case DiffPathNode::Kind::TableProperty: + { + if (!tableProperty.has_value()) + throw InternalCompilerError{"DiffPathNode has kind TableProperty but tableProperty is nullopt"}; + return *tableProperty; + break; + } + default: + { + throw InternalCompilerError{"DiffPathNode::toString is not exhaustive"}; + } + } +} + +DiffPathNode DiffPathNode::constructWithTableProperty(Name tableProperty) +{ + return DiffPathNode{DiffPathNode::Kind::TableProperty, tableProperty, std::nullopt}; +} + +DiffPathNodeLeaf DiffPathNodeLeaf::nullopts() +{ + return DiffPathNodeLeaf{std::nullopt, std::nullopt}; +} + +std::string DiffPath::toString(bool prependDot) const +{ + std::string pathStr; + bool isFirstInForLoop = !prependDot; + for (auto node = path.rbegin(); node != path.rend(); node++) + { + if (isFirstInForLoop) + { + isFirstInForLoop = false; + } + else + { + pathStr += "."; + } + pathStr += node->toString(); + } + return pathStr; +} +std::string DiffError::toStringALeaf(std::string rootName, const DiffPathNodeLeaf& leaf, const DiffPathNodeLeaf& otherLeaf) const +{ + std::string pathStr{rootName + diffPath.toString(true)}; + switch (kind) + { + case DiffError::Kind::Normal: + { + checkNonMissingPropertyLeavesHaveNulloptTableProperty(); + return pathStr + " has type " + Luau::toString(*leaf.ty); + } + case DiffError::Kind::MissingProperty: + { + if (leaf.ty.has_value()) + { + if (!leaf.tableProperty.has_value()) + throw InternalCompilerError{"leaf.tableProperty is nullopt"}; + return pathStr + "." + *leaf.tableProperty + " has type " + Luau::toString(*leaf.ty); + } + else if (otherLeaf.ty.has_value()) + { + if (!otherLeaf.tableProperty.has_value()) + throw InternalCompilerError{"otherLeaf.tableProperty is nullopt"}; + return pathStr + " is missing the property " + *otherLeaf.tableProperty; + } + throw InternalCompilerError{"Both leaf.ty and otherLeaf.ty is nullopt"}; + } + default: + { + throw InternalCompilerError{"DiffPath::toStringWithLeaf is not exhaustive"}; + } + } +} + +void DiffError::checkNonMissingPropertyLeavesHaveNulloptTableProperty() const +{ + if (left.tableProperty.has_value() || right.tableProperty.has_value()) + throw InternalCompilerError{"Non-MissingProperty DiffError should have nullopt tableProperty in both leaves"}; +} + +std::string getDevFixFriendlyName(TypeId ty) +{ + if (auto table = get(ty)) + { + if (table->name.has_value()) + return *table->name; + else if (table->syntheticName.has_value()) + return *table->syntheticName; + } + // else if (auto primitive = get(ty)) + //{ + // return ""; + //} + return ""; +} + +std::string DiffError::toString() const +{ + std::string msg = "DiffError: these two types are not equal because the left type at " + toStringALeaf(leftRootName, left, right) + + ", while the right type at " + toStringALeaf(rightRootName, right, left); + return msg; +} + +void DiffError::checkValidInitialization(const DiffPathNodeLeaf& left, const DiffPathNodeLeaf& right) +{ + if (!left.ty.has_value() || !right.ty.has_value()) + { + // TODO: think about whether this should be always thrown! + // For example, Kind::Primitive doesn't make too much sense to have a TypeId + // throw InternalCompilerError{"Left and Right fields are leaf nodes and must have a TypeId"}; + } +} + +void DifferResult::wrapDiffPath(DiffPathNode node) +{ + if (!diffError.has_value()) + { + throw InternalCompilerError{"Cannot wrap diffPath because there is no diffError"}; + } + + diffError->diffPath.path.push_back(node); +} + +static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right); +static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right); +static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right); +static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId right); + +static DifferResult diffTable(DifferEnvironment& env, TypeId left, TypeId right) +{ + const TableType* leftTable = get(left); + const TableType* rightTable = get(right); + + for (auto const& [field, value] : leftTable->props) + { + if (rightTable->props.find(field) == rightTable->props.end()) + { + // left has a field the right doesn't + return DifferResult{DiffError{ + DiffError::Kind::MissingProperty, + DiffPathNodeLeaf{value.type(), field}, + DiffPathNodeLeaf::nullopts(), + getDevFixFriendlyName(env.rootLeft), + getDevFixFriendlyName(env.rootRight), + }}; + } + } + for (auto const& [field, value] : rightTable->props) + { + if (leftTable->props.find(field) == leftTable->props.end()) + { + // right has a field the left doesn't + return DifferResult{DiffError{DiffError::Kind::MissingProperty, DiffPathNodeLeaf::nullopts(), DiffPathNodeLeaf{value.type(), field}, + getDevFixFriendlyName(env.rootLeft), getDevFixFriendlyName(env.rootRight)}}; + } + } + // left and right have the same set of keys + for (auto const& [field, leftValue] : leftTable->props) + { + auto const& rightValue = rightTable->props.at(field); + DifferResult differResult = diffUsingEnv(env, leftValue.type(), rightValue.type()); + if (differResult.diffError.has_value()) + { + differResult.wrapDiffPath(DiffPathNode::constructWithTableProperty(field)); + return differResult; + } + } + return DifferResult{}; +} + +static DifferResult diffPrimitive(DifferEnvironment& env, TypeId left, TypeId right) +{ + const PrimitiveType* leftPrimitive = get(left); + const PrimitiveType* rightPrimitive = get(right); + + if (leftPrimitive->type != rightPrimitive->type) + { + return DifferResult{DiffError{ + DiffError::Kind::Normal, + DiffPathNodeLeaf{left, std::nullopt}, + DiffPathNodeLeaf{right, std::nullopt}, + getDevFixFriendlyName(env.rootLeft), + getDevFixFriendlyName(env.rootRight), + }}; + } + return DifferResult{}; +} + +static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId right) +{ + const SingletonType* leftSingleton = get(left); + const SingletonType* rightSingleton = get(right); + + if (*leftSingleton != *rightSingleton) + { + return DifferResult{DiffError{ + DiffError::Kind::Normal, + DiffPathNodeLeaf{left, std::nullopt}, + DiffPathNodeLeaf{right, std::nullopt}, + getDevFixFriendlyName(env.rootLeft), + getDevFixFriendlyName(env.rootRight), + }}; + } + return DifferResult{}; +} + +static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId right) +{ + left = follow(left); + right = follow(right); + + if (left->ty.index() != right->ty.index()) + { + return DifferResult{DiffError{ + DiffError::Kind::Normal, + DiffPathNodeLeaf{left, std::nullopt}, + DiffPathNodeLeaf{right, std::nullopt}, + getDevFixFriendlyName(env.rootLeft), + getDevFixFriendlyName(env.rootRight), + }}; + } + + // Both left and right are the same variant + + if (isSimple(left)) + { + if (auto lp = get(left)) + return diffPrimitive(env, left, right); + else if (auto ls = get(left)) + { + return diffSingleton(env, left, right); + } + + throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"}; + } + + // Both left and right are the same non-Simple + + if (auto lt = get(left)) + { + return diffTable(env, left, right); + } + throw InternalCompilerError{"Unimplemented non-simple TypeId variant for diffing"}; +} + +DifferResult diff(TypeId ty1, TypeId ty2) +{ + DifferEnvironment differEnv{ty1, ty2}; + return diffUsingEnv(differEnv, ty1, ty2); +} + +bool isSimple(TypeId ty) +{ + ty = follow(ty); + // TODO: think about GenericType, etc. + return get(ty) || get(ty); +} + +} // namespace Luau diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index f88425b5..9f1fd726 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -415,6 +415,18 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c { } +void Frontend::parse(const ModuleName& name) +{ + LUAU_TIMETRACE_SCOPE("Frontend::parse", "Frontend"); + LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); + + if (getCheckResult(name, false, false)) + return; + + std::vector buildQueue; + parseGraph(buildQueue, name, false); +} + CheckResult Frontend::check(const ModuleName& name, std::optional optionOverride) { LUAU_TIMETRACE_SCOPE("Frontend::check", "Frontend"); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index a7e3bb6e..33a8b6eb 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000); LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false); +LUAU_FASTFLAGVARIABLE(LuauNormalizeCyclicUnions, false); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauTransitiveSubtyping) LUAU_FASTFLAG(DebugLuauReadWriteProperties) @@ -247,6 +248,11 @@ bool NormalizedType::isSubtypeOfString() const !hasTables() && !hasFunctions() && !hasTyvars(); } +bool NormalizedType::shouldSuppressErrors() const +{ + return hasErrors() || get(tops); +} + bool NormalizedType::hasTops() const { return !get(tops); @@ -690,7 +696,8 @@ const NormalizedType* Normalizer::normalize(TypeId ty) return found->second.get(); NormalizedType norm{builtinTypes}; - if (!unionNormalWithTy(norm, ty)) + std::unordered_set seenSetTypes; + if (!unionNormalWithTy(norm, ty, seenSetTypes)) return nullptr; std::unique_ptr uniq = std::make_unique(std::move(norm)); const NormalizedType* result = uniq.get(); @@ -705,9 +712,12 @@ bool Normalizer::normalizeIntersections(const std::vector& intersections NormalizedType norm{builtinTypes}; norm.tops = builtinTypes->anyType; // Now we need to intersect the two types + std::unordered_set seenSetTypes; for (auto ty : intersections) - if (!intersectNormalWithTy(norm, ty)) + { + if (!intersectNormalWithTy(norm, ty, seenSetTypes)) return false; + } if (!unionNormals(outType, norm)) return false; @@ -1438,13 +1448,14 @@ bool Normalizer::withinResourceLimits() } // See above for an explaination of `ignoreSmallerTyvars`. -bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignoreSmallerTyvars) +bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set& seenSetTypes, int ignoreSmallerTyvars) { RecursionCounter _rc(&sharedState->counters.recursionCount); if (!withinResourceLimits()) return false; there = follow(there); + if (get(there) || get(there)) { TypeId tops = unionOfTops(here.tops, there); @@ -1465,9 +1476,23 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor } else if (const UnionType* utv = get(there)) { + if (FFlag::LuauNormalizeCyclicUnions) + { + if (seenSetTypes.count(there)) + return true; + seenSetTypes.insert(there); + } + for (UnionTypeIterator it = begin(utv); it != end(utv); ++it) - if (!unionNormalWithTy(here, *it)) + { + if (!unionNormalWithTy(here, *it, seenSetTypes)) + { + seenSetTypes.erase(there); return false; + } + } + + seenSetTypes.erase(there); return true; } else if (const IntersectionType* itv = get(there)) @@ -1475,8 +1500,10 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor NormalizedType norm{builtinTypes}; norm.tops = builtinTypes->anyType; for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it) - if (!intersectNormalWithTy(norm, *it)) + { + if (!intersectNormalWithTy(norm, *it, seenSetTypes)) return false; + } return unionNormals(here, norm); } else if (FFlag::LuauTransitiveSubtyping && get(here.tops)) @@ -1560,7 +1587,7 @@ bool Normalizer::unionNormalWithTy(NormalizedType& here, TypeId there, int ignor LUAU_ASSERT(!"Unreachable"); for (auto& [tyvar, intersect] : here.tyvars) - if (!unionNormalWithTy(*intersect, there, tyvarIndex(tyvar))) + if (!unionNormalWithTy(*intersect, there, seenSetTypes, tyvarIndex(tyvar))) return false; assertInvariant(here); @@ -2463,12 +2490,12 @@ void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const Normali } } -bool Normalizer::intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there) +bool Normalizer::intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there, std::unordered_set& seenSetTypes) { for (auto it = here.begin(); it != here.end();) { NormalizedType& inter = *it->second; - if (!intersectNormalWithTy(inter, there)) + if (!intersectNormalWithTy(inter, there, seenSetTypes)) return false; if (isShallowInhabited(inter)) ++it; @@ -2541,13 +2568,14 @@ bool Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& th return true; } -bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there) +bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, std::unordered_set& seenSetTypes) { RecursionCounter _rc(&sharedState->counters.recursionCount); if (!withinResourceLimits()) return false; there = follow(there); + if (get(there) || get(there)) { here.tops = intersectionOfTops(here.tops, there); @@ -2556,20 +2584,20 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there) else if (!get(here.tops)) { clearNormal(here); - return unionNormalWithTy(here, there); + return unionNormalWithTy(here, there, seenSetTypes); } else if (const UnionType* utv = get(there)) { NormalizedType norm{builtinTypes}; for (UnionTypeIterator it = begin(utv); it != end(utv); ++it) - if (!unionNormalWithTy(norm, *it)) + if (!unionNormalWithTy(norm, *it, seenSetTypes)) return false; return intersectNormals(here, norm); } else if (const IntersectionType* itv = get(there)) { for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it) - if (!intersectNormalWithTy(here, *it)) + if (!intersectNormalWithTy(here, *it, seenSetTypes)) return false; return true; } @@ -2691,7 +2719,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there) return true; } else if (auto nt = get(t)) - return intersectNormalWithTy(here, nt->ty); + return intersectNormalWithTy(here, nt->ty, seenSetTypes); else { // TODO negated unions, intersections, table, and function. @@ -2706,7 +2734,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there) else LUAU_ASSERT(!"Unreachable"); - if (!intersectTyvarsWithTy(tyvars, there)) + if (!intersectTyvarsWithTy(tyvars, there, seenSetTypes)) return false; here.tyvars = std::move(tyvars); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 6c1908bf..9c34cd7c 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -191,16 +191,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a { if (alwaysClone) { - if (FFlag::LuauTypecheckClassTypeIndexers) - { - ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.indexer}; - return dest.addType(std::move(clone)); - } - else - { - ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName}; - return dest.addType(std::move(clone)); - } + ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.indexer}; + return dest.addType(std::move(clone)); } else return ty; @@ -316,13 +308,10 @@ void Tarjan::visitChildren(TypeId ty, int index) if (ctv->metatable) visitChild(*ctv->metatable); - if (FFlag::LuauTypecheckClassTypeIndexers) + if (ctv->indexer) { - if (ctv->indexer) - { - visitChild(ctv->indexer->indexType); - visitChild(ctv->indexer->indexResultType); - } + visitChild(ctv->indexer->indexType); + visitChild(ctv->indexer->indexResultType); } } else if (const NegationType* ntv = get(ty)) @@ -1038,13 +1027,10 @@ void Substitution::replaceChildren(TypeId ty) if (ctv->metatable) ctv->metatable = replace(*ctv->metatable); - if (FFlag::LuauTypecheckClassTypeIndexers) + if (ctv->indexer) { - if (ctv->indexer) - { - ctv->indexer->indexType = replace(ctv->indexer->indexType); - ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType); - } + ctv->indexer->indexType = replace(ctv->indexer->indexType); + ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType); } } else if (NegationType* ntv = getMutable(ty)) diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index f2f15e85..c3a1db4c 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -258,13 +258,10 @@ void StateDot::visitChildren(TypeId ty, int index) if (ctv->metatable) visitChild(*ctv->metatable, index, "[metatable]"); - if (FFlag::LuauTypecheckClassTypeIndexers) + if (ctv->indexer) { - if (ctv->indexer) - { - visitChild(ctv->indexer->indexType, index, "[index]"); - visitChild(ctv->indexer->indexResultType, index, "[value]"); - } + visitChild(ctv->indexer->indexType, index, "[index]"); + visitChild(ctv->indexer->indexResultType, index, "[value]"); } } else if (const SingletonType* stv = get(ty)) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 7a46bf96..103f0dca 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1090,6 +1090,11 @@ struct TypeChecker2 args.head.push_back(lookupType(indexExpr->expr)); argLocs.push_back(indexExpr->expr->location); } + else if (findMetatableEntry(builtinTypes, module->errors, *originalCallTy, "__call", call->func->location)) + { + args.head.insert(args.head.begin(), lookupType(call->func)); + argLocs.push_back(call->func->location); + } for (size_t i = 0; i < call->args.size; ++i) { diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index c9da34f4..c4a6d103 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -38,7 +38,6 @@ LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure) LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) -LUAU_FASTFLAGVARIABLE(LuauTypecheckClassTypeIndexers, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAG(LuauParseDeclareClassIndexer) @@ -2107,21 +2106,18 @@ std::optional TypeChecker::getIndexTypeFromTypeImpl( if (prop) return prop->type(); - if (FFlag::LuauTypecheckClassTypeIndexers) + if (auto indexer = cls->indexer) { - if (auto indexer = cls->indexer) - { - // TODO: Property lookup should work with string singletons or unions thereof as the indexer key type. - ErrorVec errors = tryUnify(stringType, indexer->indexType, scope, location); + // TODO: Property lookup should work with string singletons or unions thereof as the indexer key type. + ErrorVec errors = tryUnify(stringType, indexer->indexType, scope, location); - if (errors.empty()) - return indexer->indexResultType; + if (errors.empty()) + return indexer->indexResultType; - if (addErrors) - reportError(location, UnknownProperty{type, name}); + if (addErrors) + reportError(location, UnknownProperty{type, name}); - return std::nullopt; - } + return std::nullopt; } } else if (const UnionType* utv = get(type)) @@ -3312,38 +3308,24 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex } else if (const ClassType* lhsClass = get(lhs)) { - if (FFlag::LuauTypecheckClassTypeIndexers) + if (const Property* prop = lookupClassProp(lhsClass, name)) { - if (const Property* prop = lookupClassProp(lhsClass, name)) - { - return prop->type(); - } - - if (auto indexer = lhsClass->indexer) - { - Unifier state = mkUnifier(scope, expr.location); - state.tryUnify(stringType, indexer->indexType); - if (state.errors.empty()) - { - state.log.commit(); - return indexer->indexResultType; - } - } - - reportError(TypeError{expr.location, UnknownProperty{lhs, name}}); - return errorRecoveryType(scope); - } - else - { - const Property* prop = lookupClassProp(lhsClass, name); - if (!prop) - { - reportError(TypeError{expr.location, UnknownProperty{lhs, name}}); - return errorRecoveryType(scope); - } - return prop->type(); } + + if (auto indexer = lhsClass->indexer) + { + Unifier state = mkUnifier(scope, expr.location); + state.tryUnify(stringType, indexer->indexType); + if (state.errors.empty()) + { + state.log.commit(); + return indexer->indexResultType; + } + } + + reportError(TypeError{expr.location, UnknownProperty{lhs, name}}); + return errorRecoveryType(scope); } else if (get(lhs)) { @@ -3385,45 +3367,29 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex { if (const ClassType* exprClass = get(exprType)) { - if (FFlag::LuauTypecheckClassTypeIndexers) + if (const Property* prop = lookupClassProp(exprClass, value->value.data)) { - if (const Property* prop = lookupClassProp(exprClass, value->value.data)) - { - return prop->type(); - } - - if (auto indexer = exprClass->indexer) - { - unify(stringType, indexer->indexType, scope, expr.index->location); - return indexer->indexResultType; - } - - reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}}); - return errorRecoveryType(scope); - } - else - { - const Property* prop = lookupClassProp(exprClass, value->value.data); - if (!prop) - { - reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}}); - return errorRecoveryType(scope); - } return prop->type(); } + + if (auto indexer = exprClass->indexer) + { + unify(stringType, indexer->indexType, scope, expr.index->location); + return indexer->indexResultType; + } + + reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}}); + return errorRecoveryType(scope); } } else { - if (FFlag::LuauTypecheckClassTypeIndexers) + if (const ClassType* exprClass = get(exprType)) { - if (const ClassType* exprClass = get(exprType)) + if (auto indexer = exprClass->indexer) { - if (auto indexer = exprClass->indexer) - { - unify(indexType, indexer->indexType, scope, expr.index->location); - return indexer->indexResultType; - } + unify(indexType, indexer->indexType, scope, expr.index->location); + return indexer->indexResultType; } } diff --git a/Ast/include/Luau/Location.h b/Ast/include/Luau/Location.h index dbe36bec..41ca379d 100644 --- a/Ast/include/Luau/Location.h +++ b/Ast/include/Luau/Location.h @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include namespace Luau { @@ -38,6 +39,11 @@ struct Location bool containsClosed(const Position& p) const; void extend(const Location& other); void shift(const Position& start, const Position& oldEnd, const Position& newEnd); + + /** + * Use offset=1 when displaying for the user. + */ + std::string toString(int offset = 0, bool useBegin = true) const; }; } // namespace Luau diff --git a/Ast/include/Luau/TimeTrace.h b/Ast/include/Luau/TimeTrace.h index be282827..2f7daf2c 100644 --- a/Ast/include/Luau/TimeTrace.h +++ b/Ast/include/Luau/TimeTrace.h @@ -4,6 +4,7 @@ #include "Luau/Common.h" #include +#include #include @@ -54,7 +55,7 @@ struct Event struct GlobalContext; struct ThreadContext; -GlobalContext& getGlobalContext(); +std::shared_ptr getGlobalContext(); uint16_t createToken(GlobalContext& context, const char* name, const char* category); uint32_t createThread(GlobalContext& context, ThreadContext* threadContext); @@ -66,7 +67,7 @@ struct ThreadContext ThreadContext() : globalContext(getGlobalContext()) { - threadId = createThread(globalContext, this); + threadId = createThread(*globalContext, this); } ~ThreadContext() @@ -74,16 +75,16 @@ struct ThreadContext if (!events.empty()) flushEvents(); - releaseThread(globalContext, this); + releaseThread(*globalContext, this); } void flushEvents() { - static uint16_t flushToken = createToken(globalContext, "flushEvents", "TimeTrace"); + static uint16_t flushToken = createToken(*globalContext, "flushEvents", "TimeTrace"); events.push_back({EventType::Enter, flushToken, {getClockMicroseconds()}}); - TimeTrace::flushEvents(globalContext, threadId, events, data); + TimeTrace::flushEvents(*globalContext, threadId, events, data); events.clear(); data.clear(); @@ -125,7 +126,7 @@ struct ThreadContext events.push_back({EventType::ArgValue, 0, {pos}}); } - GlobalContext& globalContext; + std::shared_ptr globalContext; uint32_t threadId; std::vector events; std::vector data; diff --git a/Ast/src/Location.cpp b/Ast/src/Location.cpp index d01d8a18..e0ae867f 100644 --- a/Ast/src/Location.cpp +++ b/Ast/src/Location.cpp @@ -1,5 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Location.h" +#include namespace Luau { @@ -128,4 +129,12 @@ void Location::shift(const Position& start, const Position& oldEnd, const Positi end.shift(start, oldEnd, newEnd); } +std::string Location::toString(int offset, bool useBegin) const +{ + const Position& pos = useBegin ? this->begin : this->end; + std::string line{std::to_string(pos.line + offset)}; + std::string column{std::to_string(pos.column + offset)}; + return "(" + line + ", " + column + ")"; +} + } // namespace Luau diff --git a/Ast/src/TimeTrace.cpp b/Ast/src/TimeTrace.cpp index e3807683..8b95cf0b 100644 --- a/Ast/src/TimeTrace.cpp +++ b/Ast/src/TimeTrace.cpp @@ -90,7 +90,6 @@ namespace TimeTrace { struct GlobalContext { - GlobalContext() = default; ~GlobalContext() { // Ideally we would want all ThreadContext destructors to run @@ -110,11 +109,15 @@ struct GlobalContext uint32_t nextThreadId = 0; std::vector tokens; FILE* traceFile = nullptr; + +private: + friend std::shared_ptr getGlobalContext(); + GlobalContext() = default; }; -GlobalContext& getGlobalContext() +std::shared_ptr getGlobalContext() { - static GlobalContext context; + static std::shared_ptr context = std::shared_ptr{new GlobalContext}; return context; } @@ -261,7 +264,7 @@ ThreadContext& getThreadContext() uint16_t createScopeData(const char* name, const char* category) { - return createToken(Luau::TimeTrace::getGlobalContext(), name, category); + return createToken(*Luau::TimeTrace::getGlobalContext(), name, category); } } // namespace TimeTrace } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 8cbe7e8b..16c8df62 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -236,6 +236,10 @@ enum class IrCmd : uint8_t // A: pointer (Table) TABLE_LEN, + // Get string length + // A: pointer (string) + STRING_LEN, + // Allocate new table // A: int (array element count) // B: int (node element count) @@ -361,8 +365,10 @@ enum class IrCmd : uint8_t // Guard against tag mismatch // A, B: tag // C: block/undef + // D: bool (finish execution in VM on failure) // In final x64 lowering, A can also be Rn - // When undef is specified instead of a block, execution is aborted on check failure + // When undef is specified instead of a block, execution is aborted on check failure; if D is true, execution is continued in VM interpreter + // instead. CHECK_TAG, // Guard against readonly table @@ -377,9 +383,9 @@ enum class IrCmd : uint8_t // When undef is specified instead of a block, execution is aborted on check failure CHECK_NO_METATABLE, - // Guard against executing in unsafe environment - // A: block/undef - // When undef is specified instead of a block, execution is aborted on check failure + // Guard against executing in unsafe environment, exits to VM on check failure + // A: unsigned int (pcpos)/undef + // When undef is specified, execution is aborted on check failure CHECK_SAFE_ENV, // Guard against index overflowing the table array size @@ -610,7 +616,6 @@ struct IrConst union { - bool valueBool; int valueInt; unsigned valueUint; double valueDouble; diff --git a/CodeGen/include/Luau/IrRegAllocX64.h b/CodeGen/include/Luau/IrRegAllocX64.h index f83cc220..95930811 100644 --- a/CodeGen/include/Luau/IrRegAllocX64.h +++ b/CodeGen/include/Luau/IrRegAllocX64.h @@ -39,6 +39,8 @@ struct IrRegAllocX64 RegisterX64 allocRegOrReuse(SizeX64 size, uint32_t instIdx, std::initializer_list oprefs); RegisterX64 takeReg(RegisterX64 reg, uint32_t instIdx); + bool canTakeReg(RegisterX64 reg) const; + void freeReg(RegisterX64 reg); void freeLastUseReg(IrInst& target, uint32_t instIdx); void freeLastUseRegs(const IrInst& inst, uint32_t instIdx); diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index a3e97894..6481342f 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -167,6 +167,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::ABS_NUM: case IrCmd::NOT_ANY: case IrCmd::TABLE_LEN: + case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: case IrCmd::TRY_NUM_TO_INDEX: @@ -256,5 +257,8 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 uint32_t getNativeContextOffset(int bfid); +// Cleans up blocks that were created with no users +void killUnusedBlocks(IrFunction& function); + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index cc013182..37af59aa 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -24,6 +24,15 @@ struct EntryLocations Label epilogueStart; }; +static void emitClearNativeFlag(AssemblyBuilderA64& build) +{ + build.ldr(x0, mem(rState, offsetof(lua_State, ci))); + build.ldr(w1, mem(x0, offsetof(CallInfo, flags))); + build.mov(w2, ~LUA_CALLINFO_NATIVE); + build.and_(w1, w1, w2); + build.str(w1, mem(x0, offsetof(CallInfo, flags))); +} + static void emitExit(AssemblyBuilderA64& build, bool continueInVm) { build.mov(x0, continueInVm); @@ -31,6 +40,16 @@ static void emitExit(AssemblyBuilderA64& build, bool continueInVm) build.br(x1); } +static void emitUpdatePcAndContinueInVm(AssemblyBuilderA64& build) +{ + // x0 = pcpos * sizeof(Instruction) + build.add(x0, rCode, x0); + build.ldr(x1, mem(rState, offsetof(lua_State, ci))); + build.str(x0, mem(x1, offsetof(CallInfo, savedpc))); + + emitExit(build, /* continueInVm */ true); +} + static void emitInterrupt(AssemblyBuilderA64& build) { // x0 = pc offset @@ -286,6 +305,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; exitContinueVmClearNativeFlag\n"); + build.setLabel(helpers.exitContinueVmClearNativeFlag); + emitClearNativeFlag(build); + if (build.logText) build.logAppend("; exitContinueVm\n"); build.setLabel(helpers.exitContinueVm); @@ -296,6 +320,11 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcAndContinueInVm(build); + if (build.logText) build.logAppend("; reentry\n"); build.setLabel(helpers.reentry); diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 5b6c4ffc..4b74e9f2 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -221,6 +221,8 @@ inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers template inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options) { + killUnusedBlocks(ir.function); + computeCfgInfo(ir.function); if (!FFlag::DebugCodegenNoOpt) diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 41c3dbd0..1e62a4d4 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -180,6 +180,11 @@ bool initHeaderFunctions(NativeState& data) void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) { + if (build.logText) + build.logAppend("; exitContinueVmClearNativeFlag\n"); + build.setLabel(helpers.exitContinueVmClearNativeFlag); + emitClearNativeFlag(build); + if (build.logText) build.logAppend("; exitContinueVm\n"); build.setLabel(helpers.exitContinueVm); @@ -190,6 +195,11 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); + if (build.logText) + build.logAppend("; updatePcAndContinueInVm\n"); + build.setLabel(helpers.updatePcAndContinueInVm); + emitUpdatePcAndContinueInVm(build); + if (build.logText) build.logAppend("; continueCallInVm\n"); build.setLabel(helpers.continueCallInVm); diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index f912ffba..08666064 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -24,6 +24,8 @@ struct ModuleHelpers // A64/X64 Label exitContinueVm; Label exitNoContinueVm; + Label exitContinueVmClearNativeFlag; + Label updatePcAndContinueInVm; Label return_; Label interrupt; diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 4d70bb7a..1d707fad 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -268,6 +268,13 @@ void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build) build.setLabel(skip); } + +void emitClearNativeFlag(AssemblyBuilderX64& build) +{ + build.mov(rax, qword[rState + offsetof(lua_State, ci)]); + build.and_(dword[rax + offsetof(CallInfo, flags)], ~LUA_CALLINFO_NATIVE); +} + void emitExit(AssemblyBuilderX64& build, bool continueInVm) { if (continueInVm) @@ -345,6 +352,16 @@ void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, in emitUpdateBase(build); } +void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build) +{ + // edx = pcpos * sizeof(Instruction) + build.add(rdx, sCode); + build.mov(rax, qword[rState + offsetof(lua_State, ci)]); + build.mov(qword[rax + offsetof(CallInfo, savedpc)], rdx); + + emitExit(build, /* continueInVm */ true); +} + void emitContinueCallInVm(AssemblyBuilderX64& build) { RegisterX64 proto = rcx; // Sync with emitInstCall diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index 5a3548f6..02d9f40b 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -175,11 +175,13 @@ void callBarrierObject(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, RegisterX64 table, IrOp tableOp); void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build); +void emitClearNativeFlag(AssemblyBuilderX64& build); void emitExit(AssemblyBuilderX64& build, bool continueInVm); void emitUpdateBase(AssemblyBuilderX64& build); void emitInterrupt(AssemblyBuilderX64& build); void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos); +void emitUpdatePcAndContinueInVm(AssemblyBuilderX64& build); void emitContinueCallInVm(AssemblyBuilderX64& build); void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers); diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index 61d5ac63..ea511958 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -90,6 +90,11 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int build.mov(qword[rState + offsetof(lua_State, top)], argi); build.setLabel(skipVararg); + // Keep executing new function + // ci->savedpc = p->code; + build.mov(rax, qword[proto + offsetof(Proto, code)]); + build.mov(qword[ci + offsetof(CallInfo, savedpc)], rax); + // Get native function entry build.mov(rax, qword[proto + offsetof(Proto, exectarget)]); build.test(rax, rax); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 98db2977..69ac295a 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -22,6 +22,82 @@ IrBuilder::IrBuilder() { } +static void buildArgumentTypeChecks(IrBuilder& build, Proto* proto) +{ + if (!proto->typeinfo || proto->numparams == 0) + return; + + for (int i = 0; i < proto->numparams; ++i) + { + uint8_t et = proto->typeinfo[2 + i]; + + uint8_t tag = et & ~LBC_TYPE_OPTIONAL_BIT; + uint8_t optional = et & LBC_TYPE_OPTIONAL_BIT; + + if (tag == LBC_TYPE_ANY) + continue; + + IrOp load = build.inst(IrCmd::LOAD_TAG, build.vmReg(i)); + + IrOp nextCheck; + if (optional) + { + nextCheck = build.block(IrBlockKind::Internal); + IrOp fallbackCheck = build.block(IrBlockKind::Internal); + + build.inst(IrCmd::JUMP_EQ_TAG, load, build.constTag(LUA_TNIL), nextCheck, fallbackCheck); + + build.beginBlock(fallbackCheck); + } + + switch (tag) + { + case LBC_TYPE_NIL: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNIL), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_BOOLEAN: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBOOLEAN), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_NUMBER: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TNUMBER), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_STRING: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TSTRING), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_TABLE: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTABLE), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_FUNCTION: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TFUNCTION), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_THREAD: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TTHREAD), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_USERDATA: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.undef(), build.constInt(1)); + break; + case LBC_TYPE_VECTOR: + build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TVECTOR), build.undef(), build.constInt(1)); + break; + } + + if (optional) + { + build.inst(IrCmd::JUMP, nextCheck); + build.beginBlock(nextCheck); + } + } + + // If the last argument is optional, we can skip creating a new internal block since one will already have been created. + if (!(proto->typeinfo[2 + proto->numparams - 1] & LBC_TYPE_OPTIONAL_BIT)) + { + IrOp next = build.block(IrBlockKind::Internal); + build.inst(IrCmd::JUMP, next); + + build.beginBlock(next); + } +} + void IrBuilder::buildFunctionIr(Proto* proto) { function.proto = proto; @@ -47,6 +123,9 @@ void IrBuilder::buildFunctionIr(Proto* proto) if (instIndexToBlock[i] != kNoAssociatedBlockIndex) beginBlock(blockAtInst(i)); + if (i == 0) + buildArgumentTypeChecks(*this, proto); + // We skip dead bytecode instructions when they appear after block was already terminated if (!inTerminatedBlock) translateInst(op, pc, i); diff --git a/CodeGen/src/IrCallWrapperX64.cpp b/CodeGen/src/IrCallWrapperX64.cpp index f466df4a..816e0184 100644 --- a/CodeGen/src/IrCallWrapperX64.cpp +++ b/CodeGen/src/IrCallWrapperX64.cpp @@ -212,7 +212,13 @@ RegisterX64 IrCallWrapperX64::suggestNextArgumentRegister(SizeX64 size) const { OperandX64 target = getNextArgumentTarget(size); - return target.cat == CategoryX64::reg ? regs.takeReg(target.base, kInvalidInstIdx) : regs.allocReg(size, kInvalidInstIdx); + if (target.cat != CategoryX64::reg) + return regs.allocReg(size, kInvalidInstIdx); + + if (!regs.canTakeReg(target.base)) + return regs.allocReg(size, kInvalidInstIdx); + + return regs.takeReg(target.base, kInvalidInstIdx); } OperandX64 IrCallWrapperX64::getNextArgumentTarget(SizeX64 size) const diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index e699229c..dfd7236a 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -163,6 +163,8 @@ const char* getCmdName(IrCmd cmd) return "JUMP_SLOT_MATCH"; case IrCmd::TABLE_LEN: return "TABLE_LEN"; + case IrCmd::STRING_LEN: + return "STRING_LEN"; case IrCmd::NEW_TABLE: return "NEW_TABLE"; case IrCmd::DUP_TABLE: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 3cf92173..38e840ab 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -376,8 +376,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.add(inst.regA64, regOp(inst.b), uint16_t(intOp(inst.a))); else { - RegisterA64 temp = tempInt(inst.b); - build.add(inst.regA64, regOp(inst.a), temp); + RegisterA64 temp1 = tempInt(inst.a); + RegisterA64 temp2 = tempInt(inst.b); + build.add(inst.regA64, temp1, temp2); } break; case IrCmd::SUB_INT: @@ -386,8 +387,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.sub(inst.regA64, regOp(inst.a), uint16_t(intOp(inst.b))); else { - RegisterA64 temp = tempInt(inst.b); - build.sub(inst.regA64, regOp(inst.a), temp); + RegisterA64 temp1 = tempInt(inst.a); + RegisterA64 temp2 = tempInt(inst.b); + build.sub(inst.regA64, temp1, temp2); } break; case IrCmd::ADD_NUM: @@ -689,6 +691,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.scvtf(inst.regA64, x0); break; } + case IrCmd::STRING_LEN: + { + RegisterA64 reg = regOp(inst.a); + inst.regA64 = regs.allocReg(KindA64::w, index); + + build.ldr(inst.regA64, mem(reg, offsetof(TString, len))); + break; + } case IrCmd::NEW_TABLE: { regs.spill(build, index); @@ -816,7 +826,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::FASTCALL: regs.spill(build, index); - error |= emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), inst.d, intOp(inst.e), intOp(inst.f)); + error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), inst.d, intOp(inst.e), intOp(inst.f)); break; case IrCmd::INVOKE_FASTCALL: { @@ -1018,8 +1028,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; case IrCmd::CHECK_TAG: { + bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); Label abort; // used when guard aborts execution - Label& fail = inst.c.kind == IrOpKind::Undef ? abort : labelOp(inst.c); + Label& fail = inst.c.kind == IrOpKind::Undef ? (continueInVm ? helpers.exitContinueVmClearNativeFlag : abort) : labelOp(inst.c); if (tagOp(inst.b) == 0) { build.cbnz(regOp(inst.a), fail); @@ -1029,7 +1040,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.cmp(regOp(inst.a), tagOp(inst.b)); build.b(ConditionA64::NotEqual, fail); } - if (abort.id) + if (abort.id && !continueInVm) emitAbort(build, abort); break; } @@ -1060,9 +1071,18 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) RegisterA64 tempw = castReg(KindA64::w, temp); build.ldr(temp, mem(rClosure, offsetof(Closure, env))); build.ldrb(tempw, mem(temp, offsetof(Table, safeenv))); - build.cbz(tempw, inst.a.kind == IrOpKind::Undef ? abort : labelOp(inst.a)); - if (abort.id) + + if (inst.a.kind == IrOpKind::Undef) + { + build.cbz(tempw, abort); emitAbort(build, abort); + } + else + { + Label self; + build.cbz(tempw, self); + exitHandlers.push_back({self, uintOp(inst.a)}); + } break; } case IrCmd::CHECK_ARRAY_SIZE: @@ -1528,7 +1548,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITAND_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) build.and_(inst.regA64, regOp(inst.a), unsigned(intOp(inst.b))); else { @@ -1541,7 +1561,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITXOR_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) build.eor(inst.regA64, regOp(inst.a), unsigned(intOp(inst.b))); else { @@ -1554,7 +1574,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITOR_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant && AssemblyBuilderA64::isMaskSupported(unsigned(intOp(inst.b)))) build.orr(inst.regA64, regOp(inst.a), unsigned(intOp(inst.b))); else { @@ -1574,7 +1594,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITLSHIFT_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) build.lsl(inst.regA64, regOp(inst.a), uint8_t(unsigned(intOp(inst.b)) & 31)); else { @@ -1587,7 +1607,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITRSHIFT_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) build.lsr(inst.regA64, regOp(inst.a), uint8_t(unsigned(intOp(inst.b)) & 31)); else { @@ -1600,7 +1620,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITARSHIFT_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) build.asr(inst.regA64, regOp(inst.a), uint8_t(unsigned(intOp(inst.b)) & 31)); else { @@ -1612,7 +1632,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::BITLROTATE_UINT: { - if (inst.b.kind == IrOpKind::Constant) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a}); build.ror(inst.regA64, regOp(inst.a), uint8_t((32 - unsigned(intOp(inst.b))) & 31)); @@ -1630,7 +1650,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::BITRROTATE_UINT: { inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); - if (inst.b.kind == IrOpKind::Constant) + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) build.ror(inst.regA64, regOp(inst.a), uint8_t(unsigned(intOp(inst.b)) & 31)); else { @@ -1751,11 +1771,21 @@ void IrLoweringA64::finishFunction() build.adr(x1, handler.next); build.b(helpers.interrupt); } + + if (build.logText) + build.logAppend("; exit handlers\n"); + + for (ExitHandler& handler : exitHandlers) + { + build.setLabel(handler.self); + build.mov(x0, handler.pcpos * sizeof(Instruction)); + build.b(helpers.updatePcAndContinueInVm); + } } bool IrLoweringA64::hasError() const { - return error; + return error || regs.error; } bool IrLoweringA64::isFallthroughBlock(IrBlock target, IrBlock next) diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 57c18b2e..5b196889 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -60,6 +60,12 @@ struct IrLoweringA64 Label next; }; + struct ExitHandler + { + Label self; + unsigned int pcpos; + }; + AssemblyBuilderA64& build; ModuleHelpers& helpers; @@ -70,6 +76,7 @@ struct IrLoweringA64 IrValueLocationTracking valueTracker; std::vector interruptHandlers; + std::vector exitHandlers; bool error = false; }; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index abe02eed..813f5123 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -584,6 +584,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.vcvtsi2sd(inst.regX64, inst.regX64, eax); break; } + case IrCmd::STRING_LEN: + { + RegisterX64 ptr = regOp(inst.a); + inst.regX64 = regs.allocReg(SizeX64::dword, index); + build.mov(inst.regX64, dword[ptr + offsetof(TString, len)]); + break; + } case IrCmd::NEW_TABLE: { IrCallWrapperX64 callWrap(regs, build, index); @@ -720,9 +727,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) int nparams = intOp(inst.e); int nresults = intOp(inst.f); - ScopedRegX64 func{regs, SizeX64::qword}; - build.mov(func.reg, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]); - IrCallWrapperX64 callWrap(regs, build, index); callWrap.addArgument(SizeX64::qword, rState); callWrap.addArgument(SizeX64::qword, luauRegAddress(ra)); @@ -748,6 +752,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) callWrap.addArgument(SizeX64::dword, nparams); } + ScopedRegX64 func{regs, SizeX64::qword}; + build.mov(func.reg, qword[rNativeContext + offsetof(NativeContext, luauF_table) + bfid * sizeof(luau_FastFunction)]); + callWrap.call(func.release()); inst.regX64 = regs.takeReg(eax, index); // Result of a builtin call is returned in eax break; @@ -878,9 +885,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) callPrepareForN(regs, build, vmRegOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c)); break; case IrCmd::CHECK_TAG: + { + bool continueInVm = (inst.d.kind == IrOpKind::Constant && intOp(inst.d)); build.cmp(memRegTagOp(inst.a), tagOp(inst.b)); - jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c); + jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.c, continueInVm); break; + } case IrCmd::CHECK_READONLY: build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, ConditionX64::Equal, inst.b); @@ -896,7 +906,20 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) build.mov(tmp.reg, sClosure); build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); - jumpOrAbortOnUndef(ConditionX64::Equal, ConditionX64::NotEqual, inst.a); + + if (inst.a.kind == IrOpKind::Undef) + { + Label skip; + build.jcc(ConditionX64::NotEqual, skip); + build.ud2(); + build.setLabel(skip); + } + else + { + Label self; + build.jcc(ConditionX64::Equal, self); + exitHandlers.push_back({self, uintOp(inst.a)}); + } break; } case IrCmd::CHECK_ARRAY_SIZE: @@ -1403,6 +1426,16 @@ void IrLoweringX64::finishFunction() build.lea(rbx, handler.next); build.jmp(helpers.interrupt); } + + if (build.logText) + build.logAppend("; exit handlers\n"); + + for (ExitHandler& handler : exitHandlers) + { + build.setLabel(handler.self); + build.mov(edx, handler.pcpos * sizeof(Instruction)); + build.jmp(helpers.updatePcAndContinueInVm); + } } bool IrLoweringX64::hasError() const @@ -1425,10 +1458,16 @@ void IrLoweringX64::jumpOrFallthrough(IrBlock& target, IrBlock& next) build.jmp(target.label); } -void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef) +void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm) { if (targetOrUndef.kind == IrOpKind::Undef) { + if (continueInVm) + { + build.jcc(cond, helpers.exitContinueVmClearNativeFlag); + return; + } + Label skip; build.jcc(condInverse, skip); build.ud2(); diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index f50812e4..8ea4b41e 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -34,7 +34,7 @@ struct IrLoweringX64 bool isFallthroughBlock(IrBlock target, IrBlock next); void jumpOrFallthrough(IrBlock& target, IrBlock& next); - void jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef); + void jumpOrAbortOnUndef(ConditionX64 cond, ConditionX64 condInverse, IrOp targetOrUndef, bool continueInVm = false); void storeDoubleAsFloat(OperandX64 dst, IrOp src); @@ -60,6 +60,12 @@ struct IrLoweringX64 Label next; }; + struct ExitHandler + { + Label self; + unsigned int pcpos; + }; + AssemblyBuilderX64& build; ModuleHelpers& helpers; @@ -70,6 +76,7 @@ struct IrLoweringX64 IrValueLocationTracking valueTracker; std::vector interruptHandlers; + std::vector exitHandlers; }; } // namespace X64 diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index a4cfeaed..02d7df98 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -18,6 +18,8 @@ namespace CodeGen namespace A64 { +static const int8_t kInvalidSpill = 64; + static int allocSpill(uint32_t& free, KindA64 kind) { LUAU_ASSERT(kStackSize <= 256); // to support larger stack frames, we need to ensure qN is allocated at 16b boundary to fit in ldr/str encoding @@ -91,7 +93,8 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF { build.ldr(reg, mem(sp, sSpillArea.data + s.slot * 8)); - freeSpill(freeSpillSlots, reg.kind, s.slot); + if (s.slot != kInvalidSpill) + freeSpill(freeSpillSlots, reg.kind, s.slot); } else { @@ -135,9 +138,8 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index) if (set.free == 0) { - // TODO: remember the error and fail lowering - LUAU_ASSERT(!"Out of registers to allocate"); - return noreg; + error = true; + return RegisterA64{kind, 0}; } int reg = 31 - countlz(set.free); @@ -157,9 +159,8 @@ RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind) if (set.free == 0) { - // TODO: remember the error and fail lowering - LUAU_ASSERT(!"Out of registers to allocate"); - return noreg; + error = true; + return RegisterA64{kind, 0}; } int reg = 31 - countlz(set.free); @@ -332,7 +333,11 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init else { int slot = allocSpill(freeSpillSlots, def.regA64.kind); - LUAU_ASSERT(slot >= 0); // TODO: remember the error and fail lowering + if (slot < 0) + { + slot = kInvalidSpill; + error = true; + } build.str(def.regA64, mem(sp, sSpillArea.data + slot * 8)); diff --git a/CodeGen/src/IrRegAllocA64.h b/CodeGen/src/IrRegAllocA64.h index 68974378..854a9f10 100644 --- a/CodeGen/src/IrRegAllocA64.h +++ b/CodeGen/src/IrRegAllocA64.h @@ -77,6 +77,8 @@ struct IrRegAllocA64 // which 8-byte slots are free uint32_t freeSpillSlots = 0; + + bool error = false; }; } // namespace A64 diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 27374077..b81aec8c 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -121,6 +121,14 @@ RegisterX64 IrRegAllocX64::takeReg(RegisterX64 reg, uint32_t instIdx) return reg; } +bool IrRegAllocX64::canTakeReg(RegisterX64 reg) const +{ + const std::array& freeMap = reg.size == SizeX64::xmmword ? freeXmmMap : freeGprMap; + const std::array& instUsers = reg.size == SizeX64::xmmword ? xmmInstUsers : gprInstUsers; + + return freeMap[reg.index] || instUsers[reg.index] != kInvalidInstIdx; +} + void IrRegAllocX64::freeReg(RegisterX64 reg) { if (reg.size == SizeX64::xmmword) diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index e99a991a..960be4ed 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -737,6 +737,23 @@ static BuiltinImplResult translateBuiltinVector(IrBuilder& build, int nparams, i return {BuiltinImplType::UsesFallback, 1}; } +static BuiltinImplResult translateBuiltinStringLen(IrBuilder& build, int nparams, int ra, int arg, IrOp args, int nresults, IrOp fallback) +{ + if (nparams < 1 || nresults > 1) + return {BuiltinImplType::None, -1}; + + build.loadAndCheckTag(build.vmReg(arg), LUA_TSTRING, fallback); + + IrOp ts = build.inst(IrCmd::LOAD_POINTER, build.vmReg(arg)); + + IrOp len = build.inst(IrCmd::STRING_LEN, ts); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), build.inst(IrCmd::INT_TO_NUM, len)); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + + return {BuiltinImplType::UsesFallback, 1}; +} + BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, IrOp args, int nparams, int nresults, IrOp fallback) { // Builtins are not allowed to handle variadic arguments @@ -821,6 +838,8 @@ BuiltinImplResult translateBuiltin(IrBuilder& build, int bfid, int ra, int arg, return translateBuiltinTypeof(build, nparams, ra, arg, args, nresults, fallback); case LBF_VECTOR: return translateBuiltinVector(build, nparams, ra, arg, args, nresults, fallback); + case LBF_STRING_LEN: + return translateBuiltinStringLen(build, nparams, ra, arg, args, nresults, fallback); default: return {BuiltinImplType::None, -1}; } diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 8f18827b..3cbcd3cb 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -516,6 +516,7 @@ void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc) void translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool customParams, int customParamCount, IrOp customArgs, IrOp next) { + LuauOpcode opcode = LuauOpcode(LUAU_INSN_OP(*pc)); int bfid = LUAU_INSN_A(*pc); int skip = LUAU_INSN_C(*pc); @@ -540,7 +541,8 @@ void translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool IrOp fallback = build.block(IrBlockKind::Fallback); - build.inst(IrCmd::CHECK_SAFE_ENV, fallback); + // In unsafe environment, instead of retrying fastcall at 'pcpos' we side-exit directly to fallback sequence + build.inst(IrCmd::CHECK_SAFE_ENV, build.constUint(pcpos + getOpLength(opcode))); BuiltinImplResult br = translateBuiltin(build, LuauBuiltinFunction(bfid), ra, arg, builtinArgs, nparams, nresults, fallback); @@ -554,7 +556,7 @@ void translateFastCallN(IrBuilder& build, const Instruction* pc, int pcpos, bool else { // TODO: we can skip saving pc for some well-behaved builtins which we didn't inline - build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + getOpLength(opcode))); IrOp res = build.inst(IrCmd::INVOKE_FASTCALL, build.constUint(bfid), build.vmReg(ra), build.vmReg(arg), args, build.constInt(nparams), build.constInt(nresults)); @@ -668,7 +670,7 @@ void translateInstForGPrepNext(IrBuilder& build, const Instruction* pc, int pcpo IrOp fallback = build.block(IrBlockKind::Fallback); // fast-path: pairs/next - build.inst(IrCmd::CHECK_SAFE_ENV, fallback); + build.inst(IrCmd::CHECK_SAFE_ENV, build.constUint(pcpos)); IrOp tagB = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); build.inst(IrCmd::CHECK_TAG, tagB, build.constTag(LUA_TTABLE), fallback); IrOp tagC = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); @@ -695,7 +697,7 @@ void translateInstForGPrepInext(IrBuilder& build, const Instruction* pc, int pcp IrOp finish = build.block(IrBlockKind::Internal); // fast-path: ipairs/inext - build.inst(IrCmd::CHECK_SAFE_ENV, fallback); + build.inst(IrCmd::CHECK_SAFE_ENV, build.constUint(pcpos)); IrOp tagB = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 1)); build.inst(IrCmd::CHECK_TAG, tagB, build.constTag(LUA_TTABLE), fallback); IrOp tagC = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra + 2)); @@ -921,7 +923,7 @@ void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos) IrOp fastPath = build.block(IrBlockKind::Internal); IrOp fallback = build.block(IrBlockKind::Fallback); - build.inst(IrCmd::CHECK_SAFE_ENV, fallback); + build.inst(IrCmd::CHECK_SAFE_ENV, build.constUint(pcpos)); // note: if import failed, k[] is nil; we could check this during codegen, but we instead use runtime fallback // this allows us to handle ahead-of-time codegen smoothly when an import fails to resolve at runtime diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 833d1cdd..2395fb1e 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -80,6 +80,8 @@ IrValueKind getCmdValueKind(IrCmd cmd) return IrValueKind::None; case IrCmd::TABLE_LEN: return IrValueKind::Double; + case IrCmd::STRING_LEN: + return IrValueKind::Int; case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: return IrValueKind::Pointer; @@ -684,8 +686,7 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 unsigned op1 = unsigned(function.intOp(inst.a)); int op2 = function.intOp(inst.b); - if (unsigned(op2) < 32) - substitute(function, inst, build.constInt(op1 << op2)); + substitute(function, inst, build.constInt(op1 << (op2 & 31))); } else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) { @@ -698,8 +699,7 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 unsigned op1 = unsigned(function.intOp(inst.a)); int op2 = function.intOp(inst.b); - if (unsigned(op2) < 32) - substitute(function, inst, build.constInt(op1 >> op2)); + substitute(function, inst, build.constInt(op1 >> (op2 & 31))); } else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) { @@ -712,12 +712,9 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 int op1 = function.intOp(inst.a); int op2 = function.intOp(inst.b); - if (unsigned(op2) < 32) - { - // note: technically right shift of negative values is UB, but this behavior is getting defined in C++20 and all compilers do the - // right (shift) thing. - substitute(function, inst, build.constInt(op1 >> op2)); - } + // note: technically right shift of negative values is UB, but this behavior is getting defined in C++20 and all compilers do the + // right (shift) thing. + substitute(function, inst, build.constInt(op1 >> (op2 & 31))); } else if (inst.b.kind == IrOpKind::Constant && function.intOp(inst.b) == 0) { @@ -794,5 +791,17 @@ uint32_t getNativeContextOffset(int bfid) return 0; } +void killUnusedBlocks(IrFunction& function) +{ + // Start from 1 as the first block is the entry block + for (unsigned i = 1; i < function.blocks.size(); i++) + { + IrBlock& block = function.blocks[i]; + + if (block.kind != IrBlockKind::Dead && block.useCount == 0) + kill(function, block); + } +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index e3cbef41..eeedd6cb 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -441,17 +441,25 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.substituteOrRecordVmRegLoad(inst); break; case IrCmd::LOAD_DOUBLE: - if (IrOp value = state.tryGetValue(inst.a); value.kind == IrOpKind::Constant) + { + IrOp value = state.tryGetValue(inst.a); + + if (function.asDoubleOp(value)) substitute(function, inst, value); else if (inst.a.kind == IrOpKind::VmReg) state.substituteOrRecordVmRegLoad(inst); break; + } case IrCmd::LOAD_INT: - if (IrOp value = state.tryGetValue(inst.a); value.kind == IrOpKind::Constant) + { + IrOp value = state.tryGetValue(inst.a); + + if (function.asIntOp(value)) substitute(function, inst, value); else if (inst.a.kind == IrOpKind::VmReg) state.substituteOrRecordVmRegLoad(inst); break; + } case IrCmd::LOAD_TVALUE: if (inst.a.kind == IrOpKind::VmReg) state.substituteOrRecordVmRegLoad(inst); @@ -775,6 +783,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::JUMP_EQ_POINTER: case IrCmd::JUMP_SLOT_MATCH: case IrCmd::TABLE_LEN: + case IrCmd::STRING_LEN: case IrCmd::NEW_TABLE: case IrCmd::DUP_TABLE: case IrCmd::TRY_NUM_TO_INDEX: diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index 9296519b..8d360f87 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -2314,9 +2314,9 @@ static const char* getBaseTypeString(uint8_t type) case LBC_TYPE_STRING: return "string"; case LBC_TYPE_TABLE: - return "{ }"; + return "table"; case LBC_TYPE_FUNCTION: - return "function( )"; + return "function"; case LBC_TYPE_THREAD: return "thread"; case LBC_TYPE_USERDATA: diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 8dd9876c..83aad3d8 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,8 +26,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(CompileFunctionType, false) -LUAU_FASTFLAG(BytecodeVersion4) +LUAU_FASTFLAGVARIABLE(LuauCompileFunctionType, false) namespace Luau { @@ -103,6 +102,7 @@ struct Compiler , locstants(nullptr) , tableShapes(nullptr) , builtins(nullptr) + , typeMap(nullptr) { // preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays localStack.reserve(16); @@ -204,11 +204,11 @@ struct Compiler setDebugLine(func); - if (FFlag::BytecodeVersion4 && FFlag::CompileFunctionType) + if (FFlag::LuauCompileFunctionType) { - std::string funcType = getFunctionType(func); - if (!funcType.empty()) - bytecode.setFunctionTypeInfo(std::move(funcType)); + // note: we move types out of typeMap which is safe because compileFunction is only called once per function + if (std::string* funcType = typeMap.find(func)) + bytecode.setFunctionTypeInfo(std::move(*funcType)); } if (func->vararg) @@ -3807,6 +3807,8 @@ struct Compiler DenseHashMap locstants; DenseHashMap tableShapes; DenseHashMap builtins; + DenseHashMap typeMap; + const DenseHashMap* builtinsFold = nullptr; unsigned int regTop = 0; @@ -3870,6 +3872,11 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c root->visit(&fenvVisitor); } + if (FFlag::LuauCompileFunctionType) + { + buildTypeMap(compiler.typeMap, root); + } + // gathers all functions with the invariant that all function references are to functions earlier in the list // for example, function foo() return function() end end will result in two vector entries, [0] = anonymous and [1] = foo std::vector functions; diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index 02041986..b8db7fb2 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -6,22 +6,58 @@ namespace Luau { -static LuauBytecodeEncodedType getType(AstType* ty) +static bool isGeneric(AstName name, const AstArray& generics) +{ + for (const AstGenericType& gt : generics) + if (gt.name == name) + return true; + + return false; +} + +static LuauBytecodeEncodedType getPrimitiveType(AstName name) +{ + if (name == "nil") + return LBC_TYPE_NIL; + else if (name == "boolean") + return LBC_TYPE_BOOLEAN; + else if (name == "number") + return LBC_TYPE_NUMBER; + else if (name == "string") + return LBC_TYPE_STRING; + else if (name == "thread") + return LBC_TYPE_THREAD; + else if (name == "any" || name == "unknown") + return LBC_TYPE_ANY; + else + return LBC_TYPE_INVALID; +} + +static LuauBytecodeEncodedType getType( + AstType* ty, const AstArray& generics, const DenseHashMap& typeAliases, bool resolveAliases) { if (AstTypeReference* ref = ty->as()) { - if (ref->name == "nil") - return LBC_TYPE_NIL; - else if (ref->name == "boolean") - return LBC_TYPE_BOOLEAN; - else if (ref->name == "number") - return LBC_TYPE_NUMBER; - else if (ref->name == "string") - return LBC_TYPE_STRING; - else if (ref->name == "thread") - return LBC_TYPE_THREAD; - else if (ref->name == "any" || ref->name == "unknown") + if (ref->prefix) return LBC_TYPE_ANY; + + if (AstStatTypeAlias* const* alias = typeAliases.find(ref->name); alias && *alias) + { + // note: we only resolve aliases to the depth of 1 to avoid dealing with recursive aliases + if (resolveAliases) + return getType((*alias)->type, (*alias)->generics, typeAliases, /* resolveAliases= */ false); + else + return LBC_TYPE_ANY; + } + + if (isGeneric(ref->name, generics)) + return LBC_TYPE_ANY; + + if (LuauBytecodeEncodedType prim = getPrimitiveType(ref->name); prim != LBC_TYPE_INVALID) + return prim; + + // not primitive or alias or generic => host-provided, we assume userdata for now + return LBC_TYPE_USERDATA; } else if (AstTypeTable* table = ty->as()) { @@ -38,7 +74,7 @@ static LuauBytecodeEncodedType getType(AstType* ty) for (AstType* ty : un->types) { - LuauBytecodeEncodedType et = getType(ty); + LuauBytecodeEncodedType et = getType(ty, generics, typeAliases, resolveAliases); if (et == LBC_TYPE_NIL) { @@ -69,11 +105,8 @@ static LuauBytecodeEncodedType getType(AstType* ty) return LBC_TYPE_ANY; } -std::string getFunctionType(const AstExprFunction* func) +static std::string getFunctionType(const AstExprFunction* func, const DenseHashMap& typeAliases) { - if (func->vararg || func->generics.size || func->genericPacks.size) - return {}; - bool self = func->self != 0; std::string typeInfo; @@ -88,7 +121,8 @@ std::string getFunctionType(const AstExprFunction* func) bool haveNonAnyParam = false; for (AstLocal* arg : func->args) { - LuauBytecodeEncodedType ty = arg->annotation ? getType(arg->annotation) : LBC_TYPE_ANY; + LuauBytecodeEncodedType ty = + arg->annotation ? getType(arg->annotation, func->generics, typeAliases, /* resolveAliases= */ true) : LBC_TYPE_ANY; if (ty != LBC_TYPE_ANY) haveNonAnyParam = true; @@ -103,4 +137,88 @@ std::string getFunctionType(const AstExprFunction* func) return typeInfo; } -} // namespace Luau \ No newline at end of file +struct TypeMapVisitor : AstVisitor +{ + DenseHashMap& typeMap; + + DenseHashMap typeAliases; + std::vector> typeAliasStack; + + TypeMapVisitor(DenseHashMap& typeMap) + : typeMap(typeMap) + , typeAliases(AstName()) + { + } + + size_t pushTypeAliases(AstStatBlock* block) + { + size_t aliasStackTop = typeAliasStack.size(); + + for (AstStat* stat : block->body) + if (AstStatTypeAlias* alias = stat->as()) + { + AstStatTypeAlias*& prevAlias = typeAliases[alias->name]; + + typeAliasStack.push_back(std::make_pair(alias->name, prevAlias)); + prevAlias = alias; + } + + return aliasStackTop; + } + + void popTypeAliases(size_t aliasStackTop) + { + while (typeAliasStack.size() > aliasStackTop) + { + std::pair& top = typeAliasStack.back(); + + typeAliases[top.first] = top.second; + typeAliasStack.pop_back(); + } + } + + bool visit(AstStatBlock* node) override + { + size_t aliasStackTop = pushTypeAliases(node); + + for (AstStat* stat : node->body) + stat->visit(this); + + popTypeAliases(aliasStackTop); + + return false; + } + + // repeat..until scoping rules are such that condition (along with any possible functions declared in it) has aliases from repeat body in scope + bool visit(AstStatRepeat* node) override + { + size_t aliasStackTop = pushTypeAliases(node->body); + + for (AstStat* stat : node->body->body) + stat->visit(this); + + node->condition->visit(this); + + popTypeAliases(aliasStackTop); + + return false; + } + + bool visit(AstExprFunction* node) override + { + std::string type = getFunctionType(node, typeAliases); + + if (!type.empty()) + typeMap[node] = std::move(type); + + return true; + } +}; + +void buildTypeMap(DenseHashMap& typeMap, AstNode* root) +{ + TypeMapVisitor visitor(typeMap); + root->visit(&visitor); +} + +} // namespace Luau diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h index 1be9155f..c3dd1620 100644 --- a/Compiler/src/Types.h +++ b/Compiler/src/Types.h @@ -3,7 +3,11 @@ #include "Luau/Ast.h" +#include + namespace Luau { -std::string getFunctionType(const AstExprFunction* func); + +void buildTypeMap(DenseHashMap& typeMap, AstNode* root); + } // namespace Luau diff --git a/Makefile b/Makefile index 852b14f8..17bae919 100644 --- a/Makefile +++ b/Makefile @@ -176,9 +176,9 @@ coverage: $(TESTS_TARGET) $(COMPILE_CLI_TARGET) mv default.profraw codegen-x64.profraw llvm-profdata merge *.profraw -o default.profdata rm *.profraw - llvm-cov show -format=html -show-instantiations=false -show-line-counts=true -show-region-summary=false -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -output-dir=coverage --instr-profile default.profdata build/coverage/luau-tests - llvm-cov report -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -show-region-summary=false --instr-profile default.profdata build/coverage/luau-tests - llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata build/coverage/luau-tests >coverage.info + llvm-cov show -format=html -show-instantiations=false -show-line-counts=true -show-region-summary=false -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -output-dir=coverage --instr-profile default.profdata -object build/coverage/luau-tests -object build/coverage/luau-compile + llvm-cov report -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -show-region-summary=false --instr-profile default.profdata -object build/coverage/luau-tests -object build/coverage/luau-compile + llvm-cov export -ignore-filename-regex=\(tests\|extern\|CLI\)/.* -format lcov --instr-profile default.profdata -object build/coverage/luau-tests -object build/coverage/luau-compile >coverage.info format: git ls-files '*.h' '*.cpp' | xargs clang-format-11 -i diff --git a/Sources.cmake b/Sources.cmake index 74709b4b..ccf2e1df 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -152,6 +152,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/DataFlowGraph.h Analysis/include/Luau/DcrLogger.h Analysis/include/Luau/Def.h + Analysis/include/Luau/Differ.h Analysis/include/Luau/Documentation.h Analysis/include/Luau/Error.h Analysis/include/Luau/FileResolver.h @@ -209,6 +210,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/DataFlowGraph.cpp Analysis/src/DcrLogger.cpp Analysis/src/Def.cpp + Analysis/src/Differ.cpp Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/Error.cpp Analysis/src/Frontend.cpp @@ -366,6 +368,7 @@ if(TARGET Luau.UnitTest) tests/CostModel.test.cpp tests/DataFlowGraph.test.cpp tests/DenseHash.test.cpp + tests/Differ.test.cpp tests/Error.test.cpp tests/Frontend.test.cpp tests/IrBuilder.test.cpp diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 6330b4c3..a47ad34f 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -34,6 +34,7 @@ Proto* luaF_newproto(lua_State* L) f->codeentry = NULL; f->execdata = NULL; f->exectarget = 0; + f->typeinfo = NULL; return f; } @@ -162,6 +163,9 @@ void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page) } #endif + if (f->typeinfo) + luaM_freearray(L, f->typeinfo, f->numparams + 2, uint8_t, f->memcat); + luaM_freegco(L, f, sizeof(Proto), f->memcat, page); } diff --git a/VM/src/lobject.h b/VM/src/lobject.h index a4261633..560969d6 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -279,6 +279,8 @@ typedef struct Proto void* execdata; uintptr_t exectarget; + uint8_t* typeinfo; + GCObject* gclist; diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index edbe5035..a26dd0b8 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -13,6 +13,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauLoadCheckGC, false) + // TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens template struct TempBuffer @@ -178,6 +180,10 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size return 1; } + // we will allocate a fair amount of memory so check GC before we do + if (FFlag::LuauLoadCheckGC) + luaC_checkGC(L); + // pause GC for the duration of deserialization - some objects we're creating aren't rooted // TODO: if an allocation error happens mid-load, we do not unpause GC! size_t GCthreshold = L->global->GCthreshold; @@ -188,11 +194,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size TString* source = luaS_new(L, chunkname); + uint8_t typesversion = 0; if (version >= 4) { - uint8_t typesversion = read(data, size, offset); - LUAU_ASSERT(typesversion == 1); + typesversion = read(data, size, offset); } // string table @@ -229,7 +235,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size uint32_t typesize = readVarInt(data, size, offset); - if (typesize) + if (typesize && typesversion == LBC_TYPE_VERSION) { uint8_t* types = (uint8_t*)data + offset; @@ -237,8 +243,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION); LUAU_ASSERT(types[1] == p->numparams); - offset += typesize; + p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat); + memcpy(p->typeinfo, types, typesize); } + + offset += typesize; } p->sizecode = readVarInt(data, size, offset); diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index 9366da5e..c30e9377 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -27,12 +27,16 @@ const bool kFuzzTypeck = true; const bool kFuzzVM = true; const bool kFuzzTranspile = true; const bool kFuzzCodegen = true; +const bool kFuzzCodegenAssembly = true; // Should we generate type annotations? const bool kFuzzTypes = true; +const Luau::CodeGen::AssemblyOptions::Target kFuzzCodegenTarget = Luau::CodeGen::AssemblyOptions::A64; + static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); static_assert(!(kFuzzCodegen && !kFuzzVM), "Codegen requires the VM!"); +static_assert(!(kFuzzCodegenAssembly && !kFuzzCompiler), "Codegen requires the compiler!"); std::vector protoprint(const luau::ModuleSet& stat, bool types); @@ -348,6 +352,23 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) } } + // run codegen on resulting bytecode (in separate state) + if (kFuzzCodegenAssembly && bytecode.size()) + { + static lua_State* globalState = luaL_newstate(); + + if (luau_load(globalState, "=fuzz", bytecode.data(), bytecode.size(), 0) == 0) + { + Luau::CodeGen::AssemblyOptions options; + options.outputBinary = true; + options.target = kFuzzCodegenTarget; + Luau::CodeGen::getAssembly(globalState, -1, options); + } + + lua_pop(globalState, 1); + lua_gc(globalState, LUA_GCCOLLECT, 0); + } + // run resulting bytecode (from last successfully compiler module) if (kFuzzVM && bytecode.size()) { diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index 5e28e8d9..78406933 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -107,7 +107,6 @@ ClassFixture::ClassFixture() globals.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType}; auto addIndexableClass = [&arena, &globals](const char* className, TypeId keyType, TypeId returnType) { - ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true); TypeId indexableClassMetaType = arena.addType(TableType{}); TypeId indexableClassType = arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", TableIndexer{keyType, returnType}}); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 97cc3263..fde6e90e 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -49,7 +49,7 @@ static std::string compileFunction0Coverage(const char* source, int level) return bcb.dumpFunction(0); } -static std::string compileFunction0TypeTable(const char* source) +static std::string compileTypeTable(const char* source) { Luau::BytecodeBuilder bcb; bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); @@ -7080,12 +7080,9 @@ L1: RETURN R3 1 TEST_CASE("EncodedTypeTable") { - ScopedFastFlag sffs[] = { - {"BytecodeVersion4", true}, - {"CompileFunctionType", true}, - }; + ScopedFastFlag sff("LuauCompileFunctionType", true); - CHECK_EQ("\n" + compileFunction0TypeTable(R"( + CHECK_EQ("\n" + compileTypeTable(R"( function myfunc(test: string, num: number) print(test) end @@ -7104,6 +7101,9 @@ end function myfunc5(test: string | number, n: number | boolean) end +function myfunc6(test: (number) -> string) +end + myfunc('test') )"), R"( @@ -7111,9 +7111,10 @@ myfunc('test') 1: function(number?) 2: function(string, number) 3: function(any, number) +5: function(function) )"); - CHECK_EQ("\n" + compileFunction0TypeTable(R"( + CHECK_EQ("\n" + compileTypeTable(R"( local Str = { a = 1 } @@ -7126,7 +7127,95 @@ end Str:test(234) )"), R"( -0: function({ }, number) +0: function(table, number) +)"); +} + +TEST_CASE("HostTypesAreUserdata") +{ + ScopedFastFlag sff("LuauCompileFunctionType", true); + + CHECK_EQ("\n" + compileTypeTable(R"( +function myfunc(test: string, num: number) + print(test) +end + +function myfunc2(test: Instance, num: number) +end + +type Foo = string + +function myfunc3(test: string, n: Foo) +end + +function myfunc4(test: Bar, n: Part) +end +)"), + R"( +0: function(string, number) +1: function(userdata, number) +2: function(string, string) +3: function(any, userdata) +)"); +} + +TEST_CASE("TypeAliasScoping") +{ + ScopedFastFlag sff("LuauCompileFunctionType", true); + + CHECK_EQ("\n" + compileTypeTable(R"( +do + type Part = number +end + +function myfunc1(test: Part, num: number) +end + +do + type Part = number + + function myfunc2(test: Part, num: number) + end +end + +repeat + type Part = number +until (function(test: Part, num: number) end)() + +function myfunc4(test: Instance, num: number) +end + +type Instance = string +)"), + R"( +0: function(userdata, number) +1: function(number, number) +2: function(number, number) +3: function(string, number) +)"); +} + +TEST_CASE("TypeAliasResolve") +{ + ScopedFastFlag sff("LuauCompileFunctionType", true); + + CHECK_EQ("\n" + compileTypeTable(R"( +type Foo1 = number +type Foo2 = { number } +type Foo3 = Part +type Foo4 = Foo1 -- we do not resolve aliases within aliases +type Foo5 = X + +function myfunc(f1: Foo1, f2: Foo2, f3: Foo3, f4: Foo4, f5: Foo5) +end + +function myfuncerr(f1: Foo1, f2: Foo5) +end + +)"), + R"( +0: function(number, table, userdata, any, any) +1: function(number, any) )"); } diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp new file mode 100644 index 00000000..6b570f39 --- /dev/null +++ b/tests/Differ.test.cpp @@ -0,0 +1,316 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Differ.h" +#include "Luau/Error.h" +#include "Luau/Frontend.h" + +#include "Fixture.h" + +#include "doctest.h" +#include + +using namespace Luau; + +TEST_SUITE_BEGIN("Differ"); + +TEST_CASE_FIXTURE(Fixture, "equal_numbers") +{ + CheckResult result = check(R"( + local foo = 5 + local almostFoo = 78 + almostFoo = foo + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + try + { + DifferResult diffRes = diff(foo, almostFoo); + CHECK(!diffRes.diffError.has_value()); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } +} + +TEST_CASE_FIXTURE(Fixture, "equal_strings") +{ + CheckResult result = check(R"( + local foo = "hello" + local almostFoo = "world" + almostFoo = foo + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + try + { + DifferResult diffRes = diff(foo, almostFoo); + CHECK(!diffRes.diffError.has_value()); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } +} + +TEST_CASE_FIXTURE(Fixture, "equal_tables") +{ + CheckResult result = check(R"( + local foo = { x = 1, y = "where" } + local almostFoo = { x = 5, y = "when" } + almostFoo = foo + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + try + { + DifferResult diffRes = diff(foo, almostFoo); + CHECK(!diffRes.diffError.has_value()); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } +} + +TEST_CASE_FIXTURE(Fixture, "a_table_missing_property") +{ + CheckResult result = check(R"( + local foo = { x = 1, y = 2 } + local almostFoo = { x = 1, z = 3 } + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo is missing " + "the property y", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "left_table_missing_property") +{ + CheckResult result = check(R"( + local foo = { x = 1 } + local almostFoo = { x = 1, z = 3 } + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at foo is missing the property z, while the right type at almostFoo.z " + "has type number", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "a_table_wrong_type") +{ + CheckResult result = check(R"( + local foo = { x = 1, y = 2 } + local almostFoo = { x = 1, y = "two" } + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo.y has type " + "string", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "a_table_wrong_type") +{ + CheckResult result = check(R"( + local foo: string + local almostFoo: number + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at has type string, while the right type at " + " has type number", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "a_nested_table_wrong_type") +{ + CheckResult result = check(R"( + local foo = { x = 1, inner = { table = { has = { wrong = { value = 5 } } } } } + local almostFoo = { x = 1, inner = { table = { has = { wrong = { value = "five" } } } } } + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.value has type number, while the right " + "type at almostFoo.inner.table.has.wrong.value has type string", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "a_nested_table_wrong_match") +{ + CheckResult result = check(R"( + local foo = { x = 1, inner = { table = { has = { wrong = { variant = { because = { it = { goes = { on = "five" } } } } } } } } } + local almostFoo = { x = 1, inner = { table = { has = { wrong = { variant = "five" } } } } } + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ("DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.variant has type { because: { it: { goes: " + "{ on: string } } } }, while the right type at almostFoo.inner.table.has.wrong.variant has type string", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "singleton") +{ + CheckResult result = check(R"( + local foo: "hello" = "hello" + local almostFoo: true = true + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ( + R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type true)", + diffMessage); +} + +TEST_CASE_FIXTURE(Fixture, "equal_singleton") +{ + CheckResult result = check(R"( + local foo: "hello" = "hello" + local almostFoo: "hello" + almostFoo = foo + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + try + { + DifferResult diffRes = diff(foo, almostFoo); + INFO(diffRes.diffError->toString()); + CHECK(!diffRes.diffError.has_value()); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } +} + +TEST_CASE_FIXTURE(Fixture, "singleton_string") +{ + CheckResult result = check(R"( + local foo: "hello" = "hello" + local almostFoo: "world" = "world" + almostFoo = foo + )"); + LUAU_REQUIRE_ERRORS(result); + + TypeId foo = requireType("foo"); + TypeId almostFoo = requireType("almostFoo"); + std::string diffMessage; + try + { + diffMessage = diff(foo, almostFoo).diffError->toString(); + } + catch (InternalCompilerError e) + { + INFO(("InternalCompilerError: " + e.message)); + CHECK(false); + } + CHECK_EQ( + R"(DiffError: these two types are not equal because the left type at has type "hello", while the right type at has type "world")", + diffMessage); +} + +TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 0b9c872c..8f6834a1 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1146,4 +1146,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "module_scope_check") CHECK_EQ(toString(ty), "number"); } +TEST_CASE_FIXTURE(FrontendFixture, "parse_only") +{ + fileResolver.source["game/Gui/Modules/A"] = R"( + local a: number = 'oh no a type error' + return {a=a} + )"; + + fileResolver.source["game/Gui/Modules/B"] = R"( + local Modules = script.Parent + local A = require(Modules.A) + local b: number = 2 + )"; + + frontend.parse("game/Gui/Modules/B"); + + REQUIRE(frontend.sourceNodes.count("game/Gui/Modules/A")); + REQUIRE(frontend.sourceNodes.count("game/Gui/Modules/B")); + + auto node = frontend.sourceNodes["game/Gui/Modules/B"]; + CHECK_EQ(node->requireSet.count("game/Gui/Modules/A"), 1); + REQUIRE_EQ(node->requireLocations.size(), 1); + CHECK_EQ(node->requireLocations[0].second, Luau::Location(Position(2, 18), Position(2, 36))); + + // Early parse doesn't cause typechecking to be skipped + CheckResult result = frontend.check("game/Gui/Modules/B"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + + CHECK_EQ("game/Gui/Modules/A", result.errors[0].moduleName); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index f1399a59..57103352 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -526,7 +526,7 @@ bb_0: )"); } -TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32Blocked") +TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32RangeReduction") { IrOp block = build.block(IrBlockKind::Internal); @@ -534,10 +534,10 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32Blocked") build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(-10))); build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(140))); - build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xf), build.constInt(-10))); - build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xf), build.constInt(140))); - build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xf), build.constInt(-10))); - build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xf), build.constInt(140))); + build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10))); + build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(140))); + build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10))); + build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(140))); build.inst(IrCmd::RETURN, build.constUint(0)); @@ -546,18 +546,12 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32Blocked") CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( bb_0: - %0 = BITLSHIFT_UINT 15i, -10i - STORE_INT R10, %0 - %2 = BITLSHIFT_UINT 15i, 140i - STORE_INT R10, %2 - %4 = BITRSHIFT_UINT 15i, -10i - STORE_INT R10, %4 - %6 = BITRSHIFT_UINT 15i, 140i - STORE_INT R10, %6 - %8 = BITARSHIFT_UINT 15i, -10i - STORE_INT R10, %8 - %10 = BITARSHIFT_UINT 15i, 140i - STORE_INT R10, %10 + STORE_INT R10, 62914560i + STORE_INT R10, 61440i + STORE_INT R10, 3i + STORE_INT R10, 4095i + STORE_INT R10, 3i + STORE_INT R10, 4095i RETURN 0u )"); @@ -1864,6 +1858,34 @@ bb_0: )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType") +{ + IrOp block = build.block(IrBlockKind::Internal); + + build.beginBlock(block); + + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2)); + IrOp value1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), value1); + IrOp value2 = build.inst(IrCmd::LOAD_INT, build.vmReg(1)); + build.inst(IrCmd::STORE_INT, build.vmReg(2), value2); + build.inst(IrCmd::RETURN, build.constUint(0)); + + updateUseCounts(build.function); + constPropInBlockChains(build, true); + + CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"( +bb_0: + STORE_INT R0, 2i + %1 = LOAD_DOUBLE R0 + STORE_DOUBLE R1, %1 + %3 = LOAD_INT R1 + STORE_INT R2, %3 + RETURN 0u + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Analysis"); diff --git a/tests/IrCallWrapperX64.test.cpp b/tests/IrCallWrapperX64.test.cpp index c8918dbd..ec04e531 100644 --- a/tests/IrCallWrapperX64.test.cpp +++ b/tests/IrCallWrapperX64.test.cpp @@ -10,8 +10,8 @@ using namespace Luau::CodeGen::X64; class IrCallWrapperX64Fixture { public: - IrCallWrapperX64Fixture() - : build(/* logText */ true, ABIX64::Windows) + IrCallWrapperX64Fixture(ABIX64 abi = ABIX64::Windows) + : build(/* logText */ true, abi) , regs(build, function) , callWrap(regs, build, ~0u) { @@ -42,6 +42,15 @@ public: static constexpr RegisterX64 rArg4d = r9d; }; +class IrCallWrapperX64FixtureSystemV : public IrCallWrapperX64Fixture +{ +public: + IrCallWrapperX64FixtureSystemV() + : IrCallWrapperX64Fixture(ABIX64::SystemV) + { + } +}; + TEST_SUITE_BEGIN("IrCallWrapperX64"); TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "SimpleRegs") @@ -519,4 +528,35 @@ TEST_CASE_FIXTURE(IrCallWrapperX64Fixture, "ImmediateConflictWithFunction") )"); } +TEST_CASE_FIXTURE(IrCallWrapperX64FixtureSystemV, "SuggestedConflictWithReserved") +{ + ScopedRegX64 tmp{regs, regs.takeReg(r9, kInvalidInstIdx)}; + + IrCallWrapperX64 callWrap(regs, build); + callWrap.addArgument(SizeX64::qword, r12); + callWrap.addArgument(SizeX64::qword, r13); + callWrap.addArgument(SizeX64::qword, r14); + callWrap.addArgument(SizeX64::dword, 2); + callWrap.addArgument(SizeX64::qword, 1); + + RegisterX64 reg = callWrap.suggestNextArgumentRegister(SizeX64::dword); + build.mov(reg, 10); + callWrap.addArgument(SizeX64::dword, reg); + + callWrap.call(tmp.release()); + + checkMatch(R"( + mov eax,Ah + mov rdi,r12 + mov rsi,r13 + mov rdx,r14 + mov rcx,r9 + mov r9d,eax + mov rax,rcx + mov ecx,2 + mov r8,1 + call rax +)"); +} + TEST_SUITE_END(); diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 8fe86655..afcc08f2 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -735,6 +735,37 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metata CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>"))); } +TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection") +{ + CheckResult result = check(R"( + type A = any? + type B = A & A + )"); + + std::optional t = lookupType("B"); + REQUIRE(t); + + const NormalizedType* nt = normalizer.normalize(*t); + REQUIRE(nt); + + CHECK("any" == toString(normalizer.typeFromNormal(*nt))); +} + +TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union") +{ + ScopedFastFlag sff{"LuauNormalizeCyclicUnions", true}; + + // T where T = any & (number | T) + TypeId t = arena.addType(BlockedType{}); + TypeId u = arena.addType(UnionType{{builtinTypes->numberType, t}}); + asMutable(t)->ty.emplace(IntersectionType{{builtinTypes->anyType, u}}); + + const NormalizedType* nt = normalizer.normalize(t); + REQUIRE(nt); + + CHECK("number" == toString(normalizer.typeFromNormal(*nt))); +} + TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable") { CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>"))); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 9e3a63f7..07471d44 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -479,7 +479,6 @@ TEST_CASE_FIXTURE(ClassFixture, "callable_classes") TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") { // Test reading from an index - ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true); { CheckResult result = check(R"( local x : IndexableClass diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 0ca9bd73..615b81d6 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -398,7 +398,6 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props") TEST_CASE_FIXTURE(Fixture, "class_definition_indexer") { ScopedFastFlag LuauParseDeclareClassIndexer("LuauParseDeclareClassIndexer", true); - ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true); loadDefinition(R"( declare class Foo diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index f0630ca9..d456f378 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -2096,4 +2096,46 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_wi LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") +{ + CheckResult result = check(R"( + function num() + return 5 + end + + local function num_or_str() + if math.random() > 0.5 then + return num() + else + return "some string" + end + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); + CHECK_EQ("() -> number", toString(requireType("num_or_str"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str") +{ + CheckResult result = check(R"( + local function num_or_str() + if math.random() > 0.5 then + return num() + else + return "some string" + end + end + + function num() + return 5 + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); + CHECK_EQ("() -> number", toString(requireType("num_or_str"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 3b0654a0..0c888740 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -917,6 +917,52 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_comparison_ifelse_expression") CHECK_EQ("any", toString(requireTypeAtPosition({6, 66}))); } + +TEST_CASE_FIXTURE(BuiltinsFixture, "is_truthy_constraint_while_expression") +{ + CheckResult result = check(R"( + function f(v:string?) + while v do + local foo = v + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("string", toString(requireTypeAtPosition({3, 28}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "invert_is_truthy_constraint_while_expression") +{ + CheckResult result = check(R"( + function f(v:string?) + while not v do + local foo = v + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("nil", toString(requireTypeAtPosition({3, 28}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "refine_the_correct_types_opposite_of_while_a_is_not_number_or_string") +{ + CheckResult result = check(R"( + local function f(a: string | number | boolean) + while type(a) ~= "number" and type(a) ~= "string" do + local foo = a + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("boolean", toString(requireTypeAtPosition({3, 28}))); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_lookup_a_shadowed_local_that_which_was_previously_refined") { CheckResult result = check(R"( @@ -1580,8 +1626,6 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_duri TEST_CASE_FIXTURE(Fixture, "refine_a_property_of_some_global") { - ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true}; - CheckResult result = check(R"( foo = { bar = 5 :: number? } @@ -1590,9 +1634,12 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_of_some_global") end )"); - LUAU_REQUIRE_ERROR_COUNT(3, result); + if (FFlag::DebugLuauDeferredConstraintResolution) + { + LUAU_REQUIRE_ERROR_COUNT(3, result); - CHECK_EQ("~(false?)", toString(requireTypeAtPosition({4, 30}))); + CHECK_EQ("~(false?)", toString(requireTypeAtPosition({4, 30}))); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never") @@ -1757,4 +1804,20 @@ TEST_CASE_FIXTURE(Fixture, "refinements_should_not_affect_assignment") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_should_preserve_error_suppression") +{ + CheckResult result = check(R"( + local a: any = {} + local b + if typeof(a) == "table" then + b = a.field + end + )"); + + if (FFlag::DebugLuauDeferredConstraintResolution) + LUAU_REQUIRE_NO_ERRORS(result); + else + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index f028e8e0..c61ff16e 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -9,6 +9,20 @@ using namespace Luau; TEST_SUITE_BEGIN("TypeSingletons"); +TEST_CASE_FIXTURE(Fixture, "function_args_infer_singletons") +{ + CheckResult result = check(R"( +--!strict +type Phase = "A" | "B" | "C" +local function f(e : Phase) : number + return 0 +end +local e = f("B") +)"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "bool_singletons") { CheckResult result = check(R"( diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua index 85cc06bf..bc70df3e 100644 --- a/tests/conformance/native.lua +++ b/tests/conformance/native.lua @@ -61,4 +61,35 @@ end assert(pcall(fuzzfail5) == false) +local function fuzzfail6(_) + return bit32.extract(_,671088640,_) +end + +assert(pcall(fuzzfail6, 1) == false) + +local function fuzzfail7(_) + return bit32.extract(_,_,671088640) +end + +assert(pcall(fuzzfail7, 1) == false) + +local function fuzzfail8(...) + local _ = _,_ + _.n0,_,_,_,_,_,_,_,_._,_,_,_[...],_,_,_ = nil + _,n0,_,_,_,_,_,_,_,_,l0,_,_,_,_ = nil + function _() + end + _._,_,_,_,_,_,_,_,_,_,_[...],_,n0[l0],_ = nil + _[...],_,_,_,_,_,_,_,_()[_],_,_,_,_,_ = _(),... +end + +assert(pcall(fuzzfail8) == false) + +local function fuzzfail9() + local _ = bit32.bor + local x = _(_(_,_),_(_,_),_(-16834560,_),_(_(- _,-2130706432)),- _),_(_(_,_),_(-16834560,-2130706432)) +end + +assert(pcall(fuzzfail9) == false) + return('OK') diff --git a/tools/faillist.txt b/tools/faillist.txt index 1233837f..31c42eb8 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -54,6 +54,8 @@ ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.typeguard_inference_incomplete RefinementTest.discriminate_from_truthiness_of_x RefinementTest.not_t_or_some_prop_of_t +RefinementTest.refine_a_property_of_some_global +RefinementTest.refinements_should_preserve_error_suppression RefinementTest.truthy_constraint_on_properties RefinementTest.type_narrow_to_vector RefinementTest.typeguard_cast_free_table_to_vector @@ -96,7 +98,6 @@ TableTests.shared_selfs TableTests.shared_selfs_from_free_param TableTests.shared_selfs_through_metatables TableTests.table_call_metamethod_basic -TableTests.table_call_metamethod_generic TableTests.table_simple_call TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors TableTests.used_colon_instead_of_dot @@ -131,7 +132,6 @@ TypeInfer.tc_after_error_recovery_no_replacement_name_in_error TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_normalizer TypeInferAnyError.for_in_loop_iterator_is_any2 -TypeInferClasses.callable_classes TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.index_instance_property TypeInferFunctions.cannot_hoist_interior_defns_into_signature