diff --git a/Analysis/include/Luau/AstQuery.h b/Analysis/include/Luau/AstQuery.h index bf738462..aa7ef8d3 100644 --- a/Analysis/include/Luau/AstQuery.h +++ b/Analysis/include/Luau/AstQuery.h @@ -64,7 +64,7 @@ private: }; std::vector findAncestryAtPositionForAutocomplete(const SourceModule& source, Position pos); -std::vector findAstAncestryOfPosition(const SourceModule& source, Position pos); +std::vector findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes = false); AstNode* findNodeAtPosition(const SourceModule& source, Position pos); AstExpr* findExprAtPosition(const SourceModule& source, Position pos); ScopePtr findScopeAtPosition(const Module& module, Position pos); diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 3a67610a..a1caf85a 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -240,20 +240,28 @@ struct ConstraintGraphBuilder * Resolves a type from its AST annotation. * @param scope the scope that the type annotation appears within. * @param ty the AST annotation to resolve. - * @param topLevel whether the annotation is a "top-level" annotation. + * @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`. * @return the type of the AST annotation. **/ - TypeId resolveType(const ScopePtr& scope, AstType* ty, bool topLevel = false); + TypeId resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments); /** * Resolves a type pack from its AST annotation. * @param scope the scope that the type annotation appears within. * @param tp the AST annotation to resolve. + * @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`. * @return the type pack of the AST annotation. **/ - TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp); + TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArguments); - TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list); + /** + * Resolves a type pack from its AST annotation. + * @param scope the scope that the type annotation appears within. + * @param list the AST annotation to resolve. + * @param inTypeArguments whether we are resolving a type that's contained within type arguments, `<...>`. + * @return the type pack of the AST annotation. + **/ + TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments); std::vector> createGenerics(const ScopePtr& scope, AstArray generics); std::vector> createGenericPacks(const ScopePtr& scope, AstArray packs); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 5c235a35..66d3e8f3 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -111,7 +111,7 @@ struct ConstraintSolver bool tryDispatch(const FunctionCallConstraint& c, NotNull constraint); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint); bool tryDispatch(const HasPropConstraint& c, NotNull constraint); - bool tryDispatch(const SetPropConstraint& c, NotNull constraint); + bool tryDispatch(const SetPropConstraint& c, NotNull constraint, bool force); bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull constraint); // for a, ... in some_table do diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 797c9cb0..a8f83e2f 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -43,6 +43,8 @@ struct Scope std::unordered_map exportedTypeBindings; std::unordered_map privateTypeBindings; std::unordered_map typeAliasLocations; + std::unordered_map typeAliasNameLocations; + std::unordered_map importedModules; // Mapping from the name in the require statement to the internal moduleName. std::unordered_map> importedTypeBindings; DenseHashSet builtinTypeNames{""}; diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index 461a8fff..7758e8f9 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -132,7 +132,9 @@ std::optional getFunctionNameAsString(const AstExpr& expr); // It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class // These functions will dump the type to stdout and can be evaluated in Watch/Immediate windows or as gdb/lldb expression std::string dump(TypeId ty); +std::string dump(const std::optional& ty); std::string dump(TypePackId ty); +std::string dump(const std::optional& ty); std::string dump(const Constraint& c); std::string dump(const std::shared_ptr& scope, const char* name); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 9d4d9940..4962274c 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -657,8 +657,11 @@ public: const TypeId unknownType; const TypeId neverType; const TypeId errorType; - const TypeId falsyType; // No type binding! - const TypeId truthyType; // No type binding! + const TypeId falsyType; + const TypeId truthyType; + + const TypeId optionalNumberType; + const TypeId optionalStringType; const TypePackId anyTypePack; const TypePackId neverTypePack; diff --git a/Analysis/include/Luau/TypeReduction.h b/Analysis/include/Luau/TypeReduction.h index a7cec946..7cc16978 100644 --- a/Analysis/include/Luau/TypeReduction.h +++ b/Analysis/include/Luau/TypeReduction.h @@ -9,11 +9,28 @@ namespace Luau { -/// If it's desirable to allocate into a different arena than the TypeReduction instance you have, you will need -/// to create a temporary TypeReduction in that case. This is because TypeReduction caches the reduced type. +namespace detail +{ +template +struct ReductionContext +{ + T type = nullptr; + bool irreducible = false; +}; +} // namespace detail + +struct TypeReductionOptions +{ + /// If it's desirable for type reduction to allocate into a different arena than the TypeReduction instance you have, you will need + /// to create a temporary TypeReduction in that case, and set [`TypeReductionOptions::allowTypeReductionsFromOtherArenas`] to true. + /// This is because TypeReduction caches the reduced type. + bool allowTypeReductionsFromOtherArenas = false; +}; + struct TypeReduction { - explicit TypeReduction(NotNull arena, NotNull builtinTypes, NotNull handle); + explicit TypeReduction( + NotNull arena, NotNull builtinTypes, NotNull handle, const TypeReductionOptions& opts = {}); std::optional reduce(TypeId ty); std::optional reduce(TypePackId tp); @@ -23,12 +40,10 @@ private: NotNull arena; NotNull builtinTypes; NotNull handle; + TypeReductionOptions options; - DenseHashMap cachedTypes{nullptr}; - DenseHashMap cachedTypePacks{nullptr}; - - std::pair, bool> reduceImpl(TypeId ty); - std::pair, bool> reduceImpl(TypePackId tp); + DenseHashMap> memoizedTypes{nullptr}; + DenseHashMap> memoizedTypePacks{nullptr}; // Computes an *estimated length* of the cartesian product of the given type. size_t cartesianProductSize(TypeId ty) const; diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index fdac6585..e0ab12e7 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -318,7 +318,10 @@ struct GenericTypeVisitor } } else if (auto ntv = get(ty)) - visit(ty, *ntv); + { + if (visit(ty, *ntv)) + traverse(ntv->ty); + } else if (!FFlag::LuauCompleteVisitor) return visit_detail::unsee(seen, ty); else diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index ffab734a..e95b0017 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -12,6 +12,7 @@ #include LUAU_FASTFLAG(LuauCompleteTableKeysBetter); +LUAU_FASTFLAGVARIABLE(SupportTypeAliasGoToDeclaration, false); namespace Luau { @@ -183,14 +184,31 @@ struct FindFullAncestry final : public AstVisitor std::vector nodes; Position pos; Position documentEnd; + bool includeTypes = false; - explicit FindFullAncestry(Position pos, Position documentEnd) + explicit FindFullAncestry(Position pos, Position documentEnd, bool includeTypes = false) : pos(pos) , documentEnd(documentEnd) + , includeTypes(includeTypes) { } - bool visit(AstNode* node) + bool visit(AstType* type) override + { + if (FFlag::SupportTypeAliasGoToDeclaration) + { + if (includeTypes) + return visit(static_cast(type)); + else + return false; + } + else + { + return AstVisitor::visit(type); + } + } + + bool visit(AstNode* node) override { if (node->location.contains(pos)) { @@ -220,13 +238,13 @@ std::vector findAncestryAtPositionForAutocomplete(const SourceModule& return finder.ancestry; } -std::vector findAstAncestryOfPosition(const SourceModule& source, Position pos) +std::vector findAstAncestryOfPosition(const SourceModule& source, Position pos, bool includeTypes) { const Position end = source.root->location.end; if (pos > end) pos = end; - FindFullAncestry finder(pos, end); + FindFullAncestry finder(pos, end, includeTypes); source.root->visit(&finder); return finder.nodes; } diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index dd75aa02..4e5403f8 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -14,6 +14,9 @@ LUAU_FASTFLAGVARIABLE(LuauCompleteTableKeysBetter, false); LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInIf, false); +LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInWhile, false); +LUAU_FASTFLAGVARIABLE(LuauFixAutocompleteInFor, false); +LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringContent, false); static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -1265,6 +1268,9 @@ static bool isSimpleInterpolatedString(const AstNode* node) static std::optional getStringContents(const AstNode* node) { + if (!FFlag::LuauAutocompleteStringContent) + return std::nullopt; + if (const AstExprConstantString* string = node->as()) { return std::string(string->value.data, string->value.size); @@ -1314,8 +1320,7 @@ static std::optional autocompleteStringParams(const Source std::optional candidateString = getStringContents(nodes.back()); - auto performCallback = [&](const FunctionType* funcType) -> std::optional - { + auto performCallback = [&](const FunctionType* funcType) -> std::optional { for (const std::string& tag : funcType->tags) { if (std::optional ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString)) @@ -1349,6 +1354,15 @@ static std::optional autocompleteStringParams(const Source return std::nullopt; } +static AutocompleteResult autocompleteWhileLoopKeywords(std::vector ancestry) +{ + AutocompleteEntryMap ret; + ret["do"] = {AutocompleteEntryKind::Keyword}; + ret["and"] = {AutocompleteEntryKind::Keyword}; + ret["or"] = {AutocompleteEntryKind::Keyword}; + return {std::move(ret), std::move(ancestry), AutocompleteContext::Keyword}; +} + static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull builtinTypes, TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback) { @@ -1407,13 +1421,24 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M { if (!statFor->hasDo || position < statFor->doLocation.begin) { - if (!statFor->from->is() && !statFor->to->is() && (!statFor->step || !statFor->step->is())) - return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + if (FFlag::LuauFixAutocompleteInFor) + { + if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) || + (statFor->step && statFor->step->location.containsClosed(position))) + return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); - if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) || - (statFor->step && statFor->step->location.containsClosed(position))) - return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); + if (!statFor->from->is() && !statFor->to->is() && (!statFor->step || !statFor->step->is())) + return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + } + else + { + if (!statFor->from->is() && !statFor->to->is() && (!statFor->step || !statFor->step->is())) + return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) || + (statFor->step && statFor->step->location.containsClosed(position))) + return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); + } return {}; } @@ -1463,7 +1488,16 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M else if (AstStatWhile* statWhile = parent->as(); node->is() && statWhile) { if (!statWhile->hasDo && !statWhile->condition->is() && position > statWhile->condition->location.end) - return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + { + if (FFlag::LuauFixAutocompleteInWhile) + { + return autocompleteWhileLoopKeywords(ancestry); + } + else + { + return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + } + } if (!statWhile->hasDo || position < statWhile->doLocation.begin) return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position); @@ -1472,9 +1506,20 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement}; } - else if (AstStatWhile* statWhile = extractStat(ancestry); statWhile && !statWhile->hasDo) - return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; - + else if (AstStatWhile* statWhile = extractStat(ancestry); + FFlag::LuauFixAutocompleteInWhile ? (statWhile && (!statWhile->hasDo || statWhile->doLocation.containsClosed(position)) && + statWhile->condition && !statWhile->condition->location.containsClosed(position)) + : (statWhile && !statWhile->hasDo)) + { + if (FFlag::LuauFixAutocompleteInWhile) + { + return autocompleteWhileLoopKeywords(ancestry); + } + else + { + return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; + } + } else if (AstStatIf* statIf = node->as(); statIf && !statIf->elseLocation.has_value()) { return {{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, @@ -1488,7 +1533,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword}; } else if (AstStatIf* statIf = extractStat(ancestry); - statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) && + statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)) && (!FFlag::LuauFixAutocompleteInIf || (statIf->condition && !statIf->condition->location.containsClosed(position)))) { if (FFlag::LuauFixAutocompleteInIf) diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 26aaf54f..1fb915e9 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -15,7 +15,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false) LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false) LUAU_FASTFLAG(LuauReportShadowedTypeAlias) @@ -583,7 +582,7 @@ static std::optional> magicFunctionSetMetaTable( TypeId mtTy = arena.addType(mtv); - if (FFlag::LuauSetMetaTableArgsCheck && expr.args.size < 1) + if (expr.args.size < 1) { if (FFlag::LuauUnknownAndNeverType) return std::nullopt; @@ -591,7 +590,7 @@ static std::optional> magicFunctionSetMetaTable( return WithPredicate{}; } - if (!FFlag::LuauSetMetaTableArgsCheck || !expr.self) + if (!expr.self) { AstExpr* targetExpr = expr.args.data[0]; if (AstExprLocal* targetLocal = targetExpr->as()) diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 6a80fed2..7181e4f0 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -16,6 +16,7 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(LuauNegatedClassTypes); LUAU_FASTFLAG(LuauScopelessModule); +LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration); namespace Luau { @@ -418,7 +419,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) TypeId ty = nullptr; if (local->annotation) - ty = resolveType(scope, local->annotation, /* topLevel */ true); + ty = resolveType(scope, local->annotation, /* inTypeArguments */ false); varTypes.push_back(ty); } @@ -521,8 +522,12 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) const Name name{local->vars.data[i]->name.value}; if (ModulePtr module = moduleResolver->getModule(moduleInfo->name)) + { scope->importedTypeBindings[name] = FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings; + if (FFlag::SupportTypeAliasGoToDeclaration) + scope->importedModules[name] = moduleName; + } } } } @@ -775,7 +780,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alia } ScopePtr resolvingScope = *defnIt; - TypeId ty = resolveType(resolvingScope, alias->type, /* topLevel */ true); + TypeId ty = resolveType(resolvingScope, alias->type, /* inTypeArguments */ false); if (alias->exported) { @@ -798,7 +803,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareGlobal* { LUAU_ASSERT(global->type); - TypeId globalTy = resolveType(scope, global->type); + TypeId globalTy = resolveType(scope, global->type, /* inTypeArguments */ false); Name globalName(global->name.value); module->declaredGlobals[globalName] = globalTy; @@ -854,7 +859,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareClass* d for (const AstDeclaredClassProp& prop : declaredClass->props) { Name propName(prop.name.value); - TypeId propTy = resolveType(scope, prop.ty); + TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false); bool assignToMetatable = isMetamethod(propName); @@ -937,8 +942,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction if (!generics.empty() || !genericPacks.empty()) funScope = childScope(global, scope); - TypePackId paramPack = resolveTypePack(funScope, global->params); - TypePackId retPack = resolveTypePack(funScope, global->retTypes); + TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false); + TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false); TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack}); FunctionType* ftv = getMutable(fnType); @@ -1501,7 +1506,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) { check(scope, typeAssert->expr, std::nullopt); - return Inference{resolveType(scope, typeAssert->annotation)}; + return Inference{resolveType(scope, typeAssert->annotation, /* inTypeArguments */ false)}; } std::tuple ConstraintGraphBuilder::checkBinary( @@ -1563,7 +1568,7 @@ std::tuple ConstraintGraphBuilder::checkBinary( TypeId ty = follow(typeFun->type); // We're only interested in the root class of any classes. - if (auto ctv = get(ty); !ctv || !ctv->parent) + if (auto ctv = get(ty); !ctv || (FFlag::LuauNegatedClassTypes ? (ctv->parent == builtinTypes->classType) : !ctv->parent)) discriminantTy = ty; } @@ -1618,39 +1623,6 @@ TypePackId ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, AstArray< return arena->addTypePack(std::move(types)); } -/** - * If the expr is a dotted set of names, and if the root symbol refers to an - * unsealed table, return that table type, plus the indeces that follow as a - * vector. - */ -static std::optional>> extractDottedName(AstExpr* expr) -{ - std::vector names; - - while (expr) - { - if (auto global = expr->as()) - { - std::reverse(begin(names), end(names)); - return std::pair{global->name, std::move(names)}; - } - else if (auto local = expr->as()) - { - std::reverse(begin(names), end(names)); - return std::pair{local->local, std::move(names)}; - } - else if (auto indexName = expr->as()) - { - names.push_back(indexName->index.value); - expr = indexName->expr; - } - else - return std::nullopt; - } - - return std::nullopt; -} - /** * This function is mostly about identifying properties that are being inserted into unsealed tables. * @@ -1671,13 +1643,38 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) else if (!expr->is()) return check(scope, expr).ty; - auto dottedPath = extractDottedName(expr); - if (!dottedPath) - return check(scope, expr).ty; - const auto [sym, segments] = std::move(*dottedPath); + Symbol sym; + std::vector segments; + std::vector exprs; + + AstExpr* e = expr; + while (e) + { + if (auto global = e->as()) + { + sym = global->name; + break; + } + else if (auto local = e->as()) + { + sym = local->local; + break; + } + else if (auto indexName = e->as()) + { + segments.push_back(indexName->index.value); + exprs.push_back(e); + e = indexName->expr; + } + else + return check(scope, expr).ty; + } LUAU_ASSERT(!segments.empty()); + std::reverse(begin(segments), end(segments)); + std::reverse(begin(exprs), end(exprs)); + auto lookupResult = scope->lookupEx(sym); if (!lookupResult) return check(scope, expr).ty; @@ -1695,7 +1692,18 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr) symbolScope->bindings[sym].typeId = updatedType; symbolScope->dcrRefinements[*def] = updatedType; - astTypes[expr] = propTy; + TypeId prevSegmentTy = updatedType; + for (size_t i = 0; i < segments.size(); ++i) + { + TypeId segmentTy = arena->addType(BlockedType{}); + astTypes[exprs[i]] = segmentTy; + addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]}); + prevSegmentTy = segmentTy; + } + + astTypes[expr] = prevSegmentTy; + astTypes[e] = updatedType; + // astTypes[expr] = propTy; return propTy; } @@ -1845,7 +1853,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS if (local->annotation) { - annotationTy = resolveType(signatureScope, local->annotation, /* topLevel */ true); + annotationTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false); addConstraint(signatureScope, local->annotation->location, SubtypeConstraint{t, annotationTy}); } else if (i < expectedArgPack.head.size()) @@ -1866,7 +1874,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS { if (fn->varargAnnotation) { - TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation); + TypePackId annotationType = resolveTypePack(signatureScope, fn->varargAnnotation, /* inTypeArguments */ false); varargPack = annotationType; } else if (expectedArgPack.tail && get(*expectedArgPack.tail)) @@ -1893,7 +1901,7 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS // Type checking will sort out any discrepancies later. if (fn->returnAnnotation) { - TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); + TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false); // We bind the annotated type directly here so that, when we need to // generate constraints for return types, we have a guarantee that we @@ -1942,7 +1950,7 @@ void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFun } } -TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool topLevel) +TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments) { TypeId result = nullptr; @@ -1960,7 +1968,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b return builtinTypes->errorRecoveryType(); } else - return resolveType(scope, ref->parameters.data[0].type, topLevel); + return resolveType(scope, ref->parameters.data[0].type, inTypeArguments); } } @@ -1994,11 +2002,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b // that is done in the parser. if (p.type) { - parameters.push_back(resolveType(scope, p.type)); + parameters.push_back(resolveType(scope, p.type, /* inTypeArguments */ true)); } else if (p.typePack) { - packParameters.push_back(resolveTypePack(scope, p.typePack)); + packParameters.push_back(resolveTypePack(scope, p.typePack, /* inTypeArguments */ true)); } else { @@ -2010,10 +2018,11 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b result = arena->addType(PendingExpansionType{ref->prefix, ref->name, parameters, packParameters}); - if (topLevel) - { + // If we're not in a type argument context, we need to create a constraint that expands this. + // The dispatching of the above constraint will queue up additional constraints for nested + // type function applications. + if (!inTypeArguments) addConstraint(scope, ty->location, TypeAliasExpansionConstraint{/* target */ result}); - } } } else @@ -2035,7 +2044,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b { std::string name = prop.name.value; // TODO: Recursion limit. - TypeId propTy = resolveType(scope, prop.type); + TypeId propTy = resolveType(scope, prop.type, inTypeArguments); // TODO: Fill in location. props[name] = {propTy}; } @@ -2044,8 +2053,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b { // TODO: Recursion limit. indexer = TableIndexer{ - resolveType(scope, tab->indexer->indexType), - resolveType(scope, tab->indexer->resultType), + resolveType(scope, tab->indexer->indexType, inTypeArguments), + resolveType(scope, tab->indexer->resultType, inTypeArguments), }; } @@ -2089,8 +2098,8 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b signatureScope = scope; } - TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes); - TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes); + TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes, inTypeArguments); + TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes, inTypeArguments); // TODO: FunctionType needs a pointer to the scope so that we know // how to quantify/instantiate it. @@ -2130,7 +2139,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b for (AstType* part : unionAnnotation->types) { // TODO: Recursion limit. - parts.push_back(resolveType(scope, part, topLevel)); + parts.push_back(resolveType(scope, part, inTypeArguments)); } result = arena->addType(UnionType{parts}); @@ -2141,7 +2150,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b for (AstType* part : intersectionAnnotation->types) { // TODO: Recursion limit. - parts.push_back(resolveType(scope, part, topLevel)); + parts.push_back(resolveType(scope, part, inTypeArguments)); } result = arena->addType(IntersectionType{parts}); @@ -2168,16 +2177,16 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp, bool inTypeArgument) { TypePackId result; if (auto expl = tp->as()) { - result = resolveTypePack(scope, expl->typeList); + result = resolveTypePack(scope, expl->typeList, inTypeArgument); } else if (auto var = tp->as()) { - TypeId ty = resolveType(scope, var->variadicType); + TypeId ty = resolveType(scope, var->variadicType, inTypeArgument); result = arena->addTypePack(TypePackVar{VariadicTypePack{ty}}); } else if (auto gen = tp->as()) @@ -2202,19 +2211,19 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTyp return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list, bool inTypeArguments) { std::vector head; for (AstType* headTy : list.types) { - head.push_back(resolveType(scope, headTy)); + head.push_back(resolveType(scope, headTy, inTypeArguments)); } std::optional tail = std::nullopt; if (list.tailType) { - tail = resolveTypePack(scope, list.tailType); + tail = resolveTypePack(scope, list.tailType, inTypeArguments); } return arena->addTypePack(TypePack{head, tail}); @@ -2229,7 +2238,7 @@ std::vector> ConstraintGraphBuilder::crea std::optional defaultTy = std::nullopt; if (generic.defaultValue) - defaultTy = resolveType(scope, generic.defaultValue); + defaultTy = resolveType(scope, generic.defaultValue, /* inTypeArguments */ false); result.push_back({generic.name.value, GenericTypeDefinition{genericTy, defaultTy}}); } @@ -2247,7 +2256,7 @@ std::vector> ConstraintGraphBuilder:: std::optional defaultTy = std::nullopt; if (generic.defaultValue) - defaultTy = resolveTypePack(scope, generic.defaultValue); + defaultTy = resolveTypePack(scope, generic.defaultValue, /* inTypeArguments */ false); result.push_back({generic.name.value, GenericTypePackDefinition{genericTy, defaultTy}}); } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 8092144c..3fbd7d9e 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -417,7 +417,7 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo else if (auto hpc = get(*constraint)) success = tryDispatch(*hpc, constraint); else if (auto spc = get(*constraint)) - success = tryDispatch(*spc, constraint); + success = tryDispatch(*spc, constraint, force); else if (auto sottc = get(*constraint)) success = tryDispatch(*sottc, constraint); else @@ -933,13 +933,11 @@ struct InfiniteTypeFinder : TypeOnceVisitor struct InstantiationQueuer : TypeOnceVisitor { ConstraintSolver* solver; - const InstantiationSignature& signature; NotNull scope; Location location; - explicit InstantiationQueuer(NotNull scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature) + explicit InstantiationQueuer(NotNull scope, const Location& location, ConstraintSolver* solver) : solver(solver) - , signature(signature) , scope(scope) , location(location) { @@ -1061,8 +1059,17 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul TypeId instantiated = *maybeInstantiated; TypeId target = follow(instantiated); + // The application is not recursive, so we need to queue up application of + // any child type function instantiations within the result in order for it + // to be complete. + InstantiationQueuer queuer{constraint->scope, constraint->location, this}; + queuer.traverse(target); + if (target->persistent) + { + bindResult(target); return true; + } // Type function application will happily give us the exact same type if // there are e.g. generic saturatedTypeArguments that go unused. @@ -1102,12 +1109,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul bindResult(target); - // The application is not recursive, so we need to queue up application of - // any child type function instantiations within the result in order for it - // to be complete. - InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature}; - queuer.traverse(target); - instantiatedAliases[signature] = target; return true; @@ -1326,13 +1327,16 @@ static std::optional updateTheTableType(NotNull arena, TypeId return res; } -bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint) +bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint, bool force) { TypeId subjectType = follow(c.subjectType); if (isBlocked(subjectType)) return block(subjectType, constraint); + if (!force && get(subjectType)) + return block(subjectType, constraint); + std::optional existingPropType = subjectType; for (const std::string& segment : c.path) { @@ -1399,6 +1403,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull(subjectType)) + { + // Classes never change shape as a result of property assignments. + // The result is always the subject. + bind(c.resultType, subjectType); + return true; + } else if (get(subjectType) || get(subjectType)) { bind(c.resultType, subjectType); diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 4250b311..752259bd 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -13,7 +13,6 @@ #include LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) -LUAU_FASTFLAGVARIABLE(LuauLintGlobalNeverReadBeforeWritten, false) namespace Luau { @@ -331,8 +330,7 @@ private: "Global '%s' is only used in the enclosing function defined at line %d; consider changing it to local", g.firstRef->name.value, top->location.begin.line + 1); } - else if (FFlag::LuauLintGlobalNeverReadBeforeWritten && g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && - g.firstRef->name != context->placeholder) + else if (g.assigned && !g.readBeforeWritten && !g.definedInModuleScope && g.firstRef->name != context->placeholder) { emitWarning(*context, LintWarning::Code_GlobalUsedAsLocal, g.firstRef->location, "Global '%s' is never read before being written. Consider changing it to local", g.firstRef->name.value); @@ -353,7 +351,7 @@ private: bool visit(AstExprGlobal* node) override { - if (FFlag::LuauLintGlobalNeverReadBeforeWritten && !functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name)) + if (!functionStack.empty() && !functionStack.back().dominatedGlobals.contains(node->name)) { Global& g = globals[node->name]; g.readBeforeWritten = true; @@ -386,18 +384,15 @@ private: { Global& g = globals[gv->name]; - if (FFlag::LuauLintGlobalNeverReadBeforeWritten) + if (functionStack.empty()) { - if (functionStack.empty()) + g.definedInModuleScope = true; + } + else + { + if (!functionStack.back().conditionalExecution) { - g.definedInModuleScope = true; - } - else - { - if (!functionStack.back().conditionalExecution) - { - functionStack.back().dominatedGlobals.insert(gv->name); - } + functionStack.back().dominatedGlobals.insert(gv->name); } } @@ -437,11 +432,8 @@ private: else { g.assigned = true; - if (FFlag::LuauLintGlobalNeverReadBeforeWritten) - { - g.definedAsFunction = true; - g.definedInModuleScope = functionStack.empty(); - } + g.definedAsFunction = true; + g.definedInModuleScope = functionStack.empty(); } trackGlobalRef(gv); @@ -475,9 +467,6 @@ private: bool visit(AstStatIf* node) override { - if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) - return true; - HoldConditionalExecution ce(*this); node->condition->visit(this); node->thenbody->visit(this); @@ -489,9 +478,6 @@ private: bool visit(AstStatWhile* node) override { - if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) - return true; - HoldConditionalExecution ce(*this); node->condition->visit(this); node->body->visit(this); @@ -501,9 +487,6 @@ private: bool visit(AstStatRepeat* node) override { - if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) - return true; - HoldConditionalExecution ce(*this); node->condition->visit(this); node->body->visit(this); @@ -513,9 +496,6 @@ private: bool visit(AstStatFor* node) override { - if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) - return true; - HoldConditionalExecution ce(*this); node->from->visit(this); node->to->visit(this); @@ -530,9 +510,6 @@ private: bool visit(AstStatForIn* node) override { - if (!FFlag::LuauLintGlobalNeverReadBeforeWritten) - return true; - HoldConditionalExecution ce(*this); for (AstExpr* expr : node->values) expr->visit(this); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index e19d48f8..901144e4 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -17,13 +17,10 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) // This could theoretically be 2000 on amd64, but x86 requires this. LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000); -LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false); -LUAU_FASTFLAGVARIABLE(LuauTypeNormalization2, false); LUAU_FASTFLAGVARIABLE(LuauNegatedClassTypes, false); LUAU_FASTFLAGVARIABLE(LuauNegatedFunctionTypes, false); LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) -LUAU_FASTFLAG(LuauOverloadedFunctionSubtypingPerf); LUAU_FASTFLAG(LuauUninhabitedSubAnything2) namespace Luau @@ -2165,7 +2162,7 @@ std::optional Normalizer::intersectionOfFunctions(TypeId here, TypeId th argTypes = *argTypesOpt; retTypes = hftv->retTypes; } - else if (FFlag::LuauOverloadedFunctionSubtypingPerf && hftv->argTypes == tftv->argTypes) + else if (hftv->argTypes == tftv->argTypes) { std::optional retTypesOpt = intersectionOfTypePacks(hftv->argTypes, tftv->argTypes); if (!retTypesOpt) diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 22c5875b..aac7864a 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -157,6 +157,8 @@ struct PureQuantifier : Substitution Scope* scope; std::vector insertedGenerics; std::vector insertedGenericPacks; + bool seenMutableType = false; + bool seenGenericType = false; PureQuantifier(TypeArena* arena, Scope* scope) : Substitution(TxnLog::empty(), arena) @@ -170,11 +172,18 @@ struct PureQuantifier : Substitution if (auto ftv = get(ty)) { - return subsumes(scope, ftv->scope); + bool result = subsumes(scope, ftv->scope); + seenMutableType |= result; + return result; } else if (auto ttv = get(ty)) { - return ttv->state == TableState::Free && subsumes(scope, ttv->scope); + if (ttv->state == TableState::Free) + seenMutableType = true; + else if (ttv->state == TableState::Generic) + seenGenericType = true; + + return ttv->state == TableState::Unsealed || (ttv->state == TableState::Free && subsumes(scope, ttv->scope)); } return false; @@ -207,7 +216,11 @@ struct PureQuantifier : Substitution *resultTable = *ttv; resultTable->level = TypeLevel{}; resultTable->scope = scope; - resultTable->state = TableState::Generic; + + if (ttv->state == TableState::Free) + resultTable->state = TableState::Generic; + else if (ttv->state == TableState::Unsealed) + resultTable->state = TableState::Sealed; return result; } @@ -251,7 +264,7 @@ TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope) ftv->scope = scope; ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end()); ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end()); - ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty(); + ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType; return *result; } diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 3e8dc65a..89d3c555 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -16,7 +16,6 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAGVARIABLE(LuauFunctionReturnStringificationFixup, false) -LUAU_FASTFLAGVARIABLE(LuauUnseeArrayTtv, false) /* * Prefix generic typenames with gen- @@ -311,8 +310,7 @@ struct TypeStringifier } Luau::visit( - [this, tv](auto&& t) - { + [this, tv](auto&& t) { return (*this)(tv, t); }, tv->ty); @@ -607,9 +605,7 @@ struct TypeStringifier stringify(ttv.indexer->indexResultType); state.emit("}"); - if (FFlag::LuauUnseeArrayTtv) - state.unsee(&ttv); - + state.unsee(&ttv); return; } @@ -910,8 +906,7 @@ struct TypePackStringifier } Luau::visit( - [this, tp](auto&& t) - { + [this, tp](auto&& t) { return (*this)(tp, t); }, tp->ty); @@ -1061,11 +1056,9 @@ static void assignCycleNames(const std::set& cycles, const std::set(follow(cycleTy)); !exhaustive && ttv && (ttv->syntheticName || ttv->name)) { // If we have a cycle type in type parameters, assign a cycle name for this named table - if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), - [&](auto&& el) - { - return cycles.count(follow(el)); - }) != ttv->instantiatedTypeParams.end()) + if (std::find_if(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), [&](auto&& el) { + return cycles.count(follow(el)); + }) != ttv->instantiatedTypeParams.end()) cycleNames[cycleTy] = ttv->name ? *ttv->name : *ttv->syntheticName; continue; @@ -1160,11 +1153,9 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) state.exhaustive = true; std::vector> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()}; - std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), - [](const auto& a, const auto& b) - { - return a.second < b.second; - }); + std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) { + return a.second < b.second; + }); bool semi = false; for (const auto& [cycleTy, name] : sortedCycleNames) @@ -1175,8 +1166,7 @@ ToStringResult toStringDetailed(TypeId ty, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tvs, cycleTy = cycleTy](auto&& t) - { + [&tvs, cycleTy = cycleTy](auto&& t) { return tvs(cycleTy, t); }, cycleTy->ty); @@ -1257,11 +1247,9 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) state.exhaustive = true; std::vector> sortedCycleNames{state.cycleNames.begin(), state.cycleNames.end()}; - std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), - [](const auto& a, const auto& b) - { - return a.second < b.second; - }); + std::sort(sortedCycleNames.begin(), sortedCycleNames.end(), [](const auto& a, const auto& b) { + return a.second < b.second; + }); bool semi = false; for (const auto& [cycleTy, name] : sortedCycleNames) @@ -1272,8 +1260,7 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts) state.emit(name); state.emit(" = "); Luau::visit( - [&tvs, cycleTy = cycleTy](auto t) - { + [&tvs, cycleTy = cycleTy](auto t) { return tvs(cycleTy, t); }, cycleTy->ty); @@ -1413,6 +1400,15 @@ std::string dump(TypeId ty) return s; } +std::string dump(const std::optional& ty) +{ + if (ty) + return dump(*ty); + + printf("nullopt\n"); + return "nullopt"; +} + std::string dump(TypePackId ty) { std::string s = toString(ty, dumpOptions()); @@ -1420,6 +1416,15 @@ std::string dump(TypePackId ty) return s; } +std::string dump(const std::optional& ty) +{ + if (ty) + return dump(*ty); + + printf("nullopt\n"); + return "nullopt"; +} + std::string dump(const ScopePtr& scope, const char* name) { auto binding = scope->linearSearchForBinding(name); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index f03061a8..4b216518 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -27,6 +27,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauUnknownAndNeverType) LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) +LUAU_FASTFLAGVARIABLE(LuauMatchReturnsOptionalString, false); namespace Luau { @@ -768,15 +769,16 @@ BuiltinTypes::BuiltinTypes() , errorType(arena->addType(Type{ErrorType{}, /*persistent*/ true})) , falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true})) , truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true})) + , optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true})) + , optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true})) , anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true})) , neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true})) - , uninhabitableTypePack(arena->addTypePack({neverType}, neverTypePack)) + , uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true})) , errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true})) { TypeId stringMetatable = makeStringMetatable(); asMutable(stringType)->ty = PrimitiveType{PrimitiveType::String, stringMetatable}; persist(stringMetatable); - persist(uninhabitableTypePack); freeze(*arena); } @@ -1231,12 +1233,12 @@ static std::vector parsePatternString(NotNull builtinTypes if (i + 1 < size && data[i + 1] == ')') { i++; - result.push_back(builtinTypes->numberType); + result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalNumberType : builtinTypes->numberType); continue; } ++depth; - result.push_back(builtinTypes->stringType); + result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType); } else if (data[i] == ')') { @@ -1254,7 +1256,7 @@ static std::vector parsePatternString(NotNull builtinTypes return std::vector(); if (result.empty()) - result.push_back(builtinTypes->stringType); + result.push_back(FFlag::LuauMatchReturnsOptionalString ? builtinTypes->optionalStringType : builtinTypes->stringType); return result; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 1d212851..1972f26f 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/AstQuery.h" #include "Luau/Clone.h" +#include "Luau/Error.h" #include "Luau/Instantiation.h" #include "Luau/Metamethods.h" #include "Luau/Normalize.h" @@ -212,6 +213,12 @@ struct TypeChecker2 return bestScope; } + enum ValueContext + { + LValue, + RValue + }; + void visit(AstStat* stat) { auto pusher = pushStack(stat); @@ -273,7 +280,7 @@ struct TypeChecker2 void visit(AstStatIf* ifStatement) { - visit(ifStatement->condition); + visit(ifStatement->condition, RValue); visit(ifStatement->thenbody); if (ifStatement->elsebody) visit(ifStatement->elsebody); @@ -281,14 +288,14 @@ struct TypeChecker2 void visit(AstStatWhile* whileStatement) { - visit(whileStatement->condition); + visit(whileStatement->condition, RValue); visit(whileStatement->body); } void visit(AstStatRepeat* repeatStatement) { visit(repeatStatement->body); - visit(repeatStatement->condition); + visit(repeatStatement->condition, RValue); } void visit(AstStatBreak*) {} @@ -315,12 +322,12 @@ struct TypeChecker2 } for (AstExpr* expr : ret->list) - visit(expr); + visit(expr, RValue); } void visit(AstStatExpr* expr) { - visit(expr->expr); + visit(expr->expr, RValue); } void visit(AstStatLocal* local) @@ -331,7 +338,7 @@ struct TypeChecker2 AstExpr* value = i < local->values.size ? local->values.data[i] : nullptr; if (value) - visit(value); + visit(value, RValue); TypeId* maybeValueType = value ? module->astTypes.find(value) : nullptr; if (i != local->values.size - 1 || maybeValueType) @@ -387,10 +394,10 @@ struct TypeChecker2 if (forStatement->var->annotation) visit(forStatement->var->annotation); - visit(forStatement->from); - visit(forStatement->to); + visit(forStatement->from, RValue); + visit(forStatement->to, RValue); if (forStatement->step) - visit(forStatement->step); + visit(forStatement->step, RValue); visit(forStatement->body); } @@ -403,7 +410,7 @@ struct TypeChecker2 } for (AstExpr* expr : forInStatement->values) - visit(expr); + visit(expr, RValue); visit(forInStatement->body); @@ -610,11 +617,11 @@ struct TypeChecker2 for (size_t i = 0; i < count; ++i) { AstExpr* lhs = assign->vars.data[i]; - visit(lhs); + visit(lhs, LValue); TypeId lhsType = lookupType(lhs); AstExpr* rhs = assign->values.data[i]; - visit(rhs); + visit(rhs, RValue); TypeId rhsType = lookupType(rhs); if (!isSubtype(rhsType, lhsType, stack.back())) @@ -635,7 +642,7 @@ struct TypeChecker2 void visit(AstStatFunction* stat) { - visit(stat->name); + visit(stat->name, LValue); visit(stat->func); } @@ -698,13 +705,13 @@ struct TypeChecker2 void visit(AstStatError* stat) { for (AstExpr* expr : stat->expressions) - visit(expr); + visit(expr, RValue); for (AstStat* s : stat->statements) visit(s); } - void visit(AstExpr* expr) + void visit(AstExpr* expr, ValueContext context) { auto StackPusher = pushStack(expr); @@ -712,7 +719,7 @@ struct TypeChecker2 { } else if (auto e = expr->as()) - return visit(e); + return visit(e, context); else if (auto e = expr->as()) return visit(e); else if (auto e = expr->as()) @@ -730,9 +737,9 @@ struct TypeChecker2 else if (auto e = expr->as()) return visit(e); else if (auto e = expr->as()) - return visit(e); + return visit(e, context); else if (auto e = expr->as()) - return visit(e); + return visit(e, context); else if (auto e = expr->as()) return visit(e); else if (auto e = expr->as()) @@ -754,9 +761,9 @@ struct TypeChecker2 LUAU_ASSERT(!"TypeChecker2 encountered an unknown expression type"); } - void visit(AstExprGroup* expr) + void visit(AstExprGroup* expr, ValueContext context) { - visit(expr->expr); + visit(expr->expr, context); } void visit(AstExprConstantNil* expr) @@ -808,10 +815,10 @@ struct TypeChecker2 void visit(AstExprCall* call) { - visit(call->func); + visit(call->func, RValue); for (AstExpr* arg : call->args) - visit(arg); + visit(arg, RValue); TypeArena* arena = &testArena; Instantiation instantiation{TxnLog::empty(), arena, TypeLevel{}, stack.back()}; @@ -820,6 +827,8 @@ struct TypeChecker2 TypeId functionType = lookupType(call->func); TypeId testFunctionType = functionType; TypePack args; + std::vector argLocs; + argLocs.reserve(call->args.size + 1); if (get(functionType) || get(functionType)) return; @@ -830,6 +839,7 @@ struct TypeChecker2 if (std::optional instantiatedCallMm = instantiation.substitute(*callMm)) { args.head.push_back(functionType); + argLocs.push_back(call->func->location); testFunctionType = follow(*instantiatedCallMm); } else @@ -899,11 +909,13 @@ struct TypeChecker2 ice.ice("method call expression has no 'self'"); args.head.push_back(lookupType(indexExpr->expr)); + argLocs.push_back(indexExpr->expr->location); } for (size_t i = 0; i < call->args.size; ++i) { AstExpr* arg = call->args.data[i]; + argLocs.push_back(arg->location); TypeId* argTy = module->astTypes.find(arg); if (argTy) args.head.push_back(*argTy); @@ -919,19 +931,34 @@ struct TypeChecker2 args.head.push_back(builtinTypes->anyType); } - TypePackId argsTp = arena->addTypePack(args); - FunctionType ftv{argsTp, expectedRetType}; - TypeId expectedType = arena->addType(ftv); + TypePackId expectedArgTypes = arena->addTypePack(args); - if (!isSubtype(testFunctionType, expectedType, stack.back())) + const FunctionType* inferredFunctionType = get(testFunctionType); + LUAU_ASSERT(inferredFunctionType); // testFunctionType should always be a FunctionType here + + size_t argIndex = 0; + auto inferredArgIt = begin(inferredFunctionType->argTypes); + auto expectedArgIt = begin(expectedArgTypes); + while (inferredArgIt != end(inferredFunctionType->argTypes) && expectedArgIt != end(expectedArgTypes)) { - CloneState cloneState; - expectedType = clone(expectedType, testArena, cloneState); - reportError(TypeMismatch{expectedType, functionType}, call->location); + Location argLoc = (argIndex >= argLocs.size()) ? argLocs.back() : argLocs[argIndex]; + reportErrors(tryUnify(stack.back(), argLoc, *expectedArgIt, *inferredArgIt)); + + ++argIndex; + ++inferredArgIt; + ++expectedArgIt; } + + // piggyback on the unifier for arity checking, but we can't do this for checking the actual arguments since the locations would be bad + ErrorVec errors = tryUnify(stack.back(), call->location, expectedArgTypes, inferredFunctionType->argTypes); + for (TypeError e : errors) + if (get(e) != nullptr) + reportError(std::move(e)); + + reportErrors(tryUnify(stack.back(), call->location, inferredFunctionType->retTypes, expectedRetType, CountMismatch::FunctionResult)); } - void visit(AstExprIndexName* indexName) + void visit(AstExprIndexName* indexName, ValueContext context) { TypeId leftType = lookupType(indexName->expr); @@ -939,14 +966,14 @@ struct TypeChecker2 if (!norm) reportError(NormalizationTooComplex{}, indexName->indexLocation); - checkIndexTypeFromType(leftType, *norm, indexName->index.value, indexName->location); + checkIndexTypeFromType(leftType, *norm, indexName->index.value, indexName->location, context); } - void visit(AstExprIndexExpr* indexExpr) + void visit(AstExprIndexExpr* indexExpr, ValueContext context) { // TODO! - visit(indexExpr->expr); - visit(indexExpr->index); + visit(indexExpr->expr, LValue); + visit(indexExpr->index, RValue); } void visit(AstExprFunction* fn) @@ -986,14 +1013,14 @@ struct TypeChecker2 for (const AstExprTable::Item& item : expr->items) { if (item.key) - visit(item.key); - visit(item.value); + visit(item.key, LValue); + visit(item.value, RValue); } } void visit(AstExprUnary* expr) { - visit(expr->expr); + visit(expr->expr, RValue); NotNull scope = stack.back(); TypeId operandType = lookupType(expr->expr); @@ -1053,8 +1080,8 @@ struct TypeChecker2 TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr) { - visit(expr->left); - visit(expr->right); + visit(expr->left, LValue); + visit(expr->right, LValue); NotNull scope = stack.back(); @@ -1307,7 +1334,7 @@ struct TypeChecker2 void visit(AstExprTypeAssertion* expr) { - visit(expr->expr); + visit(expr->expr, RValue); visit(expr->annotation); TypeId annotationType = lookupAnnotation(expr->annotation); @@ -1326,16 +1353,16 @@ struct TypeChecker2 void visit(AstExprIfElse* expr) { // TODO! - visit(expr->condition); - visit(expr->trueExpr); - visit(expr->falseExpr); + visit(expr->condition, RValue); + visit(expr->trueExpr, RValue); + visit(expr->falseExpr, RValue); } void visit(AstExprError* expr) { // TODO! for (AstExpr* e : expr->expressions) - visit(e); + visit(e, RValue); } /** Extract a TypeId for the first type of the provided pack. @@ -1550,7 +1577,7 @@ struct TypeChecker2 void visit(AstTypeTypeof* ty) { - visit(ty->expr); + visit(ty->expr, RValue); } void visit(AstTypeUnion* ty) @@ -1630,9 +1657,10 @@ struct TypeChecker2 } template - ErrorVec tryUnify(NotNull scope, const Location& location, TID subTy, TID superTy) + ErrorVec tryUnify(NotNull scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg) { Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant}; + u.ctx = context; u.useScopes = true; u.tryUnify(subTy, superTy); @@ -1658,7 +1686,7 @@ struct TypeChecker2 reportError(std::move(e)); } - void checkIndexTypeFromType(TypeId denormalizedTy, const NormalizedType& norm, const std::string& prop, const Location& location) + void checkIndexTypeFromType(TypeId tableTy, const NormalizedType& norm, const std::string& prop, const Location& location, ValueContext context) { bool foundOneProp = false; std::vector typesMissingTheProp; @@ -1723,9 +1751,11 @@ struct TypeChecker2 if (!typesMissingTheProp.empty()) { if (foundOneProp) - reportError(TypeError{location, MissingUnionProperty{denormalizedTy, typesMissingTheProp, prop}}); + reportError(MissingUnionProperty{tableTy, typesMissingTheProp, prop}, location); + else if (context == LValue) + reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); else - reportError(TypeError{location, UnknownProperty{denormalizedTy, prop}}); + reportError(UnknownProperty{tableTy, prop}, location); } } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 25fe37ec..de52a526 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,16 +32,13 @@ LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000) LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) -LUAU_FASTFLAG(LuauTypeNormalization2) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false) -LUAU_FASTFLAGVARIABLE(LuauTypeInferMissingFollows, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false) -LUAU_FASTFLAGVARIABLE(LuauFollowInLvalueIndexCheck, false) LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false) LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false) LUAU_FASTFLAG(LuauInstantiateInSubtyping) @@ -52,9 +49,8 @@ LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false) LUAU_FASTFLAGVARIABLE(LuauImplicitElseRefinement, false) LUAU_FASTFLAG(LuauNegatedClassTypes) LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false) -LUAU_FASTFLAGVARIABLE(LuauDeclareClassPrototype, false) LUAU_FASTFLAG(LuauUninhabitedSubAnything2) -LUAU_FASTFLAGVARIABLE(LuauCallableClasses, false) +LUAU_FASTFLAG(SupportTypeAliasGoToDeclaration) namespace Luau { @@ -333,12 +329,9 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo prepareErrorsForDisplay(currentModule->errors); - if (FFlag::LuauTypeNormalization2) - { - // Clear the normalizer caches, since they contain types from the internal type surface - normalizer.clearCaches(); - normalizer.arena = nullptr; - } + // Clear the normalizer caches, since they contain types from the internal type surface + normalizer.clearCaches(); + normalizer.arena = nullptr; currentModule->clonePublicInterface(builtinTypes, *iceHandler); @@ -512,7 +505,7 @@ void TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const A prototype(scope, *typealias, subLevel); ++subLevel; } - else if (const auto& declaredClass = stat->as(); FFlag::LuauDeclareClassPrototype && declaredClass) + else if (const auto& declaredClass = stat->as()) { prototype(scope, *declaredClass); } @@ -1137,8 +1130,12 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local) const Name name{local.vars.data[i]->name.value}; if (ModulePtr module = resolver->getModule(moduleInfo->name)) + { scope->importedTypeBindings[name] = FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings; + if (FFlag::SupportTypeAliasGoToDeclaration) + scope->importedModules[name] = moduleInfo->name; + } // In non-strict mode we force the module type on the variable, in strict mode it is already unified if (isNonstrictMode()) @@ -1622,6 +1619,8 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; scope->typeAliasLocations[name] = typealias.location; + if (FFlag::SupportTypeAliasGoToDeclaration) + scope->typeAliasNameLocations[name] = typealias.nameLocation; } } else @@ -1640,12 +1639,13 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; scope->typeAliasLocations[name] = typealias.location; + if (FFlag::SupportTypeAliasGoToDeclaration) + scope->typeAliasNameLocations[name] = typealias.nameLocation; } } void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) { - LUAU_ASSERT(FFlag::LuauDeclareClassPrototype); std::optional superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt; if (declaredClass.superName) { @@ -1684,166 +1684,74 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& de void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) { - if (FFlag::LuauDeclareClassPrototype) + Name className(declaredClass.name.value); + + // Don't bother checking if the class definition was incorrect + if (incorrectClassDefinitions.find(&declaredClass)) + return; + + std::optional binding; + if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) + binding = it->second; + + // This class definition must have been `prototype()`d first. + if (!binding) + ice("Class not predeclared"); + + TypeId classTy = binding->type; + ClassType* ctv = getMutable(classTy); + + if (!ctv->metatable) + ice("No metatable for declared class"); + + TableType* metatable = getMutable(*ctv->metatable); + for (const AstDeclaredClassProp& prop : declaredClass.props) { - Name className(declaredClass.name.value); + Name propName(prop.name.value); + TypeId propTy = resolveType(scope, *prop.ty); - // Don't bother checking if the class definition was incorrect - if (incorrectClassDefinitions.find(&declaredClass)) - return; + bool assignToMetatable = isMetamethod(propName); + Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; - std::optional binding; - if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) - binding = it->second; - - // This class definition must have been `prototype()`d first. - if (!binding) - ice("Class not predeclared"); - - TypeId classTy = binding->type; - ClassType* ctv = getMutable(classTy); - - if (!ctv->metatable) - ice("No metatable for declared class"); - - TableType* metatable = getMutable(*ctv->metatable); - for (const AstDeclaredClassProp& prop : declaredClass.props) + // Function types always take 'self', but this isn't reflected in the + // parsed annotation. Add it here. + if (prop.isMethod) { - Name propName(prop.name.value); - TypeId propTy = resolveType(scope, *prop.ty); - - bool assignToMetatable = isMetamethod(propName); - Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; - - // Function types always take 'self', but this isn't reflected in the - // parsed annotation. Add it here. - if (prop.isMethod) + if (FunctionType* ftv = getMutable(propTy)) { - if (FunctionType* ftv = getMutable(propTy)) - { - ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); - ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); - ftv->hasSelf = true; - } - } - - if (assignTo.count(propName) == 0) - { - assignTo[propName] = {propTy}; - } - else - { - TypeId currentTy = assignTo[propName].type; - - // We special-case this logic to keep the intersection flat; otherwise we - // would create a ton of nested intersection types. - if (const IntersectionType* itv = get(currentTy)) - { - std::vector options = itv->parts; - options.push_back(propTy); - TypeId newItv = addType(IntersectionType{std::move(options)}); - - assignTo[propName] = {newItv}; - } - else if (get(currentTy)) - { - TypeId intersection = addType(IntersectionType{{currentTy, propTy}}); - - assignTo[propName] = {intersection}; - } - else - { - reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); - } - } - } - } - else - { - std::optional superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt; - if (declaredClass.superName) - { - Name superName = Name(declaredClass.superName->value); - std::optional lookupType = scope->lookupType(superName); - - if (!lookupType) - { - reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type}); - return; - } - - // We don't have generic classes, so this assertion _should_ never be hit. - LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); - superTy = lookupType->type; - - if (!get(follow(*superTy))) - { - reportError(declaredClass.location, GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", - superName.c_str(), declaredClass.name.value)}); - return; + ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); + ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); + ftv->hasSelf = true; } } - Name className(declaredClass.name.value); - - TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModuleName)); - - ClassType* ctv = getMutable(classTy); - TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); - TableType* metatable = getMutable(metaTy); - - ctv->metatable = metaTy; - - scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; - - for (const AstDeclaredClassProp& prop : declaredClass.props) + if (assignTo.count(propName) == 0) { - Name propName(prop.name.value); - TypeId propTy = resolveType(scope, *prop.ty); + assignTo[propName] = {propTy}; + } + else + { + TypeId currentTy = assignTo[propName].type; - bool assignToMetatable = isMetamethod(propName); - Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; - - // Function types always take 'self', but this isn't reflected in the - // parsed annotation. Add it here. - if (prop.isMethod) + // We special-case this logic to keep the intersection flat; otherwise we + // would create a ton of nested intersection types. + if (const IntersectionType* itv = get(currentTy)) { - if (FunctionType* ftv = getMutable(propTy)) - { - ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); - ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); - ftv->hasSelf = true; - } + std::vector options = itv->parts; + options.push_back(propTy); + TypeId newItv = addType(IntersectionType{std::move(options)}); + + assignTo[propName] = {newItv}; } - - if (assignTo.count(propName) == 0) + else if (get(currentTy)) { - assignTo[propName] = {propTy}; + TypeId intersection = addType(IntersectionType{{currentTy, propTy}}); + + assignTo[propName] = {intersection}; } else { - TypeId currentTy = assignTo[propName].type; - - // We special-case this logic to keep the intersection flat; otherwise we - // would create a ton of nested intersection types. - if (const IntersectionType* itv = get(currentTy)) - { - std::vector options = itv->parts; - options.push_back(propTy); - TypeId newItv = addType(IntersectionType{std::move(options)}); - - assignTo[propName] = {newItv}; - } - else if (get(currentTy)) - { - TypeId intersection = addType(IntersectionType{{currentTy, propTy}}); - - assignTo[propName] = {intersection}; - } - else - { - reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); - } + reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); } } } @@ -3364,8 +3272,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex TypeId indexType = checkExpr(scope, *expr.index).type; - if (FFlag::LuauFollowInLvalueIndexCheck) - exprType = follow(exprType); + exprType = follow(exprType); if (get(exprType) || get(exprType)) return exprType; @@ -4282,7 +4189,7 @@ std::optional> TypeChecker::checkCallOverload(const Sc { callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false); } - else if (const ClassType* ctv = get(fn); FFlag::LuauCallableClasses && ctv && ctv->metatable) + else if (const ClassType* ctv = get(fn); ctv && ctv->metatable) { callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false); } @@ -4479,7 +4386,7 @@ void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const Ast std::string s; for (size_t i = 0; i < overloadTypes.size(); ++i) { - TypeId overload = FFlag::LuauTypeInferMissingFollows ? follow(overloadTypes[i]) : overloadTypes[i]; + TypeId overload = follow(overloadTypes[i]); Unifier state = mkUnifier(scope, expr.location); // Unify return types @@ -4861,7 +4768,7 @@ TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp) if (const VariadicTypePack* vtp = get(tp)) { - TypeId ty = FFlag::LuauTypeInferMissingFollows ? follow(vtp->ty) : vtp->ty; + TypeId ty = follow(vtp->ty); return get(ty) ? anyTypePack : tp; } @@ -6105,11 +6012,11 @@ void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const Sc if (optionIsSubtype && !targetIsSubtype) return option; else if (!optionIsSubtype && targetIsSubtype) - return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type; + return follow(eqP.type); else if (!optionIsSubtype && !targetIsSubtype) return nope; else if (optionIsSubtype && targetIsSubtype) - return FFlag::LuauTypeInferMissingFollows ? follow(eqP.type) : eqP.type; + return follow(eqP.type); } else { diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index e41bf2fe..ccea604f 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauTxnLogTypePackIterator, false) - namespace Luau { @@ -62,8 +60,8 @@ TypePackIterator::TypePackIterator(TypePackId typePack) } TypePackIterator::TypePackIterator(TypePackId typePack, const TxnLog* log) - : currentTypePack(FFlag::LuauTxnLogTypePackIterator ? log->follow(typePack) : follow(typePack)) - , tp(FFlag::LuauTxnLogTypePackIterator ? log->get(currentTypePack) : get(currentTypePack)) + : currentTypePack(log->follow(typePack)) + , tp(log->get(currentTypePack)) , currentIndex(0) , log(log) { diff --git a/Analysis/src/TypeReduction.cpp b/Analysis/src/TypeReduction.cpp index 8d837ddb..c748f863 100644 --- a/Analysis/src/TypeReduction.cpp +++ b/Analysis/src/TypeReduction.cpp @@ -4,6 +4,7 @@ #include "Luau/Common.h" #include "Luau/Error.h" #include "Luau/RecursionCounter.h" +#include "Luau/VisitType.h" #include #include @@ -18,24 +19,7 @@ namespace Luau namespace { -struct RecursionGuard : RecursionLimiter -{ - std::deque* seen; - - RecursionGuard(int* count, int limit, std::deque* seen) - : RecursionLimiter(count, limit) - , seen(seen) - { - // count has been incremented, which should imply that seen has already had an element pushed in. - LUAU_ASSERT(size_t(*count) == seen->size()); - } - - ~RecursionGuard() - { - LUAU_ASSERT(!seen->empty()); // It is UB to pop_back() on an empty deque. - seen->pop_back(); - } -}; +using detail::ReductionContext; template std::pair get2(const Thing& one, const Thing& two) @@ -51,15 +35,11 @@ struct TypeReducer NotNull builtinTypes; NotNull handle; - std::unordered_map copies; - std::deque seen; - int depth = 0; + DenseHashMap>* memoizedTypes; + DenseHashMap>* memoizedTypePacks; + DenseHashSet* cyclicTypes; - // When we encounter _any type_ that which is usually mutated in-place, we need to not cache the result. - // e.g. `'a & {} T` may have an upper bound constraint `{}` placed upon `'a`, but this constraint was not - // known when we decided to reduce this intersection type. By not caching, we'll always be forced to perform - // the reduction calculus over again. - bool cacheOk = true; + int depth = 0; TypeId reduce(TypeId ty); TypePackId reduce(TypePackId tp); @@ -70,62 +50,73 @@ struct TypeReducer TypeId functionType(TypeId ty); TypeId negationType(TypeId ty); - RecursionGuard guard(TypeId ty); - RecursionGuard guard(TypePackId tp); + bool isIrreducible(TypeId ty); + bool isIrreducible(TypePackId tp); - void checkCacheable(TypeId ty); - void checkCacheable(TypePackId tp); + TypeId memoize(TypeId ty, TypeId reducedTy); + TypePackId memoize(TypePackId tp, TypePackId reducedTp); + + // It's either cyclic with no memoized result, so we should terminate, or + // there is a memoized result but one that's being reduced top-down, so + // we need to return the root of that memoized result to tighten up things. + TypeId memoizedOr(TypeId ty) const; + TypePackId memoizedOr(TypePackId tp) const; + + using BinaryFold = std::optional (TypeReducer::*)(TypeId, TypeId); + using UnaryFold = TypeId (TypeReducer::*)(TypeId); template LUAU_NOINLINE std::pair copy(TypeId ty, const T* t) { - if (auto it = copies.find(ty); it != copies.end()) - return {it->second, getMutable(it->second)}; + ty = follow(ty); + + if (auto ctx = memoizedTypes->find(ty)) + return {ctx->type, getMutable(ctx->type)}; TypeId copiedTy = arena->addType(*t); - copies[ty] = copiedTy; + (*memoizedTypes)[ty] = {copiedTy, false}; + (*memoizedTypes)[copiedTy] = {copiedTy, false}; return {copiedTy, getMutable(copiedTy)}; } - using Folder = std::optional (TypeReducer::*)(TypeId, TypeId); - template - void foldl_impl(Iter it, Iter endIt, Folder f, NotNull> result) + void foldl_impl(Iter it, Iter endIt, BinaryFold f, std::vector* result, bool* didReduce) { + RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; + while (it != endIt) { - bool replaced = false; - TypeId currentTy = reduce(*it); - RecursionGuard rg = guard(*it); + TypeId right = reduce(*it); + *didReduce |= right != follow(*it); // We're hitting a case where the `currentTy` returned a type that's the same as `T`. // e.g. `(string?) & ~(false | nil)` became `(string?) & (~false & ~nil)` but the current iterator we're consuming doesn't know this. // We will need to recurse and traverse that first. - if (auto t = get(currentTy)) + if (auto t = get(right)) { - foldl_impl(begin(t), end(t), f, result); + foldl_impl(begin(t), end(t), f, result, didReduce); ++it; continue; } + bool replaced = false; auto resultIt = result->begin(); while (resultIt != result->end()) { - TypeId& ty = *resultIt; - - std::optional reduced = (this->*f)(ty, currentTy); - if (reduced && replaced) + TypeId left = *resultIt; + if (left == right) { - // We want to erase any other elements that occurs after the first replacement too. - // e.g. `"a" | "b" | string` where `"a"` and `"b"` is in the `result` vector, then `string` replaces both `"a"` and `"b"`. - // If we don't erase redundant elements, `"b"` may be kept or be replaced by `string`, leaving us with `string | string`. - resultIt = result->erase(resultIt); + replaced = true; + ++resultIt; + continue; } - else if (reduced && !replaced) + + std::optional reduced = (this->*f)(left, right); + if (reduced) { + *resultIt = *reduced; ++resultIt; replaced = true; - ty = *reduced; } else { @@ -135,21 +126,65 @@ struct TypeReducer } if (!replaced) - result->push_back(currentTy); + result->push_back(right); + *didReduce |= replaced; ++it; } } + template + TypeId flatten(std::vector&& types) + { + if (types.size() == 1) + return types[0]; + else + return arena->addType(T{std::move(types)}); + } + template - TypeId foldl(Iter it, Iter endIt, Folder f) + TypeId foldl(Iter it, Iter endIt, std::optional ty, BinaryFold f) { std::vector result; - foldl_impl(it, endIt, f, NotNull{&result}); - if (result.size() == 1) - return result[0]; + bool didReduce = false; + foldl_impl(it, endIt, f, &result, &didReduce); + if (!didReduce && ty) + return *ty; else - return arena->addType(T{std::move(result)}); + { + // If we've done any reduction, then we'll need to reduce it again, e.g. + // `"a" | "b" | string` is reduced into `string | string`, which is then reduced into `string`. + return reduce(flatten(std::move(result))); + } + } + + template + TypeId apply(BinaryFold f, TypeId left, TypeId right) + { + left = follow(left); + right = follow(right); + + if (get(left) || get(right)) + { + std::vector types{left, right}; + return foldl(begin(types), end(types), std::nullopt, f); + } + else if (auto reduced = (this->*f)(left, right)) + return *reduced; + else + return arena->addType(T{{left, right}}); + } + + template + TypeId distribute(TypeIterator it, TypeIterator endIt, BinaryFold f, TypeId ty) + { + std::vector result; + while (it != endIt) + { + result.push_back(apply(f, *it, ty)); + ++it; + } + return flatten(std::move(result)); } }; @@ -157,42 +192,48 @@ TypeId TypeReducer::reduce(TypeId ty) { ty = follow(ty); - if (std::find(seen.begin(), seen.end(), ty) != seen.end()) - return ty; + if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible) + return ctx->type; + else if (auto cyclicTy = cyclicTypes->find(ty)) + return *cyclicTy; - RecursionGuard rg = guard(ty); - checkCacheable(ty); + RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; + TypeId result = nullptr; if (auto i = get(ty)) - return foldl(begin(i), end(i), &TypeReducer::intersectionType); + result = foldl(begin(i), end(i), ty, &TypeReducer::intersectionType); else if (auto u = get(ty)) - return foldl(begin(u), end(u), &TypeReducer::unionType); + result = foldl(begin(u), end(u), ty, &TypeReducer::unionType); else if (get(ty) || get(ty)) - return tableType(ty); + result = tableType(ty); else if (get(ty)) - return functionType(ty); - else if (auto n = get(ty)) - return negationType(follow(n->ty)); + result = functionType(ty); + else if (get(ty)) + result = negationType(ty); else - return ty; + result = ty; + + return memoize(ty, result); } TypePackId TypeReducer::reduce(TypePackId tp) { tp = follow(tp); - if (std::find(seen.begin(), seen.end(), tp) != seen.end()) - return tp; + if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible) + return ctx->type; - RecursionGuard rg = guard(tp); - checkCacheable(tp); + RecursionLimiter rl{&depth, FInt::LuauTypeReductionRecursionLimit}; + bool didReduce = false; TypePackIterator it = begin(tp); std::vector head; while (it != end(tp)) { - head.push_back(reduce(*it)); + TypeId reducedTy = reduce(*it); + head.push_back(reducedTy); + didReduce |= follow(*it) != follow(reducedTy); ++it; } @@ -200,10 +241,22 @@ TypePackId TypeReducer::reduce(TypePackId tp) if (tail) { if (auto vtp = get(follow(*it.tail()))) - tail = arena->addTypePack(VariadicTypePack{reduce(vtp->ty), vtp->hidden}); + { + TypeId reducedTy = reduce(vtp->ty); + if (follow(vtp->ty) != follow(reducedTy)) + { + tail = arena->addTypePack(VariadicTypePack{reducedTy, vtp->hidden}); + didReduce = true; + } + } } - return arena->addTypePack(TypePack{std::move(head), tail}); + if (!didReduce) + return memoize(tp, tp); + else if (head.empty() && tail) + return memoize(tp, *tail); + else + return memoize(tp, arena->addTypePack(TypePack{std::move(head), tail})); } std::optional TypeReducer::intersectionType(TypeId left, TypeId right) @@ -236,18 +289,7 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) else if (get(right)) return std::nullopt; // T & error ~ T & error else if (auto ut = get(left)) - { - std::vector options; - for (TypeId option : ut) - { - if (auto result = intersectionType(option, right)) - options.push_back(*result); - else - options.push_back(arena->addType(IntersectionType{{option, right}})); - } - - return foldl(begin(options), end(options), &TypeReducer::unionType); // (A | B) & T ~ (A & T) | (B & T) - } + return reduce(distribute(begin(ut), end(ut), &TypeReducer::intersectionType, right)); // (A | B) & T ~ (A & T) | (B & T) else if (get(right)) return intersectionType(right, left); // T & (A | B) ~ (A | B) & T else if (auto [p1, p2] = get2(left, right); p1 && p2) @@ -294,14 +336,7 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) return builtinTypes->neverType; // Base & Unrelated ~ never } else if (auto [f1, f2] = get2(left, right); f1 && f2) - { - if (std::find(seen.begin(), seen.end(), left) != seen.end()) - return std::nullopt; - else if (std::find(seen.begin(), seen.end(), right) != seen.end()) - return std::nullopt; - return std::nullopt; // TODO - } else if (auto [t1, t2] = get2(left, right); t1 && t2) { if (t1->state == TableState::Free || t2->state == TableState::Free) @@ -309,10 +344,10 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) else if (t1->state == TableState::Generic || t2->state == TableState::Generic) return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U } - if (std::find(seen.begin(), seen.end(), left) != seen.end()) - return std::nullopt; - else if (std::find(seen.begin(), seen.end(), right) != seen.end()) - return std::nullopt; + if (cyclicTypes->find(left)) + return std::nullopt; // (t1 where t1 = { p: t1 }) & {} ~ t1 & {} + else if (cyclicTypes->find(right)) + return std::nullopt; // {} & (t1 where t1 = { p: t1 }) ~ {} & t1 TypeId resultTy = arena->addType(TableType{}); TableType* table = getMutable(resultTy); @@ -324,8 +359,7 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) // even if we have the corresponding property in the other one. if (auto other = t2->props.find(name); other != t2->props.end()) { - std::vector parts{prop.type, other->second.type}; - TypeId propTy = foldl(begin(parts), end(parts), &TypeReducer::intersectionType); + TypeId propTy = apply(&TypeReducer::intersectionType, prop.type, other->second.type); if (get(propTy)) return builtinTypes->neverType; // { p : string } & { p : number } ~ { p : string & number } ~ { p : never } ~ never else @@ -340,27 +374,33 @@ std::optional TypeReducer::intersectionType(TypeId left, TypeId right) // TODO: And vice versa, t2 properties against t1 indexer if it exists, // even if we have the corresponding property in the other one. if (!t1->props.count(name)) - table->props[name] = prop; // {} & { p : string } ~ { p : string } + table->props[name] = {reduce(prop.type)}; // {} & { p : string & string } ~ { p : string } } if (t1->indexer && t2->indexer) { - std::vector keyParts{t1->indexer->indexType, t2->indexer->indexType}; - TypeId keyTy = foldl(begin(keyParts), end(keyParts), &TypeReducer::intersectionType); + TypeId keyTy = apply(&TypeReducer::intersectionType, t1->indexer->indexType, t2->indexer->indexType); if (get(keyTy)) - return builtinTypes->neverType; // { [string]: _ } & { [number]: _ } ~ { [string & number]: _ } ~ { [never]: _ } ~ never + return std::nullopt; // { [string]: _ } & { [number]: _ } ~ { [string]: _ } & { [number]: _ } - std::vector valueParts{t1->indexer->indexResultType, t2->indexer->indexResultType}; - TypeId valueTy = foldl(begin(valueParts), end(valueParts), &TypeReducer::intersectionType); + TypeId valueTy = apply(&TypeReducer::intersectionType, t1->indexer->indexResultType, t2->indexer->indexResultType); if (get(valueTy)) return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never table->indexer = TableIndexer{keyTy, valueTy}; } else if (t1->indexer) - table->indexer = t1->indexer; // { [number]: boolean } & { p : string } ~ { p : string, [number]: boolean } + { + TypeId keyTy = reduce(t1->indexer->indexType); + TypeId valueTy = reduce(t1->indexer->indexResultType); + table->indexer = TableIndexer{keyTy, valueTy}; // { [number]: boolean } & { p : string } ~ { p : string, [number]: boolean } + } else if (t2->indexer) - table->indexer = t2->indexer; // { p : string } & { [number]: boolean } ~ { p : string, [number]: boolean } + { + TypeId keyTy = reduce(t2->indexer->indexType); + TypeId valueTy = reduce(t2->indexer->indexResultType); + table->indexer = TableIndexer{keyTy, valueTy}; // { p : string } & { [number]: boolean } ~ { p : string, [number]: boolean } + } return resultTy; } @@ -506,22 +546,7 @@ std::optional TypeReducer::unionType(TypeId left, TypeId right) return std::nullopt; // Base | Unrelated ~ Base | Unrelated } else if (auto [nt, it] = get2(left, right); nt && it) - { - std::vector parts; - for (TypeId option : it) - { - if (auto result = unionType(left, option)) - parts.push_back(*result); - else - { - // TODO: does there exist a reduced form such that `~T | A` hasn't already reduced it, if `A & B` is irreducible? - // I want to say yes, but I can't generate a case that hits this code path. - parts.push_back(arena->addType(UnionType{{left, option}})); - } - } - - return foldl(begin(parts), end(parts), &TypeReducer::intersectionType); // ~T | (A & B) ~ (~T | A) & (~T | B) - } + return reduce(distribute(begin(it), end(it), &TypeReducer::unionType, left)); // ~T | (A & B) ~ (~T | A) & (~T | B) else if (auto [it, nt] = get2(left, right); it && nt) return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B) else if (auto [nl, nr] = get2(left, right); nl && nr) @@ -628,8 +653,6 @@ std::optional TypeReducer::unionType(TypeId left, TypeId right) TypeId TypeReducer::tableType(TypeId ty) { - RecursionGuard rg = guard(ty); - if (auto mt = get(ty)) { auto [copiedTy, copied] = copy(ty, mt); @@ -639,15 +662,30 @@ TypeId TypeReducer::tableType(TypeId ty) } else if (auto tt = get(ty)) { + // Because of `typeof()`, we need to preserve pointer identity of free/unsealed tables so that + // all mutations that occurs on this will be applied without leaking the implementation details. + // As a result, we'll just use the type instead of cloning it if it's free/unsealed. + // + // We could choose to do in-place reductions here, but to be on the safer side, I propose that we do not. + if (tt->state == TableState::Free || tt->state == TableState::Unsealed) + return ty; + auto [copiedTy, copied] = copy(ty, tt); for (auto& [name, prop] : copied->props) - prop.type = reduce(prop.type); - - if (auto& indexer = copied->indexer) { - indexer->indexType = reduce(indexer->indexType); - indexer->indexResultType = reduce(indexer->indexResultType); + TypeId propTy = reduce(prop.type); + if (get(propTy)) + return builtinTypes->neverType; + else + prop.type = propTy; + } + + if (copied->indexer) + { + TypeId keyTy = reduce(copied->indexer->indexType); + TypeId valueTy = reduce(copied->indexer->indexResultType); + copied->indexer = TableIndexer{keyTy, valueTy}; } for (TypeId& ty : copied->instantiatedTypeParams) @@ -659,16 +697,14 @@ TypeId TypeReducer::tableType(TypeId ty) return copiedTy; } else - handle->ice("Unexpected type in TypeReducer::tableType"); + handle->ice("TypeReducer::tableType expects a TableType or MetatableType"); } TypeId TypeReducer::functionType(TypeId ty) { - RecursionGuard rg = guard(ty); - const FunctionType* f = get(ty); if (!f) - handle->ice("TypeReducer::reduce expects a FunctionType"); + handle->ice("TypeReducer::functionType expects a FunctionType"); // TODO: once we have bounded quantification, we need to be able to reduce the generic bounds. auto [copiedTy, copied] = copy(ty, f); @@ -679,140 +715,238 @@ TypeId TypeReducer::functionType(TypeId ty) TypeId TypeReducer::negationType(TypeId ty) { - RecursionGuard rg = guard(ty); + const NegationType* n = get(ty); + if (!n) + return arena->addType(NegationType{ty}); - if (auto nn = get(ty)) + if (auto nn = get(n->ty)) return nn->ty; // ~~T ~ T - else if (get(ty)) + else if (get(n->ty)) return builtinTypes->unknownType; // ~never ~ unknown - else if (get(ty)) + else if (get(n->ty)) return builtinTypes->neverType; // ~unknown ~ never - else if (get(ty)) + else if (get(n->ty)) return builtinTypes->anyType; // ~any ~ any - else if (auto ni = get(ty)) + else if (auto ni = get(n->ty)) { std::vector options; for (TypeId part : ni) - options.push_back(negationType(part)); - return foldl(begin(options), end(options), &TypeReducer::unionType); // ~(T & U) ~ (~T | ~U) + options.push_back(negationType(arena->addType(NegationType{part}))); + return reduce(flatten(std::move(options))); // ~(T & U) ~ (~T | ~U) } - else if (auto nu = get(ty)) + else if (auto nu = get(n->ty)) { std::vector parts; for (TypeId option : nu) - parts.push_back(negationType(option)); - return foldl(begin(parts), end(parts), &TypeReducer::intersectionType); // ~(T | U) ~ (~T & ~U) + parts.push_back(negationType(arena->addType(NegationType{option}))); + return reduce(flatten(std::move(parts))); // ~(T | U) ~ (~T & ~U) } else - return arena->addType(NegationType{ty}); // for all T except the ones handled above, ~T ~ ~T + return ty; // for all T except the ones handled above, ~T ~ ~T } -RecursionGuard TypeReducer::guard(TypeId ty) +bool TypeReducer::isIrreducible(TypeId ty) { - seen.push_back(ty); - return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen}; -} - -RecursionGuard TypeReducer::guard(TypePackId tp) -{ - seen.push_back(tp); - return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen}; -} - -void TypeReducer::checkCacheable(TypeId ty) -{ - if (!cacheOk) - return; - ty = follow(ty); // Only does shallow check, the TypeReducer itself already does deep traversal. - if (get(ty) || get(ty) || get(ty)) - cacheOk = false; + if (auto ctx = memoizedTypes->find(ty); ctx && ctx->irreducible) + return true; + else if (get(ty) || get(ty) || get(ty)) + return false; else if (auto tt = get(ty); tt && (tt->state == TableState::Free || tt->state == TableState::Unsealed)) - cacheOk = false; + return false; + else + return true; } -void TypeReducer::checkCacheable(TypePackId tp) +bool TypeReducer::isIrreducible(TypePackId tp) { - if (!cacheOk) - return; - tp = follow(tp); // Only does shallow check, the TypeReducer itself already does deep traversal. - if (get(tp) || get(tp)) - cacheOk = false; + if (auto ctx = memoizedTypePacks->find(tp); ctx && ctx->irreducible) + return true; + else if (get(tp) || get(tp)) + return false; + else if (auto vtp = get(tp)) + return isIrreducible(vtp->ty); + else + return true; } +TypeId TypeReducer::memoize(TypeId ty, TypeId reducedTy) +{ + ty = follow(ty); + reducedTy = follow(reducedTy); + + // The irreducibility of this [`reducedTy`] depends on whether its contents are themselves irreducible. + // We don't need to recurse much further than that, because we already record the irreducibility from + // the bottom up. + bool irreducible = isIrreducible(reducedTy); + if (auto it = get(reducedTy)) + { + for (TypeId part : it) + irreducible &= isIrreducible(part); + } + else if (auto ut = get(reducedTy)) + { + for (TypeId option : ut) + irreducible &= isIrreducible(option); + } + else if (auto tt = get(reducedTy)) + { + for (auto& [k, p] : tt->props) + irreducible &= isIrreducible(p.type); + + if (tt->indexer) + { + irreducible &= isIrreducible(tt->indexer->indexType); + irreducible &= isIrreducible(tt->indexer->indexResultType); + } + + for (auto ta : tt->instantiatedTypeParams) + irreducible &= isIrreducible(ta); + + for (auto tpa : tt->instantiatedTypePackParams) + irreducible &= isIrreducible(tpa); + } + else if (auto mt = get(reducedTy)) + { + irreducible &= isIrreducible(mt->table); + irreducible &= isIrreducible(mt->metatable); + } + else if (auto ft = get(reducedTy)) + { + irreducible &= isIrreducible(ft->argTypes); + irreducible &= isIrreducible(ft->retTypes); + } + else if (auto nt = get(reducedTy)) + irreducible &= isIrreducible(nt->ty); + + (*memoizedTypes)[ty] = {reducedTy, irreducible}; + (*memoizedTypes)[reducedTy] = {reducedTy, irreducible}; + return reducedTy; +} + +TypePackId TypeReducer::memoize(TypePackId tp, TypePackId reducedTp) +{ + tp = follow(tp); + reducedTp = follow(reducedTp); + + bool irreducible = isIrreducible(reducedTp); + TypePackIterator it = begin(tp); + while (it != end(tp)) + { + irreducible &= isIrreducible(*it); + ++it; + } + + if (it.tail()) + irreducible &= isIrreducible(*it.tail()); + + (*memoizedTypePacks)[tp] = {reducedTp, irreducible}; + (*memoizedTypePacks)[reducedTp] = {reducedTp, irreducible}; + return reducedTp; +} + +TypeId TypeReducer::memoizedOr(TypeId ty) const +{ + ty = follow(ty); + + if (auto ctx = memoizedTypes->find(ty)) + return ctx->type; + else + return ty; +}; + +TypePackId TypeReducer::memoizedOr(TypePackId tp) const +{ + tp = follow(tp); + + if (auto ctx = memoizedTypePacks->find(tp)) + return ctx->type; + else + return tp; +}; + +struct MarkCycles : TypeVisitor +{ + DenseHashSet cyclicTypes{nullptr}; + + void cycle(TypeId ty) override + { + cyclicTypes.insert(ty); + } + + bool visit(TypeId ty) override + { + return !cyclicTypes.find(ty); + } +}; + } // namespace -TypeReduction::TypeReduction(NotNull arena, NotNull builtinTypes, NotNull handle) +TypeReduction::TypeReduction( + NotNull arena, NotNull builtinTypes, NotNull handle, const TypeReductionOptions& opts) : arena(arena) , builtinTypes(builtinTypes) , handle(handle) + , options(opts) { } std::optional TypeReduction::reduce(TypeId ty) { - if (auto found = cachedTypes.find(ty)) - return *found; + ty = follow(ty); - auto [reducedTy, cacheOk] = reduceImpl(ty); - if (cacheOk) - cachedTypes[ty] = *reducedTy; + if (FFlag::DebugLuauDontReduceTypes) + return ty; + else if (!options.allowTypeReductionsFromOtherArenas && ty->owningArena != arena) + return ty; + else if (auto ctx = memoizedTypes.find(ty); ctx && ctx->irreducible) + return ctx->type; + else if (hasExceededCartesianProductLimit(ty)) + return std::nullopt; - return reducedTy; + try + { + MarkCycles finder; + finder.traverse(ty); + + TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes}; + return reducer.reduce(ty); + } + catch (const RecursionLimitException&) + { + return std::nullopt; + } } std::optional TypeReduction::reduce(TypePackId tp) { - if (auto found = cachedTypePacks.find(tp)) - return *found; + tp = follow(tp); - auto [reducedTp, cacheOk] = reduceImpl(tp); - if (cacheOk) - cachedTypePacks[tp] = *reducedTp; - - return reducedTp; -} - -std::pair, bool> TypeReduction::reduceImpl(TypeId ty) -{ if (FFlag::DebugLuauDontReduceTypes) - return {ty, false}; - - if (hasExceededCartesianProductLimit(ty)) - return {std::nullopt, false}; + return tp; + else if (!options.allowTypeReductionsFromOtherArenas && tp->owningArena != arena) + return tp; + else if (auto ctx = memoizedTypePacks.find(tp); ctx && ctx->irreducible) + return ctx->type; + else if (hasExceededCartesianProductLimit(tp)) + return std::nullopt; try { - TypeReducer reducer{arena, builtinTypes, handle}; - return {reducer.reduce(ty), reducer.cacheOk}; + MarkCycles finder; + finder.traverse(tp); + + TypeReducer reducer{arena, builtinTypes, handle, &memoizedTypes, &memoizedTypePacks, &finder.cyclicTypes}; + return reducer.reduce(tp); } catch (const RecursionLimitException&) { - return {std::nullopt, false}; - } -} - -std::pair, bool> TypeReduction::reduceImpl(TypePackId tp) -{ - if (FFlag::DebugLuauDontReduceTypes) - return {tp, false}; - - if (hasExceededCartesianProductLimit(tp)) - return {std::nullopt, false}; - - try - { - TypeReducer reducer{arena, builtinTypes, handle}; - return {reducer.reduce(tp), reducer.cacheOk}; - } - catch (const RecursionLimitException&) - { - return {std::nullopt, false}; + return std::nullopt; } } diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index e0cc1414..9db8f7f0 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -1,8 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Unifiable.h" -LUAU_FASTFLAG(LuauTypeNormalization2); - namespace Luau { namespace Unifiable @@ -11,19 +9,19 @@ namespace Unifiable static int nextIndex = 0; Free::Free(TypeLevel level) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , level(level) { } Free::Free(Scope* scope) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , scope(scope) { } Free::Free(Scope* scope, TypeLevel level) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , level(level) , scope(scope) { @@ -32,33 +30,33 @@ Free::Free(Scope* scope, TypeLevel level) int Free::DEPRECATED_nextIndex = 0; Generic::Generic() - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , name("g" + std::to_string(index)) { } Generic::Generic(TypeLevel level) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , level(level) , name("g" + std::to_string(index)) { } Generic::Generic(const Name& name) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , name(name) , explicitName(true) { } Generic::Generic(Scope* scope) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , scope(scope) { } Generic::Generic(TypeLevel level, const Name& name) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , level(level) , name(name) , explicitName(true) @@ -66,7 +64,7 @@ Generic::Generic(TypeLevel level, const Name& name) } Generic::Generic(Scope* scope, const Name& name) - : index(FFlag::LuauTypeNormalization2 ? ++nextIndex : ++DEPRECATED_nextIndex) + : index(++nextIndex) , scope(scope) , name(name) , explicitName(true) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index d35e3771..80f63f10 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -18,15 +18,13 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit); LUAU_FASTFLAG(LuauErrorRecoveryType); LUAU_FASTFLAG(LuauUnknownAndNeverType) -LUAU_FASTFLAGVARIABLE(LuauReportTypeMismatchForTypePackUnificationFailure, false) -LUAU_FASTFLAGVARIABLE(LuauSubtypeNormalizer, false); LUAU_FASTFLAGVARIABLE(LuauScalarShapeSubtyping, false) +LUAU_FASTFLAGVARIABLE(LuauUnifyAnyTxnLog, false) LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false) -LUAU_FASTFLAGVARIABLE(LuauOverloadedFunctionSubtypingPerf, false); LUAU_FASTFLAGVARIABLE(LuauScalarShapeUnifyToMtOwner2, false) LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false) +LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false) LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution) -LUAU_FASTFLAG(LuauTxnLogTypePackIterator) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(LuauNegatedFunctionTypes) LUAU_FASTFLAG(LuauNegatedClassTypes) @@ -378,7 +376,7 @@ Unifier::Unifier(NotNull normalizer, Mode mode, NotNull scope , variance(variance) , sharedState(*normalizer->sharedState) { - normalize = FFlag::LuauSubtypeNormalizer; + normalize = true; LUAU_ASSERT(sharedState.iceHandler); } @@ -480,17 +478,40 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool return; } - if (get(superTy) || get(superTy) || get(superTy)) - return tryUnifyWithAny(subTy, superTy); + if (FFlag::LuauUnifyAnyTxnLog) + { + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->anyType); - if (get(subTy)) - return tryUnifyWithAny(superTy, subTy); + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->errorType); - if (log.get(subTy)) - return tryUnifyWithAny(superTy, subTy); + if (log.get(superTy)) + return tryUnifyWithAny(subTy, builtinTypes->unknownType); - if (log.get(subTy)) - return tryUnifyWithAny(superTy, subTy); + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->anyType); + + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->errorType); + + if (log.get(subTy)) + return tryUnifyWithAny(superTy, builtinTypes->neverType); + } + else + { + if (get(superTy) || get(superTy) || get(superTy)) + return tryUnifyWithAny(subTy, superTy); + + if (get(subTy)) + return tryUnifyWithAny(superTy, subTy); + + if (log.get(subTy)) + return tryUnifyWithAny(superTy, subTy); + + if (log.get(subTy)) + return tryUnifyWithAny(superTy, subTy); + } auto& cache = sharedState.cachedUnify; @@ -524,10 +545,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool { tryUnifyUnionWithType(subTy, subUnion, superTy); } - else if (const UnionType* uv = (FFlag::LuauSubtypeNormalizer ? nullptr : log.getMutable(superTy))) - { - tryUnifyTypeWithUnion(subTy, superTy, uv, cacheEnabled, isFunctionCall); - } else if (const IntersectionType* uv = log.getMutable(superTy)) { tryUnifyTypeWithIntersection(subTy, superTy, uv); @@ -915,8 +932,6 @@ void Unifier::tryUnifyIntersectionWithType(TypeId subTy, const IntersectionType* void Unifier::tryUnifyNormalizedTypes( TypeId subTy, TypeId superTy, const NormalizedType& subNorm, const NormalizedType& superNorm, std::string reason, std::optional error) { - LUAU_ASSERT(FFlag::LuauSubtypeNormalizer); - if (get(superNorm.tops) || get(superNorm.tops) || get(subNorm.tops)) return; else if (get(subNorm.tops)) @@ -1096,12 +1111,9 @@ TypePackId Unifier::tryApplyOverloadedFunction(TypeId function, const Normalized log.concat(std::move(innerState.log)); if (result) { - if (FFlag::LuauOverloadedFunctionSubtypingPerf) - { - innerState.log.clear(); - innerState.tryUnify_(*result, ftv->retTypes); - } - if (FFlag::LuauOverloadedFunctionSubtypingPerf && innerState.errors.empty()) + innerState.log.clear(); + innerState.tryUnify_(*result, ftv->retTypes); + if (innerState.errors.empty()) log.concat(std::move(innerState.log)); // Annoyingly, since we don't support intersection of generic type packs, // the intersection may fail. We rather arbitrarily use the first matching overload @@ -1250,8 +1262,11 @@ struct WeirdIter LUAU_ASSERT(canGrow()); LUAU_ASSERT(log.getMutable(newTail)); - level = log.getMutable(packId)->level; - scope = log.getMutable(packId)->scope; + auto freePack = log.getMutable(packId); + + level = freePack->level; + if (FFlag::LuauMaintainScopesInUnifier && freePack->scope != nullptr) + scope = freePack->scope; log.replace(packId, BoundTypePack(newTail)); packId = newTail; pack = log.getMutable(newTail); @@ -1380,6 +1395,12 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal auto superIter = WeirdIter(superTp, log); auto subIter = WeirdIter(subTp, log); + if (FFlag::LuauMaintainScopesInUnifier) + { + superIter.scope = scope.get(); + subIter.scope = scope.get(); + } + auto mkFreshType = [this](Scope* scope, TypeLevel level) { return types->freshType(scope, level); }; @@ -1420,15 +1441,9 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal // If both are at the end, we're done if (!superIter.good() && !subIter.good()) { - if (!FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail) - { - tryUnify_(*subTpv->tail, *superTpv->tail); - break; - } - const bool lFreeTail = superTpv->tail && log.getMutable(log.follow(*superTpv->tail)) != nullptr; const bool rFreeTail = subTpv->tail && log.getMutable(log.follow(*subTpv->tail)) != nullptr; - if (FFlag::LuauTxnLogTypePackIterator && lFreeTail && rFreeTail) + if (lFreeTail && rFreeTail) { tryUnify_(*subTpv->tail, *superTpv->tail); } @@ -1440,7 +1455,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal { tryUnify_(emptyTp, *subTpv->tail); } - else if (FFlag::LuauTxnLogTypePackIterator && subTpv->tail && superTpv->tail) + else if (subTpv->tail && superTpv->tail) { if (log.getMutable(superIter.packId)) tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index)); @@ -1523,10 +1538,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal } else { - if (FFlag::LuauReportTypeMismatchForTypePackUnificationFailure) - reportError(location, TypePackMismatch{subTp, superTp}); - else - reportError(location, GenericError{"Failed to unify type packs"}); + reportError(location, TypePackMismatch{subTp, superTp}); } } @@ -2356,11 +2368,11 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever if (!superVariadic) ice("passed non-variadic pack to tryUnifyVariadics"); - if (const VariadicTypePack* subVariadic = FFlag::LuauTxnLogTypePackIterator ? log.get(subTp) : get(subTp)) + if (const VariadicTypePack* subVariadic = log.get(subTp)) { tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty); } - else if (FFlag::LuauTxnLogTypePackIterator ? log.get(subTp) : get(subTp)) + else if (log.get(subTp)) { TypePackIterator subIter = begin(subTp, &log); TypePackIterator subEnd = end(subTp); @@ -2465,9 +2477,18 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy) { LUAU_ASSERT(get(anyTy) || get(anyTy) || get(anyTy) || get(anyTy)); - // These types are not visited in general loop below - if (get(subTy) || get(subTy) || get(subTy)) - return; + if (FFlag::LuauUnifyAnyTxnLog) + { + // These types are not visited in general loop below + if (log.get(subTy) || log.get(subTy) || log.get(subTy)) + return; + } + else + { + // These types are not visited in general loop below + if (get(subTy) || get(subTy) || get(subTy)) + return; + } TypePackId anyTp; if (FFlag::LuauUnknownAndNeverType) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index aa87d9e8..7731312d 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -983,12 +983,13 @@ class AstStatTypeAlias : public AstStat public: LUAU_RTTI(AstStatTypeAlias) - AstStatTypeAlias(const Location& location, const AstName& name, const AstArray& generics, + AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation, const AstArray& generics, const AstArray& genericPacks, AstType* type, bool exported); void visit(AstVisitor* visitor) override; AstName name; + Location nameLocation; AstArray generics; AstArray genericPacks; AstType* type; diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index cbed8bae..e01ced04 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -647,10 +647,11 @@ void AstStatLocalFunction::visit(AstVisitor* visitor) func->visit(visitor); } -AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const AstArray& generics, - const AstArray& genericPacks, AstType* type, bool exported) +AstStatTypeAlias::AstStatTypeAlias(const Location& location, const AstName& name, const Location& nameLocation, + const AstArray& generics, const AstArray& genericPacks, AstType* type, bool exported) : AstStat(ClassIndex(), location) , name(name) + , nameLocation(nameLocation) , generics(generics) , genericPacks(genericPacks) , type(type) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index dea54c16..c71bd7c5 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -768,7 +768,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported) AstType* type = parseTypeAnnotation(); - return allocator.alloc(Location(start, type->location), name->name, generics, genericPacks, type, exported); + return allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); } AstDeclaredClassProp Parser::parseDeclaredClassMethod() diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 72c1294f..405b92dd 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -244,7 +244,7 @@ static int emitInst(AssemblyBuilderX64& build, NativeState& data, ModuleHelpers& emitInstForNPrep(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]); break; case LOP_FORNLOOP: - emitInstForNLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)]); + emitInstForNLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next); break; case LOP_FORGLOOP: emitinstForGLoop(build, pc, i, labelarr[i + 1 + LUAU_INSN_D(*pc)], next, fallback); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 2c410ae8..f1f6ba66 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -251,7 +251,7 @@ void callGetFastTmOrFallback(AssemblyBuilderX64& build, RegisterX64 table, TMS t // rArg1 is already prepared build.mov(rArg2, tm); build.mov(rax, qword[rState + offsetof(lua_State, global)]); - build.mov(rArg3, qword[rax + offsetof(global_State, tmname[tm])]); + build.mov(rArg3, qword[rax + offsetof(global_State, tmname) + tm * sizeof(TString*)]); build.call(qword[rNativeContext + offsetof(NativeContext, luaT_gettm)]); } diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index 7b6e1c64..e1212eab 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -1010,17 +1010,15 @@ static int emitInstFastCallN( if (nparams == LUA_MULTRET) { - // TODO: for SystemV ABI we can compute the result directly into rArg6 // L->top - (ra + 1) - build.mov(rcx, qword[rState + offsetof(lua_State, top)]); + RegisterX64 reg = (build.abi == ABIX64::Windows) ? rcx : rArg6; + build.mov(reg, qword[rState + offsetof(lua_State, top)]); build.lea(rdx, addr[rBase + (ra + 1) * sizeof(TValue)]); - build.sub(rcx, rdx); - build.shr(rcx, kTValueSizeLog2); + build.sub(reg, rdx); + build.shr(reg, kTValueSizeLog2); if (build.abi == ABIX64::Windows) - build.mov(sArg6, rcx); - else - build.mov(rArg6, rcx); + build.mov(sArg6, reg); } else { @@ -1126,7 +1124,7 @@ void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpo build.setLabel(exit); } -void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat) +void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit) { emitInterrupt(build, pcpos); @@ -1144,20 +1142,18 @@ void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpo build.vaddsd(idx, idx, step); build.vmovsd(luauRegValue(ra + 2), idx); - Label reverse, exit; + Label reverse; // step <= 0 jumpOnNumberCmp(build, noreg, step, zero, ConditionX64::LessEqual, reverse); // false: idx <= limit jumpOnNumberCmp(build, noreg, idx, limit, ConditionX64::LessEqual, loopRepeat); - build.jmp(exit); + build.jmp(loopExit); // true: limit <= idx build.setLabel(reverse); jumpOnNumberCmp(build, noreg, limit, idx, ConditionX64::LessEqual, loopRepeat); - - build.setLabel(exit); } void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit, Label& fallback) diff --git a/CodeGen/src/EmitInstructionX64.h b/CodeGen/src/EmitInstructionX64.h index 96501e63..83dfa8c8 100644 --- a/CodeGen/src/EmitInstructionX64.h +++ b/CodeGen/src/EmitInstructionX64.h @@ -61,7 +61,7 @@ int emitInstFastCall2(AssemblyBuilderX64& build, const Instruction* pc, int pcpo int emitInstFastCall2K(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback); int emitInstFastCall(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& fallback); void emitInstForNPrep(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopExit); -void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat); +void emitInstForNLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit); void emitinstForGLoop(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat, Label& loopExit, Label& fallback); void emitinstForGLoopFallback(AssemblyBuilderX64& build, const Instruction* pc, int pcpos, Label& loopRepeat); void emitInstForGPrepNext(AssemblyBuilderX64& build, const Instruction* pc, Label& target, Label& fallback); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp new file mode 100644 index 00000000..56968c1a --- /dev/null +++ b/CodeGen/src/IrBuilder.cpp @@ -0,0 +1,563 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "IrBuilder.h" + +#include "Luau/Common.h" + +#include "CustomExecUtils.h" +#include "IrTranslation.h" +#include "IrUtils.h" + +#include "lapi.h" + +namespace Luau +{ +namespace CodeGen +{ + +constexpr unsigned kNoAssociatedBlockIndex = ~0u; + +void IrBuilder::buildFunctionIr(Proto* proto) +{ + function.proto = proto; + + // Rebuild original control flow blocks + rebuildBytecodeBasicBlocks(proto); + + function.bcMapping.resize(proto->sizecode, {~0u, 0}); + + // Translate all instructions to IR inside blocks + for (int i = 0; i < proto->sizecode;) + { + const Instruction* pc = &proto->code[i]; + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc)); + + int nexti = i + getOpLength(op); + LUAU_ASSERT(nexti <= proto->sizecode); + + function.bcMapping[i] = {uint32_t(function.instructions.size()), 0}; + + // Begin new block at this instruction if it was in the bytecode or requested during translation + if (instIndexToBlock[i] != kNoAssociatedBlockIndex) + beginBlock(blockAtInst(i)); + + translateInst(op, pc, i); + + i = nexti; + LUAU_ASSERT(i <= proto->sizecode); + + // If we are going into a new block at the next instruction and it's a fallthrough, jump has to be placed to mark block termination + if (i < int(instIndexToBlock.size()) && instIndexToBlock[i] != kNoAssociatedBlockIndex) + { + if (!isBlockTerminator(function.instructions.back().cmd)) + inst(IrCmd::JUMP, blockAtInst(i)); + } + } +} + +void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto) +{ + instIndexToBlock.resize(proto->sizecode, kNoAssociatedBlockIndex); + + // Mark jump targets + std::vector jumpTargets(proto->sizecode, 0); + + for (int i = 0; i < proto->sizecode;) + { + const Instruction* pc = &proto->code[i]; + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(*pc)); + + int target = getJumpTarget(*pc, uint32_t(i)); + + if (target >= 0 && !isFastCall(op)) + jumpTargets[target] = true; + + i += getOpLength(op); + LUAU_ASSERT(i <= proto->sizecode); + } + + + // Bytecode blocks are created at bytecode jump targets and the start of a function + jumpTargets[0] = true; + + for (int i = 0; i < proto->sizecode; i++) + { + if (jumpTargets[i]) + { + IrOp b = block(IrBlockKind::Bytecode); + instIndexToBlock[i] = b.index; + } + } +} + +void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) +{ + switch (op) + { + case LOP_NOP: + break; + case LOP_LOADNIL: + translateInstLoadNil(*this, pc); + break; + case LOP_LOADB: + translateInstLoadB(*this, pc, i); + break; + case LOP_LOADN: + translateInstLoadN(*this, pc); + break; + case LOP_LOADK: + translateInstLoadK(*this, pc); + break; + case LOP_LOADKX: + translateInstLoadKX(*this, pc); + break; + case LOP_MOVE: + translateInstMove(*this, pc); + break; + case LOP_GETGLOBAL: + translateInstGetGlobal(*this, pc, i); + break; + case LOP_SETGLOBAL: + translateInstSetGlobal(*this, pc, i); + break; + case LOP_CALL: + inst(IrCmd::LOP_CALL, constUint(i)); + + if (activeFastcallFallback) + { + inst(IrCmd::JUMP, fastcallFallbackReturn); + + beginBlock(fastcallFallbackReturn); + + activeFastcallFallback = false; + } + break; + case LOP_RETURN: + inst(IrCmd::LOP_RETURN, constUint(i)); + break; + case LOP_GETTABLE: + translateInstGetTable(*this, pc, i); + break; + case LOP_SETTABLE: + translateInstSetTable(*this, pc, i); + break; + case LOP_GETTABLEKS: + translateInstGetTableKS(*this, pc, i); + break; + case LOP_SETTABLEKS: + translateInstSetTableKS(*this, pc, i); + break; + case LOP_GETTABLEN: + translateInstGetTableN(*this, pc, i); + break; + case LOP_SETTABLEN: + translateInstSetTableN(*this, pc, i); + break; + case LOP_JUMP: + translateInstJump(*this, pc, i); + break; + case LOP_JUMPBACK: + translateInstJumpBack(*this, pc, i); + break; + case LOP_JUMPIF: + translateInstJumpIf(*this, pc, i, /* not_ */ false); + break; + case LOP_JUMPIFNOT: + translateInstJumpIf(*this, pc, i, /* not_ */ true); + break; + case LOP_JUMPIFEQ: + translateInstJumpIfEq(*this, pc, i, /* not_ */ false); + break; + case LOP_JUMPIFLE: + translateInstJumpIfCond(*this, pc, i, IrCondition::LessEqual); + break; + case LOP_JUMPIFLT: + translateInstJumpIfCond(*this, pc, i, IrCondition::Less); + break; + case LOP_JUMPIFNOTEQ: + translateInstJumpIfEq(*this, pc, i, /* not_ */ true); + break; + case LOP_JUMPIFNOTLE: + translateInstJumpIfCond(*this, pc, i, IrCondition::NotLessEqual); + break; + case LOP_JUMPIFNOTLT: + translateInstJumpIfCond(*this, pc, i, IrCondition::NotLess); + break; + case LOP_JUMPX: + translateInstJumpX(*this, pc, i); + break; + case LOP_JUMPXEQKNIL: + translateInstJumpxEqNil(*this, pc, i); + break; + case LOP_JUMPXEQKB: + translateInstJumpxEqB(*this, pc, i); + break; + case LOP_JUMPXEQKN: + translateInstJumpxEqN(*this, pc, i); + break; + case LOP_JUMPXEQKS: + translateInstJumpxEqS(*this, pc, i); + break; + case LOP_ADD: + translateInstBinary(*this, pc, i, TM_ADD); + break; + case LOP_SUB: + translateInstBinary(*this, pc, i, TM_SUB); + break; + case LOP_MUL: + translateInstBinary(*this, pc, i, TM_MUL); + break; + case LOP_DIV: + translateInstBinary(*this, pc, i, TM_DIV); + break; + case LOP_MOD: + translateInstBinary(*this, pc, i, TM_MOD); + break; + case LOP_POW: + translateInstBinary(*this, pc, i, TM_POW); + break; + case LOP_ADDK: + translateInstBinaryK(*this, pc, i, TM_ADD); + break; + case LOP_SUBK: + translateInstBinaryK(*this, pc, i, TM_SUB); + break; + case LOP_MULK: + translateInstBinaryK(*this, pc, i, TM_MUL); + break; + case LOP_DIVK: + translateInstBinaryK(*this, pc, i, TM_DIV); + break; + case LOP_MODK: + translateInstBinaryK(*this, pc, i, TM_MOD); + break; + case LOP_POWK: + translateInstBinaryK(*this, pc, i, TM_POW); + break; + case LOP_NOT: + translateInstNot(*this, pc); + break; + case LOP_MINUS: + translateInstMinus(*this, pc, i); + break; + case LOP_LENGTH: + translateInstLength(*this, pc, i); + break; + case LOP_NEWTABLE: + translateInstNewTable(*this, pc, i); + break; + case LOP_DUPTABLE: + translateInstDupTable(*this, pc, i); + break; + case LOP_SETLIST: + inst(IrCmd::LOP_SETLIST, constUint(i)); + break; + case LOP_GETUPVAL: + translateInstGetUpval(*this, pc, i); + break; + case LOP_SETUPVAL: + translateInstSetUpval(*this, pc, i); + break; + case LOP_CLOSEUPVALS: + translateInstCloseUpvals(*this, pc); + break; + case LOP_FASTCALL: + { + IrOp fallback = block(IrBlockKind::Fallback); + IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2); + + inst(IrCmd::LOP_FASTCALL, constUint(i), fallback); + inst(IrCmd::JUMP, next); + + beginBlock(fallback); + + activeFastcallFallback = true; + fastcallFallbackReturn = next; + break; + } + case LOP_FASTCALL1: + { + IrOp fallback = block(IrBlockKind::Fallback); + IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2); + + inst(IrCmd::LOP_FASTCALL1, constUint(i), fallback); + inst(IrCmd::JUMP, next); + + beginBlock(fallback); + + activeFastcallFallback = true; + fastcallFallbackReturn = next; + break; + } + case LOP_FASTCALL2: + { + IrOp fallback = block(IrBlockKind::Fallback); + IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2); + + inst(IrCmd::LOP_FASTCALL2, constUint(i), fallback); + inst(IrCmd::JUMP, next); + + beginBlock(fallback); + + activeFastcallFallback = true; + fastcallFallbackReturn = next; + break; + } + case LOP_FASTCALL2K: + { + IrOp fallback = block(IrBlockKind::Fallback); + IrOp next = blockAtInst(i + LUAU_INSN_C(*pc) + 2); + + inst(IrCmd::LOP_FASTCALL2K, constUint(i), fallback); + inst(IrCmd::JUMP, next); + + beginBlock(fallback); + + activeFastcallFallback = true; + fastcallFallbackReturn = next; + break; + } + case LOP_FORNPREP: + { + IrOp loopExit = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); + + inst(IrCmd::LOP_FORNPREP, constUint(i), loopExit); + break; + } + case LOP_FORNLOOP: + { + IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); + IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORNLOOP)); + + inst(IrCmd::LOP_FORNLOOP, constUint(i), loopRepeat, loopExit); + + beginBlock(loopExit); + break; + } + case LOP_FORGLOOP: + { + IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); + IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORGLOOP)); + IrOp fallback = block(IrBlockKind::Fallback); + + inst(IrCmd::LOP_FORGLOOP, constUint(i), loopRepeat, loopExit, fallback); + + beginBlock(fallback); + inst(IrCmd::LOP_FORGLOOP_FALLBACK, constUint(i), loopRepeat, loopExit); + + beginBlock(loopExit); + break; + } + case LOP_FORGPREP_NEXT: + { + IrOp target = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); + IrOp fallback = block(IrBlockKind::Fallback); + + inst(IrCmd::LOP_FORGPREP_NEXT, constUint(i), target, fallback); + + beginBlock(fallback); + inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, constUint(i), target); + break; + } + case LOP_FORGPREP_INEXT: + { + IrOp target = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); + IrOp fallback = block(IrBlockKind::Fallback); + + inst(IrCmd::LOP_FORGPREP_INEXT, constUint(i), target, fallback); + + beginBlock(fallback); + inst(IrCmd::LOP_FORGPREP_XNEXT_FALLBACK, constUint(i), target); + break; + } + case LOP_AND: + inst(IrCmd::LOP_AND, constUint(i)); + break; + case LOP_ANDK: + inst(IrCmd::LOP_ANDK, constUint(i)); + break; + case LOP_OR: + inst(IrCmd::LOP_OR, constUint(i)); + break; + case LOP_ORK: + inst(IrCmd::LOP_ORK, constUint(i)); + break; + case LOP_COVERAGE: + inst(IrCmd::LOP_COVERAGE, constUint(i)); + break; + case LOP_GETIMPORT: + translateInstGetImport(*this, pc, i); + break; + case LOP_CONCAT: + translateInstConcat(*this, pc, i); + break; + case LOP_CAPTURE: + translateInstCapture(*this, pc, i); + break; + case LOP_NAMECALL: + { + IrOp next = blockAtInst(i + getOpLength(LOP_NAMECALL)); + IrOp fallback = block(IrBlockKind::Fallback); + + inst(IrCmd::LOP_NAMECALL, constUint(i), next, fallback); + + beginBlock(fallback); + inst(IrCmd::FALLBACK_NAMECALL, constUint(i)); + inst(IrCmd::JUMP, next); + + beginBlock(next); + break; + } + case LOP_PREPVARARGS: + inst(IrCmd::FALLBACK_PREPVARARGS, constUint(i)); + break; + case LOP_GETVARARGS: + inst(IrCmd::FALLBACK_GETVARARGS, constUint(i)); + break; + case LOP_NEWCLOSURE: + inst(IrCmd::FALLBACK_NEWCLOSURE, constUint(i)); + break; + case LOP_DUPCLOSURE: + inst(IrCmd::FALLBACK_DUPCLOSURE, constUint(i)); + break; + case LOP_FORGPREP: + inst(IrCmd::FALLBACK_FORGPREP, constUint(i)); + break; + default: + LUAU_ASSERT(!"unknown instruction"); + break; + } +} + +bool IrBuilder::isInternalBlock(IrOp block) +{ + IrBlock& target = function.blocks[block.index]; + + return target.kind == IrBlockKind::Internal; +} + +void IrBuilder::beginBlock(IrOp block) +{ + function.blocks[block.index].start = uint32_t(function.instructions.size()); +} + +IrOp IrBuilder::constBool(bool value) +{ + IrConst constant; + constant.kind = IrConstKind::Bool; + constant.valueBool = value; + return constAny(constant); +} + +IrOp IrBuilder::constInt(int value) +{ + IrConst constant; + constant.kind = IrConstKind::Int; + constant.valueInt = value; + return constAny(constant); +} + +IrOp IrBuilder::constUint(unsigned value) +{ + IrConst constant; + constant.kind = IrConstKind::Uint; + constant.valueUint = value; + return constAny(constant); +} + +IrOp IrBuilder::constDouble(double value) +{ + IrConst constant; + constant.kind = IrConstKind::Double; + constant.valueDouble = value; + return constAny(constant); +} + +IrOp IrBuilder::constTag(uint8_t value) +{ + IrConst constant; + constant.kind = IrConstKind::Tag; + constant.valueTag = value; + return constAny(constant); +} + +IrOp IrBuilder::constAny(IrConst constant) +{ + uint32_t index = uint32_t(function.constants.size()); + function.constants.push_back(constant); + return {IrOpKind::Constant, index}; +} + +IrOp IrBuilder::cond(IrCondition cond) +{ + return {IrOpKind::Condition, uint32_t(cond)}; +} + +IrOp IrBuilder::inst(IrCmd cmd) +{ + return inst(cmd, {}, {}, {}, {}, {}); +} + +IrOp IrBuilder::inst(IrCmd cmd, IrOp a) +{ + return inst(cmd, a, {}, {}, {}, {}); +} + +IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b) +{ + return inst(cmd, a, b, {}, {}, {}); +} + +IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c) +{ + return inst(cmd, a, b, c, {}, {}); +} + +IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d) +{ + return inst(cmd, a, b, c, d, {}); +} + +IrOp IrBuilder::inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e) +{ + uint32_t index = uint32_t(function.instructions.size()); + function.instructions.push_back({cmd, a, b, c, d, e}); + return {IrOpKind::Inst, index}; +} + +IrOp IrBuilder::block(IrBlockKind kind) +{ + if (kind == IrBlockKind::Internal && activeFastcallFallback) + kind = IrBlockKind::Fallback; + + uint32_t index = uint32_t(function.blocks.size()); + function.blocks.push_back(IrBlock{kind, ~0u}); + return IrOp{IrOpKind::Block, index}; +} + +IrOp IrBuilder::blockAtInst(uint32_t index) +{ + uint32_t blockIndex = instIndexToBlock[index]; + + if (blockIndex != kNoAssociatedBlockIndex) + return IrOp{IrOpKind::Block, blockIndex}; + + return block(IrBlockKind::Internal); +} + +IrOp IrBuilder::vmReg(uint8_t index) +{ + return {IrOpKind::VmReg, index}; +} + +IrOp IrBuilder::vmConst(uint32_t index) +{ + return {IrOpKind::VmConst, index}; +} + +IrOp IrBuilder::vmUpvalue(uint8_t index) +{ + return {IrOpKind::VmUpvalue, index}; +} + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrBuilder.h b/CodeGen/src/IrBuilder.h new file mode 100644 index 00000000..c8f9b4ec --- /dev/null +++ b/CodeGen/src/IrBuilder.h @@ -0,0 +1,63 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Common.h" +#include "Luau/Bytecode.h" + +#include "IrData.h" + +#include + +struct Proto; +typedef uint32_t Instruction; + +namespace Luau +{ +namespace CodeGen +{ + +struct AssemblyOptions; + +struct IrBuilder +{ + void buildFunctionIr(Proto* proto); + + void rebuildBytecodeBasicBlocks(Proto* proto); + void translateInst(LuauOpcode op, const Instruction* pc, int i); + + bool isInternalBlock(IrOp block); + void beginBlock(IrOp block); + + IrOp constBool(bool value); + IrOp constInt(int value); + IrOp constUint(unsigned value); + IrOp constDouble(double value); + IrOp constTag(uint8_t value); + IrOp constAny(IrConst constant); + + IrOp cond(IrCondition cond); + + IrOp inst(IrCmd cmd); + IrOp inst(IrCmd cmd, IrOp a); + IrOp inst(IrCmd cmd, IrOp a, IrOp b); + IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c); + IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d); + IrOp inst(IrCmd cmd, IrOp a, IrOp b, IrOp c, IrOp d, IrOp e); + + IrOp block(IrBlockKind kind); // Requested kind can be ignored if we are in an outlined sequence + IrOp blockAtInst(uint32_t index); + + IrOp vmReg(uint8_t index); + IrOp vmConst(uint32_t index); + IrOp vmUpvalue(uint8_t index); + + bool activeFastcallFallback = false; + IrOp fastcallFallbackReturn; + + IrFunction function; + + std::vector instIndexToBlock; // Block index at the bytecode instruction +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrData.h b/CodeGen/src/IrData.h index c4ed47cc..1c70c801 100644 --- a/CodeGen/src/IrData.h +++ b/CodeGen/src/IrData.h @@ -9,6 +9,8 @@ #include +struct Proto; + namespace Luau { namespace CodeGen @@ -99,6 +101,7 @@ enum class IrCmd : uint8_t // Operations that don't have an IR representation yet LOP_SETLIST, + LOP_NAMECALL, LOP_CALL, LOP_RETURN, LOP_FASTCALL, @@ -116,21 +119,21 @@ enum class IrCmd : uint8_t LOP_ANDK, LOP_OR, LOP_ORK, + LOP_COVERAGE, // Operations that have a translation, but use a full instruction fallback FALLBACK_GETGLOBAL, FALLBACK_SETGLOBAL, FALLBACK_GETTABLEKS, FALLBACK_SETTABLEKS, + FALLBACK_NAMECALL, // Operations that don't have assembly lowering at all - FALLBACK_NAMECALL, FALLBACK_PREPVARARGS, FALLBACK_GETVARARGS, FALLBACK_NEWCLOSURE, FALLBACK_DUPCLOSURE, FALLBACK_FORGPREP, - FALLBACK_COVERAGE, }; enum class IrConstKind : uint8_t @@ -274,6 +277,8 @@ struct IrFunction std::vector constants; std::vector bcMapping; + + Proto* proto = nullptr; }; } // namespace CodeGen diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 5d54026a..4dc5c6c5 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -186,6 +186,8 @@ const char* getCmdName(IrCmd cmd) return "CAPTURE"; case IrCmd::LOP_SETLIST: return "LOP_SETLIST"; + case IrCmd::LOP_NAMECALL: + return "LOP_NAMECALL"; case IrCmd::LOP_CALL: return "LOP_CALL"; case IrCmd::LOP_RETURN: @@ -220,6 +222,8 @@ const char* getCmdName(IrCmd cmd) return "LOP_OR"; case IrCmd::LOP_ORK: return "LOP_ORK"; + case IrCmd::LOP_COVERAGE: + return "LOP_COVERAGE"; case IrCmd::FALLBACK_GETGLOBAL: return "FALLBACK_GETGLOBAL"; case IrCmd::FALLBACK_SETGLOBAL: @@ -240,8 +244,6 @@ const char* getCmdName(IrCmd cmd) return "FALLBACK_DUPCLOSURE"; case IrCmd::FALLBACK_FORGPREP: return "FALLBACK_FORGPREP"; - case IrCmd::FALLBACK_COVERAGE: - return "FALLBACK_COVERAGE"; } LUAU_UNREACHABLE(); @@ -375,5 +377,48 @@ void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index) append(ctx.result, "; useCount: %d, lastUse: %%%u\n", inst.useCount, inst.lastUse); } +std::string dump(IrFunction& function) +{ + std::string result; + IrToStringContext ctx{result, function.blocks, function.constants}; + + for (size_t i = 0; i < function.blocks.size(); i++) + { + IrBlock& block = function.blocks[i]; + + append(ctx.result, "%s_%u:\n", getBlockKindName(block.kind), unsigned(i)); + + if (block.start == ~0u) + { + append(ctx.result, " *empty*\n\n"); + continue; + } + + for (uint32_t index = block.start; true; index++) + { + LUAU_ASSERT(index < function.instructions.size()); + + IrInst& inst = function.instructions[index]; + + // Nop is used to replace dead instructions in-place, so it's not that useful to see them + if (inst.cmd == IrCmd::NOP) + continue; + + append(ctx.result, " "); + toStringDetailed(ctx, inst, index); + + if (isBlockTerminator(inst.cmd)) + { + append(ctx.result, "\n"); + break; + } + } + } + + printf("%s\n", result.c_str()); + + return result; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrDump.h b/CodeGen/src/IrDump.h index 8fb4d6e5..c803e8db 100644 --- a/CodeGen/src/IrDump.h +++ b/CodeGen/src/IrDump.h @@ -28,5 +28,7 @@ void toString(std::string& result, IrConst constant); void toStringDetailed(IrToStringContext& ctx, IrInst inst, uint32_t index); +std::string dump(IrFunction& function); + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp new file mode 100644 index 00000000..82257815 --- /dev/null +++ b/CodeGen/src/IrTranslation.cpp @@ -0,0 +1,780 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "IrTranslation.h" + +#include "Luau/Bytecode.h" + +#include "IrBuilder.h" + +#include "lobject.h" +#include "ltm.h" + +namespace Luau +{ +namespace CodeGen +{ + +// Helper to consistently define a switch to instruction fallback code +struct FallbackStreamScope +{ + FallbackStreamScope(IrBuilder& build, IrOp fallback, IrOp next) + : build(build) + , next(next) + { + LUAU_ASSERT(fallback.kind == IrOpKind::Block); + LUAU_ASSERT(next.kind == IrOpKind::Block); + + build.inst(IrCmd::JUMP, next); + build.beginBlock(fallback); + } + + ~FallbackStreamScope() + { + build.beginBlock(next); + } + + IrBuilder& build; + IrOp next; +}; + +void translateInstLoadNil(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNIL)); +} + +void translateInstLoadB(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + + build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(LUAU_INSN_B(*pc))); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); + + if (int target = LUAU_INSN_C(*pc)) + build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + target)); +} + +void translateInstLoadN(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), build.constDouble(double(LUAU_INSN_D(*pc)))); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); +} + +void translateInstLoadK(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + + // TODO: per-component loads and stores might be preferable + IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(LUAU_INSN_D(*pc))); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load); +} + +void translateInstLoadKX(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + + // TODO: per-component loads and stores might be preferable + IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(aux)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load); +} + +void translateInstMove(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + + // TODO: per-component loads and stores might be preferable + IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load); +} + +void translateInstJump(IrBuilder& build, const Instruction* pc, int pcpos) +{ + build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc))); +} + +void translateInstJumpBack(IrBuilder& build, const Instruction* pc, int pcpos) +{ + build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc))); +} + +void translateInstJumpIf(IrBuilder& build, const Instruction* pc, int pcpos, bool not_) +{ + int ra = LUAU_INSN_A(*pc); + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 1); + + // TODO: falsy/truthy conditions should be deconstructed into more primitive operations + if (not_) + build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(ra), target, next); + else + build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(ra), target, next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, bool not_) +{ + int ra = LUAU_INSN_A(*pc); + int rb = pc[1]; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + IrOp numberCheck = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::JUMP_EQ_TAG, ta, tb, numberCheck, not_ ? target : next); + + build.beginBlock(numberCheck); + + // fast-path: number + build.inst(IrCmd::CHECK_TAG, ta, build.constTag(LUA_TNUMBER), fallback); + + IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra)); + IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb)); + + build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(IrCondition::NotEqual), not_ ? target : next, not_ ? next : target); + + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal), target, next); +} + +void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, IrCondition cond) +{ + int ra = LUAU_INSN_A(*pc); + int rb = pc[1]; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + IrOp fallback = build.block(IrBlockKind::Fallback); + + // fast-path: number + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + build.inst(IrCmd::CHECK_TAG, ta, build.constTag(LUA_TNUMBER), fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback); + + IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra)); + IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb)); + + build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(cond), target, next); + + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::JUMP_CMP_ANY, build.vmReg(ra), build.vmReg(rb), build.cond(cond), target, next); +} + +void translateInstJumpX(IrBuilder& build, const Instruction* pc, int pcpos) +{ + build.inst(IrCmd::INTERRUPT, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, build.blockAtInst(pcpos + 1 + LUAU_INSN_E(*pc))); +} + +void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + bool not_ = (pc[1] & 0x80000000) != 0; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TNIL), not_ ? next : target, not_ ? target : next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = (aux & 0x80000000) != 0; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + IrOp checkValue = build.block(IrBlockKind::Internal); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + + build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TBOOLEAN), checkValue, not_ ? target : next); + + build.beginBlock(checkValue); + IrOp va = build.inst(IrCmd::LOAD_INT, build.vmReg(ra)); + + build.inst(IrCmd::JUMP_EQ_BOOLEAN, va, build.constBool(aux & 0x1), not_ ? next : target, not_ ? target : next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = (aux & 0x80000000) != 0; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + IrOp checkValue = build.block(IrBlockKind::Internal); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + + build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TNUMBER), checkValue, not_ ? target : next); + + build.beginBlock(checkValue); + IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra)); + IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmConst(aux & 0xffffff)); + + build.inst(IrCmd::JUMP_CMP_NUM, va, vb, build.cond(IrCondition::NotEqual), not_ ? target : next, not_ ? next : target); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = (aux & 0x80000000) != 0; + + IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); + IrOp next = build.blockAtInst(pcpos + 2); + IrOp checkValue = build.block(IrBlockKind::Internal); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + build.inst(IrCmd::JUMP_EQ_TAG, ta, build.constTag(LUA_TSTRING), checkValue, not_ ? target : next); + + build.beginBlock(checkValue); + IrOp va = build.inst(IrCmd::LOAD_POINTER, build.vmReg(ra)); + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmConst(aux & 0xffffff)); + + build.inst(IrCmd::JUMP_EQ_POINTER, va, vb, not_ ? next : target, not_ ? target : next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opc, int pcpos, TMS tm) +{ + IrOp fallback = build.block(IrBlockKind::Fallback); + + // fast-path: number + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback); + + if (rc != -1 && rc != rb) // TODO: optimization should handle second check, but we'll test it later + { + IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)); + build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback); + } + + IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb)); + IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, opc); + + IrOp va; + + switch (tm) + { + case TM_ADD: + va = build.inst(IrCmd::ADD_NUM, vb, vc); + break; + case TM_SUB: + va = build.inst(IrCmd::SUB_NUM, vb, vc); + break; + case TM_MUL: + va = build.inst(IrCmd::MUL_NUM, vb, vc); + break; + case TM_DIV: + va = build.inst(IrCmd::DIV_NUM, vb, vc); + break; + case TM_MOD: + va = build.inst(IrCmd::MOD_NUM, vb, vc); + break; + case TM_POW: + va = build.inst(IrCmd::POW_NUM, vb, vc); + break; + default: + LUAU_ASSERT(!"unsupported binary op"); + } + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va); + + if (ra != rb && ra != rc) // TODO: optimization should handle second check, but we'll test this later + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::DO_ARITH, build.vmReg(ra), build.vmReg(rb), opc, build.constInt(tm)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm) +{ + translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), LUAU_INSN_C(*pc), build.vmReg(LUAU_INSN_C(*pc)), pcpos, tm); +} + +void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm) +{ + translateInstBinaryNumeric(build, LUAU_INSN_A(*pc), LUAU_INSN_B(*pc), -1, build.vmConst(LUAU_INSN_C(*pc)), pcpos, tm); +} + +void translateInstNot(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + IrOp vb = build.inst(IrCmd::LOAD_INT, build.vmReg(rb)); + + IrOp va = build.inst(IrCmd::NOT_ANY, tb, vb); + + build.inst(IrCmd::STORE_INT, build.vmReg(ra), va); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); +} + +void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TNUMBER), fallback); + + // fast-path: number + IrOp vb = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rb)); + IrOp va = build.inst(IrCmd::UNM_NUM, vb); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va); + + if (ra != rb) + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::DO_ARITH, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.vmReg(LUAU_INSN_B(*pc)), build.constInt(TM_UNM)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + + // fast-path: table without __len + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); + + IrOp va = build.inst(IrCmd::TABLE_LEN, vb); + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), va); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::DO_LEN, build.vmReg(LUAU_INSN_A(*pc)), build.vmReg(LUAU_INSN_B(*pc))); + build.inst(IrCmd::JUMP, next); +} + +void translateInstNewTable(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int b = LUAU_INSN_B(*pc); + uint32_t aux = pc[1]; + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + + IrOp va = build.inst(IrCmd::NEW_TABLE, build.constUint(aux), build.constUint(b == 0 ? 0 : 1 << (b - 1))); + build.inst(IrCmd::STORE_POINTER, build.vmReg(ra), va); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TTABLE)); + + build.inst(IrCmd::CHECK_GC); +} + +void translateInstDupTable(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int k = LUAU_INSN_D(*pc); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + + IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmConst(k)); + IrOp va = build.inst(IrCmd::DUP_TABLE, table); + build.inst(IrCmd::STORE_POINTER, build.vmReg(ra), va); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TTABLE)); + + build.inst(IrCmd::CHECK_GC); +} + +void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int up = LUAU_INSN_B(*pc); + + build.inst(IrCmd::GET_UPVALUE, build.vmReg(ra), build.vmUpvalue(up)); +} + +void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int up = LUAU_INSN_B(*pc); + + build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(up), build.vmReg(ra)); +} + +void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc) +{ + int ra = LUAU_INSN_A(*pc); + + build.inst(IrCmd::CLOSE_UPVALS, build.vmReg(ra)); +} + +void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int c = LUAU_INSN_C(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + + build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constUint(c), fallback); + build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); + + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constUint(c)); + + // TODO: per-component loads and stores might be preferable + IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::GET_TABLE, build.vmReg(ra), build.vmReg(rb), build.constUint(c + 1)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int c = LUAU_INSN_C(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + + build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, build.constUint(c), fallback); + build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); + build.inst(IrCmd::CHECK_READONLY, vb, fallback); + + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, build.constUint(c)); + + // TODO: per-component loads and stores might be preferable + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_TVALUE, arrEl, tva); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra)); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::SET_TABLE, build.vmReg(ra), build.vmReg(rb), build.constUint(c + 1)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)); + build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback); + + // fast-path: table with a number index + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rc)); + + IrOp index = build.inst(IrCmd::NUM_TO_INDEX, vc, fallback); + + index = build.inst(IrCmd::SUB_INT, index, build.constInt(1)); + + build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, index, fallback); + build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); + + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, index); + + // TODO: per-component loads and stores might be preferable + IrOp arrElTval = build.inst(IrCmd::LOAD_TVALUE, arrEl); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), arrElTval); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::GET_TABLE, build.vmReg(ra), build.vmReg(rb), build.vmReg(rc)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + IrOp tc = build.inst(IrCmd::LOAD_TAG, build.vmReg(rc)); + build.inst(IrCmd::CHECK_TAG, tc, build.constTag(LUA_TNUMBER), fallback); + + // fast-path: table with a number index + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + IrOp vc = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(rc)); + + IrOp index = build.inst(IrCmd::NUM_TO_INDEX, vc, fallback); + + index = build.inst(IrCmd::SUB_INT, index, build.constInt(1)); + + build.inst(IrCmd::CHECK_ARRAY_SIZE, vb, index, fallback); + build.inst(IrCmd::CHECK_NO_METATABLE, vb, fallback); + build.inst(IrCmd::CHECK_READONLY, vb, fallback); + + IrOp arrEl = build.inst(IrCmd::GET_ARR_ADDR, vb, index); + + // TODO: per-component loads and stores might be preferable + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_TVALUE, arrEl, tva); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra)); + + IrOp next = build.blockAtInst(pcpos + 1); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::SET_TABLE, build.vmReg(ra), build.vmReg(rb), build.vmReg(rc)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int k = LUAU_INSN_D(*pc); + uint32_t aux = pc[1]; + + IrOp fastPath = build.block(IrBlockKind::Internal); + IrOp fallback = build.block(IrBlockKind::Fallback); + + build.inst(IrCmd::CHECK_SAFE_ENV, fallback); + + // 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 + IrOp tk = build.inst(IrCmd::LOAD_TAG, build.vmConst(k)); + build.inst(IrCmd::JUMP_EQ_TAG, tk, build.constTag(LUA_TNIL), fallback, fastPath); + + build.beginBlock(fastPath); + + // TODO: per-component loads and stores might be preferable + IrOp tvk = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(k)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvk); + + IrOp next = build.blockAtInst(pcpos + 2); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::GET_IMPORT, build.vmReg(ra), build.constUint(aux)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + uint32_t aux = pc[1]; + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + + build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); + + // TODO: per-component loads and stores might be preferable + IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn); + + IrOp next = build.blockAtInst(pcpos + 2); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + uint32_t aux = pc[1]; + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); + build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), fallback); + + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); + + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, vb, build.constUint(pcpos)); + + build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); + build.inst(IrCmd::CHECK_READONLY, vb, fallback); + + // TODO: per-component loads and stores might be preferable + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, vb, build.vmReg(ra)); + + IrOp next = build.blockAtInst(pcpos + 2); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::FALLBACK_SETTABLEKS, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp env = build.inst(IrCmd::LOAD_ENV); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + + build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); + + // TODO: per-component loads and stores might be preferable + IrOp tvn = build.inst(IrCmd::LOAD_NODE_VALUE_TV, addrSlotEl); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvn); + + IrOp next = build.blockAtInst(pcpos + 2); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::FALLBACK_GETGLOBAL, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + + IrOp fallback = build.block(IrBlockKind::Fallback); + + IrOp env = build.inst(IrCmd::LOAD_ENV); + IrOp addrSlotEl = build.inst(IrCmd::GET_SLOT_NODE_ADDR, env, build.constUint(pcpos)); + + build.inst(IrCmd::CHECK_SLOT_MATCH, addrSlotEl, build.vmConst(aux), fallback); + build.inst(IrCmd::CHECK_READONLY, env, fallback); + + // TODO: per-component loads and stores might be preferable + IrOp tva = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(ra)); + build.inst(IrCmd::STORE_NODE_VALUE_TV, addrSlotEl, tva); + + build.inst(IrCmd::BARRIER_TABLE_FORWARD, env, build.vmReg(ra)); + + IrOp next = build.blockAtInst(pcpos + 2); + FallbackStreamScope scope(build, fallback, next); + + build.inst(IrCmd::FALLBACK_SETGLOBAL, build.constUint(pcpos)); + build.inst(IrCmd::JUMP, next); +} + +void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); + + build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); + build.inst(IrCmd::CONCAT, build.constUint(rc - rb + 1), build.constUint(rc)); + + // TODO: per-component loads and stores might be preferable + IrOp tvb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvb); + + build.inst(IrCmd::CHECK_GC); +} + +void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos) +{ + int type = LUAU_INSN_A(*pc); + int index = LUAU_INSN_B(*pc); + + switch (type) + { + case LCT_VAL: + build.inst(IrCmd::CAPTURE, build.vmReg(index), build.constBool(false)); + break; + case LCT_REF: + build.inst(IrCmd::CAPTURE, build.vmReg(index), build.constBool(true)); + break; + case LCT_UPVAL: + build.inst(IrCmd::CAPTURE, build.vmUpvalue(index), build.constBool(false)); + break; + default: + LUAU_ASSERT(!"Unknown upvalue capture type"); + } +} + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrTranslation.h b/CodeGen/src/IrTranslation.h new file mode 100644 index 00000000..53030a20 --- /dev/null +++ b/CodeGen/src/IrTranslation.h @@ -0,0 +1,58 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +#include "ltm.h" + +typedef uint32_t Instruction; + +namespace Luau +{ +namespace CodeGen +{ + +enum class IrCondition : uint8_t; +struct IrOp; +struct IrBuilder; + +void translateInstLoadNil(IrBuilder& build, const Instruction* pc); +void translateInstLoadB(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstLoadN(IrBuilder& build, const Instruction* pc); +void translateInstLoadK(IrBuilder& build, const Instruction* pc); +void translateInstLoadKX(IrBuilder& build, const Instruction* pc); +void translateInstMove(IrBuilder& build, const Instruction* pc); +void translateInstJump(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpBack(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpIf(IrBuilder& build, const Instruction* pc, int pcpos, bool not_); +void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, bool not_); +void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, IrCondition cond); +void translateInstJumpX(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm); +void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm); +void translateInstNot(IrBuilder& build, const Instruction* pc); +void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstLength(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstNewTable(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstDupTable(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstGetUpval(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstSetUpval(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstCloseUpvals(IrBuilder& build, const Instruction* pc); +void translateInstGetTableN(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstSetTableN(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstGetTable(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstSetTable(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstGetGlobal(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstSetGlobal(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstConcat(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstCapture(IrBuilder& build, const Instruction* pc, int pcpos); + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/IrUtils.h b/CodeGen/src/IrUtils.h index f0e4cee6..55881789 100644 --- a/CodeGen/src/IrUtils.h +++ b/CodeGen/src/IrUtils.h @@ -98,6 +98,7 @@ inline bool isBlockTerminator(IrCmd cmd) case IrCmd::JUMP_CMP_NUM: case IrCmd::JUMP_CMP_STR: case IrCmd::JUMP_CMP_ANY: + case IrCmd::LOP_NAMECALL: case IrCmd::LOP_RETURN: case IrCmd::LOP_FORNPREP: case IrCmd::LOP_FORNLOOP: diff --git a/Common/include/Luau/Common.h b/Common/include/Luau/Common.h index e590987c..31b416fb 100644 --- a/Common/include/Luau/Common.h +++ b/Common/include/Luau/Common.h @@ -35,7 +35,10 @@ inline AssertHandler& assertHandler() return handler; } -inline int assertCallHandler(const char* expression, const char* file, int line, const char* function) +// We want 'inline' to correctly link this function declared in the header +// But we also want to prevent compiler from inlining this function when optimization and assertions are enabled together +// Reason for that is that compilation times can increase significantly in such a configuration +LUAU_NOINLINE inline int assertCallHandler(const char* expression, const char* file, int line, const char* function) { if (AssertHandler handler = assertHandler()) return handler(expression, file, line, function); diff --git a/Ast/include/Luau/DenseHash.h b/Common/include/Luau/DenseHash.h similarity index 100% rename from Ast/include/Luau/DenseHash.h rename to Common/include/Luau/DenseHash.h diff --git a/Sources.cmake b/Sources.cmake index 36e4f04d..636b42f7 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -4,6 +4,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19") target_sources(Luau.Common PRIVATE Common/include/Luau/Common.h Common/include/Luau/Bytecode.h + Common/include/Luau/DenseHash.h Common/include/Luau/ExperimentalFlags.h ) endif() @@ -12,7 +13,6 @@ endif() target_sources(Luau.Ast PRIVATE Ast/include/Luau/Ast.h Ast/include/Luau/Confusables.h - Ast/include/Luau/DenseHash.h Ast/include/Luau/Lexer.h Ast/include/Luau/Location.h Ast/include/Luau/ParseOptions.h @@ -82,7 +82,9 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/EmitCommonX64.cpp CodeGen/src/EmitInstructionX64.cpp CodeGen/src/Fallbacks.cpp + CodeGen/src/IrBuilder.cpp CodeGen/src/IrDump.cpp + CodeGen/src/IrTranslation.cpp CodeGen/src/NativeState.cpp CodeGen/src/UnwindBuilderDwarf2.cpp CodeGen/src/UnwindBuilderWin.cpp @@ -96,8 +98,10 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/src/EmitInstructionX64.h CodeGen/src/Fallbacks.h CodeGen/src/FallbacksProlog.h + CodeGen/src/IrBuilder.h CodeGen/src/IrDump.h CodeGen/src/IrData.h + CodeGen/src/IrTranslation.h CodeGen/src/IrUtils.h CodeGen/src/NativeState.h ) diff --git a/fuzz/luau.proto b/fuzz/luau.proto index 190b8c5b..e51d687b 100644 --- a/fuzz/luau.proto +++ b/fuzz/luau.proto @@ -20,6 +20,7 @@ message Expr { ExprUnary unary = 14; ExprBinary binary = 15; ExprIfElse ifelse = 16; + ExprInterpString interpstring = 17; } } @@ -161,6 +162,10 @@ message ExprIfElse { } } +message ExprInterpString { + repeated Expr parts = 1; +} + message LValue { oneof lvalue_oneof { ExprLocal local = 1; diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index d4d52276..5c7c5bf6 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -282,6 +282,8 @@ struct ProtoToLuau print(expr.binary()); else if (expr.has_ifelse()) print(expr.ifelse()); + else if (expr.has_interpstring()) + print(expr.interpstring()); else source += "_"; } @@ -538,6 +540,28 @@ struct ProtoToLuau } } + void print(const luau::ExprInterpString& expr) + { + source += "`"; + + for (int i = 0; i < expr.parts_size(); ++i) + { + if (expr.parts(i).has_string()) + { + // String literal is added surrounded with "", but that's ok + print(expr.parts(i)); + } + else + { + source += "{"; + print(expr.parts(i)); + source += "}"; + } + } + + source += "`"; + } + void print(const luau::LValue& expr) { if (expr.has_local()) diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index b5787523..1b8bb3da 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -16,6 +16,8 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauFixAutocompleteInIf) +LUAU_FASTFLAG(LuauFixAutocompleteInWhile) +LUAU_FASTFLAG(LuauFixAutocompleteInFor) using namespace Luau; @@ -380,7 +382,7 @@ TEST_CASE_FIXTURE(ACFixture, "table_intersection") { check(R"( type t1 = { a1 : string, b2 : number } - type t2 = { b2 : string, c3 : string } + type t2 = { b2 : number, c3 : string } function func(abc : t1 & t2) abc. @1 end @@ -629,9 +631,19 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords") )"); auto ac5 = autocomplete('1'); - CHECK_EQ(ac5.entryMap.count("do"), 1); - CHECK_EQ(ac5.entryMap.count("end"), 0); - CHECK_EQ(ac5.context, AutocompleteContext::Keyword); + if (FFlag::LuauFixAutocompleteInFor) + { + CHECK_EQ(ac5.entryMap.count("math"), 1); + CHECK_EQ(ac5.entryMap.count("do"), 0); + CHECK_EQ(ac5.entryMap.count("end"), 0); + CHECK_EQ(ac5.context, AutocompleteContext::Expression); + } + else + { + CHECK_EQ(ac5.entryMap.count("do"), 1); + CHECK_EQ(ac5.entryMap.count("end"), 0); + CHECK_EQ(ac5.context, AutocompleteContext::Keyword); + } check(R"( for x = 1, 2, 5 f@1 @@ -649,6 +661,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_middle_keywords") auto ac7 = autocomplete('1'); CHECK_EQ(ac7.entryMap.count("end"), 1); CHECK_EQ(ac7.context, AutocompleteContext::Statement); + + if (FFlag::LuauFixAutocompleteInFor) + { + check(R"(local Foo = 1 + for x = @11, @22, @35 + )"); + + for (int i = 0; i < 3; ++i) + { + auto ac8 = autocomplete('1' + i); + CHECK_EQ(ac8.entryMap.count("Foo"), 1); + CHECK_EQ(ac8.entryMap.count("do"), 0); + } + + check(R"(local Foo = 1 + for x = @11, @22 + )"); + + for (int i = 0; i < 2; ++i) + { + auto ac9 = autocomplete('1' + i); + CHECK_EQ(ac9.entryMap.count("Foo"), 1); + CHECK_EQ(ac9.entryMap.count("do"), 0); + } + } } TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_in_middle_keywords") @@ -740,8 +777,18 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords") )"); auto ac2 = autocomplete('1'); - CHECK_EQ(1, ac2.entryMap.size()); - CHECK_EQ(ac2.entryMap.count("do"), 1); + if (FFlag::LuauFixAutocompleteInWhile) + { + CHECK_EQ(3, ac2.entryMap.size()); + CHECK_EQ(ac2.entryMap.count("do"), 1); + CHECK_EQ(ac2.entryMap.count("and"), 1); + CHECK_EQ(ac2.entryMap.count("or"), 1); + } + else + { + CHECK_EQ(1, ac2.entryMap.size()); + CHECK_EQ(ac2.entryMap.count("do"), 1); + } CHECK_EQ(ac2.context, AutocompleteContext::Keyword); check(R"( @@ -757,9 +804,31 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_while_middle_keywords") )"); auto ac4 = autocomplete('1'); - CHECK_EQ(1, ac4.entryMap.size()); - CHECK_EQ(ac4.entryMap.count("do"), 1); + if (FFlag::LuauFixAutocompleteInWhile) + { + CHECK_EQ(3, ac4.entryMap.size()); + CHECK_EQ(ac4.entryMap.count("do"), 1); + CHECK_EQ(ac4.entryMap.count("and"), 1); + CHECK_EQ(ac4.entryMap.count("or"), 1); + } + else + { + CHECK_EQ(1, ac4.entryMap.size()); + CHECK_EQ(ac4.entryMap.count("do"), 1); + } CHECK_EQ(ac4.context, AutocompleteContext::Keyword); + + if (FFlag::LuauFixAutocompleteInWhile) + { + check(R"( + while t@1 + )"); + + auto ac5 = autocomplete('1'); + CHECK_EQ(ac5.entryMap.count("do"), 0); + CHECK_EQ(ac5.entryMap.count("true"), 1); + CHECK_EQ(ac5.entryMap.count("false"), 1); + } } TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords") @@ -856,7 +925,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_if_middle_keywords") CHECK_EQ(ac5.entryMap.count("elseif"), 0); CHECK_EQ(ac5.entryMap.count("end"), 0); CHECK_EQ(ac5.context, AutocompleteContext::Statement); - + if (FFlag::LuauFixAutocompleteInIf) { check(R"( @@ -3399,6 +3468,8 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete") TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") { + ScopedFastFlag luauAutocompleteStringContent{"LuauAutocompleteStringContent", true}; + loadDefinition(R"( declare function require(path: string): any )"); @@ -3414,10 +3485,9 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") )"); bool isCorrect = false; - auto ac1 = autocomplete('1', - [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional - { - isCorrect = contents.has_value() && contents.value() == "testing/"; + auto ac1 = autocomplete( + '1', [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional { + isCorrect = contents && *contents == "testing/"; return std::nullopt; }); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 13600cb7..4d3146b3 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -701,8 +701,7 @@ TEST_CASE("NDebugGetUpValue") copts.optimizationLevel = 0; runConformance( - "ndebug_upvalues.lua", - nullptr, + "ndebug_upvalues.lua", nullptr, [](lua_State* L) { lua_checkstack(L, LUA_MINSTACK); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 5ff00627..cb6eefc0 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -606,12 +606,14 @@ void createSomeClasses(Frontend* frontend) TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"}); - ClassType* childClass = getMutable(childType); - childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})}; - addGlobalBinding(*frontend, "Child", {childType}); moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType}; + TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test"}); + + addGlobalBinding(*frontend, "AnotherChild", {anotherChildType}); + moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType}; + TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"}); addGlobalBinding(*frontend, "Unrelated", {unrelatedType}); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 426b520c..84e28601 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -171,7 +171,6 @@ return bar() TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx") { - ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; LintResult result = lint(R"( function bar() foo = 6 @@ -192,7 +191,6 @@ return bar() + baz() TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead") { - ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; LintResult result = lint(R"( function bar() foo = 6 @@ -216,7 +214,6 @@ return bar() + baz() + read() TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional") { - ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; LintResult result = lint(R"( function bar() if true then foo = 6 end @@ -236,7 +233,6 @@ return bar() + baz() TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead") { - ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; LintResult result = lint(R"( function bar() foo = 6 @@ -260,7 +256,6 @@ return bar() + baz() + read() TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead") { - ScopedFastFlag sff{"LuauLintGlobalNeverReadBeforeWritten", true}; LintResult result = lint(R"( function foo() local f = function() return bar end diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 615fc997..13a956cf 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -174,11 +174,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop") TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - check(R"( local a: number & string local b: number diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index dc08ae1c..80e82fdb 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -9,6 +9,8 @@ using namespace Luau; +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); + struct ToDotClassFixture : Fixture { ToDotClassFixture() @@ -109,7 +111,27 @@ local function f(a, ...: string) return a end ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(R"(digraph graphname { +n1 [label="FunctionType 1"]; +n1 -> n2 [label="arg"]; +n2 [label="TypePack 2"]; +n2 -> n3; +n3 [label="GenericType 3"]; +n2 -> n4 [label="tail"]; +n4 [label="VariadicTypePack 4"]; +n4 -> n5; +n5 [label="string"]; +n1 -> n6 [label="ret"]; +n6 [label="TypePack 6"]; +n6 -> n3; +})", + toDot(requireType("f"), opts)); + } + else + { + CHECK_EQ(R"(digraph graphname { n1 [label="FunctionType 1"]; n1 -> n2 [label="arg"]; n2 [label="TypePack 2"]; @@ -125,7 +147,8 @@ n6 -> n7; n7 [label="TypePack 7"]; n7 -> n3; })", - toDot(requireType("f"), opts)); + toDot(requireType("f"), opts)); + } } TEST_CASE_FIXTURE(Fixture, "union") @@ -176,7 +199,35 @@ local a: A ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(R"(digraph graphname { +n1 [label="TableType A"]; +n1 -> n2 [label="x"]; +n2 [label="number"]; +n1 -> n3 [label="y"]; +n3 [label="FunctionType 3"]; +n3 -> n4 [label="arg"]; +n4 [label="TypePack 4"]; +n4 -> n5 [label="tail"]; +n5 [label="VariadicTypePack 5"]; +n5 -> n6; +n6 [label="string"]; +n3 -> n7 [label="ret"]; +n7 [label="TypePack 7"]; +n1 -> n8 [label="[index]"]; +n8 [label="string"]; +n1 -> n9 [label="[value]"]; +n9 [label="any"]; +n1 -> n10 [label="typeParam"]; +n10 [label="number"]; +n1 -> n5 [label="typePackParam"]; +})", + toDot(requireType("a"), opts)); + } + else + { + CHECK_EQ(R"(digraph graphname { n1 [label="TableType A"]; n1 -> n2 [label="x"]; n2 [label="number"]; @@ -196,7 +247,8 @@ n1 -> n9 [label="typeParam"]; n9 [label="number"]; n1 -> n4 [label="typePackParam"]; })", - toDot(requireType("a"), opts)); + toDot(requireType("a"), opts)); + } // Extra coverage with pointers (unstable values) (void)toDot(requireType("a")); @@ -357,14 +409,31 @@ b = a ToDotOptions opts; opts.showPointers = false; - CHECK_EQ(R"(digraph graphname { + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ(R"(digraph graphname { +n1 [label="BoundType 1"]; +n1 -> n2; +n2 [label="TableType 2"]; +n2 -> n3 [label="boundTo"]; +n3 [label="TableType 3"]; +n3 -> n4 [label="x"]; +n4 [label="number"]; +})", + toDot(*ty, opts)); + } + else + { + CHECK_EQ(R"(digraph graphname { n1 [label="TableType 1"]; n1 -> n2 [label="boundTo"]; n2 [label="TableType a"]; n2 -> n3 [label="x"]; n3 [label="number"]; })", - toDot(*ty, opts)); + toDot(*ty, opts)); + } } TEST_CASE_FIXTURE(Fixture, "builtintypes") diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 7d27437d..0e51f976 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -814,8 +814,6 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param") TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") { - ScopedFastFlag sff("LuauUnseeArrayTtv", true); - CheckResult result = check(R"( local x: {string} -- This code is constructed very specifically to use the same (by pointer diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 6c2d3108..860dcfd0 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -8,7 +8,8 @@ using namespace Luau; -LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) +LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(LuauMatchReturnsOptionalString); TEST_SUITE_BEGIN("BuiltinTests"); @@ -174,7 +175,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_checks_for_numbers") local n = math.max(1,2,"3") )"); - CHECK(!result.errors.empty()); + LUAU_REQUIRE_ERRORS(result); CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); } @@ -1004,7 +1005,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic") TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments") { - ScopedFastFlag sff{"LuauSetMetaTableArgsCheck", true}; CheckResult result = check(R"( local a = {b=setmetatable} a.b() @@ -1055,6 +1055,20 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "string_match") +{ + CheckResult result = check(R"( + local s:string + local p = s:match("foo") + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauMatchReturnsOptionalString) + CHECK_EQ(toString(requireType("p")), "string?"); + else + CHECK_EQ(toString(requireType("p")), "string"); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") { CheckResult result = check(R"END( @@ -1063,12 +1077,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } } -TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2") +TEST_CASE_FIXTURE(Fixture, "gmatch_capture_types2") { CheckResult result = check(R"END( local a, b, c = ("This is a string"):gmatch("(.()(%a+))")() @@ -1076,9 +1099,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types2") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") @@ -1095,7 +1127,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture") CHECK_EQ(acm->expected, 1); CHECK_EQ(acm->actual, 4); - CHECK_EQ(toString(requireType("a")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + CHECK_EQ(toString(requireType("a")), "string?"); + else + CHECK_EQ(toString(requireType("a")), "string"); } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens") @@ -1112,9 +1147,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens CHECK_EQ(acm->expected, 3); CHECK_EQ(acm->actual, 4); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "string"); - CHECK_EQ(toString(requireType("c")), "number"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "string?"); + CHECK_EQ(toString(requireType("c")), "number?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "string"); + CHECK_EQ(toString(requireType("c")), "number"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored") @@ -1131,8 +1175,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_igno CHECK_EQ(acm->expected, 2); CHECK_EQ(acm->actual, 3); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket") @@ -1143,8 +1195,16 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "number"); - CHECK_EQ(toString(requireType("b")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "number?"); + CHECK_EQ(toString(requireType("b")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "number"); + CHECK_EQ(toString(requireType("b")), "string"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_leading_end_bracket_is_part_of_set") @@ -1192,9 +1252,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") @@ -1210,9 +1279,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2") CHECK_EQ(toString(tm->wantedType), "number?"); CHECK_EQ(toString(tm->givenType), "string"); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") @@ -1223,9 +1301,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } @@ -1243,9 +1330,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2") CHECK_EQ(toString(tm->wantedType), "number?"); CHECK_EQ(toString(tm->givenType), "string"); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } @@ -1263,9 +1359,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3") CHECK_EQ(toString(tm->wantedType), "boolean?"); CHECK_EQ(toString(tm->givenType), "string"); - CHECK_EQ(toString(requireType("a")), "string"); - CHECK_EQ(toString(requireType("b")), "number"); - CHECK_EQ(toString(requireType("c")), "string"); + if (FFlag::LuauMatchReturnsOptionalString) + { + CHECK_EQ(toString(requireType("a")), "string?"); + CHECK_EQ(toString(requireType("b")), "number?"); + CHECK_EQ(toString(requireType("c")), "string?"); + } + else + { + CHECK_EQ(toString(requireType("a")), "string"); + CHECK_EQ(toString(requireType("b")), "number"); + CHECK_EQ(toString(requireType("c")), "string"); + } CHECK_EQ(toString(requireType("d")), "number?"); CHECK_EQ(toString(requireType("e")), "number?"); } diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 28315b67..becc88aa 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -398,11 +398,6 @@ local a: ChildClass = i TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (BaseClass | Vector2) & (ChildClass | AnotherChild) local y : (ChildClass | AnotherChild) @@ -415,11 +410,6 @@ TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes") TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2) local y : (ChildClass | AnotherChild) @@ -482,8 +472,6 @@ caused by: TEST_CASE_FIXTURE(ClassFixture, "callable_classes") { - ScopedFastFlag luauCallableClasses{"LuauCallableClasses", true}; - CheckResult result = check(R"( local x : CallableClass local y = x("testing") diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 93b405c2..2a681d1a 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -396,8 +396,6 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props") TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") { - ScopedFastFlag LuauDeclareClassPrototype("LuauDeclareClassPrototype", true); - unfreeze(typeChecker.globalTypes); LoadDefinitionFileResult result = loadDefinitionFile(typeChecker, typeChecker.globalScope, R"( declare class Channel diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 70de13d1..de338fe1 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -1726,12 +1726,6 @@ foo(string.find("hello", "e")) TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - {"LuauOverloadedFunctionSubtypingPerf", true}, - }; - CheckResult result = check(R"( --!strict @@ -1834,8 +1828,6 @@ TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_must_follow_in_overload_resolution") { - ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true}; - CheckResult result = check(R"( for _ in function():(t0)&((()->())&(()->())) end do diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index b57d8820..e18a7378 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -463,11 +463,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : ((number?) -> number?) & ((string?) -> string?) local y : (nil) -> nil = x -- OK @@ -481,11 +476,6 @@ TEST_CASE_FIXTURE(Fixture, "intersect_saturate_overloaded_functions") TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) local y : ((number | string) -> (number | string)) = x -- OK @@ -499,11 +489,6 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : { p : number?, q : string? } & { p : number?, q : number?, r : number? } local y : { p : number?, q : nil, r : number? } = x -- OK @@ -531,8 +516,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") { ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, {"LuauUninhabitedSubAnything2", true}, }; @@ -547,11 +530,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number })) local y : (nil) -> { p : number, q : number, r : number} = x -- OK @@ -566,11 +544,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number?) -> (a | number)) & ((string?) -> (a | string)) @@ -586,11 +559,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((a?) -> (a | b)) & ((c?) -> (b | c)) @@ -606,11 +574,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...)) @@ -626,11 +589,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> unknown) @@ -646,11 +604,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((unknown) -> string?) @@ -666,11 +619,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number) -> number) & ((nil) -> never) @@ -686,11 +634,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : ((number) -> number?) & ((never) -> string?) @@ -779,11 +722,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local a : string? = nil local b : number? = nil @@ -807,11 +745,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x = setmetatable({ a = 5 }, { p = 5 }); local y = setmetatable({ b = "hi" }, { p = 5, q = "hi" }); @@ -833,11 +766,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_subtypes") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x = setmetatable({ a = 5 }, { p = 5 }); local y = setmetatable({ b = "hi" }, { q = "hi" }); @@ -856,11 +784,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables_with_properties") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x = setmetatable({ a = 5 }, { p = 5 }); local z = setmetatable({ a = 5, b = "hi" }, { p = 5 }); @@ -881,11 +804,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatable_with_table") TEST_CASE_FIXTURE(Fixture, "CLI-44817") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( type X = {x: number} type Y = {y: number} diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index fe52d168..ed3af11b 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -475,8 +475,6 @@ return l0 TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_anyify_variadic_return_must_follow") { - ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true}; - CheckResult result = check(R"( return unpack(l0[_]) )"); diff --git a/tests/TypeInfer.negations.test.cpp b/tests/TypeInfer.negations.test.cpp index 261314a6..adf03653 100644 --- a/tests/TypeInfer.negations.test.cpp +++ b/tests/TypeInfer.negations.test.cpp @@ -14,9 +14,6 @@ namespace struct NegationFixture : Fixture { TypeArena arena; - ScopedFastFlag sff[1]{ - {"LuauSubtypeNormalizer", true}, - }; NegationFixture() { diff --git a/tests/TypeInfer.primitives.test.cpp b/tests/TypeInfer.primitives.test.cpp index 7e99f0b0..02fdfa36 100644 --- a/tests/TypeInfer.primitives.test.cpp +++ b/tests/TypeInfer.primitives.test.cpp @@ -72,17 +72,6 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect") CHECK_EQ(*requireType("p"), *typeChecker.stringType); } -TEST_CASE_FIXTURE(Fixture, "string_function_other") -{ - CheckResult result = check(R"( - local s:string - local p = s:match("foo") - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireType("p")), "string"); -} - TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index f77cacfa..dced3f58 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -800,7 +800,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_ta LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}))); + ToStringOptions opts; + opts.exhaustive = true; + CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts)); CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28}))); } @@ -1436,6 +1438,32 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata") CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28}))); } +TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type_isnt_a_class") +{ + CheckResult result = check(R"( + local function f(x: string | number | Instance | Vector3) + if type(x) == "any" then + local foo = x + else + local foo = x + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::DebugLuauDeferredConstraintResolution) + { + CHECK_EQ("(Instance | Vector3 | number | string) & never", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("(Instance | Vector3 | number | string) & ~never", toString(requireTypeAtPosition({5, 28}))); + } + else + { + CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("*error-type*", toString(requireTypeAtPosition({5, 28}))); + } +} + TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance") { CheckResult result = check(R"( @@ -1721,8 +1749,6 @@ TEST_CASE_FIXTURE(Fixture, "else_with_no_explicit_expression_should_also_refine_ TEST_CASE_FIXTURE(Fixture, "fuzz_filtered_refined_types_are_followed") { - ScopedFastFlag luauTypeInferMissingFollows{"LuauTypeInferMissingFollows", true}; - CheckResult result = check(R"( local _ do diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index dc3b7ceb..01e1ead7 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -55,7 +55,10 @@ TEST_CASE_FIXTURE(Fixture, "augment_table") TEST_CASE_FIXTURE(Fixture, "augment_nested_table") { - CheckResult result = check("local t = { p = {} } t.p.foo = 'bar'"); + CheckResult result = check(R"( + local t = { p = {} } + t.p.foo = 'bar' + )"); LUAU_REQUIRE_NO_ERRORS(result); TableType* tType = getMutable(requireType("t")); @@ -70,19 +73,28 @@ TEST_CASE_FIXTURE(Fixture, "augment_nested_table") TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table") { - CheckResult result = check("function mkt() return {prop=999} end local t = mkt() t.foo = 'bar'"); + CheckResult result = check(R"( + function mkt() + return {prop=999} + end + + local t = mkt() + t.foo = 'bar' + )"); LUAU_REQUIRE_ERROR_COUNT(1, result); TypeError& err = result.errors[0]; + + CHECK(err.location == Location{Position{6, 8}, Position{6, 13}}); + CannotExtendTable* error = get(err); - REQUIRE(error != nullptr); + REQUIRE_MESSAGE(error != nullptr, "Expected CannotExtendTable but got: " << toString(err)); // TODO: better, more robust comparison of type vars auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true}); CHECK_EQ(s, "{| prop: number |}"); CHECK_EQ(error->prop, "foo"); CHECK_EQ(error->context, CannotExtendTable::Property); - CHECK_EQ(err.location, (Location{Position{0, 59}, Position{0, 64}})); } TEST_CASE_FIXTURE(Fixture, "dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index f4b84262..fcbe2b14 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -1029,10 +1029,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice") TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer") { ScopedFastInt sfi("LuauTypeInferRecursionLimit", 10); - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; CheckResult result = check(R"( function f() @@ -1048,10 +1044,6 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer") TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer") { ScopedFastInt sfi("LuauNormalizeCacheLimit", 10); - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; CheckResult result = check(R"( local x : ((number) -> number) & ((string) -> string) & ((nil) -> nil) & (({}) -> {}) @@ -1161,8 +1153,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "it_is_ok_to_have_inconsistent_number_of_retu TEST_CASE_FIXTURE(Fixture, "fuzz_free_table_type_change_during_index_check") { - ScopedFastFlag luauFollowInLvalueIndexCheck{"LuauFollowInLvalueIndexCheck", true}; - CheckResult result = check(R"( local _ = nil while _["" >= _] do diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 80c7ab57..8a55c5cf 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -112,11 +112,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "incompatible_tables_are_preserved") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f(arg : string & number) : never return arg @@ -127,11 +122,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_never") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f(arg : string & number) : boolean return arg @@ -143,8 +133,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_intersection_sub_anything") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never") { ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, {"LuauUninhabitedSubAnything2", true}, }; @@ -159,8 +147,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_never") TEST_CASE_FIXTURE(TryUnifyFixture, "uninhabited_table_sub_anything") { ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, {"LuauUninhabitedSubAnything2", true}, }; @@ -363,8 +349,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "metatables_unify_against_shape_of_free_table TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") { - ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true}; - TypePackVar variadicAny{VariadicTypePack{typeChecker.anyType}}; TypePackVar packTmp{TypePack{{typeChecker.anyType}, &variadicAny}}; TypePackVar packSub{TypePack{{typeChecker.anyType, typeChecker.anyType}, &packTmp}}; @@ -376,4 +360,18 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "fuzz_tail_unification_issue") state.tryUnify(&packSub, &packSuper); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_unify_any_should_check_log") +{ + ScopedFastFlag luauUnifyAnyTxnLog{"LuauUnifyAnyTxnLog", true}; + + CheckResult result = check(R"( +repeat +_._,_ = nil +until _ +local l0:(any)&(typeof(_)),l0:(any)|(any) = _,_ + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.cpp b/tests/TypeInfer.typePacks.cpp index 94448cfa..5486b969 100644 --- a/tests/TypeInfer.typePacks.cpp +++ b/tests/TypeInfer.typePacks.cpp @@ -1039,8 +1039,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generalize_expectedTypes_with_proper_scope") TEST_CASE_FIXTURE(Fixture, "fuzz_typepack_iter_follow") { - ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true}; - CheckResult result = check(R"( local _ local _ = _,_(),_(_) @@ -1051,8 +1049,6 @@ local _ = _,_(),_(_) TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2") { - ScopedFastFlag luauTxnLogTypePackIterator{"LuauTxnLogTypePackIterator", true}; - CheckResult result = check(R"( function test(name, searchTerm) local found = string.find(name:lower(), searchTerm:lower()) diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 8831bb2e..6f69d682 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -544,11 +544,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") TEST_CASE_FIXTURE(Fixture, "union_true_and_false") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : boolean local y1 : (true | false) = x -- OK @@ -562,11 +557,6 @@ TEST_CASE_FIXTURE(Fixture, "union_true_and_false") TEST_CASE_FIXTURE(Fixture, "union_of_functions") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number?) | ((number) -> number) = x -- OK @@ -599,11 +589,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_generic_typepack_functions") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : (a) -> a? @@ -619,11 +604,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( function f() local x : (number, a...) -> (number?, a...) @@ -639,11 +619,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (number) -> number? local y : ((number?) -> number) | ((number | string) -> nil) = x -- OK @@ -657,11 +632,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : () -> (number | string) local y : (() -> number) | (() -> string) = x -- OK @@ -675,11 +645,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (...nil) -> (...number?) local y : ((...string?) -> (...number)) | ((...number?) -> nil) = x -- OK @@ -693,11 +658,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : (number) -> () local y : ((number?) -> ()) | ((...number) -> ()) = x -- OK @@ -711,11 +671,6 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics") { - ScopedFastFlag sffs[]{ - {"LuauSubtypeNormalizer", true}, - {"LuauTypeNormalization2", true}, - }; - CheckResult result = check(R"( local x : () -> (number?, ...number) local y : (() -> (...number)) | (() -> nil) = x -- OK diff --git a/tests/TypeReduction.test.cpp b/tests/TypeReduction.test.cpp index f2d7b027..f1d5eae9 100644 --- a/tests/TypeReduction.test.cpp +++ b/tests/TypeReduction.test.cpp @@ -10,10 +10,13 @@ namespace { struct ReductionFixture : Fixture { + TypeReductionOptions typeReductionOpts{/* allowTypeReductionsFromOtherArenas */ true}; + ToStringOptions toStringOpts{true}; + TypeArena arena; InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - TypeReduction reduction{NotNull{&arena}, builtinTypes, NotNull{&iceHandler}}; + TypeReduction reduction{NotNull{&arena}, builtinTypes, NotNull{&iceHandler}, typeReductionOpts}; ReductionFixture() { @@ -28,18 +31,15 @@ struct ReductionFixture : Fixture return *reducedTy; } - std::optional tryReduce(const std::string& annotation) - { - CheckResult result = check("type _Res = " + annotation); - LUAU_REQUIRE_NO_ERRORS(result); - return reduction.reduce(requireTypeAlias("_Res")); - } - TypeId reductionof(const std::string& annotation) { - std::optional reducedTy = tryReduce(annotation); - REQUIRE_MESSAGE(reducedTy, "Exceeded the cartesian product of the type"); - return *reducedTy; + check("type _Res = " + annotation); + return reductionof(requireTypeAlias("_Res")); + } + + std::string toStringFull(TypeId ty) + { + return toString(ty, toStringOpts); } }; } // namespace @@ -50,42 +50,54 @@ TEST_CASE_FIXTURE(ReductionFixture, "cartesian_product_exceeded") { ScopedFastInt sfi{"LuauTypeReductionCartesianProductLimit", 5}; - std::optional ty = tryReduce(R"( - string & (number | string | boolean) & (number | string | boolean) + CheckResult result = check(R"( + type T + = string + & (number | string | boolean) + & (number | string | boolean) )"); - CHECK(!ty); + CHECK(!reduction.reduce(requireTypeAlias("T"))); + // LUAU_REQUIRE_ERROR_COUNT(1, result); + // CHECK("Code is too complex to typecheck! Consider simplifying the code around this area" == toString(result.errors[0])); } TEST_CASE_FIXTURE(ReductionFixture, "cartesian_product_exceeded_with_normal_limit") { - std::optional ty = tryReduce(R"( - string -- 1 = 1 - & (number | string | boolean) -- 1 * 3 = 3 - & (number | string | boolean) -- 3 * 3 = 9 - & (number | string | boolean) -- 9 * 3 = 27 - & (number | string | boolean) -- 27 * 3 = 81 - & (number | string | boolean) -- 81 * 3 = 243 - & (number | string | boolean) -- 243 * 3 = 729 - & (number | string | boolean) -- 729 * 3 = 2187 - & (number | string | boolean) -- 2187 * 3 = 6561 - & (number | string | boolean) -- 6561 * 3 = 19683 - & (number | string | boolean) -- 19683 * 3 = 59049 - & (number | string) -- 59049 * 2 = 118098 + CheckResult result = check(R"( + type T + = string -- 1 = 1 + & (number | string | boolean) -- 1 * 3 = 3 + & (number | string | boolean) -- 3 * 3 = 9 + & (number | string | boolean) -- 9 * 3 = 27 + & (number | string | boolean) -- 27 * 3 = 81 + & (number | string | boolean) -- 81 * 3 = 243 + & (number | string | boolean) -- 243 * 3 = 729 + & (number | string | boolean) -- 729 * 3 = 2187 + & (number | string | boolean) -- 2187 * 3 = 6561 + & (number | string | boolean) -- 6561 * 3 = 19683 + & (number | string | boolean) -- 19683 * 3 = 59049 + & (number | string) -- 59049 * 2 = 118098 )"); - CHECK(!ty); + CHECK(!reduction.reduce(requireTypeAlias("T"))); + // LUAU_REQUIRE_ERROR_COUNT(1, result); + // CHECK("Code is too complex to typecheck! Consider simplifying the code around this area" == toString(result.errors[0])); } TEST_CASE_FIXTURE(ReductionFixture, "cartesian_product_is_zero") { ScopedFastInt sfi{"LuauTypeReductionCartesianProductLimit", 5}; - std::optional ty = tryReduce(R"( - string & (number | string | boolean) & (number | string | boolean) & never + CheckResult result = check(R"( + type T + = string + & (number | string | boolean) + & (number | string | boolean) + & never )"); - CHECK(ty); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(ReductionFixture, "stress_test_recursion_limits") @@ -115,13 +127,10 @@ TEST_CASE_FIXTURE(ReductionFixture, "caching") TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}}); - ToStringOptions opts; - opts.exhaustive = true; - - CHECK("{- x: string -} & {| |}" == toString(reductionof(intersectionTy))); + CHECK("{- x: string -} & {| |}" == toStringFull(reductionof(intersectionTy))); getMutable(ty1)->state = TableState::Sealed; - CHECK("{| x: string |}" == toString(reductionof(intersectionTy))); + CHECK("{| x: string |}" == toStringFull(reductionof(intersectionTy))); } SUBCASE("unsealed_tables") @@ -135,13 +144,10 @@ TEST_CASE_FIXTURE(ReductionFixture, "caching") TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}}); - ToStringOptions opts; - opts.exhaustive = true; - - CHECK("{| x: string |}" == toString(reductionof(intersectionTy))); + CHECK("{| x: string |}" == toStringFull(reductionof(intersectionTy))); getMutable(ty1)->state = TableState::Sealed; - CHECK("{| x: string |}" == toString(reductionof(intersectionTy))); + CHECK("{| x: string |}" == toStringFull(reductionof(intersectionTy))); } SUBCASE("free_types") @@ -152,13 +158,10 @@ TEST_CASE_FIXTURE(ReductionFixture, "caching") TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}}); - ToStringOptions opts; - opts.exhaustive = true; - - CHECK("a & {| |}" == toString(reductionof(intersectionTy))); + CHECK("a & {| |}" == toStringFull(reductionof(intersectionTy))); *asMutable(ty1) = BoundType{ty2}; - CHECK("{| |}" == toString(reductionof(intersectionTy))); + CHECK("{| |}" == toStringFull(reductionof(intersectionTy))); } SUBCASE("we_can_see_that_the_cache_works_if_we_mutate_a_normally_not_mutated_type") @@ -168,13 +171,42 @@ TEST_CASE_FIXTURE(ReductionFixture, "caching") TypeId intersectionTy = arena.addType(IntersectionType{{ty1, ty2}}); - ToStringOptions opts; - opts.exhaustive = true; - - CHECK("never" == toString(reductionof(intersectionTy))); // Bound & number ~ never + CHECK("never" == toStringFull(reductionof(intersectionTy))); // Bound & number ~ never *asMutable(ty1) = BoundType{ty2}; - CHECK("never" == toString(reductionof(intersectionTy))); // Bound & number ~ number, but the cache is `never`. + CHECK("never" == toStringFull(reductionof(intersectionTy))); // Bound & number ~ number, but the cache is `never`. + } + + SUBCASE("ptr_eq_irreducible_unions") + { + TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, builtinTypes->numberType}}); + TypeId reducedTy = reductionof(unionTy); + REQUIRE(unionTy == reducedTy); + } + + SUBCASE("ptr_eq_irreducible_intersections") + { + TypeId intersectionTy = arena.addType(IntersectionType{{builtinTypes->stringType, arena.addType(GenericType{"G"})}}); + TypeId reducedTy = reductionof(intersectionTy); + REQUIRE(intersectionTy == reducedTy); + } + + SUBCASE("ptr_eq_free_table") + { + TypeId tableTy = arena.addType(TableType{}); + getMutable(tableTy)->state = TableState::Free; + + TypeId reducedTy = reductionof(tableTy); + REQUIRE(tableTy == reducedTy); + } + + SUBCASE("ptr_eq_unsealed_table") + { + TypeId tableTy = arena.addType(TableType{}); + getMutable(tableTy)->state = TableState::Unsealed; + + TypeId reducedTy = reductionof(tableTy); + REQUIRE(tableTy == reducedTy); } } // caching @@ -183,169 +215,169 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations") SUBCASE("string_and_string") { TypeId ty = reductionof("string & string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("never_and_string") { TypeId ty = reductionof("never & string"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("string_and_never") { TypeId ty = reductionof("string & never"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("unknown_and_string") { TypeId ty = reductionof("unknown & string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_and_unknown") { TypeId ty = reductionof("string & unknown"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("any_and_string") { TypeId ty = reductionof("any & string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_and_any") { TypeId ty = reductionof("string & any"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_number_and_string") { TypeId ty = reductionof("(string | number) & string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_and_string_or_number") { TypeId ty = reductionof("string & (string | number)"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_and_a") { TypeId ty = reductionof(R"(string & "a")"); - CHECK(R"("a")" == toString(ty)); + CHECK(R"("a")" == toStringFull(ty)); } SUBCASE("boolean_and_true") { TypeId ty = reductionof("boolean & true"); - CHECK("true" == toString(ty)); + CHECK("true" == toStringFull(ty)); } SUBCASE("boolean_and_a") { TypeId ty = reductionof(R"(boolean & "a")"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("a_and_a") { TypeId ty = reductionof(R"("a" & "a")"); - CHECK(R"("a")" == toString(ty)); + CHECK(R"("a")" == toStringFull(ty)); } SUBCASE("a_and_b") { TypeId ty = reductionof(R"("a" & "b")"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("a_and_true") { TypeId ty = reductionof(R"("a" & true)"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("a_and_true") { TypeId ty = reductionof(R"(true & false)"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("function_type_and_function") { TypeId ty = reductionof("() -> () & fun"); - CHECK("() -> ()" == toString(ty)); + CHECK("() -> ()" == toStringFull(ty)); } SUBCASE("function_type_and_string") { TypeId ty = reductionof("() -> () & string"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("parent_and_child") { TypeId ty = reductionof("Parent & Child"); - CHECK("Child" == toString(ty)); + CHECK("Child" == toStringFull(ty)); } SUBCASE("child_and_parent") { TypeId ty = reductionof("Child & Parent"); - CHECK("Child" == toString(ty)); + CHECK("Child" == toStringFull(ty)); } SUBCASE("child_and_unrelated") { TypeId ty = reductionof("Child & Unrelated"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("string_and_table") { TypeId ty = reductionof("string & {}"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("string_and_child") { TypeId ty = reductionof("string & Child"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("string_and_function") { TypeId ty = reductionof("string & () -> ()"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("function_and_table") { TypeId ty = reductionof("() -> () & {}"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("function_and_class") { TypeId ty = reductionof("() -> () & Child"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("function_and_function") { TypeId ty = reductionof("() -> () & () -> ()"); - CHECK("(() -> ()) & (() -> ())" == toString(ty)); + CHECK("(() -> ()) & (() -> ())" == toStringFull(ty)); } SUBCASE("table_and_table") { TypeId ty = reductionof("{} & {}"); - CHECK("{| |}" == toString(ty)); + CHECK("{| |}" == toStringFull(ty)); } SUBCASE("table_and_metatable") @@ -357,125 +389,137 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_without_negations") )"); TypeId ty = reductionof(fixture.requireTypeAlias("Ty")); - CHECK("{ @metatable { }, { } } & {| |}" == toString(ty)); + CHECK("{ @metatable { }, { } } & {| |}" == toStringFull(ty)); } SUBCASE("a_and_string") { TypeId ty = reductionof(R"("a" & string)"); - CHECK(R"("a")" == toString(ty)); + CHECK(R"("a")" == toStringFull(ty)); } SUBCASE("reducible_function_and_function") { TypeId ty = reductionof("((string | string) -> (number | number)) & fun"); - CHECK("(string) -> number" == toString(ty)); + CHECK("(string) -> number" == toStringFull(ty)); } SUBCASE("string_and_error") { TypeId ty = reductionof("string & err"); - CHECK("*error-type* & string" == toString(ty)); + CHECK("*error-type* & string" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_number") { TypeId ty = reductionof("{ p: string } & { p: number }"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_string") { TypeId ty = reductionof("{ p: string } & { p: string }"); - CHECK("{| p: string |}" == toString(ty)); + CHECK("{| p: string |}" == toStringFull(ty)); } SUBCASE("table_x_table_p_string_and_table_x_table_p_number") { TypeId ty = reductionof("{ x: { p: string } } & { x: { p: number } }"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("table_p_and_table_q") { TypeId ty = reductionof("{ p: string } & { q: number }"); - CHECK("{| p: string, q: number |}" == toString(ty)); + CHECK("{| p: string, q: number |}" == toStringFull(ty)); } SUBCASE("table_tag_a_or_table_tag_b_and_table_b") { TypeId ty = reductionof("({ tag: string, a: number } | { tag: number, b: string }) & { b: string }"); - CHECK("{| a: number, b: string, tag: string |} | {| b: string, tag: number |}" == toString(ty)); + CHECK("{| a: number, b: string, tag: string |} | {| b: string, tag: number |}" == toStringFull(ty)); } SUBCASE("table_string_number_indexer_and_table_string_number_indexer") { TypeId ty = reductionof("{ [string]: number } & { [string]: number }"); - CHECK("{| [string]: number |}" == toString(ty)); + CHECK("{| [string]: number |}" == toStringFull(ty)); } SUBCASE("table_string_number_indexer_and_empty_table") { TypeId ty = reductionof("{ [string]: number } & {}"); - CHECK("{| [string]: number |}" == toString(ty)); + CHECK("{| [string]: number |}" == toStringFull(ty)); } SUBCASE("empty_table_table_string_number_indexer") { TypeId ty = reductionof("{} & { [string]: number }"); - CHECK("{| [string]: number |}" == toString(ty)); + CHECK("{| [string]: number |}" == toStringFull(ty)); } SUBCASE("string_number_indexer_and_number_number_indexer") { TypeId ty = reductionof("{ [string]: number } & { [number]: number }"); - CHECK("never" == toString(ty)); + CHECK("{number} & {| [string]: number |}" == toStringFull(ty)); } SUBCASE("table_p_string_and_indexer_number_number") { TypeId ty = reductionof("{ p: string } & { [number]: number }"); - CHECK("{| [number]: number, p: string |}" == toString(ty)); + CHECK("{| [number]: number, p: string |}" == toStringFull(ty)); } SUBCASE("table_p_string_and_indexer_string_number") { TypeId ty = reductionof("{ p: string } & { [string]: number }"); - CHECK("{| [string]: number, p: string |}" == toString(ty)); + CHECK("{| [string]: number, p: string |}" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_string_plus_indexer_string_number") { TypeId ty = reductionof("{ p: string } & { p: string, [string]: number }"); - CHECK("{| [string]: number, p: string |}" == toString(ty)); + CHECK("{| [string]: number, p: string |}" == toStringFull(ty)); } SUBCASE("fresh_type_and_string") { TypeId freshTy = arena.freshType(nullptr); TypeId ty = reductionof(arena.addType(IntersectionType{{freshTy, builtinTypes->stringType}})); - CHECK("a & string" == toString(ty)); + CHECK("a & string" == toStringFull(ty)); } SUBCASE("string_and_fresh_type") { TypeId freshTy = arena.freshType(nullptr); TypeId ty = reductionof(arena.addType(IntersectionType{{builtinTypes->stringType, freshTy}})); - CHECK("a & string" == toString(ty)); + CHECK("a & string" == toStringFull(ty)); } SUBCASE("generic_and_string") { TypeId genericTy = arena.addType(GenericType{"G"}); TypeId ty = reductionof(arena.addType(IntersectionType{{genericTy, builtinTypes->stringType}})); - CHECK("G & string" == toString(ty)); + CHECK("G & string" == toStringFull(ty)); } SUBCASE("string_and_generic") { TypeId genericTy = arena.addType(GenericType{"G"}); TypeId ty = reductionof(arena.addType(IntersectionType{{builtinTypes->stringType, genericTy}})); - CHECK("G & string" == toString(ty)); + CHECK("G & string" == toStringFull(ty)); + } + + SUBCASE("parent_and_child_or_parent_and_anotherchild_or_parent_and_unrelated") + { + TypeId ty = reductionof("Parent & (Child | AnotherChild | Unrelated)"); + CHECK("AnotherChild | Child" == toString(ty)); + } + + SUBCASE("parent_and_child_or_parent_and_anotherchild_or_parent_and_unrelated_2") + { + TypeId ty = reductionof("(Parent & Child) | (Parent & AnotherChild) | (Parent & Unrelated)"); + CHECK("AnotherChild | Child" == toString(ty)); } } // intersections_without_negations @@ -484,163 +528,163 @@ TEST_CASE_FIXTURE(ReductionFixture, "intersections_with_negations") SUBCASE("nil_and_not_nil") { TypeId ty = reductionof("nil & Not"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("nil_and_not_false") { TypeId ty = reductionof("nil & Not"); - CHECK("nil" == toString(ty)); + CHECK("nil" == toStringFull(ty)); } SUBCASE("string_or_nil_and_not_nil") { TypeId ty = reductionof("(string?) & Not"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_nil_and_not_false_or_nil") { TypeId ty = reductionof("(string?) & Not"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_nil_and_not_false_and_not_nil") { TypeId ty = reductionof("(string?) & Not & Not"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("not_false_and_bool") { TypeId ty = reductionof("Not & boolean"); - CHECK("true" == toString(ty)); + CHECK("true" == toStringFull(ty)); } SUBCASE("function_type_and_not_function") { TypeId ty = reductionof("() -> () & Not"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("function_type_and_not_string") { TypeId ty = reductionof("() -> () & Not"); - CHECK("() -> ()" == toString(ty)); + CHECK("() -> ()" == toStringFull(ty)); } SUBCASE("not_a_and_string_or_nil") { TypeId ty = reductionof(R"(Not<"a"> & (string | nil))"); - CHECK(R"((string & ~"a")?)" == toString(ty)); + CHECK(R"((string & ~"a")?)" == toStringFull(ty)); } SUBCASE("not_a_and_a") { TypeId ty = reductionof(R"(Not<"a"> & "a")"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_a_and_b") { TypeId ty = reductionof(R"(Not<"a"> & "b")"); - CHECK(R"("b")" == toString(ty)); + CHECK(R"("b")" == toStringFull(ty)); } SUBCASE("not_string_and_a") { TypeId ty = reductionof(R"(Not & "a")"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_bool_and_true") { TypeId ty = reductionof("Not & true"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_string_and_true") { TypeId ty = reductionof("Not & true"); - CHECK("true" == toString(ty)); + CHECK("true" == toStringFull(ty)); } SUBCASE("parent_and_not_child") { TypeId ty = reductionof("Parent & Not"); - CHECK("Parent & ~Child" == toString(ty)); + CHECK("Parent & ~Child" == toStringFull(ty)); } SUBCASE("not_child_and_parent") { TypeId ty = reductionof("Not & Parent"); - CHECK("Parent & ~Child" == toString(ty)); + CHECK("Parent & ~Child" == toStringFull(ty)); } SUBCASE("child_and_not_parent") { TypeId ty = reductionof("Child & Not"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_parent_and_child") { TypeId ty = reductionof("Not & Child"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_parent_and_unrelated") { TypeId ty = reductionof("Not & Unrelated"); - CHECK("Unrelated" == toString(ty)); + CHECK("Unrelated" == toStringFull(ty)); } SUBCASE("unrelated_and_not_parent") { TypeId ty = reductionof("Unrelated & Not"); - CHECK("Unrelated" == toString(ty)); + CHECK("Unrelated" == toStringFull(ty)); } SUBCASE("not_unrelated_and_parent") { TypeId ty = reductionof("Not & Parent"); - CHECK("Parent" == toString(ty)); + CHECK("Parent" == toStringFull(ty)); } SUBCASE("parent_and_not_unrelated") { TypeId ty = reductionof("Parent & Not"); - CHECK("Parent" == toString(ty)); + CHECK("Parent" == toStringFull(ty)); } SUBCASE("reducible_function_and_not_function") { TypeId ty = reductionof("((string | string) -> (number | number)) & Not"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("string_and_not_error") { TypeId ty = reductionof("string & Not"); - CHECK("string & ~*error-type*" == toString(ty)); + CHECK("string & ~*error-type*" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_not_number") { TypeId ty = reductionof("{ p: string } & { p: Not }"); - CHECK("{| p: string |}" == toString(ty)); + CHECK("{| p: string |}" == toStringFull(ty)); } SUBCASE("table_p_string_and_table_p_not_string") { TypeId ty = reductionof("{ p: string } & { p: Not }"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("table_x_table_p_string_and_table_x_table_p_not_number") { TypeId ty = reductionof("{ x: { p: string } } & { x: { p: Not } }"); - CHECK("{| x: {| p: string |} |}" == toString(ty)); + CHECK("{| x: {| p: string |} |}" == toStringFull(ty)); } } // intersections_with_negations @@ -649,223 +693,223 @@ TEST_CASE_FIXTURE(ReductionFixture, "unions_without_negations") SUBCASE("never_or_string") { TypeId ty = reductionof("never | string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_never") { TypeId ty = reductionof("string | never"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("unknown_or_string") { TypeId ty = reductionof("unknown | string"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("string_or_unknown") { TypeId ty = reductionof("string | unknown"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("any_or_string") { TypeId ty = reductionof("any | string"); - CHECK("any" == toString(ty)); + CHECK("any" == toStringFull(ty)); } SUBCASE("string_or_any") { TypeId ty = reductionof("string | any"); - CHECK("any" == toString(ty)); + CHECK("any" == toStringFull(ty)); } SUBCASE("string_or_string_and_number") { TypeId ty = reductionof("string | (string & number)"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_string") { TypeId ty = reductionof("string | string"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("string_or_number") { TypeId ty = reductionof("string | number"); - CHECK("number | string" == toString(ty)); + CHECK("number | string" == toStringFull(ty)); } SUBCASE("number_or_string") { TypeId ty = reductionof("number | string"); - CHECK("number | string" == toString(ty)); + CHECK("number | string" == toStringFull(ty)); } SUBCASE("string_or_number_or_string") { TypeId ty = reductionof("(string | number) | string"); - CHECK("number | string" == toString(ty)); + CHECK("number | string" == toStringFull(ty)); } SUBCASE("string_or_number_or_string_2") { TypeId ty = reductionof("string | (number | string)"); - CHECK("number | string" == toString(ty)); + CHECK("number | string" == toStringFull(ty)); } SUBCASE("string_or_string_or_number") { TypeId ty = reductionof("string | (string | number)"); - CHECK("number | string" == toString(ty)); + CHECK("number | string" == toStringFull(ty)); } SUBCASE("string_or_string_or_number_or_boolean") { TypeId ty = reductionof("string | (string | number | boolean)"); - CHECK("boolean | number | string" == toString(ty)); + CHECK("boolean | number | string" == toStringFull(ty)); } SUBCASE("string_or_string_or_boolean_or_number") { TypeId ty = reductionof("string | (string | boolean | number)"); - CHECK("boolean | number | string" == toString(ty)); + CHECK("boolean | number | string" == toStringFull(ty)); } SUBCASE("string_or_boolean_or_string_or_number") { TypeId ty = reductionof("string | (boolean | string | number)"); - CHECK("boolean | number | string" == toString(ty)); + CHECK("boolean | number | string" == toStringFull(ty)); } SUBCASE("boolean_or_string_or_number_or_string") { TypeId ty = reductionof("(boolean | string | number) | string"); - CHECK("boolean | number | string" == toString(ty)); + CHECK("boolean | number | string" == toStringFull(ty)); } SUBCASE("boolean_or_true") { TypeId ty = reductionof("boolean | true"); - CHECK("boolean" == toString(ty)); + CHECK("boolean" == toStringFull(ty)); } SUBCASE("boolean_or_false") { TypeId ty = reductionof("boolean | false"); - CHECK("boolean" == toString(ty)); + CHECK("boolean" == toStringFull(ty)); } SUBCASE("boolean_or_true_or_false") { TypeId ty = reductionof("boolean | true | false"); - CHECK("boolean" == toString(ty)); + CHECK("boolean" == toStringFull(ty)); } SUBCASE("string_or_a") { TypeId ty = reductionof(R"(string | "a")"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("a_or_a") { TypeId ty = reductionof(R"("a" | "a")"); - CHECK(R"("a")" == toString(ty)); + CHECK(R"("a")" == toStringFull(ty)); } SUBCASE("a_or_b") { TypeId ty = reductionof(R"("a" | "b")"); - CHECK(R"("a" | "b")" == toString(ty)); + CHECK(R"("a" | "b")" == toStringFull(ty)); } SUBCASE("a_or_b_or_string") { TypeId ty = reductionof(R"("a" | "b" | string)"); - CHECK("string" == toString(ty)); + CHECK("string" == toStringFull(ty)); } SUBCASE("unknown_or_any") { TypeId ty = reductionof("unknown | any"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("any_or_unknown") { TypeId ty = reductionof("any | unknown"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("function_type_or_function") { TypeId ty = reductionof("() -> () | fun"); - CHECK("function" == toString(ty)); + CHECK("function" == toStringFull(ty)); } SUBCASE("function_or_string") { TypeId ty = reductionof("fun | string"); - CHECK("function | string" == toString(ty)); + CHECK("function | string" == toStringFull(ty)); } SUBCASE("parent_or_child") { TypeId ty = reductionof("Parent | Child"); - CHECK("Parent" == toString(ty)); + CHECK("Parent" == toStringFull(ty)); } SUBCASE("child_or_parent") { TypeId ty = reductionof("Child | Parent"); - CHECK("Parent" == toString(ty)); + CHECK("Parent" == toStringFull(ty)); } SUBCASE("parent_or_unrelated") { TypeId ty = reductionof("Parent | Unrelated"); - CHECK("Parent | Unrelated" == toString(ty)); + CHECK("Parent | Unrelated" == toStringFull(ty)); } SUBCASE("parent_or_child_or_unrelated") { TypeId ty = reductionof("Parent | Child | Unrelated"); - CHECK("Parent | Unrelated" == toString(ty)); + CHECK("Parent | Unrelated" == toStringFull(ty)); } SUBCASE("parent_or_unrelated_or_child") { TypeId ty = reductionof("Parent | Unrelated | Child"); - CHECK("Parent | Unrelated" == toString(ty)); + CHECK("Parent | Unrelated" == toStringFull(ty)); } SUBCASE("parent_or_child_or_unrelated_or_child") { TypeId ty = reductionof("Parent | Child | Unrelated | Child"); - CHECK("Parent | Unrelated" == toString(ty)); + CHECK("Parent | Unrelated" == toStringFull(ty)); } SUBCASE("string_or_true") { TypeId ty = reductionof("string | true"); - CHECK("string | true" == toString(ty)); + CHECK("string | true" == toStringFull(ty)); } SUBCASE("string_or_function") { TypeId ty = reductionof("string | () -> ()"); - CHECK("(() -> ()) | string" == toString(ty)); + CHECK("(() -> ()) | string" == toStringFull(ty)); } SUBCASE("string_or_err") { TypeId ty = reductionof("string | err"); - CHECK("*error-type* | string" == toString(ty)); + CHECK("*error-type* | string" == toStringFull(ty)); } } // unions_without_negations @@ -874,211 +918,211 @@ TEST_CASE_FIXTURE(ReductionFixture, "unions_with_negations") SUBCASE("string_or_not_string") { TypeId ty = reductionof("string | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_string_or_string") { TypeId ty = reductionof("Not | string"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_number_or_string") { TypeId ty = reductionof("Not | string"); - CHECK("~number" == toString(ty)); + CHECK("~number" == toStringFull(ty)); } SUBCASE("string_or_not_number") { TypeId ty = reductionof("string | Not"); - CHECK("~number" == toString(ty)); + CHECK("~number" == toStringFull(ty)); } SUBCASE("not_hi_or_string_and_not_hi") { TypeId ty = reductionof(R"(Not<"hi"> | (string & Not<"hi">))"); - CHECK(R"(~"hi")" == toString(ty)); + CHECK(R"(~"hi")" == toStringFull(ty)); } SUBCASE("string_and_not_hi_or_not_hi") { TypeId ty = reductionof(R"((string & Not<"hi">) | Not<"hi">)"); - CHECK(R"(~"hi")" == toString(ty)); + CHECK(R"(~"hi")" == toStringFull(ty)); } SUBCASE("string_or_not_never") { TypeId ty = reductionof("string | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_a_or_not_a") { TypeId ty = reductionof(R"(Not<"a"> | Not<"a">)"); - CHECK(R"(~"a")" == toString(ty)); + CHECK(R"(~"a")" == toStringFull(ty)); } SUBCASE("not_a_or_a") { TypeId ty = reductionof(R"(Not<"a"> | "a")"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("a_or_not_a") { TypeId ty = reductionof(R"("a" | Not<"a">)"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_a_or_string") { TypeId ty = reductionof(R"(Not<"a"> | string)"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("string_or_not_a") { TypeId ty = reductionof(R"(string | Not<"a">)"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_string_or_a") { TypeId ty = reductionof(R"(Not | "a")"); - CHECK(R"("a" | ~string)" == toString(ty)); + CHECK(R"("a" | ~string)" == toStringFull(ty)); } SUBCASE("a_or_not_string") { TypeId ty = reductionof(R"("a" | Not)"); - CHECK(R"("a" | ~string)" == toString(ty)); + CHECK(R"("a" | ~string)" == toStringFull(ty)); } SUBCASE("not_number_or_a") { TypeId ty = reductionof(R"(Not | "a")"); - CHECK("~number" == toString(ty)); + CHECK("~number" == toStringFull(ty)); } SUBCASE("a_or_not_number") { TypeId ty = reductionof(R"("a" | Not)"); - CHECK("~number" == toString(ty)); + CHECK("~number" == toStringFull(ty)); } SUBCASE("not_a_or_not_b") { TypeId ty = reductionof(R"(Not<"a"> | Not<"b">)"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("boolean_or_not_false") { TypeId ty = reductionof("boolean | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("boolean_or_not_true") { TypeId ty = reductionof("boolean | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("false_or_not_false") { TypeId ty = reductionof("false | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("true_or_not_false") { TypeId ty = reductionof("true | Not"); - CHECK("~false" == toString(ty)); + CHECK("~false" == toStringFull(ty)); } SUBCASE("not_boolean_or_true") { TypeId ty = reductionof("Not | true"); - CHECK("~false" == toString(ty)); + CHECK("~false" == toStringFull(ty)); } SUBCASE("not_false_or_not_boolean") { TypeId ty = reductionof("Not | Not"); - CHECK("~false" == toString(ty)); + CHECK("~false" == toStringFull(ty)); } SUBCASE("function_type_or_not_function") { TypeId ty = reductionof("() -> () | Not"); - CHECK("(() -> ()) | ~function" == toString(ty)); + CHECK("(() -> ()) | ~function" == toStringFull(ty)); } SUBCASE("not_parent_or_child") { TypeId ty = reductionof("Not | Child"); - CHECK("Child | ~Parent" == toString(ty)); + CHECK("Child | ~Parent" == toStringFull(ty)); } SUBCASE("child_or_not_parent") { TypeId ty = reductionof("Child | Not"); - CHECK("Child | ~Parent" == toString(ty)); + CHECK("Child | ~Parent" == toStringFull(ty)); } SUBCASE("parent_or_not_child") { TypeId ty = reductionof("Parent | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_child_or_parent") { TypeId ty = reductionof("Not | Parent"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("parent_or_not_unrelated") { TypeId ty = reductionof("Parent | Not"); - CHECK("~Unrelated" == toString(ty)); + CHECK("~Unrelated" == toStringFull(ty)); } SUBCASE("not_string_or_string_and_not_a") { TypeId ty = reductionof(R"(Not | (string & Not<"a">))"); - CHECK(R"(~"a")" == toString(ty)); + CHECK(R"(~"a")" == toStringFull(ty)); } SUBCASE("not_string_or_not_string") { TypeId ty = reductionof("Not | Not"); - CHECK("~string" == toString(ty)); + CHECK("~string" == toStringFull(ty)); } SUBCASE("not_string_or_not_number") { TypeId ty = reductionof("Not | Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_a_or_not_boolean") { TypeId ty = reductionof(R"(Not<"a"> | Not)"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_a_or_boolean") { TypeId ty = reductionof(R"(Not<"a"> | boolean)"); - CHECK(R"(~"a")" == toString(ty)); + CHECK(R"(~"a")" == toStringFull(ty)); } SUBCASE("string_or_err") { TypeId ty = reductionof("string | Not"); - CHECK("string | ~*error-type*" == toString(ty)); + CHECK("string | ~*error-type*" == toStringFull(ty)); } } // unions_with_negations @@ -1086,20 +1130,14 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables") { SUBCASE("reduce_props") { - ToStringOptions opts; - opts.exhaustive = true; - TypeId ty = reductionof("{ x: string | string, y: number | number }"); - CHECK("{| x: string, y: number |}" == toString(ty, opts)); + CHECK("{| x: string, y: number |}" == toStringFull(ty)); } SUBCASE("reduce_indexers") { - ToStringOptions opts; - opts.exhaustive = true; - TypeId ty = reductionof("{ [string | string]: number | number }"); - CHECK("{| [string]: number |}" == toString(ty, opts)); + CHECK("{| [string]: number |}" == toStringFull(ty)); } SUBCASE("reduce_instantiated_type_parameters") @@ -1126,11 +1164,8 @@ TEST_CASE_FIXTURE(ReductionFixture, "tables") SUBCASE("reduce_tables_within_tables") { - ToStringOptions opts; - opts.exhaustive = true; - TypeId ty = reductionof("{ x: { y: string & number } }"); - CHECK("{| x: {| y: never |} |}" == toString(ty, opts)); + CHECK("never" == toStringFull(ty)); } } @@ -1139,21 +1174,23 @@ TEST_CASE_FIXTURE(ReductionFixture, "metatables") SUBCASE("reduce_table_part") { TableType table; + table.state = TableState::Sealed; table.props["x"] = {arena.addType(UnionType{{builtinTypes->stringType, builtinTypes->stringType}})}; TypeId tableTy = arena.addType(std::move(table)); TypeId ty = reductionof(arena.addType(MetatableType{tableTy, arena.addType(TableType{})})); - CHECK("{ @metatable { }, { x: string } }" == toString(ty)); + CHECK("{ @metatable { }, {| x: string |} }" == toStringFull(ty)); } SUBCASE("reduce_metatable_part") { TableType table; + table.state = TableState::Sealed; table.props["x"] = {arena.addType(UnionType{{builtinTypes->stringType, builtinTypes->stringType}})}; TypeId tableTy = arena.addType(std::move(table)); TypeId ty = reductionof(arena.addType(MetatableType{arena.addType(TableType{}), tableTy})); - CHECK("{ @metatable { x: string }, { } }" == toString(ty)); + CHECK("{ @metatable {| x: string |}, { } }" == toStringFull(ty)); } } @@ -1162,37 +1199,37 @@ TEST_CASE_FIXTURE(ReductionFixture, "functions") SUBCASE("reduce_parameters") { TypeId ty = reductionof("(string | string) -> ()"); - CHECK("(string) -> ()" == toString(ty)); + CHECK("(string) -> ()" == toStringFull(ty)); } SUBCASE("reduce_returns") { TypeId ty = reductionof("() -> (string | string)"); - CHECK("() -> string" == toString(ty)); + CHECK("() -> string" == toStringFull(ty)); } SUBCASE("reduce_parameters_and_returns") { TypeId ty = reductionof("(string | string) -> (number | number)"); - CHECK("(string) -> number" == toString(ty)); + CHECK("(string) -> number" == toStringFull(ty)); } SUBCASE("reduce_tail") { TypeId ty = reductionof("() -> ...(string | string)"); - CHECK("() -> (...string)" == toString(ty)); + CHECK("() -> (...string)" == toStringFull(ty)); } SUBCASE("reduce_head_and_tail") { TypeId ty = reductionof("() -> (string | string, number | number, ...(boolean | boolean))"); - CHECK("() -> (string, number, ...boolean)" == toString(ty)); + CHECK("() -> (string, number, ...boolean)" == toStringFull(ty)); } SUBCASE("reduce_overloaded_functions") { TypeId ty = reductionof("((number | number) -> ()) & ((string | string) -> ())"); - CHECK("((number) -> ()) & ((string) -> ())" == toString(ty)); + CHECK("((number) -> ()) & ((string) -> ())" == toStringFull(ty)); } } // functions @@ -1201,49 +1238,49 @@ TEST_CASE_FIXTURE(ReductionFixture, "negations") SUBCASE("not_unknown") { TypeId ty = reductionof("Not"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_never") { TypeId ty = reductionof("Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_any") { TypeId ty = reductionof("Not"); - CHECK("any" == toString(ty)); + CHECK("any" == toStringFull(ty)); } SUBCASE("not_not_reduction") { TypeId ty = reductionof("Not>"); - CHECK("never" == toString(ty)); + CHECK("never" == toStringFull(ty)); } SUBCASE("not_string") { TypeId ty = reductionof("Not"); - CHECK("~string" == toString(ty)); + CHECK("~string" == toStringFull(ty)); } SUBCASE("not_string_or_number") { TypeId ty = reductionof("Not"); - CHECK("~number & ~string" == toString(ty)); + CHECK("~number & ~string" == toStringFull(ty)); } SUBCASE("not_string_and_number") { TypeId ty = reductionof("Not"); - CHECK("unknown" == toString(ty)); + CHECK("unknown" == toStringFull(ty)); } SUBCASE("not_error") { TypeId ty = reductionof("Not"); - CHECK("~*error-type*" == toString(ty)); + CHECK("~*error-type*" == toStringFull(ty)); } } // negations @@ -1252,37 +1289,37 @@ TEST_CASE_FIXTURE(ReductionFixture, "discriminable_unions") SUBCASE("cat_or_dog_and_dog") { TypeId ty = reductionof(R"(({ tag: "cat", catfood: string } | { tag: "dog", dogfood: string }) & { tag: "dog" })"); - CHECK(R"({| dogfood: string, tag: "dog" |})" == toString(ty)); + CHECK(R"({| dogfood: string, tag: "dog" |})" == toStringFull(ty)); } SUBCASE("cat_or_dog_and_not_dog") { TypeId ty = reductionof(R"(({ tag: "cat", catfood: string } | { tag: "dog", dogfood: string }) & { tag: Not<"dog"> })"); - CHECK(R"({| catfood: string, tag: "cat" |})" == toString(ty)); + CHECK(R"({| catfood: string, tag: "cat" |})" == toStringFull(ty)); } SUBCASE("string_or_number_and_number") { TypeId ty = reductionof("({ tag: string, a: number } | { tag: number, b: string }) & { tag: string }"); - CHECK("{| a: number, tag: string |}" == toString(ty)); + CHECK("{| a: number, tag: string |}" == toStringFull(ty)); } SUBCASE("string_or_number_and_number") { TypeId ty = reductionof("({ tag: string, a: number } | { tag: number, b: string }) & { tag: number }"); - CHECK("{| b: string, tag: number |}" == toString(ty)); + CHECK("{| b: string, tag: number |}" == toStringFull(ty)); } SUBCASE("child_or_unrelated_and_parent") { TypeId ty = reductionof("({ tag: Child, x: number } | { tag: Unrelated, y: string }) & { tag: Parent }"); - CHECK("{| tag: Child, x: number |}" == toString(ty)); + CHECK("{| tag: Child, x: number |}" == toStringFull(ty)); } SUBCASE("child_or_unrelated_and_not_parent") { TypeId ty = reductionof("({ tag: Child, x: number } | { tag: Unrelated, y: string }) & { tag: Not }"); - CHECK("{| tag: Unrelated, y: string |}" == toString(ty)); + CHECK("{| tag: Unrelated, y: string |}" == toStringFull(ty)); } } @@ -1293,7 +1330,7 @@ TEST_CASE_FIXTURE(ReductionFixture, "cycles") check("type F = (f: F) -> ()"); TypeId ty = reductionof(requireTypeAlias("F")); - CHECK("(t1) -> () where t1 = (t1) -> ()" == toString(ty)); + CHECK("t1 where t1 = (t1) -> ()" == toStringFull(ty)); } SUBCASE("recursively_defined_function_and_function") @@ -1301,52 +1338,39 @@ TEST_CASE_FIXTURE(ReductionFixture, "cycles") check("type F = (f: F & fun) -> ()"); TypeId ty = reductionof(requireTypeAlias("F")); - CHECK("(t1) -> () where t1 = (function & t1) -> ()" == toString(ty)); + CHECK("t1 where t1 = (function & t1) -> ()" == toStringFull(ty)); } SUBCASE("recursively_defined_table") { - ToStringOptions opts; - opts.exhaustive = true; - check("type T = { x: T }"); TypeId ty = reductionof(requireTypeAlias("T")); - CHECK("{| x: t1 |} where t1 = {| x: t1 |}" == toString(ty, opts)); + CHECK("t1 where t1 = {| x: t1 |}" == toStringFull(ty)); } SUBCASE("recursively_defined_table_and_table") { - ToStringOptions opts; - opts.exhaustive = true; - check("type T = { x: T & {} }"); TypeId ty = reductionof(requireTypeAlias("T")); - CHECK("{| x: t1 & {| |} |} where t1 = {| x: t1 & {| |} |}" == toString(ty, opts)); + CHECK("t1 where t1 = {| x: t1 & {| |} |}" == toStringFull(ty)); } SUBCASE("recursively_defined_table_and_table_2") { - ToStringOptions opts; - opts.exhaustive = true; - check("type T = { x: T } & { x: number }"); TypeId ty = reductionof(requireTypeAlias("T")); - CHECK("never" == toString(ty)); + CHECK("t1 where t1 = {| x: number |} & {| x: t1 |}" == toStringFull(ty)); } SUBCASE("recursively_defined_table_and_table_3") { - ToStringOptions opts; - opts.exhaustive = true; - check("type T = { x: T } & { x: T }"); TypeId ty = reductionof(requireTypeAlias("T")); - CHECK("{| x: {| x: t1 |} & {| x: t1 |} & {| x: t2 & t2 & {| x: t1 |} & {| x: t1 |} |} |} where t1 = t2 & {| x: t1 |} ; t2 = {| x: t1 |}" == - toString(ty)); + CHECK("t1 where t1 = {| x: t1 |} & {| x: t1 |}" == toStringFull(ty)); } } diff --git a/tests/conformance/basic.lua b/tests/conformance/basic.lua index 7a05f8e9..e23c1a53 100644 --- a/tests/conformance/basic.lua +++ b/tests/conformance/basic.lua @@ -923,6 +923,21 @@ assert((function() return table.concat(res, ',') end)() == "6,8,10") +-- checking for a CFG issue that was missed in IR +assert((function(b) + local res = 0 + + if b then + for i = 1, 100 do + res += i + end + else + res += 100000 + end + + return res +end)(true) == 5050) + -- typeof and type require an argument assert(pcall(typeof) == false) assert(pcall(type) == false) diff --git a/tools/faillist.txt b/tools/faillist.txt index 3fcd4200..2ea178da 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -42,7 +42,6 @@ AutocompleteTest.type_correct_suggestion_in_argument AutocompleteTest.type_correct_suggestion_in_table BuiltinTests.aliased_string_format BuiltinTests.assert_removes_falsy_types -BuiltinTests.assert_removes_falsy_types2 BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy BuiltinTests.bad_select_should_not_crash @@ -53,9 +52,6 @@ BuiltinTests.dont_add_definitions_to_persistent_types BuiltinTests.find_capture_types BuiltinTests.find_capture_types2 BuiltinTests.find_capture_types3 -BuiltinTests.gmatch_capture_types_balanced_escaped_parens -BuiltinTests.gmatch_capture_types_default_capture -BuiltinTests.gmatch_capture_types_parens_in_sets_are_ignored BuiltinTests.gmatch_definition BuiltinTests.ipairs_iterator_should_infer_types_and_type_check BuiltinTests.match_capture_types @@ -80,7 +76,6 @@ BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload BuiltinTests.table_pack BuiltinTests.table_pack_reduce BuiltinTests.table_pack_variadic -BuiltinTests.tonumber_returns_optional_number_type DefinitionTests.class_definition_overload_metamethods DefinitionTests.class_definition_string_props DefinitionTests.declaring_generic_functions @@ -103,7 +98,6 @@ GenericsTests.duplicate_generic_type_packs GenericsTests.duplicate_generic_types GenericsTests.generic_argument_count_too_few GenericsTests.generic_argument_count_too_many -GenericsTests.generic_factories GenericsTests.generic_functions_should_be_memory_safe GenericsTests.generic_table_method GenericsTests.generic_type_pack_parentheses @@ -140,7 +134,6 @@ NonstrictModeTests.parameters_having_type_any_are_optional NonstrictModeTests.table_dot_insert_and_recursive_calls NonstrictModeTests.table_props_are_any Normalize.cyclic_table_normalizes_sensibly -Normalize.negations_of_classes ParseErrorRecovery.generic_type_list_recovery ParseErrorRecovery.recovery_of_parenthesized_expressions ParserTests.parse_nesting_based_end_detection_failsafe_earlier @@ -160,16 +153,13 @@ ProvisionalTests.specialization_binds_with_prototypes_too_early ProvisionalTests.table_insert_with_a_singleton_argument ProvisionalTests.typeguard_inference_incomplete ProvisionalTests.weirditer_should_not_loop_forever -ProvisionalTests.while_body_are_also_refined RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string RefinementTest.call_an_incompatible_function_after_using_typeguard RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2 RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false RefinementTest.discriminate_tag -RefinementTest.eliminate_subclasses_of_instance RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil -RefinementTest.narrow_from_subclasses_of_instance_or_string_or_vector3 RefinementTest.narrow_property_of_a_bounded_variable RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table @@ -179,7 +169,6 @@ RefinementTest.type_guard_narrowed_into_nothingness RefinementTest.type_narrow_for_all_the_userdata RefinementTest.type_narrow_to_vector RefinementTest.typeguard_cast_free_table_to_vector -RefinementTest.typeguard_cast_instance_or_vector3_to_vector RefinementTest.typeguard_in_assert_position RefinementTest.typeguard_narrows_for_table RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table @@ -192,8 +181,6 @@ TableTests.accidentally_checked_prop_in_opposite_branch TableTests.builtin_table_names TableTests.call_method TableTests.call_method_with_explicit_self_argument -TableTests.cannot_augment_sealed_table -TableTests.casting_sealed_tables_with_props_into_table_with_indexer TableTests.casting_tables_with_props_into_table_with_indexer3 TableTests.casting_tables_with_props_into_table_with_indexer4 TableTests.checked_prop_too_early @@ -218,12 +205,10 @@ TableTests.function_calls_produces_sealed_table_given_unsealed_table TableTests.generic_table_instantiation_potential_regression TableTests.getmetatable_returns_pointer_to_metatable TableTests.give_up_after_one_metatable_index_look_up -TableTests.hide_table_error_properties TableTests.indexer_on_sealed_table_must_unify_with_free_table TableTests.indexing_from_a_table_should_prefer_properties_when_possible TableTests.inequality_operators_imply_exactly_matching_types TableTests.infer_array_2 -TableTests.infer_indexer_from_value_property_in_literal TableTests.inferred_return_type_of_free_table TableTests.inferring_crazy_table_should_also_be_quick TableTests.instantiate_table_cloning_3 @@ -243,7 +228,6 @@ TableTests.only_ascribe_synthetic_names_at_module_scope TableTests.oop_indexer_works TableTests.oop_polymorphic TableTests.open_table_unification_2 -TableTests.persistent_sealed_table_is_immutable TableTests.property_lookup_through_tabletypevar_metatable TableTests.quantify_even_that_table_was_never_exported_at_all TableTests.quantify_metatables_of_metatables_of_table @@ -252,7 +236,6 @@ TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_ TableTests.result_is_always_any_if_lhs_is_any TableTests.result_is_bool_for_equality_operators_if_lhs_is_any TableTests.right_table_missing_key2 -TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type TableTests.shared_selfs TableTests.shared_selfs_from_free_param TableTests.shared_selfs_through_metatables @@ -261,7 +244,6 @@ TableTests.table_function_check_use_after_free TableTests.table_indexing_error_location TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict TableTests.table_insert_should_cope_with_optional_properties_in_strict -TableTests.table_param_row_polymorphism_2 TableTests.table_param_row_polymorphism_3 TableTests.table_simple_call TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors @@ -276,12 +258,10 @@ TableTests.used_colon_correctly TableTests.used_colon_instead_of_dot TableTests.used_dot_instead_of_colon TableTests.used_dot_instead_of_colon_but_correctly -ToDot.bound_table -ToDot.function -ToDot.table ToString.exhaustive_toString_of_cyclic_table ToString.function_type_with_argument_names_and_self ToString.function_type_with_argument_names_generic +ToString.named_metatable_toStringNamedFunction ToString.toStringDetailed2 ToString.toStringErrorPack ToString.toStringNamedFunction_generic_pack @@ -297,10 +277,12 @@ TryUnifyTests.result_of_failed_typepack_unification_is_constrained TryUnifyTests.typepack_unification_should_trim_free_tails TryUnifyTests.variadics_should_use_reversed_properly TypeAliases.cannot_create_cyclic_type_with_unknown_module +TypeAliases.corecursive_types_generic TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any_2 TypeAliases.generic_param_remap TypeAliases.mismatched_generic_type_param +TypeAliases.mutually_recursive_types_errors TypeAliases.mutually_recursive_types_restriction_not_ok_1 TypeAliases.mutually_recursive_types_restriction_not_ok_2 TypeAliases.mutually_recursive_types_swapsies_not_ok @@ -308,8 +290,6 @@ TypeAliases.recursive_types_restriction_not_ok TypeAliases.report_shadowed_aliases TypeAliases.stringify_optional_parameterized_alias TypeAliases.stringify_type_alias_of_recursive_template_table_type -TypeAliases.stringify_type_alias_of_recursive_template_table_type2 -TypeAliases.type_alias_fwd_declaration_is_precise TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_rename TypeAliases.type_alias_of_an_imported_recursive_generic_type @@ -337,15 +317,12 @@ TypeInferAnyError.metatable_of_any_can_be_a_table TypeInferClasses.can_read_prop_of_base_class_using_string TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.classes_without_overloaded_operators_cannot_be_added -TypeInferClasses.detailed_class_unification_error TypeInferClasses.higher_order_function_arguments_are_contravariant TypeInferClasses.index_instance_property TypeInferClasses.optional_class_field_access_error TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties TypeInferClasses.warn_when_prop_almost_matches -TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types -TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site @@ -374,9 +351,7 @@ TypeInferFunctions.return_type_by_overload TypeInferFunctions.too_few_arguments_variadic TypeInferFunctions.too_few_arguments_variadic_generic TypeInferFunctions.too_few_arguments_variadic_generic2 -TypeInferFunctions.too_many_arguments TypeInferFunctions.too_many_arguments_error_location -TypeInferFunctions.too_many_return_values TypeInferFunctions.too_many_return_values_in_parentheses TypeInferFunctions.too_many_return_values_no_function TypeInferFunctions.vararg_function_is_quantified @@ -399,8 +374,6 @@ TypeInferModules.module_type_conflict_instantiated TypeInferModules.require_a_variadic_function TypeInferModules.type_error_of_unknown_qualified_type TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works -TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 -TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory TypeInferOOP.method_depends_on_table TypeInferOOP.methods_are_topologically_sorted @@ -461,10 +434,7 @@ TypePackTests.type_pack_type_parameters TypePackTests.unify_variadic_tails_in_arguments TypePackTests.unify_variadic_tails_in_arguments_free TypePackTests.variadic_packs -TypeReductionTests.discriminable_unions -TypeReductionTests.intersections_with_negations TypeReductionTests.negations -TypeReductionTests.unions_with_negations TypeSingletons.error_detailed_tagged_union_mismatch_bool TypeSingletons.error_detailed_tagged_union_mismatch_string TypeSingletons.function_call_with_singletons