diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index ea7366425..d209cb818 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -887,9 +887,6 @@ std::optional generalize( if (ty->owningArena != arena || ty->persistent) return ty; - if (const FunctionType* ft = get(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty())) - return ty; - FreeTypeSearcher fts{scope, cachedTypes}; fts.traverse(ty); @@ -912,8 +909,17 @@ std::optional generalize( FunctionType* ftv = getMutable(ty); if (ftv) { - ftv->generics = std::move(gen.generics); - ftv->genericPacks = std::move(gen.genericPacks); + // If we're generalizing a function type, add any of the newly inferred + // generics to the list of existing generic types. + for (const auto g : std::move(gen.generics)) + { + ftv->generics.push_back(g); + } + // Ditto for generic packs. + for (const auto gp : std::move(gen.genericPacks)) + { + ftv->genericPacks.push_back(gp); + } } return ty; diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 987a2708d..972c9e3ac 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -107,6 +107,7 @@ std::optional OverloadResolver::testIsSubtype(const Location& location case ErrorSuppression::NormalizationFailed: errors.emplace_back(location, NormalizationTooComplex{}); // intentionally fallthrough here since we couldn't prove this was error-suppressing + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: errors.emplace_back(location, TypeMismatch{superTy, subTy}); break; @@ -136,6 +137,7 @@ std::optional OverloadResolver::testIsSubtype(const Location& location case ErrorSuppression::NormalizationFailed: errors.emplace_back(location, NormalizationTooComplex{}); // intentionally fallthrough here since we couldn't prove this was error-suppressing + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: errors.emplace_back(location, TypePackMismatch{superTy, subTy}); break; @@ -301,6 +303,7 @@ std::pair OverloadResolver::checkOverload_ case ErrorSuppression::NormalizationFailed: errors.emplace_back(argLocation, NormalizationTooComplex{}); // intentionally fallthrough here since we couldn't prove this was error-suppressing + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: // TODO extract location from the SubtypingResult path and argExprs switch (reason.variance) diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 9138b50ee..099e6a0d1 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -141,6 +141,7 @@ Relation combine(Relation a, Relation b) case Relation::Superset: return Relation::Intersects; } + break; case Relation::Coincident: switch (b) { @@ -155,6 +156,7 @@ Relation combine(Relation a, Relation b) case Relation::Superset: return Relation::Intersects; } + break; case Relation::Superset: switch (b) { @@ -169,6 +171,7 @@ Relation combine(Relation a, Relation b) case Relation::Superset: return Relation::Superset; } + break; case Relation::Subset: switch (b) { @@ -183,6 +186,7 @@ Relation combine(Relation a, Relation b) case Relation::Superset: return Relation::Intersects; } + break; case Relation::Intersects: switch (b) { @@ -197,6 +201,7 @@ Relation combine(Relation a, Relation b) case Relation::Superset: return Relation::Intersects; } + break; } LUAU_UNREACHABLE(); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index ee3d7a9f5..7023fba91 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -1378,7 +1378,7 @@ void TypeChecker2::visitCall(AstExprCall* call) break; case ErrorSuppression::NormalizationFailed: reportError(NormalizationTooComplex{}, call->func->location); - // fallthrough intentional + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: reportError(OptionalValueAccess{fnTy}, call->func->location); } @@ -1582,7 +1582,7 @@ TypeId TypeChecker2::stripFromNilAndReport(TypeId ty, const Location& location) break; case ErrorSuppression::NormalizationFailed: reportError(NormalizationTooComplex{}, location); - // fallthrough intentional + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: reportError(OptionalValueAccess{ty}, location); } @@ -1666,7 +1666,7 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context) break; case ErrorSuppression::NormalizationFailed: reportError(NormalizationTooComplex{}, indexExpr->location); - // fallthrough intentional + [[fallthrough]]; case ErrorSuppression::DoNotSuppress: reportError(OptionalValueAccess{exprType}, indexExpr->location); } @@ -2723,6 +2723,7 @@ void TypeChecker2::explainError(TypeId subTy, TypeId superTy, Location location, return; case ErrorSuppression::NormalizationFailed: reportError(NormalizationTooComplex{}, location); + break; case ErrorSuppression::DoNotSuppress: break; } @@ -2741,6 +2742,7 @@ void TypeChecker2::explainError(TypePackId subTy, TypePackId superTy, Location l return; case ErrorSuppression::NormalizationFailed: reportError(NormalizationTooComplex{}, location); + break; case ErrorSuppression::DoNotSuppress: break; } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 5463c231b..6b2e861d2 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -2751,7 +2751,7 @@ TypeId TypeChecker::checkRelationalOperation( if (lhsIsAny || rhsIsAny) return booleanType; - // Fallthrough here is intentional + [[fallthrough]]; } case AstExprBinary::CompareLt: case AstExprBinary::CompareGt: diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 1683023ed..4b9eddda9 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -784,6 +784,7 @@ AstStat* Parser::parseAttributeStat() AstExpr* expr = parsePrimaryExpr(/* asStatement= */ true); return parseDeclaration(expr->location, attributes); } + [[fallthrough]]; default: return reportStatError( lexer.current().location, diff --git a/CLI/Require.cpp b/CLI/Require.cpp index 5de78a4ab..b6753e963 100644 --- a/CLI/Require.cpp +++ b/CLI/Require.cpp @@ -204,7 +204,18 @@ void RequireResolver::substituteAliasIfPresent(std::string& path) { if (path.size() < 1 || path[0] != '@') return; - std::string potentialAlias = path.substr(1, path.find_first_of("\\/")); + + // To ignore the '@' alias prefix when processing the alias + const size_t aliasStartPos = 1; + + // If a directory separator was found, the length of the alias is the + // distance between the start of the alias and the separator. Otherwise, + // the whole string after the alias symbol is the alias. + size_t aliasLen = path.find_first_of("\\/"); + if (aliasLen != std::string::npos) + aliasLen -= aliasStartPos; + + const std::string potentialAlias = path.substr(aliasStartPos, aliasLen); // Not worth searching when potentialAlias cannot be an alias if (!Luau::isValidAlias(potentialAlias)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34e104e19..b18cd5c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ if(MSVC) list(APPEND LUAU_OPTIONS "/we4388") # Also signed/unsigned mismatch else() list(APPEND LUAU_OPTIONS -Wall) # All warnings + list(APPEND LUAU_OPTIONS -Wimplicit-fallthrough) list(APPEND LUAU_OPTIONS -Wsign-compare) # This looks to be included in -Wall for GCC but not clang endif() diff --git a/Common/include/Luau/Common.h b/Common/include/Luau/Common.h index 406f6553c..2f4f1df8a 100644 --- a/Common/include/Luau/Common.h +++ b/Common/include/Luau/Common.h @@ -20,6 +20,19 @@ #define LUAU_DEBUGBREAK() __builtin_trap() #endif +// LUAU_FALLTHROUGH is a C++11-compatible alternative to [[fallthrough]] for use in the VM library +#if defined(__clang__) && defined(__has_warning) +#if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough") +#define LUAU_FALLTHROUGH [[clang::fallthrough]] +#else +#define LUAU_FALLTHROUGH +#endif +#elif defined(__GNUC__) && __GNUC__ >= 7 +#define LUAU_FALLTHROUGH [[gnu::fallthrough]] +#else +#define LUAU_FALLTHROUGH +#endif + #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define LUAU_BIG_ENDIAN #endif diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 85669e978..b5a4bd13a 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -552,9 +552,9 @@ static const char* match(MatchState* ms, const char* s, const char* p) } break; } - case '+': // 1 or more repetitions - s++; // 1 match already done - // go through + case '+': // 1 or more repetitions + s++; // 1 match already done + LUAU_FALLTHROUGH; // go through case '*': // 0 or more repetitions s = max_expand(ms, s, p, ep); break; @@ -1480,7 +1480,8 @@ static int str_pack(lua_State* L) break; } case Kpadding: - luaL_addchar(&b, LUAL_PACKPADBYTE); // FALLTHROUGH + luaL_addchar(&b, LUAL_PACKPADBYTE); + LUAU_FALLTHROUGH; case Kpaddalign: case Knop: arg--; // undo increment diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index c963ac8d0..dafb2b3f8 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -659,7 +659,7 @@ const TValue* luaH_get(Table* t, const TValue* key) luai_num2int(k, n); if (luai_numeq(cast_num(k), nvalue(key))) // index is int? return luaH_getnum(t, k); // use specialized version - // else go through + LUAU_FALLTHROUGH; // else go through } default: { diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 9e23f734b..376caa44d 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -133,7 +133,12 @@ static int lua_vector_cross(lua_State* L) const float* a = luaL_checkvector(L, 1); const float* b = luaL_checkvector(L, 2); +#if LUA_VECTOR_SIZE == 4 + lua_pushvector(L, a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0f); +#else lua_pushvector(L, a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]); +#endif + return 1; } @@ -144,15 +149,25 @@ static int lua_vector_index(lua_State* L) if (strcmp(name, "Magnitude") == 0) { +#if LUA_VECTOR_SIZE == 4 + lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3])); +#else lua_pushnumber(L, sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])); +#endif return 1; } if (strcmp(name, "Unit") == 0) { +#if LUA_VECTOR_SIZE == 4 + float invSqrt = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]); + + lua_pushvector(L, v[0] * invSqrt, v[1] * invSqrt, v[2] * invSqrt, v[3] * invSqrt); +#else float invSqrt = 1.0f / sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); lua_pushvector(L, v[0] * invSqrt, v[1] * invSqrt, v[2] * invSqrt); +#endif return 1; } diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 4b6f0d883..0b0e1b7c9 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -3,6 +3,7 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/Common.h" #include "Luau/Constraint.h" #include "Luau/ModuleResolver.h" #include "Luau/NotNull.h" @@ -25,6 +26,7 @@ static const char* mainModuleName = "MainModule"; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena); LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile) +LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker); extern std::optional randomSeed; // tests/main.cpp @@ -150,6 +152,8 @@ const Config& TestConfigResolver::getConfig(const ModuleName& name) const Fixture::Fixture(bool freeze, bool prepareAutocomplete) : sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, freeze) + // In tests, we *always* want to register the extra magic functions for typechecking `string.format`. + , sff_LuauDCRMagicFunctionTypeChecker(FFlag::LuauDCRMagicFunctionTypeChecker, true) , frontend( &fileResolver, &configResolver, diff --git a/tests/Fixture.h b/tests/Fixture.h index 790e6f417..f50431f38 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -99,6 +99,7 @@ struct Fixture TypeId requireExportedType(const ModuleName& moduleName, const std::string& name); ScopedFastFlag sff_DebugLuauFreezeArena; + ScopedFastFlag sff_LuauDCRMagicFunctionTypeChecker; TestFileResolver fileResolver; TestConfigResolver configResolver; diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 51b572f53..396678468 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -937,6 +937,7 @@ end ); } +#if LUA_VECTOR_SIZE == 3 TEST_CASE("FastcallTypeInferThroughLocal") { CHECK_EQ( @@ -1045,6 +1046,7 @@ end )" ); } +#endif TEST_CASE("LoadAndMoveTypePropagation") { @@ -1115,6 +1117,7 @@ end ); } +#if LUA_VECTOR_SIZE == 3 TEST_CASE("ArgumentTypeRefinement") { CHECK_EQ( @@ -1152,6 +1155,7 @@ end )" ); } +#endif TEST_CASE("InlineFunctionType") { @@ -1422,6 +1426,7 @@ end ); } +#if LUA_VECTOR_SIZE == 3 TEST_CASE("UnaryTypeResolve") { CHECK_EQ( @@ -1443,6 +1448,7 @@ end )" ); } +#endif TEST_CASE("ForInManualAnnotation") { diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 7f2300a55..f76f9faff 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -436,6 +436,12 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias") assertOutputContainsAll({"true", "result from other_dependency"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAliasPointingToDirectory") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/directory_alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from subdirectory_dependency"}); +} TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAliasThatDoesNotExist") { diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 81666c45d..59d9b5fd7 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -843,7 +843,7 @@ TEST_CASE_FIXTURE(Fixture, "pick_distinct_names_for_mixed_explicit_and_implicit_ if (FFlag::LuauSolverV2) { - CHECK("(a, 'b) -> ()" == toString(requireType("foo"))); + CHECK("(a, unknown) -> ()" == toString(requireType("foo"))); } else CHECK("(a, b) -> ()" == toString(requireType("foo"))); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index eba8b09c1..9912cc356 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -9,7 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauDCRMagicFunctionTypeChecker); TEST_SUITE_BEGIN("BuiltinTests"); @@ -793,8 +792,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_strin TEST_CASE_FIXTURE(Fixture, "string_format_as_method") { - ScopedFastFlag sff{FFlag::LuauDCRMagicFunctionTypeChecker, true}; - CheckResult result = check("local _ = ('%s'):format(5)"); LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -807,7 +804,6 @@ TEST_CASE_FIXTURE(Fixture, "string_format_as_method") TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument") { - ScopedFastFlag sff{FFlag::LuauDCRMagicFunctionTypeChecker, true}; CheckResult result = check(R"( local _ = ("%s"):format("%d", "hello") )"); @@ -819,7 +815,6 @@ TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument") TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2") { - ScopedFastFlag sff{FFlag::LuauDCRMagicFunctionTypeChecker, true}; CheckResult result = check(R"( local _ = ("%s %d").format("%d %s", "A type error", 2) )"); @@ -878,7 +873,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "debug_info_is_crazy") TEST_CASE_FIXTURE(BuiltinsFixture, "aliased_string_format") { - ScopedFastFlag sff{FFlag::LuauDCRMagicFunctionTypeChecker, true}; CheckResult result = check(R"( local fmt = string.format local s = fmt("%d", "oops") @@ -938,7 +932,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_on_variadic") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_report_all_type_errors_at_correct_positions") { - ScopedFastFlag sff{FFlag::LuauDCRMagicFunctionTypeChecker, true}; CheckResult result = check(R"( ("%s%d%s"):format(1, "hello", true) string.format("%s%d%s", 1, "hello", true) diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 155e7fd48..ffd01f245 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -7,6 +7,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" LUAU_FASTFLAG(LuauInstantiateInSubtyping); @@ -1419,10 +1420,19 @@ TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics3") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic") +TEST_CASE_FIXTURE(Fixture, "quantify_functions_with_no_generics") { - ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + CheckResult result = check(R"( + function foo(f, x) + return f(x) + end + )"); + CHECK("((a) -> (b...), a) -> (b...)" == toString(requireType("foo"))); +} + +TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_generic") +{ CheckResult result = check(R"( function foo(f, x: X) return f(x) @@ -1432,6 +1442,17 @@ TEST_CASE_FIXTURE(Fixture, "quantify_functions_even_if_they_have_an_explicit_gen CHECK("((X) -> (a...), X) -> (a...)" == toString(requireType("foo"))); } +TEST_CASE_FIXTURE(Fixture, "no_extra_quantification_for_generic_functions") +{ + CheckResult result = check(R"( + function foo(f : (X) -> Y, x: X) + return f(x) + end + )"); + + CHECK("((X) -> Y, X) -> Y" == toString(requireType("foo"))); +} + TEST_CASE_FIXTURE(Fixture, "do_not_always_instantiate_generic_intersection_types") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; @@ -1535,6 +1556,19 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter") REQUIRE(get(result.errors[1])); } +TEST_CASE_FIXTURE(Fixture, "generic_implicit_explicit_name_clash") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + auto result = check(R"( + function apply(func, argument: a) + return func(argument) + end + )"); + + CHECK("((a) -> (b...), a) -> (b...)" == toString(requireType("apply"))); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_functions_work_in_subtyping") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index a443c1874..514c31c82 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1310,9 +1310,7 @@ TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently if (FFlag::LuauSolverV2) { LUAU_CHECK_ERROR_COUNT(2, result); - - // The second argument should be unknown. CLI-111111 - CHECK("({T}, 'b) -> number" == toString(requireType("find_first"))); + CHECK("({T}, unknown) -> number" == toString(requireType("find_first"))); } else { diff --git a/tests/require/with_config/src/.luaurc b/tests/require/with_config/src/.luaurc index 87064bbc0..8c1ae683b 100644 --- a/tests/require/with_config/src/.luaurc +++ b/tests/require/with_config/src/.luaurc @@ -1,6 +1,7 @@ { "paths": ["../ProjectLuauLibraries"], "aliases": { - "dep": "dependency" + "dep": "dependency", + "subdir": "subdirectory" } } diff --git a/tests/require/with_config/src/directory_alias_requirer.luau b/tests/require/with_config/src/directory_alias_requirer.luau new file mode 100644 index 000000000..3b19d4ffa --- /dev/null +++ b/tests/require/with_config/src/directory_alias_requirer.luau @@ -0,0 +1 @@ +return(require("@subdir/subdirectory_dependency")) diff --git a/tests/require/with_config/src/subdirectory/subdirectory_dependency.luau b/tests/require/with_config/src/subdirectory/subdirectory_dependency.luau new file mode 100644 index 000000000..8bbd0bebd --- /dev/null +++ b/tests/require/with_config/src/subdirectory/subdirectory_dependency.luau @@ -0,0 +1 @@ +return {"result from subdirectory_dependency"}