From b1cfaf53050594edb9c7bc3eb9d36333ac0bcd99 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 21 Jul 2022 14:16:54 -0700 Subject: [PATCH 1/6] Sync to upstream/release/537 (#607) --- Analysis/include/Luau/JsonEncoder.h | 3 + Analysis/include/Luau/VisitTypeVar.h | 43 +- Analysis/src/JsonEncoder.cpp | 127 ++++- Analysis/src/Module.cpp | 26 +- Analysis/src/TypeInfer.cpp | 24 +- CLI/Analyze.cpp | 9 +- CLI/Ast.cpp | 3 +- CLI/Flags.cpp | 75 +++ CLI/Flags.h | 5 + CLI/Repl.cpp | 91 +-- CodeGen/include/Luau/AssemblyBuilderX64.h | 10 + CodeGen/include/Luau/Condition.h | 2 - CodeGen/src/AssemblyBuilderX64.cpp | 39 +- Common/include/Luau/ExperimentalFlags.h | 26 + Compiler/src/Compiler.cpp | 37 +- Makefile | 13 +- Sources.cmake | 7 + VM/src/lapi.cpp | 6 +- VM/src/ldebug.cpp | 5 + VM/src/ldebug.h | 1 + VM/src/ltablib.cpp | 6 +- VM/src/lvmutils.cpp | 6 +- bench/tests/tictactoe.lua | 4 +- fuzz/proto.cpp | 2 +- tests/AssemblyBuilderX64.test.cpp | 24 + tests/Compiler.test.cpp | 95 +++- tests/Conformance.test.cpp | 4 +- tests/Frontend.test.cpp | 69 +++ tests/JsonEncoder.test.cpp | 80 ++- tests/NonstrictMode.test.cpp | 93 +--- tests/Normalize.test.cpp | 3 +- tests/TypeInfer.functions.test.cpp | 30 +- tests/TypeInfer.loops.test.cpp | 13 +- tests/TypeInfer.provisional.test.cpp | 14 + tests/TypeInfer.tables.test.cpp | 9 + tests/TypeInfer.test.cpp | 34 +- tests/conformance/{nextvar.lua => tables.lua} | 20 + tests/main.cpp | 2 +- tools/faillist.txt | 521 ++++++++++++++++++ tools/natvis/Analysis.natvis | 64 +++ tools/test_dcr.py | 124 +++++ 41 files changed, 1484 insertions(+), 285 deletions(-) create mode 100644 CLI/Flags.cpp create mode 100644 CLI/Flags.h create mode 100644 Common/include/Luau/ExperimentalFlags.h rename tests/conformance/{nextvar.lua => tables.lua} (96%) create mode 100644 tools/faillist.txt create mode 100644 tools/test_dcr.py diff --git a/Analysis/include/Luau/JsonEncoder.h b/Analysis/include/Luau/JsonEncoder.h index aa00390..e79d9e6 100644 --- a/Analysis/include/Luau/JsonEncoder.h +++ b/Analysis/include/Luau/JsonEncoder.h @@ -2,12 +2,15 @@ #pragma once #include +#include namespace Luau { class AstNode; +struct Comment; std::string toJson(AstNode* node); +std::string toJson(AstNode* node, const std::vector& commentLocations); } // namespace Luau diff --git a/Analysis/include/Luau/VisitTypeVar.h b/Analysis/include/Luau/VisitTypeVar.h index ab4a397..7229daf 100644 --- a/Analysis/include/Luau/VisitTypeVar.h +++ b/Analysis/include/Luau/VisitTypeVar.h @@ -10,6 +10,7 @@ LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) +LUAU_FASTFLAG(LuauCompleteVisitor); namespace Luau { @@ -129,11 +130,11 @@ struct GenericTypeVarVisitor { return visit(ty); } - virtual bool visit(TypeId ty, const UnknownTypeVar& atv) + virtual bool visit(TypeId ty, const UnknownTypeVar& utv) { return visit(ty); } - virtual bool visit(TypeId ty, const NeverTypeVar& atv) + virtual bool visit(TypeId ty, const NeverTypeVar& ntv) { return visit(ty); } @@ -145,6 +146,14 @@ struct GenericTypeVarVisitor { return visit(ty); } + virtual bool visit(TypeId ty, const BlockedTypeVar& btv) + { + return visit(ty); + } + virtual bool visit(TypeId ty, const SingletonTypeVar& stv) + { + return visit(ty); + } virtual bool visit(TypePackId tp) { @@ -190,16 +199,12 @@ struct GenericTypeVarVisitor if (visit(ty, *btv)) traverse(btv->boundTo); } - else if (auto ftv = get(ty)) visit(ty, *ftv); - else if (auto gtv = get(ty)) visit(ty, *gtv); - else if (auto etv = get(ty)) visit(ty, *etv); - else if (auto ctv = get(ty)) { if (visit(ty, *ctv)) @@ -208,10 +213,8 @@ struct GenericTypeVarVisitor traverse(part); } } - else if (auto ptv = get(ty)) visit(ty, *ptv); - else if (auto ftv = get(ty)) { if (visit(ty, *ftv)) @@ -220,7 +223,6 @@ struct GenericTypeVarVisitor traverse(ftv->retTypes); } } - else if (auto ttv = get(ty)) { // Some visitors want to see bound tables, that's why we traverse the original type @@ -243,7 +245,6 @@ struct GenericTypeVarVisitor } } } - else if (auto mtv = get(ty)) { if (visit(ty, *mtv)) @@ -252,7 +253,6 @@ struct GenericTypeVarVisitor traverse(mtv->metatable); } } - else if (auto ctv = get(ty)) { if (visit(ty, *ctv)) @@ -267,10 +267,8 @@ struct GenericTypeVarVisitor traverse(*ctv->metatable); } } - else if (auto atv = get(ty)) visit(ty, *atv); - else if (auto utv = get(ty)) { if (visit(ty, *utv)) @@ -279,7 +277,6 @@ struct GenericTypeVarVisitor traverse(optTy); } } - else if (auto itv = get(ty)) { if (visit(ty, *itv)) @@ -288,6 +285,24 @@ struct GenericTypeVarVisitor traverse(partTy); } } + else if (!FFlag::LuauCompleteVisitor) + return visit_detail::unsee(seen, ty); + else if (get(ty)) + { + // Visiting into LazyTypeVar may necessarily cause infinite expansion, so we don't do that on purpose. + // Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassTypeVar + // that doesn't need to be expanded. + } + else if (auto stv = get(ty)) + visit(ty, *stv); + else if (auto btv = get(ty)) + visit(ty, *btv); + else if (auto utv = get(ty)) + visit(ty, *utv); + else if (auto ntv = get(ty)) + visit(ty, *ntv); + else + LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypeId) is not exhaustive!"); visit_detail::unsee(seen, ty); } diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/JsonEncoder.cpp index 829ffa0..550c9b5 100644 --- a/Analysis/src/JsonEncoder.cpp +++ b/Analysis/src/JsonEncoder.cpp @@ -2,6 +2,7 @@ #include "Luau/JsonEncoder.h" #include "Luau/Ast.h" +#include "Luau/ParseResult.h" #include "Luau/StringUtils.h" #include "Luau/Common.h" @@ -75,6 +76,11 @@ struct AstJsonEncoder : public AstVisitor writeRaw(std::string_view{&c, 1}); } + void writeType(std::string_view propValue) + { + write("type", propValue); + } + template void write(std::string_view propName, const T& value) { @@ -98,7 +104,7 @@ struct AstJsonEncoder : public AstVisitor void write(double d) { char b[256]; - sprintf(b, "%g", d); + sprintf(b, "%.17g", d); writeRaw(b); } @@ -111,8 +117,12 @@ struct AstJsonEncoder : public AstVisitor { if (c == '"') writeRaw("\\\""); - else if (c == '\0') - writeRaw("\\\0"); + else if (c == '\\') + writeRaw("\\\\"); + else if (c < ' ') + writeRaw(format("\\u%04x", c)); + else if (c == '\n') + writeRaw("\\n"); else writeRaw(c); } @@ -189,10 +199,11 @@ struct AstJsonEncoder : public AstVisitor writeRaw("{"); bool c = pushComma(); if (local->annotation != nullptr) - write("type", local->annotation); + write("luauType", local->annotation); else - write("type", nullptr); + write("luauType", nullptr); write("name", local->name); + writeType("AstLocal"); write("location", local->location); popComma(c); writeRaw("}"); @@ -208,7 +219,7 @@ struct AstJsonEncoder : public AstVisitor { writeRaw("{"); bool c = pushComma(); - write("type", name); + writeType(name); writeNode(node); f(); popComma(c); @@ -358,6 +369,7 @@ struct AstJsonEncoder : public AstVisitor { writeRaw("{"); bool c = pushComma(); + writeType("AstTypeList"); write("types", typeList.types); if (typeList.tailType) write("tailType", typeList.tailType); @@ -369,9 +381,10 @@ struct AstJsonEncoder : public AstVisitor { writeRaw("{"); bool c = pushComma(); + writeType("AstGenericType"); write("name", genericType.name); if (genericType.defaultValue) - write("type", genericType.defaultValue); + write("luauType", genericType.defaultValue); popComma(c); writeRaw("}"); } @@ -380,9 +393,10 @@ struct AstJsonEncoder : public AstVisitor { writeRaw("{"); bool c = pushComma(); + writeType("AstGenericTypePack"); write("name", genericTypePack.name); if (genericTypePack.defaultValue) - write("type", genericTypePack.defaultValue); + write("luauType", genericTypePack.defaultValue); popComma(c); writeRaw("}"); } @@ -404,6 +418,7 @@ struct AstJsonEncoder : public AstVisitor { writeRaw("{"); bool c = pushComma(); + writeType("AstExprTableItem"); write("kind", item.kind); switch (item.kind) { @@ -419,6 +434,17 @@ struct AstJsonEncoder : public AstVisitor writeRaw("}"); } + void write(class AstExprIfElse* node) + { + writeNode(node, "AstExprIfElse", [&]() { + PROP(condition); + PROP(hasThen); + PROP(trueExpr); + PROP(hasElse); + PROP(falseExpr); + }); + } + void write(class AstExprTable* node) { writeNode(node, "AstExprTable", [&]() { @@ -431,11 +457,11 @@ struct AstJsonEncoder : public AstVisitor switch (op) { case AstExprUnary::Not: - return writeString("not"); + return writeString("Not"); case AstExprUnary::Minus: - return writeString("minus"); + return writeString("Minus"); case AstExprUnary::Len: - return writeString("len"); + return writeString("Len"); } } @@ -541,7 +567,7 @@ struct AstJsonEncoder : public AstVisitor void write(class AstStatWhile* node) { - writeNode(node, "AtStatWhile", [&]() { + writeNode(node, "AstStatWhile", [&]() { PROP(condition); PROP(body); PROP(hasDo); @@ -684,7 +710,8 @@ struct AstJsonEncoder : public AstVisitor writeRaw("{"); bool c = pushComma(); write("name", prop.name); - write("type", prop.ty); + writeType("AstDeclaredClassProp"); + write("luauType", prop.ty); popComma(c); writeRaw("}"); } @@ -731,8 +758,9 @@ struct AstJsonEncoder : public AstVisitor bool c = pushComma(); write("name", prop.name); + writeType("AstTableProp"); write("location", prop.location); - write("type", prop.type); + write("propType", prop.type); popComma(c); writeRaw("}"); @@ -745,6 +773,24 @@ struct AstJsonEncoder : public AstVisitor PROP(indexer); }); } + + void write(struct AstTableIndexer* indexer) + { + if (indexer) + { + writeRaw("{"); + bool c = pushComma(); + write("location", indexer->location); + write("indexType", indexer->indexType); + write("resultType", indexer->resultType); + popComma(c); + writeRaw("}"); + } + else + { + writeRaw("null"); + } + } void write(class AstTypeFunction* node) { @@ -836,6 +882,12 @@ struct AstJsonEncoder : public AstVisitor return false; } + bool visit(class AstExprIfElse* node) override + { + write(node); + return false; + } + bool visit(class AstExprLocal* node) override { write(node); @@ -1093,6 +1145,42 @@ struct AstJsonEncoder : public AstVisitor write(node); return false; } + + void writeComments(std::vector commentLocations) + { + bool commentComma = false; + for (Comment comment : commentLocations) + { + if (commentComma) + { + writeRaw(","); + } + else + { + commentComma = true; + } + writeRaw("{"); + bool c = pushComma(); + switch (comment.type) + { + case Lexeme::Comment: + writeType("Comment"); + break; + case Lexeme::BlockComment: + writeType("BlockComment"); + break; + case Lexeme::BrokenComment: + writeType("BrokenComment"); + break; + default: + break; + } + write("location", comment.location); + popComma(c); + writeRaw("}"); + + } + } }; std::string toJson(AstNode* node) @@ -1102,4 +1190,15 @@ std::string toJson(AstNode* node) return encoder.str(); } +std::string toJson(AstNode* node, const std::vector& commentLocations) +{ + AstJsonEncoder encoder; + encoder.writeRaw(R"({"root":)"); + node->visit(&encoder); + encoder.writeRaw(R"(,"commentLocations":[)"); + encoder.writeComments(commentLocations); + encoder.writeRaw("]}"); + return encoder.str(); +} + } // namespace Luau diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 95eb125..0603a04 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauNormalizeFlagIsConservative); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAGVARIABLE(LuauForceExportSurfacesToBeNormal, false); namespace Luau { @@ -124,15 +125,21 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) moduleScope2->returnType = returnType; // TODO varargPack } + ForceNormal forceNormal{&interfaceTypes}; + if (FFlag::LuauLowerBoundsCalculation) { normalize(returnType, interfaceTypes, ice); + if (FFlag::LuauForceExportSurfacesToBeNormal) + forceNormal.traverse(returnType); if (varargPack) + { normalize(*varargPack, interfaceTypes, ice); + if (FFlag::LuauForceExportSurfacesToBeNormal) + forceNormal.traverse(*varargPack); + } } - ForceNormal forceNormal{&interfaceTypes}; - if (exportedTypeBindings) { for (auto& [name, tf] : *exportedTypeBindings) @@ -147,6 +154,16 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) // We're about to freeze the memory. We know that the flag is conservative by design. Cyclic tables // won't be marked normal. If the types aren't normal by now, they never will be. forceNormal.traverse(tf.type); + for (GenericTypeDefinition param : tf.typeParams) + { + forceNormal.traverse(param.ty); + + if (param.defaultValue) + { + normalize(*param.defaultValue, interfaceTypes, ice); + forceNormal.traverse(*param.defaultValue); + } + } } } } @@ -166,7 +183,12 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) { ty = clone(ty, interfaceTypes, cloneState); if (FFlag::LuauLowerBoundsCalculation) + { normalize(ty, interfaceTypes, ice); + + if (FFlag::LuauForceExportSurfacesToBeNormal) + forceNormal.traverse(ty); + } } freeze(internalTypes); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 2077792..ea7d81e 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -31,6 +31,7 @@ LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300) LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauAutocompleteDynamicLimits) +LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false) LUAU_FASTFLAGVARIABLE(LuauIndexSilenceErrors, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) @@ -38,7 +39,6 @@ LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false) LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) -LUAU_FASTFLAGVARIABLE(LuauReturnTypeInferenceInNonstrict, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false); LUAU_FASTFLAGVARIABLE(LuauAlwaysQuantify, false); LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false) @@ -50,6 +50,7 @@ LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false) +LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false) namespace Luau { @@ -890,7 +891,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_) TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type; - if (FFlag::LuauReturnTypeInferenceInNonstrict ? FFlag::LuauLowerBoundsCalculation : useConstrainedIntersections()) + if (useConstrainedIntersections()) { unifyLowerBound(retPack, scope->returnType, demoter.demotedLevel(scope->level), return_.location); return; @@ -1292,6 +1293,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin) for (size_t i = 2; i < varTypes.size(); ++i) unify(nilType, varTypes[i], forin.location); } + else if (isNonstrictMode()) + { + for (TypeId var : varTypes) + unify(anyType, var, forin.location); + } else { TypeId varTy = errorRecoveryType(loopScope); @@ -1385,12 +1391,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco // If in nonstrict mode and allowing redefinition of global function, restore the previous definition type // in case this function has a differing signature. The signature discrepancy will be caught in checkBlock. if (previouslyDefined) - { - if (FFlag::LuauReturnTypeInferenceInNonstrict && FFlag::LuauLowerBoundsCalculation) - quantify(funScope, ty, exprName->location); - globalBindings[name] = oldBinding; - } else globalBindings[name] = {quantify(funScope, ty, exprName->location), exprName->location}; @@ -2365,9 +2366,16 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp { std::vector expectedResultTypes; for (TypeId expectedOption : expectedUnion) + { if (const TableTypeVar* ttv = get(follow(expectedOption))) + { if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end()) expectedResultTypes.push_back(prop->second.type); + else if (FFlag::LuauExpectedTableUnionIndexerType && ttv->indexer && maybeString(ttv->indexer->indexType)) + expectedResultTypes.push_back(ttv->indexer->indexResultType); + } + } + if (expectedResultTypes.size() == 1) expectedResultType = expectedResultTypes[0]; else if (expectedResultTypes.size() > 1) @@ -3367,7 +3375,7 @@ std::pair TypeChecker::checkFunctionSignature(const ScopePtr& TypePackId retPack; if (expr.returnAnnotation) retPack = resolveTypePack(funScope, *expr.returnAnnotation); - else if (FFlag::LuauReturnTypeInferenceInNonstrict ? (!FFlag::LuauLowerBoundsCalculation && isNonstrictMode()) : isNonstrictMode()) + else if (isNonstrictMode()) retPack = anyTypePack; else if (expectedFunctionType && (!FFlag::LuauCheckGenericHOFTypes || (expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()))) diff --git a/CLI/Analyze.cpp b/CLI/Analyze.cpp index f07f9a0..cd50ef0 100644 --- a/CLI/Analyze.cpp +++ b/CLI/Analyze.cpp @@ -7,6 +7,7 @@ #include "Luau/Transpiler.h" #include "FileUtils.h" +#include "Flags.h" #ifdef CALLGRIND #include @@ -223,9 +224,7 @@ int main(int argc, char** argv) { Luau::assertHandler() = assertionHandler; - for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) - if (strncmp(flag->name, "Luau", 4) == 0) - flag->value = true; + setLuauFlagsDefault(); if (argc >= 2 && strcmp(argv[1], "--help") == 0) { @@ -252,12 +251,14 @@ int main(int argc, char** argv) annotate = true; else if (strcmp(argv[i], "--timetrace") == 0) FFlag::DebugLuauTimeTracing.value = true; + else if (strncmp(argv[i], "--fflags=", 9) == 0) + setLuauFlags(argv[i] + 9); } #if !defined(LUAU_ENABLE_TIME_TRACE) if (FFlag::DebugLuauTimeTracing) { - printf("To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n"); + fprintf(stderr, "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n"); return 1; } #endif diff --git a/CLI/Ast.cpp b/CLI/Ast.cpp index 4ea4623..6ee608c 100644 --- a/CLI/Ast.cpp +++ b/CLI/Ast.cpp @@ -62,6 +62,7 @@ int main(int argc, char** argv) Luau::AstNameTable names(allocator); Luau::ParseOptions options; + options.captureComments = true; options.supportContinueStatement = true; options.allowTypeAnnotations = true; options.allowDeclarationSyntax = true; @@ -78,7 +79,7 @@ int main(int argc, char** argv) fprintf(stderr, "\n"); } - printf("%s", Luau::toJson(parseResult.root).c_str()); + printf("%s", Luau::toJson(parseResult.root, parseResult.commentLocations).c_str()); return parseResult.errors.size() > 0 ? 1 : 0; } diff --git a/CLI/Flags.cpp b/CLI/Flags.cpp new file mode 100644 index 0000000..4e26117 --- /dev/null +++ b/CLI/Flags.cpp @@ -0,0 +1,75 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Common.h" +#include "Luau/ExperimentalFlags.h" + +#include + +#include +#include + +static void setLuauFlag(std::string_view name, bool state) +{ + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (name == flag->name) + { + flag->value = state; + return; + } + } + + fprintf(stderr, "Warning: unrecognized flag '%.*s'.\n", int(name.length()), name.data()); +} + +static void setLuauFlags(bool state) +{ + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = state; +} + +void setLuauFlagsDefault() +{ + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + if (strncmp(flag->name, "Luau", 4) == 0 && !Luau::isFlagExperimental(flag->name)) + flag->value = true; +} + +void setLuauFlags(const char* list) +{ + std::string_view rest = list; + + while (!rest.empty()) + { + size_t ending = rest.find(","); + std::string_view element = rest.substr(0, ending); + + if (size_t separator = element.find('='); separator != std::string_view::npos) + { + std::string_view key = element.substr(0, separator); + std::string_view value = element.substr(separator + 1); + + if (value == "true" || value == "True") + setLuauFlag(key, true); + else if (value == "false" || value == "False") + setLuauFlag(key, false); + else + fprintf(stderr, "Warning: unrecognized value '%.*s' for flag '%.*s'.\n", int(value.length()), value.data(), int(key.length()), + key.data()); + } + else + { + if (element == "true" || element == "True") + setLuauFlags(true); + else if (element == "false" || element == "False") + setLuauFlags(false); + else + setLuauFlag(element, true); + } + + if (ending != std::string_view::npos) + rest.remove_prefix(ending + 1); + else + break; + } +} diff --git a/CLI/Flags.h b/CLI/Flags.h new file mode 100644 index 0000000..8dfb0a2 --- /dev/null +++ b/CLI/Flags.h @@ -0,0 +1,5 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +void setLuauFlagsDefault(); +void setLuauFlags(const char* list); diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 5fe12be..4136073 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -8,9 +8,10 @@ #include "Luau/BytecodeBuilder.h" #include "Luau/Parser.h" -#include "FileUtils.h" -#include "Profiler.h" #include "Coverage.h" +#include "FileUtils.h" +#include "Flags.h" +#include "Profiler.h" #include "isocline.h" @@ -97,7 +98,11 @@ static int lua_require(lua_State* L) // return the module from the cache lua_getfield(L, -1, name.c_str()); if (!lua_isnil(L, -1)) + { + // L stack: _MODULES result return finishrequire(L); + } + lua_pop(L, 1); std::optional source = readFile(name + ".luau"); @@ -109,6 +114,7 @@ static int lua_require(lua_State* L) } // module needs to run in a new thread, isolated from the rest + // note: we create ML on main thread so that it doesn't inherit environment of L lua_State* GL = lua_mainthread(L); lua_State* ML = lua_newthread(GL); lua_xmove(GL, L, 1); @@ -142,11 +148,12 @@ static int lua_require(lua_State* L) } } - // there's now a return value on top of ML; stack of L is MODULES thread + // there's now a return value on top of ML; L stack: _MODULES ML lua_xmove(ML, L, 1); lua_pushvalue(L, -1); lua_setfield(L, -4, name.c_str()); + // L stack: _MODULES ML result return finishrequire(L); } @@ -682,60 +689,11 @@ static int assertionHandler(const char* expr, const char* file, int line, const return 1; } -static void setLuauFlags(bool state) -{ - for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) - { - if (strncmp(flag->name, "Luau", 4) == 0) - flag->value = state; - } -} - -static void setFlag(std::string_view name, bool state) -{ - for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) - { - if (name == flag->name) - { - flag->value = state; - return; - } - } - - fprintf(stderr, "Warning: --fflag unrecognized flag '%.*s'.\n\n", int(name.length()), name.data()); -} - -static void applyFlagKeyValue(std::string_view element) -{ - if (size_t separator = element.find('='); separator != std::string_view::npos) - { - std::string_view key = element.substr(0, separator); - std::string_view value = element.substr(separator + 1); - - if (value == "true") - setFlag(key, true); - else if (value == "false") - setFlag(key, false); - else - fprintf(stderr, "Warning: --fflag unrecognized value '%.*s' for flag '%.*s'.\n\n", int(value.length()), value.data(), int(key.length()), - key.data()); - } - else - { - if (element == "true") - setLuauFlags(true); - else if (element == "false") - setLuauFlags(false); - else - setFlag(element, true); - } -} - int replMain(int argc, char** argv) { Luau::assertHandler() = assertionHandler; - setLuauFlags(true); + setLuauFlagsDefault(); CliMode mode = CliMode::Unknown; CompileFormat compileFormat{}; @@ -818,27 +776,10 @@ int replMain(int argc, char** argv) else if (strcmp(argv[i], "--timetrace") == 0) { FFlag::DebugLuauTimeTracing.value = true; - -#if !defined(LUAU_ENABLE_TIME_TRACE) - printf("To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n"); - return 1; -#endif } else if (strncmp(argv[i], "--fflags=", 9) == 0) { - std::string_view list = argv[i] + 9; - - while (!list.empty()) - { - size_t ending = list.find(","); - - applyFlagKeyValue(list.substr(0, ending)); - - if (ending != std::string_view::npos) - list.remove_prefix(ending + 1); - else - break; - } + setLuauFlags(argv[i] + 9); } else if (argv[i][0] == '-') { @@ -848,6 +789,14 @@ int replMain(int argc, char** argv) } } +#if !defined(LUAU_ENABLE_TIME_TRACE) + if (FFlag::DebugLuauTimeTracing) + { + fprintf(stderr, "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n"); + return 1; + } +#endif + const std::vector files = getSourceFiles(argc, argv); if (mode == CliMode::Unknown) { diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 65883b4..028b2d1 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -61,12 +61,22 @@ public: void call(Label& label); void call(OperandX64 op); + void int3(); + // AVX void vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vaddps(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vaddsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vsubsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vmulsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + + void vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + + void vcomisd(OperandX64 src1, OperandX64 src2); + void vsqrtpd(OperandX64 dst, OperandX64 src); void vsqrtps(OperandX64 dst, OperandX64 src); void vsqrtsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); diff --git a/CodeGen/include/Luau/Condition.h b/CodeGen/include/Luau/Condition.h index 36cbda9..78e4515 100644 --- a/CodeGen/include/Luau/Condition.h +++ b/CodeGen/include/Luau/Condition.h @@ -37,8 +37,6 @@ enum class Condition Zero, NotZero, - // TODO: ordered and unordered floating-point conditions - Count }; diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 2634722..f88063c 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -231,6 +231,7 @@ void AssemblyBuilderX64::lea(OperandX64 lhs, OperandX64 rhs) if (logText) log("lea", lhs, rhs); + LUAU_ASSERT(rhs.cat == CategoryX64::mem); placeBinaryRegAndRegMem(lhs, rhs, 0x8d, 0x8d); } @@ -314,6 +315,14 @@ void AssemblyBuilderX64::call(OperandX64 op) commit(); } +void AssemblyBuilderX64::int3() +{ + if (logText) + log("int3"); + + place(0xcc); +} + void AssemblyBuilderX64::vaddpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) { placeAvx("vaddpd", dst, src1, src2, 0x58, false, AVX_0F, AVX_66); @@ -334,6 +343,31 @@ void AssemblyBuilderX64::vaddss(OperandX64 dst, OperandX64 src1, OperandX64 src2 placeAvx("vaddss", dst, src1, src2, 0x58, false, AVX_0F, AVX_F3); } +void AssemblyBuilderX64::vsubsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vsubsd", dst, src1, src2, 0x5c, false, AVX_0F, AVX_F2); +} + +void AssemblyBuilderX64::vmulsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vmulsd", dst, src1, src2, 0x59, false, AVX_0F, AVX_F2); +} + +void AssemblyBuilderX64::vdivsd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vdivsd", dst, src1, src2, 0x5e, false, AVX_0F, AVX_F2); +} + +void AssemblyBuilderX64::vxorpd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vxorpd", dst, src1, src2, 0x57, false, AVX_0F, AVX_66); +} + +void AssemblyBuilderX64::vcomisd(OperandX64 src1, OperandX64 src2) +{ + placeAvx("vcomisd", src1, src2, 0x2f, false, AVX_0F, AVX_66); +} + void AssemblyBuilderX64::vsqrtpd(OperandX64 dst, OperandX64 src) { placeAvx("vsqrtpd", dst, src, 0x51, false, AVX_0F, AVX_66); @@ -494,9 +528,10 @@ void AssemblyBuilderX64::placeBinaryRegMemAndImm(OperandX64 lhs, OperandX64 rhs, LUAU_ASSERT(lhs.cat == CategoryX64::reg || lhs.cat == CategoryX64::mem); LUAU_ASSERT(rhs.cat == CategoryX64::imm); - SizeX64 size = lhs.base.size; + SizeX64 size = lhs.cat == CategoryX64::reg ? lhs.base.size : lhs.memSize; + LUAU_ASSERT(size == SizeX64::byte || size == SizeX64::dword || size == SizeX64::qword); - placeRex(lhs.base); + placeRex(lhs); if (size == SizeX64::byte) { diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h new file mode 100644 index 0000000..3525259 --- /dev/null +++ b/Common/include/Luau/ExperimentalFlags.h @@ -0,0 +1,26 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +namespace Luau +{ + +inline bool isFlagExperimental(const char* flag) +{ + // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, + // or critical bugs that are found after the code has been submitted. + static const char* kList[] = + { + "LuauLowerBoundsCalculation", + nullptr, // makes sure we always have at least one entry + }; + + for (const char* item: kList) + if (item && strcmp(item, flag) == 0) + return true; + + return false; +} + +} diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 2b0f04f..8f3befa 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -27,6 +27,7 @@ LUAU_FASTFLAGVARIABLE(LuauCompileNoIpairs, false) LUAU_FASTFLAGVARIABLE(LuauCompileFoldBuiltins, false) LUAU_FASTFLAGVARIABLE(LuauCompileBetterMultret, false) +LUAU_FASTFLAGVARIABLE(LuauCompileFreeReassign, false) namespace Luau { @@ -616,7 +617,7 @@ struct Compiler } else { - AstExprLocal* le = arg->as(); + AstExprLocal* le = FFlag::LuauCompileFreeReassign ? getExprLocal(arg) : arg->as(); Variable* lv = le ? variables.find(le->local) : nullptr; // if the argument is a local that isn't mutated, we will simply reuse the existing register @@ -2200,19 +2201,27 @@ struct Compiler compileLValueUse(lv, source, /* set= */ true); } - int getExprLocalReg(AstExpr* node) + AstExprLocal* getExprLocal(AstExpr* node) { if (AstExprLocal* expr = node->as()) + return expr; + else if (AstExprGroup* expr = node->as()) + return getExprLocal(expr->expr); + else if (AstExprTypeAssertion* expr = node->as()) + return getExprLocal(expr->expr); + else + return nullptr; + } + + int getExprLocalReg(AstExpr* node) + { + if (AstExprLocal* expr = getExprLocal(node)) { // note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining Local* l = locals.find(expr->local); return l && l->allocated ? l->reg : -1; } - else if (AstExprGroup* expr = node->as()) - return getExprLocalReg(expr->expr); - else if (AstExprTypeAssertion* expr = node->as()) - return getExprLocalReg(expr->expr); else return -1; } @@ -2498,6 +2507,22 @@ struct Compiler if (options.optimizationLevel >= 1 && options.debugLevel <= 1 && areLocalsRedundant(stat)) return; + // Optimization: for 1-1 local assignments, we can reuse the register *if* neither local is mutated + if (FFlag::LuauCompileFreeReassign && options.optimizationLevel >= 1 && stat->vars.size == 1 && stat->values.size == 1) + { + if (AstExprLocal* re = getExprLocal(stat->values.data[0])) + { + Variable* lv = variables.find(stat->vars.data[0]); + Variable* rv = variables.find(re->local); + + if (int reg = getExprLocalReg(re); reg >= 0 && (!lv || !lv->written) && (!rv || !rv->written)) + { + pushLocal(stat->vars.data[0], uint8_t(reg)); + return; + } + } + } + // note: allocReg in this case allocates into parent block register - note that we don't have RegScope here uint8_t vars = allocReg(stat, unsigned(stat->vars.size)); diff --git a/Makefile b/Makefile index a471e7a..33a2e5e 100644 --- a/Makefile +++ b/Makefile @@ -31,15 +31,15 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o) ISOCLINE_TARGET=$(BUILD)/libisocline.a -TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp +TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o) TESTS_TARGET=$(BUILD)/luau-tests -REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp +REPL_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Profiler.cpp CLI/Coverage.cpp CLI/Repl.cpp CLI/ReplEntry.cpp REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o) REPL_CLI_TARGET=$(BUILD)/luau -ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Analyze.cpp +ANALYZE_CLI_SOURCES=CLI/FileUtils.cpp CLI/Flags.cpp CLI/Analyze.cpp ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o) ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze @@ -117,15 +117,18 @@ $(REPL_CLI_TARGET): LDFLAGS+=-lpthread fuzz-proto fuzz-prototest: LDFLAGS+=build/libprotobuf-mutator/src/libfuzzer/libprotobuf-mutator-libfuzzer.a build/libprotobuf-mutator/src/libprotobuf-mutator.a build/libprotobuf-mutator/external.protobuf/lib/libprotobuf.a # pseudo targets -.PHONY: all test clean coverage format luau-size +.PHONY: all test clean coverage format luau-size aliases -all: $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(TESTS_TARGET) +all: $(REPL_CLI_TARGET) $(ANALYZE_CLI_TARGET) $(TESTS_TARGET) aliases + +aliases: luau luau-analyze test: $(TESTS_TARGET) $(TESTS_TARGET) $(TESTS_ARGS) clean: rm -rf $(BUILD) + rm -rf luau luau-analyze coverage: $(TESTS_TARGET) $(TESTS_TARGET) --fflags=true diff --git a/Sources.cmake b/Sources.cmake index 69f5e1b..a456301 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/ExperimentalFlags.h ) endif() @@ -220,6 +221,8 @@ if(TARGET Luau.Repl.CLI) CLI/Coverage.cpp CLI/FileUtils.h CLI/FileUtils.cpp + CLI/Flags.h + CLI/Flags.cpp CLI/Profiler.h CLI/Profiler.cpp CLI/Repl.cpp @@ -231,6 +234,8 @@ if(TARGET Luau.Analyze.CLI) target_sources(Luau.Analyze.CLI PRIVATE CLI/FileUtils.h CLI/FileUtils.cpp + CLI/Flags.h + CLI/Flags.cpp CLI/Analyze.cpp) endif() @@ -321,6 +326,8 @@ if(TARGET Luau.CLI.Test) CLI/Coverage.cpp CLI/FileUtils.h CLI/FileUtils.cpp + CLI/Flags.h + CLI/Flags.cpp CLI/Profiler.h CLI/Profiler.cpp CLI/Repl.cpp diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 8868072..e3354ea 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -854,7 +854,7 @@ void lua_rawset(lua_State* L, int idx) StkId t = index2addr(L, idx); api_check(L, ttistable(t)); if (hvalue(t)->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); setobj2t(L, luaH_set(L, hvalue(t), L->top - 2), L->top - 1); luaC_barriert(L, hvalue(t), L->top - 1); L->top -= 2; @@ -867,7 +867,7 @@ void lua_rawseti(lua_State* L, int idx, int n) StkId o = index2addr(L, idx); api_check(L, ttistable(o)); if (hvalue(o)->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top - 1); luaC_barriert(L, hvalue(o), L->top - 1); L->top--; @@ -890,7 +890,7 @@ int lua_setmetatable(lua_State* L, int objindex) case LUA_TTABLE: { if (hvalue(obj)->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); hvalue(obj)->metatable = mt; if (mt) luaC_objbarrier(L, hvalue(obj), mt); diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index ef48609..2901556 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -269,6 +269,11 @@ l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2) luaG_runerror(L, "attempt to index %s with %s", t1, t2); } +l_noret luaG_readonlyerror(lua_State* L) +{ + luaG_runerror(L, "attempt to modify a readonly table"); +} + static void pusherror(lua_State* L, const char* msg) { CallInfo* ci = L->ci; diff --git a/VM/src/ldebug.h b/VM/src/ldebug.h index 75bb8dc..8e03db3 100644 --- a/VM/src/ldebug.h +++ b/VM/src/ldebug.h @@ -19,6 +19,7 @@ LUAI_FUNC l_noret luaG_concaterror(lua_State* L, StkId p1, StkId p2); LUAI_FUNC l_noret luaG_aritherror(lua_State* L, const TValue* p1, const TValue* p2, TMS op); LUAI_FUNC l_noret luaG_ordererror(lua_State* L, const TValue* p1, const TValue* p2, TMS op); LUAI_FUNC l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2); +LUAI_FUNC l_noret luaG_readonlyerror(lua_State* L); LUAI_FUNC LUA_PRINTF_ATTR(2, 3) l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...); LUAI_FUNC void luaG_pusherror(lua_State* L, const char* error); diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 27187c6..dc65333 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -79,7 +79,7 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) Table* dst = hvalue(L->base + (dstt - 1)); if (dst->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); int n = e - f + 1; /* number of elements to move */ @@ -204,7 +204,7 @@ static int tmove(lua_State* L) Table* dst = hvalue(L->base + (tt - 1)); if (dst->readonly) /* also checked in moveelements, but this blocks resizes of r/o tables */ - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); if (t > 0 && (t - 1) <= dst->sizearray && (t - 1 + n) > dst->sizearray) { /* grow the destination table array */ @@ -482,7 +482,7 @@ static int tclear(lua_State* L) Table* tt = hvalue(L->base); if (tt->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); luaH_clear(tt); return 0; diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index b9e762e..be4e99a 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -128,7 +128,7 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) } t = tm; /* else repeat with `tm' */ } - luaG_runerror(L, "loop in gettable"); + luaG_runerror(L, "'__index' chain too long; possible loop"); } void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) @@ -143,7 +143,7 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) Table* h = hvalue(t); if (h->readonly) - luaG_runerror(L, "Attempt to modify a readonly table"); + luaG_readonlyerror(L); TValue* oldval = luaH_set(L, h, key); /* do a primitive set */ @@ -169,7 +169,7 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */ t = &temp; } - luaG_runerror(L, "loop in settable"); + luaG_runerror(L, "'__newindex' chain too long; possible loop"); } static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId res, TMS event) diff --git a/bench/tests/tictactoe.lua b/bench/tests/tictactoe.lua index ae63f5f..91d38f9 100644 --- a/bench/tests/tictactoe.lua +++ b/bench/tests/tictactoe.lua @@ -139,7 +139,7 @@ function test() for _, curr_qdr in pairs(negaMax.index_quadruplets) do -- iterate over all index quadruplets -- count the empty positions and positions occupied by the side whos move it is local player_plus_fields, player_minus_fields, empties = 0, 0, 0 - for _, index in pairs(curr_qdr) do -- iterate over all indices + for _, index in next, curr_qdr do -- iterate over all indices if board[index] == 0 then empties = empties + 1 elseif board[index] == 1 then @@ -225,4 +225,4 @@ function test() return t1-t0 end -bench.runCode(test, "tictactoe") \ No newline at end of file +bench.runCode(test, "tictactoe") diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index 22483f9..f64b615 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -333,7 +333,7 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) try { Luau::BytecodeBuilder bcb; - Luau::compileOrThrow(bcb, parseResult.root, parseNameTable, compileOptions); + Luau::compileOrThrow(bcb, parseResult, parseNameTable, compileOptions); bytecode = bcb.getBytecode(); } catch (const Luau::CompileError&) diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 15813ae..28ce6a8 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -155,6 +155,13 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseBinaryInstructionForms") SINGLE_COMPARE(add(qword[rax + r13 * 2 + 0x1b], rsi), 0x4a, 0x01, 0x74, 0x68, 0x1b); SINGLE_COMPARE(add(qword[rbp + rbx * 2], rsi), 0x48, 0x01, 0x74, 0x5d, 0x00); SINGLE_COMPARE(add(qword[rsp + r10 * 2 + 0x1b], r10), 0x4e, 0x01, 0x54, 0x54, 0x1b); + + // [addr], imm + SINGLE_COMPARE(add(byte[rax], 2), 0x80, 0x00, 0x02); + SINGLE_COMPARE(add(dword[rax], 2), 0x83, 0x00, 0x02); + SINGLE_COMPARE(add(dword[rax], 0xabcd), 0x81, 0x00, 0xcd, 0xab, 0x00, 0x00); + SINGLE_COMPARE(add(qword[rax], 2), 0x48, 0x83, 0x00, 0x02); + SINGLE_COMPARE(add(qword[rax], 0xabcd), 0x48, 0x81, 0x00, 0xcd, 0xab, 0x00, 0x00); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms") @@ -304,6 +311,13 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXBinaryInstructionForms") SINGLE_COMPARE(vaddps(xmm9, xmm12, xmmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x98, 0x58, 0x4c, 0x71, 0x1c); SINGLE_COMPARE(vaddps(ymm1, ymm2, ymm3), 0xc4, 0xe1, 0xec, 0x58, 0xcb); SINGLE_COMPARE(vaddps(ymm9, ymm12, ymmword[r9 + r14 * 2 + 0x1c]), 0xc4, 0x01, 0x9c, 0x58, 0x4c, 0x71, 0x1c); + + // Coverage for other instructions that follow the same pattern + SINGLE_COMPARE(vsubsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x5c, 0xc6); + SINGLE_COMPARE(vmulsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x59, 0xc6); + SINGLE_COMPARE(vdivsd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xab, 0x5e, 0xc6); + + SINGLE_COMPARE(vxorpd(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xa9, 0x57, 0xc6); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms") @@ -318,6 +332,9 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXUnaryMergeInstructionForms") SINGLE_COMPARE(vsqrtsd(xmm8, xmm10, qword[r9]), 0xc4, 0x41, 0xab, 0x51, 0x01); SINGLE_COMPARE(vsqrtss(xmm8, xmm10, xmm14), 0xc4, 0x41, 0xaa, 0x51, 0xc6); SINGLE_COMPARE(vsqrtss(xmm8, xmm10, dword[r9]), 0xc4, 0x41, 0xaa, 0x51, 0x01); + + // Coverage for other instructions that follow the same pattern + SINGLE_COMPARE(vcomisd(xmm8, xmm10), 0xc4, 0x41, 0xf9, 0x2f, 0xc2); } TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXMoveInstructionForms") @@ -342,6 +359,11 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXMoveInstructionForms") SINGLE_COMPARE(vmovups(ymm8, ymmword[r9]), 0xc4, 0x41, 0xfc, 0x10, 0x01); } +TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions") +{ + SINGLE_COMPARE(int3(), 0xcc); +} + TEST_CASE("LogTest") { AssemblyBuilderX64 build(/* logText= */ true); @@ -366,6 +388,7 @@ TEST_CASE("LogTest") build.vmovapd(xmmword[rax], xmm11); build.pop(r12); build.ret(); + build.int3(); build.finalize(); @@ -388,6 +411,7 @@ TEST_CASE("LogTest") vmovapd xmmword ptr [rax],xmm11 pop r12 ret + int3 )"; CHECK(same); } diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 46fde06..c191763 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -3793,6 +3793,8 @@ RETURN R0 1 TEST_CASE("SharedClosure") { + ScopedFastFlag sff("LuauCompileFreeReassign", true); + // closures can be shared even if functions refer to upvalues, as long as upvalues are top-level CHECK_EQ("\n" + compileFunction(R"( local val = ... @@ -3940,11 +3942,10 @@ LOADN R2 1 LOADN R0 10 LOADN R1 1 FORNPREP R0 L5 -L4: MOVE R3 R2 -GETIMPORT R4 1 -NEWCLOSURE R5 P2 -CAPTURE VAL R3 -CALL R4 1 0 +L4: GETIMPORT R3 1 +NEWCLOSURE R4 P2 +CAPTURE VAL R2 +CALL R3 1 0 FORNLOOP R0 L4 L5: RETURN R0 0 )"); @@ -6157,4 +6158,88 @@ RETURN R0 1 )"); } +TEST_CASE("LocalReassign") +{ + ScopedFastFlag sff("LuauCompileFreeReassign", true); + + // locals can be re-assigned and the register gets reused + CHECK_EQ("\n" + compileFunction0(R"( +local function test(a, b) + local c = a + return c + b +end +)"), R"( +ADD R2 R0 R1 +RETURN R2 1 +)"); + + // this works if the expression is using type casts or grouping + CHECK_EQ("\n" + compileFunction0(R"( +local function test(a, b) + local c = (a :: number) + return c + b +end +)"), R"( +ADD R2 R0 R1 +RETURN R2 1 +)"); + + // the optimization requires that neither local is mutated + CHECK_EQ("\n" + compileFunction0(R"( +local function test(a, b) + local c = a + c += 0 + local d = b + b += 0 + return c + d +end +)"), R"( +MOVE R2 R0 +ADDK R2 R2 K0 +MOVE R3 R1 +ADDK R1 R1 K0 +ADD R4 R2 R3 +RETURN R4 1 +)"); + + // sanity check for two values + CHECK_EQ("\n" + compileFunction0(R"( +local function test(a, b) + local c = a + local d = b + return c + d +end +)"), R"( +ADD R2 R0 R1 +RETURN R2 1 +)"); + + // note: we currently only support this for single assignments + CHECK_EQ("\n" + compileFunction0(R"( +local function test(a, b) + local c, d = a, b + return c + d +end +)"), R"( +MOVE R2 R0 +MOVE R3 R1 +ADD R4 R2 R3 +RETURN R4 1 +)"); + + // of course, captures capture the original register as well (by value since it's immutable) + CHECK_EQ("\n" + compileFunction(R"( +local function test(a, b) + local c = a + local d = b + return function() return c + d end +end +)", 1), R"( +NEWCLOSURE R2 P0 +CAPTURE VAL R0 +CAPTURE VAL R1 +RETURN R2 1 +)"); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 98c3933..fc8ab2f 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -254,9 +254,9 @@ TEST_CASE("Math") runConformance("math.lua"); } -TEST_CASE("Table") +TEST_CASE("Tables") { - runConformance("nextvar.lua", [](lua_State* L) { + runConformance("tables.lua", [](lua_State* L) { lua_pushcfunction( L, [](lua_State* L) { diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 4efa74d..0382f22 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1025,4 +1025,73 @@ TEST_CASE("check_without_builtin_next") frontend.check("Module/B"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type") +{ + ScopedFastFlag sff[] = { + {"LuauForceExportSurfacesToBeNormal", true}, + {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", true}, + }; + + fileResolver.source["Module/A"] = R"( + type F = (set: G) -> () + + export type G = { + forEach: (a: F) -> (), + } + + function X(a: F): () + end + + return X + )"; + + fileResolver.source["Module/B"] = R"( + --!strict + local A = require(script.Parent.A) + + export type G = A.G + + return { + A = A, + } + )"; + + CheckResult result = frontend.check("Module/B"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias") +{ + ScopedFastFlag sff[] = { + {"LuauForceExportSurfacesToBeNormal", true}, + {"LuauLowerBoundsCalculation", true}, + {"LuauNormalizeFlagIsConservative", true}, + }; + + fileResolver.source["Module/A"] = R"( + type KeyOfTestEvents = "test-file-start" | "test-file-success" | "test-file-failure" | "test-case-result" + type unknown = any + + export type TestFileEvent = ( + eventName: T, + args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]] + ) -> unknown + + return {} + )"; + + fileResolver.source["Module/B"] = R"( + --!strict + local A = require(script.Parent.A) + + export type TestFileEvent = A.TestFileEvent + )"; + + CheckResult result = frontend.check("Module/B"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/JsonEncoder.test.cpp b/tests/JsonEncoder.test.cpp index 8a263bd..b16ad3e 100644 --- a/tests/JsonEncoder.test.cpp +++ b/tests/JsonEncoder.test.cpp @@ -56,10 +56,19 @@ TEST_CASE("encode_constants") AstExprConstantNil nil{Location()}; AstExprConstantBool b{Location(), true}; AstExprConstantNumber n{Location(), 8.2}; + AstExprConstantNumber bigNum{Location(), 0.1677721600000003}; + + AstArray charString; + charString.data = const_cast("a\x1d\0\\\"b"); + charString.size = 6; + + AstExprConstantString needsEscaping{Location(), charString}; CHECK_EQ(R"({"type":"AstExprConstantNil","location":"0,0 - 0,0"})", toJson(&nil)); CHECK_EQ(R"({"type":"AstExprConstantBool","location":"0,0 - 0,0","value":true})", toJson(&b)); - CHECK_EQ(R"({"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":8.2})", toJson(&n)); + CHECK_EQ(R"({"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":8.1999999999999993})", toJson(&n)); + CHECK_EQ(R"({"type":"AstExprConstantNumber","location":"0,0 - 0,0","value":0.16777216000000031})", toJson(&bigNum)); + CHECK_EQ("{\"type\":\"AstExprConstantString\",\"location\":\"0,0 - 0,0\",\"value\":\"a\\u001d\\u0000\\\\\\\"b\"}", toJson(&needsEscaping)); } TEST_CASE("basic_escaping") @@ -87,7 +96,7 @@ TEST_CASE("encode_AstStatBlock") AstStatBlock block{Location(), bodyArray}; CHECK_EQ( - (R"({"type":"AstStatBlock","location":"0,0 - 0,0","body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"type":null,"name":"a_local","location":"0,0 - 0,0"}],"values":[]}]})"), + (R"({"type":"AstStatBlock","location":"0,0 - 0,0","body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})"), toJson(&block)); } @@ -106,7 +115,31 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables") CHECK( json == - R"({"type":"AstStatBlock","location":"0,0 - 6,4","body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"type":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","location":"2,12 - 2,15","type":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","parameters":[]}}],"indexer":false},"name":"x","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})"); + R"({"type":"AstStatBlock","location":"0,0 - 6,4","body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"luauType":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","type":"AstTableProp","location":"2,12 - 2,15","propType":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","parameters":[]}}],"indexer":null},"name":"x","type":"AstLocal","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})"); +} + +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array") +{ + std::string src = R"(type X = {string})"; + + AstStatBlock* root = expectParse(src); + std::string json = toJson(root); + + CHECK( + json == + R"({"type":"AstStatBlock","location":"0,0 - 0,17","body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","parameters":[]}}},"exported":false}]})"); +} + +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer") +{ + std::string src = R"(type X = {string})"; + + AstStatBlock* root = expectParse(src); + std::string json = toJson(root); + + CHECK( + json == + R"({"type":"AstStatBlock","location":"0,0 - 0,17","body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","parameters":[]}}},"exported":false}]})"); } TEST_CASE("encode_AstExprGroup") @@ -132,12 +165,23 @@ TEST_CASE("encode_AstExprGlobal") CHECK(json == expected); } +TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprIfThen") +{ + AstStat* statement = expectParseStatement("local a = if x then y else z"); + + std::string_view expected = + R"({"type":"AstStatLocal","location":"0,0 - 0,28","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,6 - 0,7"}],"values":[{"type":"AstExprIfElse","location":"0,10 - 0,28","condition":{"type":"AstExprGlobal","location":"0,13 - 0,14","global":"x"},"hasThen":true,"trueExpr":{"type":"AstExprGlobal","location":"0,20 - 0,21","global":"y"},"hasElse":true,"falseExpr":{"type":"AstExprGlobal","location":"0,27 - 0,28","global":"z"}}]})"; + + CHECK(toJson(statement) == expected); +} + + TEST_CASE("encode_AstExprLocal") { AstLocal local{AstName{"foo"}, Location{}, nullptr, 0, 0, nullptr}; AstExprLocal exprLocal{Location{}, &local, false}; - CHECK(toJson(&exprLocal) == R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"type":null,"name":"foo","location":"0,0 - 0,0"}})"); + CHECK(toJson(&exprLocal) == R"({"type":"AstExprLocal","location":"0,0 - 0,0","local":{"luauType":null,"name":"foo","type":"AstLocal","location":"0,0 - 0,0"}})"); } TEST_CASE("encode_AstExprVarargs") @@ -181,7 +225,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction") AstExpr* expr = expectParseExpr("function (a) return a end"); std::string_view expected = - R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"type":null,"name":"a","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"type":null,"name":"a","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})"; + R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})"; CHECK(toJson(expr) == expected); } @@ -191,7 +235,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprTable") AstExpr* expr = expectParseExpr("{true, key=true, [key2]=true}"); std::string_view expected = - R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})"; + R"({"type":"AstExprTable","location":"0,4 - 0,33","items":[{"type":"AstExprTableItem","kind":"item","value":{"type":"AstExprConstantBool","location":"0,5 - 0,9","value":true}},{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"0,11 - 0,14","value":"key"},"value":{"type":"AstExprConstantBool","location":"0,15 - 0,19","value":true}},{"type":"AstExprTableItem","kind":"general","key":{"type":"AstExprGlobal","location":"0,22 - 0,26","global":"key2"},"value":{"type":"AstExprConstantBool","location":"0,28 - 0,32","value":true}}]})"; CHECK(toJson(expr) == expected); } @@ -201,7 +245,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprUnary") AstExpr* expr = expectParseExpr("-b"); std::string_view expected = - R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})"; + R"({"type":"AstExprUnary","location":"0,4 - 0,6","op":"Minus","expr":{"type":"AstExprGlobal","location":"0,5 - 0,6","global":"b"}})"; CHECK(toJson(expr) == expected); } @@ -259,7 +303,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile") AstStat* statement = expectParseStatement("while true do end"); std::string_view expected = - R"({"type":"AtStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})"; + R"({"type":"AstStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})"; CHECK(toJson(statement) == expected); } @@ -279,7 +323,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak") AstStat* statement = expectParseStatement("while true do break end"); std::string_view expected = - R"({"type":"AtStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})"; + R"({"type":"AstStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})"; CHECK(toJson(statement) == expected); } @@ -289,7 +333,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue") AstStat* statement = expectParseStatement("while true do continue end"); std::string_view expected = - R"({"type":"AtStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})"; + R"({"type":"AstStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})"; CHECK(toJson(statement) == expected); } @@ -299,7 +343,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor") AstStat* statement = expectParseStatement("for a=0,1 do end"); std::string_view expected = - R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"type":null,"name":"a","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})"; + R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})"; CHECK(toJson(statement) == expected); } @@ -309,7 +353,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn") AstStat* statement = expectParseStatement("for a in b do end"); std::string_view expected = - R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"type":null,"name":"a","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})"; + R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})"; CHECK(toJson(statement) == expected); } @@ -329,7 +373,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction") AstStat* statement = expectParseStatement("local function a(b) return end"); std::string_view expected = - R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"type":null,"name":"a","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"type":null,"name":"b","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})"; + R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"luauType":null,"name":"a","type":"AstLocal","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})"; CHECK(toJson(statement) == expected); } @@ -349,7 +393,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction") AstStat* statement = expectParseStatement("declare function foo(x: number): string"); std::string_view expected = - R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})"; + R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","name":"foo","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","parameters":[]}]},"retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","parameters":[]}]},"generics":[],"genericPacks":[]})"; CHECK(toJson(statement) == expected); } @@ -370,11 +414,11 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass") REQUIRE(2 == root->body.size); std::string_view expected1 = - R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})"; + R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","parameters":[]}]}}}]})"; CHECK(toJson(root->body.data[0]) == expected1); std::string_view expected2 = - R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})"; + R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","parameters":[]}}]})"; CHECK(toJson(root->body.data[1]) == expected2); } @@ -383,7 +427,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation") AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); std::string_view expected = - R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"types":[]}}]},"exported":false})"; + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"type":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,35","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","parameters":[]}]}]}},{"type":"AstTypeFunction","location":"0,41 - 0,55","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[]}}]},"exported":false})"; CHECK(toJson(statement) == expected); } @@ -411,7 +455,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypePackExplicit") CHECK(2 == root->body.size); std::string_view expected = - R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"type":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","location":"2,14 - 2,15"}],"values":[]})"; + R"({"type":"AstStatLocal","location":"2,8 - 2,36","vars":[{"luauType":{"type":"AstTypeReference","location":"2,17 - 2,36","name":"A","parameters":[{"type":"AstTypePackExplicit","location":"2,19 - 2,20","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"2,20 - 2,26","name":"number","parameters":[]},{"type":"AstTypeReference","location":"2,28 - 2,34","name":"string","parameters":[]}]}}]},"name":"a","type":"AstLocal","location":"2,14 - 2,15"}],"values":[]})"; CHECK(toJson(root->body.data[1]) == expected); } diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index 50dcbad..02e02e6 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -13,77 +13,6 @@ using namespace Luau; TEST_SUITE_BEGIN("NonstrictModeTests"); -TEST_CASE_FIXTURE(Fixture, "globals") -{ - CheckResult result = check(R"( - --!nonstrict - foo = true - foo = "now i'm a string!" - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("any", toString(requireType("foo"))); -} - -TEST_CASE_FIXTURE(Fixture, "globals2") -{ - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - - CheckResult result = check(R"( - --!nonstrict - foo = function() return 1 end - foo = "now i'm a string!" - )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - - TypeMismatch* tm = get(result.errors[0]); - REQUIRE(tm); - CHECK_EQ("() -> number", toString(tm->wantedType)); - CHECK_EQ("string", toString(tm->givenType)); - CHECK_EQ("() -> number", toString(requireType("foo"))); -} - -TEST_CASE_FIXTURE(Fixture, "globals_everywhere") -{ - CheckResult result = check(R"( - --!nonstrict - foo = 1 - - if true then - bar = 2 - end - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK_EQ("any", toString(requireType("foo"))); - CHECK_EQ("any", toString(requireType("bar"))); -} - -TEST_CASE_FIXTURE(BuiltinsFixture, "function_returns_number_or_string") -{ - ScopedFastFlag sff[]{{"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}}; - - CheckResult result = check(R"( - --!nonstrict - local function f() - if math.random() > 0.5 then - return 5 - else - return "hi" - end - end - )"); - - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK("() -> number | string" == toString(requireType("f"))); -} - TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") { CheckResult result = check(R"( @@ -106,13 +35,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_nullary_function") REQUIRE_EQ(0, rets.size()); } -TEST_CASE_FIXTURE(Fixture, "first_return_type_dictates_number_of_return_types") +TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_could_return") { - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - CheckResult result = check(R"( --!nonstrict function getMinCardCountForWidth(width) @@ -127,18 +51,22 @@ TEST_CASE_FIXTURE(Fixture, "first_return_type_dictates_number_of_return_types") TypeId t = requireType("getMinCardCountForWidth"); REQUIRE(t); - REQUIRE_EQ("(any) -> number", toString(t)); + REQUIRE_EQ("(any) -> (...any)", toString(t)); } +#if 0 +// Maybe we want this? TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked") { CheckResult result = check(R"( - --!nonstrict function foo(x): number return 'hello' end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); + + REQUIRE_NE(*typeChecker.anyType, *requireType("foo")); } +#endif TEST_CASE_FIXTURE(Fixture, "function_parameters_are_any") { @@ -324,11 +252,6 @@ TEST_CASE_FIXTURE(Fixture, "delay_function_does_not_require_its_argument_to_retu TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok") { - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - CheckResult result = check(R"( --!nonstrict @@ -345,7 +268,7 @@ TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok") LUAU_REQUIRE_NO_ERRORS(result); - REQUIRE_EQ("((any) -> string) | {| foo: any |}", toString(getMainModule()->getModuleScope()->returnType)); + REQUIRE_EQ("any", toString(getMainModule()->getModuleScope()->returnType)); } TEST_CASE_FIXTURE(Fixture, "returning_insufficient_return_values") diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 69e7f07..84a5a38 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -621,7 +621,6 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type") { ScopedFastFlag sff[] = { {"LuauLowerBoundsCalculation", true}, - {"LuauReturnTypeInferenceInNonstrict", true}, }; check(R"( @@ -642,7 +641,7 @@ TEST_CASE_FIXTURE(Fixture, "normalize_module_return_type") end )"); - CHECK_EQ("(any, any) -> (any, any) -> any", toString(getMainModule()->getModuleScope()->returnType)); + CHECK_EQ("(any, any) -> (...any)", toString(getMainModule()->getModuleScope()->returnType)); } TEST_CASE_FIXTURE(Fixture, "return_type_is_not_a_constrained_intersection") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 6e6549d..a634ba0 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -677,11 +677,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "toposort_doesnt_break_mutual_recursion") TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it") { - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - CheckResult result = check(R"( --!nonstrict @@ -690,7 +685,7 @@ TEST_CASE_FIXTURE(Fixture, "check_function_before_lambda_that_uses_it") end return function() - return f() + return f():andThen() end )"); @@ -817,18 +812,14 @@ TEST_CASE_FIXTURE(Fixture, "calling_function_with_incorrect_argument_type_yields TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_leak_free_types") { - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - CheckResult result = check(R"( --!nonstrict - function Test(a): ...any + function Test(a) return 1, "" end + local tab = {} table.insert(tab, Test(1)); )"); @@ -1625,21 +1616,6 @@ TEST_CASE_FIXTURE(Fixture, "occurs_check_failure_in_function_return_type") CHECK(nullptr != get(result.errors[0])); } -TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack") -{ - ScopedFastFlag sff[]{ - {"LuauReturnTypeInferenceInNonstrict", true}, - {"LuauLowerBoundsCalculation", true}, - }; - - CheckResult result = check(R"( - local function f() return end - local g = function() return f() end - )"); - - LUAU_REQUIRE_NO_ERRORS(result); -} - TEST_CASE_FIXTURE(Fixture, "quantify_constrained_types") { ScopedFastFlag sff[]{ diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 56b807f..354b399 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -516,7 +516,7 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") CHECK_EQ(*typeChecker.nilType, *requireType("extra")); } -TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer") +TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_strict") { CheckResult result = check(R"( local t = {} @@ -531,6 +531,17 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer") CHECK_EQ("Cannot iterate over a table without indexer", ge->message); } +TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict") +{ + CheckResult result = check(Mode::Nonstrict, R"( + local t = {} + for k, v in t do + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(0, result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_iter_metamethod") { CheckResult result = check(R"( diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index dc68689..34afd56 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -343,6 +343,20 @@ TEST_CASE_FIXTURE(Fixture, "specialization_binds_with_prototypes_too_early") LUAU_REQUIRE_ERRORS(result); // Should not have any errors. } +TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_type_pack") +{ + ScopedFastFlag sff[] = { + {"LuauLowerBoundsCalculation", false}, + }; + + CheckResult result = check(R"( + local function f() return end + local g = function() return f() end + )"); + + LUAU_REQUIRE_ERRORS(result); // Should not have any errors. +} + TEST_CASE_FIXTURE(Fixture, "weird_fail_to_unify_variadic_pack") { ScopedFastFlag sff[] = { diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 21ad4e1..d9bfc89 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2990,6 +2990,15 @@ TEST_CASE_FIXTURE(Fixture, "expected_indexer_value_type_extra_2") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "expected_indexer_from_table_union") +{ + ScopedFastFlag luauExpectedTableUnionIndexerType{"LuauExpectedTableUnionIndexerType", true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}} = {a = {2, 's'}})")); + LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {number | string}}? = {a = {2, 's'}})")); + LUAU_REQUIRE_NO_ERRORS(check(R"(local a: {[string]: {[string]: {string?}}?} = {["a"] = {["b"] = {"a", "b"}}})")); +} + TEST_CASE_FIXTURE(Fixture, "prop_access_on_key_whose_types_mismatches") { ScopedFastFlag sff{"LuauReportErrorsOnIndexerKeyMismatch", true}; diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 7d1fb56..858e8ac 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -85,20 +85,19 @@ TEST_CASE_FIXTURE(Fixture, "infer_in_nocheck_mode") { ScopedFastFlag sff[]{ {"DebugLuauDeferredConstraintResolution", false}, - {"LuauReturnTypeInferenceInNonstrict", true}, {"LuauLowerBoundsCalculation", true}, }; CheckResult result = check(R"( --!nocheck function f(x) - return 5 + return x end -- we get type information even if there's type errors f(1, 2) )"); - CHECK_EQ("(any) -> number", toString(requireType("f"))); + CHECK_EQ("(any) -> (...any)", toString(requireType("f"))); LUAU_REQUIRE_NO_ERRORS(result); } @@ -355,6 +354,35 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit") CHECK(nullptr != get(result.errors[0])); } +TEST_CASE_FIXTURE(Fixture, "globals") +{ + CheckResult result = check(R"( + --!nonstrict + foo = true + foo = "now i'm a string!" + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("any", toString(requireType("foo"))); +} + +TEST_CASE_FIXTURE(Fixture, "globals2") +{ + CheckResult result = check(R"( + --!nonstrict + foo = function() return 1 end + foo = "now i'm a string!" + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("() -> (...any)", toString(tm->wantedType)); + CHECK_EQ("string", toString(tm->givenType)); + CHECK_EQ("() -> (...any)", toString(requireType("foo"))); +} + TEST_CASE_FIXTURE(Fixture, "globals_are_banned_in_strict_mode") { CheckResult result = check(R"( diff --git a/tests/conformance/nextvar.lua b/tests/conformance/tables.lua similarity index 96% rename from tests/conformance/nextvar.lua rename to tests/conformance/tables.lua index 93c4ddf..0eff854 100644 --- a/tests/conformance/nextvar.lua +++ b/tests/conformance/tables.lua @@ -592,4 +592,24 @@ do assert(countud() == 3) end +-- test __newindex-as-a-table indirection: this had memory safety bugs in Lua 5.1.0 +do + local hit = false + + local grandparent = {} + grandparent.__newindex = function(s,k,v) + assert(k == "foo" and v == 10) + hit = true + end + + local parent = {} + parent.__newindex = parent + setmetatable(parent, grandparent) + + local child = setmetatable({}, parent) + child.foo = 10 + + assert(hit and child.foo == nil and parent.foo == nil) +end + return"OK" diff --git a/tests/main.cpp b/tests/main.cpp index c5b844b..e7c4aed 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -63,7 +63,7 @@ static int testAssertionHandler(const char* expr, const char* file, int line, co if (debuggerPresent()) LUAU_DEBUGBREAK(); - ADD_FAIL_AT(file, line, "Assertion failed: ", expr); + ADD_FAIL_AT(file, line, "Assertion failed: ", std::string(expr)); return 1; } diff --git a/tools/faillist.txt b/tools/faillist.txt new file mode 100644 index 0000000..dc74a6a --- /dev/null +++ b/tools/faillist.txt @@ -0,0 +1,521 @@ +AnnotationTests.as_expr_does_not_propagate_type_info +AnnotationTests.as_expr_is_bidirectional +AnnotationTests.as_expr_warns_on_unrelated_cast +AnnotationTests.builtin_types_are_not_exported +AnnotationTests.cannot_use_nonexported_type +AnnotationTests.cloned_interface_maintains_pointers_between_definitions +AnnotationTests.corecursive_types_error_on_tight_loop +AnnotationTests.define_generic_type_alias +AnnotationTests.duplicate_type_param_name +AnnotationTests.for_loop_counter_annotation_is_checked +AnnotationTests.function_return_annotations_are_checked +AnnotationTests.generic_aliases_are_cloned_properly +AnnotationTests.instantiate_type_fun_should_not_trip_rbxassert +AnnotationTests.instantiation_clone_has_to_follow +AnnotationTests.interface_types_belong_to_interface_arena +AnnotationTests.luau_ice_triggers_an_ice +AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag +AnnotationTests.luau_ice_triggers_an_ice_exception_with_flag_handler +AnnotationTests.luau_ice_triggers_an_ice_handler +AnnotationTests.luau_print_is_magic_if_the_flag_is_set +AnnotationTests.luau_print_is_not_special_without_the_flag +AnnotationTests.occurs_check_on_cyclic_intersection_typevar +AnnotationTests.occurs_check_on_cyclic_union_typevar +AnnotationTests.self_referential_type_alias +AnnotationTests.too_many_type_params +AnnotationTests.two_type_params +AnnotationTests.type_alias_always_resolve_to_a_real_type +AnnotationTests.type_alias_B_should_check_with_another_aliases_until_a_non_aliased_type +AnnotationTests.type_alias_should_alias_to_number +AnnotationTests.type_aliasing_to_number_should_not_check_given_a_string +AnnotationTests.type_annotations_inside_function_bodies +AnnotationTests.type_assertion_expr +AnnotationTests.typeof_variable_type_annotation_should_return_its_type +AnnotationTests.use_generic_type_alias +AnnotationTests.use_type_required_from_another_file +AstQuery.last_argument_function_call_type +AstQuery::getDocumentationSymbolAtPosition.binding +AstQuery::getDocumentationSymbolAtPosition.event_callback_arg +AstQuery::getDocumentationSymbolAtPosition.overloaded_fn +AstQuery::getDocumentationSymbolAtPosition.prop +AutocompleteTest.argument_types +AutocompleteTest.arguments_to_global_lambda +AutocompleteTest.as_types +AutocompleteTest.autocomplete_boolean_singleton +AutocompleteTest.autocomplete_default_type_pack_parameters +AutocompleteTest.autocomplete_default_type_parameters +AutocompleteTest.autocomplete_documentation_symbols +AutocompleteTest.autocomplete_end_with_fn_exprs +AutocompleteTest.autocomplete_end_with_lambda +AutocompleteTest.autocomplete_explicit_type_pack +AutocompleteTest.autocomplete_first_function_arg_expected_type +AutocompleteTest.autocomplete_for_in_middle_keywords +AutocompleteTest.autocomplete_for_middle_keywords +AutocompleteTest.autocomplete_if_else_regression +AutocompleteTest.autocomplete_if_middle_keywords +AutocompleteTest.autocomplete_ifelse_expressions +AutocompleteTest.autocomplete_on_string_singletons +AutocompleteTest.autocomplete_oop_implicit_self +AutocompleteTest.autocomplete_repeat_middle_keyword +AutocompleteTest.autocomplete_string_singleton_equality +AutocompleteTest.autocomplete_string_singleton_escape +AutocompleteTest.autocomplete_string_singletons +AutocompleteTest.autocomplete_until_expression +AutocompleteTest.autocomplete_until_in_repeat +AutocompleteTest.autocomplete_while_middle_keywords +AutocompleteTest.autocompleteProp_index_function_metamethod_is_variadic +AutocompleteTest.bias_toward_inner_scope +AutocompleteTest.comments +AutocompleteTest.cyclic_table +AutocompleteTest.do_not_overwrite_context_sensitive_kws +AutocompleteTest.do_not_suggest_internal_module_type +AutocompleteTest.do_not_suggest_synthetic_table_name +AutocompleteTest.dont_offer_any_suggestions_from_the_end_of_a_comment +AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment +AutocompleteTest.dont_offer_any_suggestions_from_within_a_broken_comment_at_the_very_end_of_the_file +AutocompleteTest.dont_offer_any_suggestions_from_within_a_comment +AutocompleteTest.dont_suggest_local_before_its_definition +AutocompleteTest.empty_program +AutocompleteTest.function_expr_params +AutocompleteTest.function_in_assignment_has_parentheses +AutocompleteTest.function_in_assignment_has_parentheses_2 +AutocompleteTest.function_parameters +AutocompleteTest.function_result_passed_to_function_has_parentheses +AutocompleteTest.function_type_types +AutocompleteTest.generic_types +AutocompleteTest.get_member_completions +AutocompleteTest.get_string_completions +AutocompleteTest.get_suggestions_for_new_statement +AutocompleteTest.get_suggestions_for_the_very_start_of_the_script +AutocompleteTest.global_function_params +AutocompleteTest.global_functions_are_not_scoped_lexically +AutocompleteTest.if_then_else_elseif_completions +AutocompleteTest.if_then_else_full_keywords +AutocompleteTest.keyword_members +AutocompleteTest.keyword_methods +AutocompleteTest.keyword_types +AutocompleteTest.leave_numbers_alone +AutocompleteTest.library_non_self_calls_are_fine +AutocompleteTest.library_self_calls_are_invalid +AutocompleteTest.local_function +AutocompleteTest.local_function_params +AutocompleteTest.local_functions_fall_out_of_scope +AutocompleteTest.local_initializer +AutocompleteTest.local_initializer_2 +AutocompleteTest.local_names +AutocompleteTest.local_types_builtin +AutocompleteTest.method_call_inside_function_body +AutocompleteTest.method_call_inside_if_conditional +AutocompleteTest.module_type_members +AutocompleteTest.modules_with_types +AutocompleteTest.nested_member_completions +AutocompleteTest.nested_recursive_function +AutocompleteTest.no_function_name_suggestions +AutocompleteTest.no_incompatible_self_calls +AutocompleteTest.no_incompatible_self_calls_2 +AutocompleteTest.no_incompatible_self_calls_on_class +AutocompleteTest.no_incompatible_self_calls_provisional +AutocompleteTest.not_the_var_we_are_defining +AutocompleteTest.optional_members +AutocompleteTest.private_types +AutocompleteTest.recommend_statement_starting_keywords +AutocompleteTest.recursive_function +AutocompleteTest.recursive_function_global +AutocompleteTest.recursive_function_local +AutocompleteTest.return_types +AutocompleteTest.skip_current_local +AutocompleteTest.sometimes_the_metatable_is_an_error +AutocompleteTest.source_module_preservation_and_invalidation +AutocompleteTest.statement_between_two_statements +AutocompleteTest.stop_at_first_stat_when_recommending_keywords +AutocompleteTest.string_prim_non_self_calls_are_avoided +AutocompleteTest.string_prim_self_calls_are_fine +AutocompleteTest.suggest_external_module_type +AutocompleteTest.suggest_table_keys +AutocompleteTest.table_intersection +AutocompleteTest.table_union +AutocompleteTest.type_correct_argument_type_suggestion +AutocompleteTest.type_correct_expected_argument_type_pack_suggestion +AutocompleteTest.type_correct_expected_argument_type_suggestion +AutocompleteTest.type_correct_expected_argument_type_suggestion_optional +AutocompleteTest.type_correct_expected_argument_type_suggestion_self +AutocompleteTest.type_correct_expected_return_type_pack_suggestion +AutocompleteTest.type_correct_expected_return_type_suggestion +AutocompleteTest.type_correct_full_type_suggestion +AutocompleteTest.type_correct_function_no_parenthesis +AutocompleteTest.type_correct_function_return_types +AutocompleteTest.type_correct_function_type_suggestion +AutocompleteTest.type_correct_keywords +AutocompleteTest.type_correct_local_type_suggestion +AutocompleteTest.type_correct_sealed_table +AutocompleteTest.type_correct_suggestion_for_overloads +AutocompleteTest.type_correct_suggestion_in_argument +AutocompleteTest.type_correct_suggestion_in_table +AutocompleteTest.type_scoping_easy +AutocompleteTest.unsealed_table +AutocompleteTest.unsealed_table_2 +AutocompleteTest.user_defined_globals +AutocompleteTest.user_defined_local_functions_in_own_definition +BuiltinDefinitionsTest.lib_documentation_symbols +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 +BuiltinTests.builtin_tables_sealed +BuiltinTests.coroutine_resume_anything_goes +BuiltinTests.coroutine_wrap_anything_goes +BuiltinTests.debug_info_is_crazy +BuiltinTests.debug_traceback_is_crazy +BuiltinTests.dont_add_definitions_to_persistent_types +BuiltinTests.find_capture_types +BuiltinTests.find_capture_types2 +BuiltinTests.find_capture_types3 +BuiltinTests.gcinfo +BuiltinTests.getfenv +BuiltinTests.global_singleton_types_are_sealed +BuiltinTests.gmatch_capture_types +BuiltinTests.gmatch_capture_types2 +BuiltinTests.gmatch_capture_types_balanced_escaped_parens +BuiltinTests.gmatch_capture_types_default_capture +BuiltinTests.gmatch_capture_types_invalid_pattern_fallback_to_builtin +BuiltinTests.gmatch_capture_types_invalid_pattern_fallback_to_builtin2 +BuiltinTests.gmatch_capture_types_leading_end_bracket_is_part_of_set +BuiltinTests.gmatch_capture_types_parens_in_sets_are_ignored +BuiltinTests.gmatch_capture_types_set_containing_lbracket +BuiltinTests.gmatch_definition +BuiltinTests.ipairs_iterator_should_infer_types_and_type_check +BuiltinTests.lua_51_exported_globals_all_exist +BuiltinTests.match_capture_types +BuiltinTests.match_capture_types2 +BuiltinTests.math_max_checks_for_numbers +BuiltinTests.math_max_variatic +BuiltinTests.math_things_are_defined +BuiltinTests.next_iterator_should_infer_types_and_type_check +BuiltinTests.no_persistent_typelevel_change +BuiltinTests.os_time_takes_optional_date_table +BuiltinTests.pairs_iterator_should_infer_types_and_type_check +BuiltinTests.see_thru_select +BuiltinTests.see_thru_select_count +BuiltinTests.select_on_variadic +BuiltinTests.select_slightly_out_of_range +BuiltinTests.select_way_out_of_range +BuiltinTests.select_with_decimal_argument_is_rounded_down +BuiltinTests.select_with_variadic_typepack_tail +BuiltinTests.select_with_variadic_typepack_tail_and_string_head +BuiltinTests.set_metatable_needs_arguments +BuiltinTests.setmetatable_should_not_mutate_persisted_types +BuiltinTests.setmetatable_unpacks_arg_types_correctly +BuiltinTests.sort +BuiltinTests.sort_with_bad_predicate +BuiltinTests.sort_with_predicate +BuiltinTests.string_format_arg_count_mismatch +BuiltinTests.string_format_arg_types_inference +BuiltinTests.string_format_as_method +BuiltinTests.string_format_correctly_ordered_types +BuiltinTests.string_format_report_all_type_errors_at_correct_positions +BuiltinTests.string_format_use_correct_argument +BuiltinTests.string_format_use_correct_argument2 +BuiltinTests.string_lib_self_noself +BuiltinTests.table_concat_returns_string +BuiltinTests.table_dot_remove_optionally_returns_generic +BuiltinTests.table_freeze_is_generic +BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload +BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload +BuiltinTests.table_pack +BuiltinTests.table_pack_reduce +BuiltinTests.table_pack_variadic +BuiltinTests.thread_is_a_type +BuiltinTests.tonumber_returns_optional_number_type +BuiltinTests.tonumber_returns_optional_number_type2 +BuiltinTests.xpcall +DefinitionTests.class_definition_function_prop +DefinitionTests.class_definitions_cannot_extend_non_class +DefinitionTests.class_definitions_cannot_overload_non_function +DefinitionTests.declaring_generic_functions +DefinitionTests.definition_file_class_function_args +DefinitionTests.definition_file_classes +DefinitionTests.definition_file_loading +DefinitionTests.definitions_documentation_symbols +DefinitionTests.documentation_symbols_dont_attach_to_persistent_types +DefinitionTests.load_definition_file_errors_do_not_pollute_global_scope +DefinitionTests.no_cyclic_defined_classes +DefinitionTests.single_class_type_identity_in_global_types +FrontendTest.accumulate_cached_errors +FrontendTest.accumulate_cached_errors_in_consistent_order +FrontendTest.any_annotation_breaks_cycle +FrontendTest.ast_node_at_position +FrontendTest.automatically_check_cyclically_dependent_scripts +FrontendTest.automatically_check_dependent_scripts +FrontendTest.check_without_builtin_next +FrontendTest.clearStats +FrontendTest.cycle_detection_between_check_and_nocheck +FrontendTest.cycle_detection_disabled_in_nocheck +FrontendTest.cycle_error_paths +FrontendTest.cycle_errors_can_be_fixed +FrontendTest.cycle_incremental_type_surface +FrontendTest.cycle_incremental_type_surface_longer +FrontendTest.discard_type_graphs +FrontendTest.dont_recheck_script_that_hasnt_been_marked_dirty +FrontendTest.dont_reparse_clean_file_when_linting +FrontendTest.environments +FrontendTest.find_a_require +FrontendTest.find_a_require_inside_a_function +FrontendTest.ignore_require_to_nonexistent_file +FrontendTest.imported_table_modification_2 +FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded +FrontendTest.no_use_after_free_with_type_fun_instantiation +FrontendTest.nocheck_cycle_used_by_checked +FrontendTest.nocheck_modules_are_typed +FrontendTest.produce_errors_for_unchanged_file_with_a_syntax_error +FrontendTest.produce_errors_for_unchanged_file_with_errors +FrontendTest.re_report_type_error_in_required_file +FrontendTest.real_source +FrontendTest.recheck_if_dependent_script_is_dirty +FrontendTest.report_require_to_nonexistent_file +FrontendTest.report_syntax_error_in_required_file +FrontendTest.reports_errors_from_multiple_sources +FrontendTest.stats_are_not_reset_between_checks +FrontendTest.test_lint_uses_correct_config +FrontendTest.test_pruneParentSegments +FrontendTest.trace_requires_in_nonstrict_mode +FrontendTest.typecheck_twice_for_ast_types +isSubtype.functions_and_any +isSubtype.intersection_of_functions_of_different_arities +isSubtype.intersection_of_tables +isSubtype.table_with_any_prop +isSubtype.table_with_table_prop +isSubtype.tables +Linter.BuiltinGlobalWrite +Linter.DeprecatedApi +Linter.LocalShadowGlobal +Linter.TableOperations +Linter.use_all_parent_scopes_for_globals +ModuleTests.any_persistance_does_not_leak +ModuleTests.builtin_types_point_into_globalTypes_arena +ModuleTests.clone_self_property +ModuleTests.deepClone_cyclic_table +NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything +NonstrictModeTests.for_in_iterator_variables_are_any +NonstrictModeTests.function_parameters_are_any +NonstrictModeTests.inconsistent_module_return_types_are_ok +NonstrictModeTests.inconsistent_return_types_are_ok +NonstrictModeTests.infer_nullary_function +NonstrictModeTests.infer_the_maximum_number_of_values_the_function_could_return +NonstrictModeTests.inline_table_props_are_also_any +NonstrictModeTests.local_tables_are_not_any +NonstrictModeTests.locals_are_any_by_default +NonstrictModeTests.offer_a_hint_if_you_use_a_dot_instead_of_a_colon +NonstrictModeTests.parameters_having_type_any_are_optional +NonstrictModeTests.returning_insufficient_return_values +NonstrictModeTests.returning_too_many_values +NonstrictModeTests.table_dot_insert_and_recursive_calls +NonstrictModeTests.table_props_are_any +Normalize.any_wins_the_battle_over_unknown_in_unions +Normalize.constrained_intersection_of_intersections +Normalize.cyclic_intersection +Normalize.cyclic_table_is_marked_normal +Normalize.cyclic_table_is_not_marked_normal +Normalize.cyclic_table_normalizes_sensibly +Normalize.cyclic_union +Normalize.fuzz_failure_bound_type_is_normal_but_not_its_bounded_to +Normalize.fuzz_failure_instersection_combine_must_follow +Normalize.higher_order_function +Normalize.intersection_combine_on_bound_self +Normalize.intersection_inside_a_table_inside_another_intersection +Normalize.intersection_inside_a_table_inside_another_intersection_2 +Normalize.intersection_inside_a_table_inside_another_intersection_3 +Normalize.intersection_inside_a_table_inside_another_intersection_4 +Normalize.intersection_of_confluent_overlapping_tables +Normalize.intersection_of_disjoint_tables +Normalize.intersection_of_functions +Normalize.intersection_of_overlapping_tables +Normalize.intersection_of_tables_with_indexers +Normalize.nested_table_normalization_with_non_table__no_ice +Normalize.normalization_does_not_convert_ever +Normalize.normalize_module_return_type +Normalize.normalize_unions_containing_never +Normalize.normalize_unions_containing_unknown +Normalize.return_type_is_not_a_constrained_intersection +Normalize.skip_force_normal_on_external_types +Normalize.union_of_distinct_free_types +Normalize.variadic_tail_is_marked_normal +Normalize.visiting_a_type_twice_is_not_considered_normal +ParseErrorRecovery.empty_function_type_error_recovery +ParseErrorRecovery.extra_table_indexer_recovery +ParseErrorRecovery.extra_token_in_consume +ParseErrorRecovery.extra_token_in_consume_match +ParseErrorRecovery.extra_token_in_consume_match_end +ParseErrorRecovery.generic_type_list_recovery +ParseErrorRecovery.multiple_parse_errors +ParseErrorRecovery.recovery_of_parenthesized_expressions +ParseErrorRecovery.statement_error_recovery_expected +ParseErrorRecovery.statement_error_recovery_unexpected +ParserTests.break_return_not_last_error +ParserTests.continue_not_last_error +ParserTests.error_on_confusable +ParserTests.error_on_non_utf8_sequence +ParserTests.error_on_unicode +ParserTests.export_is_an_identifier_only_when_followed_by_type +ParserTests.functions_cannot_have_return_annotations_if_extensions_are_disabled +ParserTests.illegal_type_alias_if_extensions_are_disabled +ParserTests.incomplete_statement_error +ParserTests.local_cannot_have_annotation_with_extensions_disabled +ParserTests.parse_compound_assignment_error_call +ParserTests.parse_compound_assignment_error_multiple +ParserTests.parse_compound_assignment_error_not_lvalue +ParserTests.parse_error_function_call +ParserTests.parse_error_function_call_newline +ParserTests.parse_error_messages +ParserTests.parse_error_table_literal +ParserTests.parse_error_type_name +ParserTests.parse_nesting_based_end_detection +ParserTests.parse_nesting_based_end_detection_failsafe_earlier +ParserTests.parse_nesting_based_end_detection_local_function +ParserTests.parse_nesting_based_end_detection_local_repeat +ParserTests.parse_nesting_based_end_detection_nested +ParserTests.parse_nesting_based_end_detection_single_line +ParserTests.parse_numbers_error +ParserTests.parse_numbers_range_error +ParserTests.stop_if_line_ends_with_hyphen +ParserTests.type_alias_error_messages +RuntimeLimits.typescript_port_of_Result_type +ToDot.bound_table +ToDot.class +ToDot.function +ToDot.metatable +ToDot.primitive +ToDot.table +ToString.exhaustive_toString_of_cyclic_table +ToString.function_type_with_argument_names +ToString.function_type_with_argument_names_and_self +ToString.function_type_with_argument_names_generic +ToString.named_metatable_toStringNamedFunction +ToString.no_parentheses_around_cyclic_function_type_in_union +ToString.toStringDetailed2 +ToString.toStringErrorPack +ToString.toStringNamedFunction_generic_pack +ToString.toStringNamedFunction_hide_type_params +ToString.toStringNamedFunction_id +ToString.toStringNamedFunction_map +ToString.toStringNamedFunction_overrides_param_names +ToString.toStringNamedFunction_variadics +TranspilerTests.attach_types +TranspilerTests.type_lists_should_be_emitted_correctly +TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive +TypeAliases.basic_alias +TypeAliases.cannot_steal_hoisted_type_alias +TypeAliases.cli_38393_recursive_intersection_oom +TypeAliases.corecursive_function_types +TypeAliases.corecursive_types_generic +TypeAliases.cyclic_function_type_in_type_alias +TypeAliases.cyclic_types_of_named_table_fields_do_not_expand_when_stringified +TypeAliases.do_not_quantify_unresolved_aliases +TypeAliases.dont_stop_typechecking_after_reporting_duplicate_type_definition +TypeAliases.export_type_and_type_alias_are_duplicates +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.free_variables_from_typeof_in_aliases +TypeAliases.general_require_multi_assign +TypeAliases.generic_param_remap +TypeAliases.generic_typevars_are_not_considered_to_escape_their_scope_if_they_are_reused_in_multiple_aliases +TypeAliases.module_export_free_type_leak +TypeAliases.module_export_wrapped_free_type_leak +TypeAliases.mutually_recursive_aliases +TypeAliases.mutually_recursive_generic_aliases +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_restriction_ok +TypeAliases.mutually_recursive_types_swapsies_not_ok +TypeAliases.mutually_recursive_types_swapsies_ok +TypeAliases.names_are_ascribed +TypeAliases.non_recursive_aliases_that_reuse_a_generic_name +TypeAliases.recursive_types_restriction_not_ok +TypeAliases.recursive_types_restriction_ok +TypeAliases.reported_location_is_correct_when_type_alias_are_duplicates +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_import_mutation +TypeAliases.type_alias_local_mutation +TypeAliases.type_alias_local_rename +TypeAliases.type_alias_local_synthetic_mutation +TypeAliases.type_alias_of_an_imported_recursive_generic_type +TypeAliases.type_alias_of_an_imported_recursive_type +TypeAliases.use_table_name_and_generic_params_in_errors +TypeInferAnyError.any_type_propagates +TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any +TypeInferAnyError.call_to_any_yields_any +TypeInferAnyError.calling_error_type_yields_error +TypeInferAnyError.can_get_length_of_any +TypeInferAnyError.can_subscript_any +TypeInferAnyError.CheckMethodsOfAny +TypeInferAnyError.for_in_loop_iterator_is_any +TypeInferAnyError.for_in_loop_iterator_is_any2 +TypeInferAnyError.for_in_loop_iterator_is_error +TypeInferAnyError.for_in_loop_iterator_is_error2 +TypeInferAnyError.for_in_loop_iterator_returns_any +TypeInferAnyError.for_in_loop_iterator_returns_any2 +TypeInferAnyError.indexing_error_type_does_not_produce_an_error +TypeInferAnyError.length_of_error_type_does_not_produce_an_error +TypeInferAnyError.metatable_of_any_can_be_a_table +TypeInferAnyError.prop_access_on_any_with_other_options +TypeInferAnyError.quantify_any_does_not_bind_to_itself +TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any +TypeInferAnyError.type_error_addition +TypeInferClasses.assign_to_prop_of_class +TypeInferClasses.call_base_method +TypeInferClasses.call_instance_method +TypeInferClasses.call_method_of_a_child_class +TypeInferClasses.call_method_of_a_class +TypeInferClasses.can_assign_to_prop_of_base_class +TypeInferClasses.can_assign_to_prop_of_base_class_using_string +TypeInferClasses.can_read_prop_of_base_class +TypeInferClasses.can_read_prop_of_base_class_using_string +TypeInferClasses.cannot_call_method_of_child_on_base_instance +TypeInferClasses.cannot_call_unknown_method_of_a_class +TypeInferClasses.cannot_unify_class_instance_with_primitive +TypeInferClasses.class_type_mismatch_with_name_conflict +TypeInferClasses.class_unification_type_mismatch_is_correct_order +TypeInferClasses.classes_can_have_overloaded_operators +TypeInferClasses.classes_without_overloaded_operators_cannot_be_added +TypeInferClasses.detailed_class_unification_error +TypeInferClasses.function_arguments_are_covariant +TypeInferClasses.higher_order_function_arguments_are_contravariant +TypeInferClasses.higher_order_function_return_type_is_not_contravariant +TypeInferClasses.higher_order_function_return_values_are_covariant +TypeInferClasses.optional_class_field_access_error +TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties +TypeInferClasses.table_indexers_are_invariant +TypeInferClasses.table_properties_are_invariant +TypeInferClasses.warn_when_prop_almost_matches +TypeInferClasses.we_can_infer_that_a_parameter_must_be_a_particular_class +TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class +TypeInferFunctions.another_indirect_function_case_where_it_is_ok_to_provide_too_many_arguments +TypeInferFunctions.another_recursive_local_function +TypeInferFunctions.cannot_hoist_interior_defns_into_signature +TypeInferFunctions.check_function_before_lambda_that_uses_it +TypeInferFunctions.complicated_return_types_require_an_explicit_annotation +TypeInferFunctions.cyclic_function_type_in_args +TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists +TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict +TypeInferFunctions.first_argument_can_be_optional +TypeInferFunctions.func_expr_doesnt_leak_free +TypeInferFunctions.higher_order_function_2 +TypeInferFunctions.higher_order_function_4 +TypeInferFunctions.infer_return_type_from_selected_overload +TypeInferFunctions.infer_that_function_does_not_return_a_table +TypeInferFunctions.it_is_ok_not_to_supply_enough_retvals +TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument +TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count +TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count +TypeInferFunctions.mutual_recursion +TypeInferFunctions.recursive_function +TypeInferFunctions.recursive_local_function +TypeInferFunctions.too_many_arguments +TypeInferFunctions.toposort_doesnt_break_mutual_recursion +TypeInferFunctions.vararg_function_is_quantified +TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size diff --git a/tools/natvis/Analysis.natvis b/tools/natvis/Analysis.natvis index b9ea314..7d03dd3 100644 --- a/tools/natvis/Analysis.natvis +++ b/tools/natvis/Analysis.natvis @@ -38,6 +38,38 @@ {{ typeId=29, value={*($T30*)storage} }} {{ typeId=30, value={*($T31*)storage} }} {{ typeId=31, value={*($T32*)storage} }} + {{ typeId=32, value={*($T33*)storage} }} + {{ typeId=33, value={*($T34*)storage} }} + {{ typeId=34, value={*($T35*)storage} }} + {{ typeId=35, value={*($T36*)storage} }} + {{ typeId=36, value={*($T37*)storage} }} + {{ typeId=37, value={*($T38*)storage} }} + {{ typeId=38, value={*($T39*)storage} }} + {{ typeId=39, value={*($T40*)storage} }} + {{ typeId=40, value={*($T41*)storage} }} + {{ typeId=41, value={*($T42*)storage} }} + {{ typeId=42, value={*($T43*)storage} }} + {{ typeId=43, value={*($T44*)storage} }} + {{ typeId=44, value={*($T45*)storage} }} + {{ typeId=45, value={*($T46*)storage} }} + {{ typeId=46, value={*($T47*)storage} }} + {{ typeId=47, value={*($T48*)storage} }} + {{ typeId=48, value={*($T49*)storage} }} + {{ typeId=49, value={*($T50*)storage} }} + {{ typeId=50, value={*($T51*)storage} }} + {{ typeId=51, value={*($T52*)storage} }} + {{ typeId=52, value={*($T53*)storage} }} + {{ typeId=53, value={*($T54*)storage} }} + {{ typeId=54, value={*($T55*)storage} }} + {{ typeId=55, value={*($T56*)storage} }} + {{ typeId=56, value={*($T57*)storage} }} + {{ typeId=57, value={*($T58*)storage} }} + {{ typeId=58, value={*($T59*)storage} }} + {{ typeId=59, value={*($T60*)storage} }} + {{ typeId=60, value={*($T61*)storage} }} + {{ typeId=61, value={*($T62*)storage} }} + {{ typeId=62, value={*($T63*)storage} }} + {{ typeId=63, value={*($T64*)storage} }} typeId *($T1*)storage @@ -72,6 +104,38 @@ *($T30*)storage *($T31*)storage *($T32*)storage + *($T33*)storage + *($T34*)storage + *($T35*)storage + *($T36*)storage + *($T37*)storage + *($T38*)storage + *($T39*)storage + *($T40*)storage + *($T41*)storage + *($T42*)storage + *($T43*)storage + *($T44*)storage + *($T45*)storage + *($T46*)storage + *($T47*)storage + *($T48*)storage + *($T49*)storage + *($T50*)storage + *($T51*)storage + *($T52*)storage + *($T53*)storage + *($T54*)storage + *($T55*)storage + *($T56*)storage + *($T57*)storage + *($T58*)storage + *($T59*)storage + *($T60*)storage + *($T61*)storage + *($T62*)storage + *($T63*)storage + *($T64*)storage diff --git a/tools/test_dcr.py b/tools/test_dcr.py new file mode 100644 index 0000000..8b090bc --- /dev/null +++ b/tools/test_dcr.py @@ -0,0 +1,124 @@ +# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +import argparse +import os.path +import subprocess as sp +import sys +import xml.sax as x + +SCRIPT_PATH = os.path.split(sys.argv[0])[0] +FAIL_LIST_PATH = os.path.join(SCRIPT_PATH, "faillist.txt") + + +def loadFailList(): + with open(FAIL_LIST_PATH) as f: + return set(map(str.strip, f.readlines())) + + +class Handler(x.ContentHandler): + def __init__(self, failList): + self.currentTest = [] + self.failList = failList # Set of dotted test names that are expected to fail + + self.results = {} # {DottedName: TrueIfTheTestPassed} + + def startElement(self, name, attrs): + if name == "TestSuite": + self.currentTest.append(attrs["name"]) + elif name == "TestCase": + self.currentTest.append(attrs["name"]) + + elif name == "OverallResultsAsserts": + if self.currentTest: + try: + failed = 0 != int(attrs["failures"]) + except ValueError: + failed = False + + dottedName = ".".join(self.currentTest) + shouldFail = dottedName in self.failList + + if failed and not shouldFail: + print("UNEXPECTED: {} should have passed".format(dottedName)) + elif not failed and shouldFail: + print("UNEXPECTED: {} should have failed".format(dottedName)) + + self.results[dottedName] = not failed + + def endElement(self, name): + if name == "TestCase": + self.currentTest.pop() + + elif name == "TestSuite": + self.currentTest.pop() + + +def main(): + parser = argparse.ArgumentParser( + description="Run Luau.UnitTest with deferred constraint resolution enabled" + ) + parser.add_argument( + "path", action="store", help="Path to the Luau.UnitTest executable" + ) + parser.add_argument( + "--dump", + dest="dump", + action="store_true", + help="Instead of doing any processing, dump the raw output of the test run. Useful for debugging this tool.", + ) + parser.add_argument( + "--write", + dest="write", + action="store_true", + help="Write a new faillist.txt after running tests.", + ) + + args = parser.parse_args() + + failList = loadFailList() + + p = sp.Popen( + [ + args.path, + "--reporters=xml", + "--fflags=true,DebugLuauDeferredConstraintResolution=true", + ], + stdout=sp.PIPE, + ) + + handler = Handler(failList) + + if args.dump: + for line in p.stdout: + sys.stdout.buffer.write(line) + return + else: + x.parse(p.stdout, handler) + + p.wait() + + if args.write: + newFailList = sorted( + ( + dottedName + for dottedName, passed in handler.results.items() + if not passed + ), + key=str.lower, + ) + with open(FAIL_LIST_PATH, "w", newline="\n") as f: + for name in newFailList: + print(name, file=f) + print("Updated faillist.txt") + + sys.exit( + 0 + if all( + not passed == (dottedName in failList) + for dottedName, passed in handler.results.items() + ) + else 1 + ) + +if __name__ == "__main__": + main() From 9be7f85be7ec227d63b150adfcdda9164fdc119c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 22 Jul 2022 07:53:16 -0700 Subject: [PATCH 2/6] Update performance.md (#608) Mention constant folding for builtins and remove the note about possibly doing inlining in the future because we do do it now! --- docs/_pages/performance.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/_pages/performance.md b/docs/_pages/performance.md index 34b24b0..c6ef2ad 100644 --- a/docs/_pages/performance.md +++ b/docs/_pages/performance.md @@ -29,7 +29,7 @@ Unlike Lua and LuaJIT, Luau uses a multi-pass compiler with a frontend that pars > Note: Compilation throughput isn't the main focus in Luau, but our compiler is reasonably fast; with all currently implemented optimizations enabled, it compiles 400K lines of Luau code in 0.5 seconds on a single core of a desktop Core i7 CPU, producing bytecode and debug information. -While bytecode optimizations are limited due to the flexibility of Luau code (e.g. `a * 1` may not be equivalent to `a` if `*` is overloaded through metatables), even in absence of type information Luau compiler can perform some optimizations such as "deep" constant folding across functions and local variables, perform upvalue optimizations for upvalues that aren't mutated, do analysis of builtin function usage, and some peephole optimizations on the resulting bytecode. In the future we plan to do bytecode-level inlining and possibly other code transformation. +While bytecode optimizations are limited due to the flexibility of Luau code (e.g. `a * 1` may not be equivalent to `a` if `*` is overloaded through metatables), even in absence of type information Luau compiler can perform some optimizations such as "deep" constant folding across functions and local variables, perform upvalue optimizations for upvalues that aren't mutated, do analysis of builtin function usage, and some peephole optimizations on the resulting bytecode. The compiler can also be instructed to use more aggressive optimizations by enabling optimization level 2 (`-O2` in CLI tools), some of which are documented further on this page. Luau compiler currently doesn't use type information to do further optimizations, however early experiments suggest that we can extract further wins. Because we control the entire stack (unlike e.g. TypeScript where the type information is discarded completely before reaching the VM), we have more flexibility there and can make some tradeoffs during codegen even if the type system isn't completely sound. For example, it might be reasonable to assume that in presence of known types, we can infer absence of side effects for arithmetic operations and builtins - if the runtime types mismatch due to intentional violation of the type safety through global injection, the code will still be safely sandboxed; this may unlock optimizations such as common subexpression elimination and allocation hoisting without a JIT. This is speculative pending further research. @@ -90,6 +90,8 @@ As a result, builtin calls are very fast in Luau - they are still slightly slowe > Note: The partial specialization mechanism is cute in that for `assert`, it only specializes on truthy conditions; hopefully performance of `assert(false)` isn't crucial for most code! +In addition to runtime optimizations for builtin calls, many builtin calls can also be constant-folded by the bytecode compiler when using aggressive optimizations (level 2); this currently applies to most builtin calls with constant arguments and a single return value. + ## Optimized table iteration Luau implements a fully generic iteration protocol; however, for iteration through tables in addition to generalized iteration (`for .. in t`) it recognizes three common idioms (`for .. in ipairs(t)`, `for .. in pairs(t)` and `for .. in next, t`) and emits specialized bytecode that is carefully optimized using custom internal iterators. From 12c550202719b5deef932453728f5643bed23eaa Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Fri, 22 Jul 2022 10:35:03 -0700 Subject: [PATCH 3/6] Update performance.md Update compiler performance metrics to account for O2 and expanding internal codebase --- docs/_pages/performance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_pages/performance.md b/docs/_pages/performance.md index c6ef2ad..73c7f60 100644 --- a/docs/_pages/performance.md +++ b/docs/_pages/performance.md @@ -27,7 +27,7 @@ Of course the interpreter isn't typical C code - it uses many tricks to achieve Unlike Lua and LuaJIT, Luau uses a multi-pass compiler with a frontend that parses source into an AST and a backend that generates bytecode from it. This carries a small penalty in terms of compilation time, but results in more flexible code and, crucially, makes it easier to optimize the generated bytecode. -> Note: Compilation throughput isn't the main focus in Luau, but our compiler is reasonably fast; with all currently implemented optimizations enabled, it compiles 400K lines of Luau code in 0.5 seconds on a single core of a desktop Core i7 CPU, producing bytecode and debug information. +> Note: Compilation throughput isn't the main focus in Luau, but our compiler is reasonably fast; with all currently implemented optimizations enabled, it compiles 950K lines of Luau code in 1 second on a single core of a desktop Ryzen 5900X CPU, producing bytecode and debug information. While bytecode optimizations are limited due to the flexibility of Luau code (e.g. `a * 1` may not be equivalent to `a` if `*` is overloaded through metatables), even in absence of type information Luau compiler can perform some optimizations such as "deep" constant folding across functions and local variables, perform upvalue optimizations for upvalues that aren't mutated, do analysis of builtin function usage, and some peephole optimizations on the resulting bytecode. The compiler can also be instructed to use more aggressive optimizations by enabling optimization level 2 (`-O2` in CLI tools), some of which are documented further on this page. From 2a6d1c03ace747824510b741094852a8c4b02d98 Mon Sep 17 00:00:00 2001 From: JohnnyMorganz Date: Mon, 25 Jul 2022 18:19:21 +0100 Subject: [PATCH 4/6] Store AstExprFunction in astTypes for stats (#612) --- Analysis/src/TypeInfer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index ea7d81e..d460160 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -3640,6 +3640,9 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); } } + + if (!currentModule->astTypes.find(&function)) + currentModule->astTypes[&function] = ty; } else ice("Checking non functional type"); From 185370fa1d24f8e8d12d37f9bd3b38c0e5d8f96c Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Thu, 28 Jul 2022 14:48:05 -0700 Subject: [PATCH 5/6] RFC: Update the rules on string interpolation in light of feedback (#615) We make four adjustments in this RFC: 1. `{{` is not allowed. This is likely a valid but poor attempt at escaping coming from C#, Rust, or Python. 2. We now allow `` `this` `` with zero interpolating expressions. 3. We now allow `` f `this` `` also. 4. Explicitly say that `` `this` `` and `` `this {that}` `` are not valid type annotation syntax. --- rfcs/syntax-string-interpolation.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/rfcs/syntax-string-interpolation.md b/rfcs/syntax-string-interpolation.md index 208143a..ad18262 100644 --- a/rfcs/syntax-string-interpolation.md +++ b/rfcs/syntax-string-interpolation.md @@ -31,9 +31,9 @@ Because we care about backward compatibility, we need some new syntax in order t 1. A string chunk (`` `...{ ``, `}...{`, and `` }...` ``) where `...` is a range of 0 to many characters. * `\` escapes `` ` ``, `{`, and itself `\`. - * Restriction: the string interpolation literal must have at least one value to interpolate. We do not need 3 ways to express a single line string literal. * The pairs must be on the same line (unless a `\` escapes the newline) but expressions needn't be on the same line. 2. An expression between the braces. This is the value that will be interpolated into the string. + * Restriction: we explicitly reject `{{` as it is considered an attempt to escape and get a single `{` character at runtime. 3. Formatting specification may follow after the expression, delimited by an unambiguous character. * Restriction: the formatting specification must be constant at parse time. * In the absence of an explicit formatting specification, the `%*` token will be used. @@ -61,7 +61,6 @@ local set2 = Set.new({0, 5, 4}) print(`{set1} ∪ {set2} = {Set.union(set1, set2)}`) --> {0, 1, 3} ∪ {0, 5, 4} = {0, 1, 3, 4, 5} --- For illustrative purposes. These are illegal specifically because they don't interpolate anything. print(`Some example escaping the braces \{like so}`) print(`backslash \ that escapes the space is not a part of the string...`) print(`backslash \\ will escape the second backslash...`) @@ -88,13 +87,25 @@ print(`Welcome to \ -- Luau! ``` -This expression will not be allowed to come after a `prefixexp`. I believe this is fully additive, so a future RFC may allow this. So for now, we explicitly reject the following: +This expression can also come after a `prefixexp`: ``` local name = "world" print`Hello {name}` ``` +The restriction on `{{` exists solely for the people coming from languages e.g. C#, Rust, or Python which uses `{{` to escape and get the character `{` at runtime. We're also rejecting this at parse time too, since the proper way to escape it is `\{`, so: + +```lua +print(`{{1, 2, 3}} = {myCoolSet}`) -- parse error +``` + +If we did not apply this as a parse error, then the above would wind up printing as the following, which is obviously a gotcha we can and should avoid. + +``` +--> table: 0xSOMEADDRESS = {1, 2, 3} +``` + Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called. ```lua @@ -121,6 +132,13 @@ print(string.format("%* %* %*", return_two_nils())) --> error: value #3 is missing, got 2 ``` +It must be said that we are not allowing this style of string literals in type annotations at this time, regardless of zero or many interpolating expressions, so the following two type annotations below are illegal syntax: + +```lua +local foo: `foo` +local bar: `bar{baz}` +``` + ## Drawbacks If we want to use backticks for other purposes, it may introduce some potential ambiguity. One option to solve that is to only ever produce string interpolation tokens from the context of an expression. This is messy but doable because the parser and the lexer are already implemented to work in tandem. The other option is to pick a different delimiter syntax to keep backticks available for use in the future. From cb165556085607146a624a8e4b89f4d393a077be Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Thu, 28 Jul 2022 14:48:17 -0700 Subject: [PATCH 6/6] RFC: Disallow `name T` and `name(T)` in future syntactic extensions for type annotations (#589) --- ...oposals-leading-to-ambiguity-in-grammar.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md diff --git a/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md b/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md new file mode 100644 index 0000000..d9c5c7d --- /dev/null +++ b/rfcs/disallow-proposals-leading-to-ambiguity-in-grammar.md @@ -0,0 +1,129 @@ +# Disallow `name T` and `name(T)` in future syntactic extensions for type annotations + +## Summary + +We propose to disallow the syntax `` `('`` as well as ` ` in future syntax extensions for type annotations to ensure that all existing programs continue to parse correctly. This still keeps the door open for future syntax extensions of different forms such as `` `<' `>'``. + +## Motivation + +Lua and by extension Luau's syntax is very free form, which means that when the parser finishes parsing a node, it doesn't try to look for a semi-colon or any termination token e.g. a `{` to start a block, or `;` to end a statement, or a newline, etc. It just immediately invokes the next parser to figure out how to parse the next node based on the remainder's starting token. + +That feature is sometimes quite troublesome when we want to add new syntax. + +We have had cases where we talked about using syntax like `setmetatable(T, MT)` and `keyof T`. They all look innocent, but when you look beyond that, and try to apply it onto Luau's grammar, things break down really fast. + +### `F(T)`? + +An example that _will_ cause a change in semantics: + +``` +local t: F +(u):m() +``` + +where today, `local t: F` is one statement, and `(u):m()` is another. If we had the syntax for `F(T)` here, it becomes invalid input because it gets parsed as + +``` +local t: F(u) +:m() +``` + +This is important because of the `setmetatable(T, MT)` case: + +``` +type Foo = setmetatable({ x: number }, { ... }) +``` + +For `setmetatable`, the parser isn't sure whether `{}` is actually a type or an expression, because _today_ `setmetatable` is parsed as a type reference, and `({}, {})` is the remainder that we'll attempt to parse as a statement. This means `{ x: number }` is invalid table _literal_. Recovery by backtracking is technically possible here, but this means performance loss on invalid input + may introduce false positives wrt how things are parsed. We'd much rather take a very strict stance about how things get parsed. + +### `F T`? + +An example that _will_ cause a change in semantics: + +``` +local function f(t): F T + (t or u):m() +end +``` + +where today, the return type annotation `F T` is simply parsed as just `F`, followed by a ambiguous parse error from the statement `T(t or u)` because its `(` is on the next line. If at some point in the future we were to allow `T` followed by `(` on the next line, then there's yet another semantic change. `F T` could be parsed as a type annotation and the first statement is `(t or u):m()` instead of `F` followed by `T(t or u):m()`. + +For `keyof`, here's a practical example of the above issue: + +``` +type Vec2 = {x: number, y: number} + +local function f(t, u): keyof Vec2 + (t or u):m() +end +``` + +There's three possible outcomes: + 1. Return type of `f` is `keyof`, statement throws a parse error because `(` is on the next line after `Vec2`, + 2. Return type of `f` is `keyof Vec2` and next statement is `(t or u):m()`, or + 3. Return type of `f` is `keyof` and next statement is `Vec2(t or u):m()` (if we allow `(` on the next line to be part of previous line). + +This particular case is even worse when we keep going: + +``` +local function f(t): F + T(t or u):m() +end +``` + +``` +local function f(t): F T + {1, 2, 3} +end +``` + +where today, `F` is the return type annotation of `f`, and `T(t or u):m()`/`T{1, 2, 3}` is the first statement, respectively. + +Adding some syntax for `F T` **will** cause the parser to change the semantics of the above three examples. + +### But what about `typeof(...)`? + +This syntax is grandfathered in because the parser supported `typeof(...)` before we stabilized our syntax, and especially before type annotations were released to the public, so we didn't need to worry about compatibility here. We are very glad that we used parentheses in this case, because it's natural for expressions to belong within parentheses `()`, and types to belong within angles `<>`. + +## The One Exception with a caveat + +This is a strict requirement! + +`function() -> ()` has been talked about in the past, and this one is different despite falling under the same category as `` `('``. The token `function` is in actual fact a "hard keyword," meaning that it cannot be parsed as a type annotation because it is not an identifier, just a keyword. + +Likewise, we also have talked about adding standalone `function` as a type annotation (semantics of it is irrelevant for this RFC) + +It's possible that we may end up adding both, but the requirements are as such: + 1. `function() -> ()` must be added first before standalone `function`, OR + 2. `function` can be added first, but with a future-proofing parse error if `<` or `(` follows after it + +If #1 is what ends up happening, there's not much to worry about because the type annotation parser will parse greedily already, so any new valid input will remain valid and have same semantics, except it also allows omitting of `(` and `<`. + +If #2 is what ends up happening, there could be a problem if we didn't future-proof against `<` and `(` to follow `function`: + +``` + return f :: function(T) -> U +``` + +which would be a parse error because at the point of `(` we expect one of `until`, `end`, or `EOF`, and + +``` + return f :: function(a) -> a +``` + +which would also be a parse error by the time we reach `->`, that is the production of the above is semantically equivalent to `(f < a) > (a)` which would compare whether the value of `f` is less than the value of `a`, then whether the result of that value is greater than `a`. + +## Alternatives + +Only allow these syntax when used inside parentheses e.g. `(F T)` or `(F(T))`. This makes it inconsistent with the existing `typeof(...)` type annotation, and changing that over is also breaking change. + +Support backtracking in the parser, so if `: MyType(t or u):m()` is invalid syntax, revert and parse `MyType` as a type, and `(t or u):m()` as an expression statement. Even so, this option is terrible for: + 1. parsing performance (backtracking means losing progress on invalid input), + 2. user experience (why was this annotation parsed as `X(...)` instead of `X` followed by a statement `(...)`), + 3. has false positives (`foo(bar)(baz)` may be parsed as `foo(bar)` as the type annotation and `(baz)` is the remainder to parse) + +## Drawbacks + +To be able to expose some kind of type-level operations using `F` syntax, means one of the following must be chosen: + 1. introduce the concept of "magic type functions" into type inference, or + 2. introduce them into the prelude as `export type F = ...` (where `...` is to be read as "we haven't decided")