diff --git a/.gitignore b/.gitignore index 5688dff5..ec5e6578 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /default.prof* /fuzz-* /luau +__pycache__ diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index dcfb14b4..9911c4d3 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -12,7 +12,8 @@ namespace Luau { -struct Scope2; +struct Scope; + struct TypeVar; using TypeId = const TypeVar*; @@ -38,7 +39,7 @@ struct GeneralizationConstraint { TypeId generalizedType; TypeId sourceType; - Scope2* scope; + Scope* scope; }; // subType ~ inst superType diff --git a/Analysis/include/Luau/ConstraintGraphBuilder.h b/Analysis/include/Luau/ConstraintGraphBuilder.h index 513446fd..695b6cd5 100644 --- a/Analysis/include/Luau/ConstraintGraphBuilder.h +++ b/Analysis/include/Luau/ConstraintGraphBuilder.h @@ -17,21 +17,22 @@ namespace Luau { -struct Scope2; +struct Scope; +using ScopePtr = std::shared_ptr; struct ConstraintGraphBuilder { // A list of all the scopes in the module. This vector holds ownership of the // scope pointers; the scopes themselves borrow pointers to other scopes to // define the scope hierarchy. - std::vector>> scopes; + std::vector> scopes; ModuleName moduleName; SingletonTypes& singletonTypes; const NotNull arena; // The root scope of the module we're generating constraints for. // This is null when the CGB is initially constructed. - Scope2* rootScope; + Scope* rootScope; // A mapping of AST node to TypeId. DenseHashMap astTypes{nullptr}; // A mapping of AST node to TypePackId. @@ -50,42 +51,42 @@ struct ConstraintGraphBuilder // Occasionally constraint generation needs to produce an ICE. const NotNull ice; - NotNull globalScope; + NotNull globalScope; - ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope); + ConstraintGraphBuilder(const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope); /** * Fabricates a new free type belonging to a given scope. * @param scope the scope the free type belongs to. */ - TypeId freshType(NotNull scope); + TypeId freshType(const ScopePtr& scope); /** * Fabricates a new free type pack belonging to a given scope. * @param scope the scope the free type pack belongs to. */ - TypePackId freshTypePack(NotNull scope); + TypePackId freshTypePack(const ScopePtr& scope); /** * Fabricates a scope that is a child of another scope. * @param location the lexical extent of the scope in the source code. * @param parent the parent scope of the new scope. Must not be null. */ - NotNull childScope(Location location, NotNull parent); + ScopePtr childScope(Location location, const ScopePtr& parent); /** * Adds a new constraint with no dependencies to a given scope. * @param scope the scope to add the constraint to. * @param cv the constraint variant to add. */ - void addConstraint(NotNull scope, ConstraintV cv); + void addConstraint(const ScopePtr& scope, ConstraintV cv); /** * Adds a constraint to a given scope. * @param scope the scope to add the constraint to. Must not be null. * @param c the constraint to add. */ - void addConstraint(NotNull scope, std::unique_ptr c); + void addConstraint(const ScopePtr& scope, std::unique_ptr c); /** * The entry point to the ConstraintGraphBuilder. This will construct a set @@ -94,23 +95,23 @@ struct ConstraintGraphBuilder */ void visit(AstStatBlock* block); - void visitBlockWithoutChildScope(NotNull scope, AstStatBlock* block); + void visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); - void visit(NotNull scope, AstStat* stat); - void visit(NotNull scope, AstStatBlock* block); - void visit(NotNull scope, AstStatLocal* local); - void visit(NotNull scope, AstStatFor* for_); - void visit(NotNull scope, AstStatLocalFunction* function); - void visit(NotNull scope, AstStatFunction* function); - void visit(NotNull scope, AstStatReturn* ret); - void visit(NotNull scope, AstStatAssign* assign); - void visit(NotNull scope, AstStatIf* ifStatement); - void visit(NotNull scope, AstStatTypeAlias* alias); + void visit(const ScopePtr& scope, AstStat* stat); + void visit(const ScopePtr& scope, AstStatBlock* block); + void visit(const ScopePtr& scope, AstStatLocal* local); + void visit(const ScopePtr& scope, AstStatFor* for_); + void visit(const ScopePtr& scope, AstStatLocalFunction* function); + void visit(const ScopePtr& scope, AstStatFunction* function); + void visit(const ScopePtr& scope, AstStatReturn* ret); + void visit(const ScopePtr& scope, AstStatAssign* assign); + void visit(const ScopePtr& scope, AstStatIf* ifStatement); + void visit(const ScopePtr& scope, AstStatTypeAlias* alias); - TypePackId checkExprList(NotNull scope, const AstArray& exprs); + TypePackId checkExprList(const ScopePtr& scope, const AstArray& exprs); - TypePackId checkPack(NotNull scope, AstArray exprs); - TypePackId checkPack(NotNull scope, AstExpr* expr); + TypePackId checkPack(const ScopePtr& scope, AstArray exprs); + TypePackId checkPack(const ScopePtr& scope, AstExpr* expr); /** * Checks an expression that is expected to evaluate to one type. @@ -118,13 +119,13 @@ struct ConstraintGraphBuilder * @param expr the expression to check. * @return the type of the expression. */ - TypeId check(NotNull scope, AstExpr* expr); + TypeId check(const ScopePtr& scope, AstExpr* expr); - TypeId checkExprTable(NotNull scope, AstExprTable* expr); - TypeId check(NotNull scope, AstExprIndexName* indexName); - TypeId check(NotNull scope, AstExprIndexExpr* indexExpr); - TypeId check(NotNull scope, AstExprUnary* unary); - TypeId check(NotNull scope, AstExprBinary* binary); + TypeId checkExprTable(const ScopePtr& scope, AstExprTable* expr); + TypeId check(const ScopePtr& scope, AstExprIndexName* indexName); + TypeId check(const ScopePtr& scope, AstExprIndexExpr* indexExpr); + TypeId check(const ScopePtr& scope, AstExprUnary* unary); + TypeId check(const ScopePtr& scope, AstExprBinary* binary); struct FunctionSignature { @@ -133,20 +134,20 @@ struct ConstraintGraphBuilder // The scope that encompasses the function's signature. May be nullptr // if there was no need for a signature scope (the function has no // generics). - Scope2* signatureScope; + ScopePtr signatureScope; // The scope that encompasses the function's body. Is a child scope of // signatureScope, if present. - NotNull bodyScope; + ScopePtr bodyScope; }; - FunctionSignature checkFunctionSignature(NotNull parent, AstExprFunction* fn); + FunctionSignature checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn); /** * Checks the body of a function expression. * @param scope the interior scope of the body of the function. * @param fn the function expression to check. */ - void checkFunctionBody(NotNull scope, AstExprFunction* fn); + void checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn); /** * Resolves a type from its AST annotation. @@ -154,7 +155,7 @@ struct ConstraintGraphBuilder * @param ty the AST annotation to resolve. * @return the type of the AST annotation. **/ - TypeId resolveType(NotNull scope, AstType* ty); + TypeId resolveType(const ScopePtr& scope, AstType* ty); /** * Resolves a type pack from its AST annotation. @@ -162,14 +163,14 @@ struct ConstraintGraphBuilder * @param tp the AST annotation to resolve. * @return the type pack of the AST annotation. **/ - TypePackId resolveTypePack(NotNull scope, AstTypePack* tp); + TypePackId resolveTypePack(const ScopePtr& scope, AstTypePack* tp); - TypePackId resolveTypePack(NotNull scope, const AstTypeList& list); + TypePackId resolveTypePack(const ScopePtr& scope, const AstTypeList& list); - std::vector> createGenerics(NotNull scope, AstArray generics); - std::vector> createGenericPacks(NotNull scope, AstArray packs); + std::vector> createGenerics(const ScopePtr& scope, AstArray generics); + std::vector> createGenericPacks(const ScopePtr& scope, AstArray packs); - TypeId flattenPack(NotNull scope, Location location, TypePackId tp); + TypeId flattenPack(const ScopePtr& scope, Location location, TypePackId tp); void reportError(Location location, TypeErrorData err); void reportCodeTooComplex(Location location); @@ -180,7 +181,7 @@ struct ConstraintGraphBuilder * real" in a general way is going to be pretty hard, so we are choosing not to tackle that yet. For now, we do an * initial scan of the AST and note what globals are defined. */ - void prepopulateGlobalScope(NotNull globalScope, AstStatBlock* program); + void prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program); }; /** @@ -193,6 +194,6 @@ struct ConstraintGraphBuilder * @return a list of pointers to constraints contained within the scope graph. * None of these pointers should be null. */ -std::vector> collectConstraints(NotNull rootScope); +std::vector> collectConstraints(NotNull rootScope); } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index cf88efb6..0b9b4ae3 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -25,7 +25,7 @@ struct ConstraintSolver // is important to not add elements to this vector, lest the underlying // storage that we retain pointers to be mutated underneath us. const std::vector> constraints; - NotNull rootScope; + NotNull rootScope; // This includes every constraint that has not been fully solved. // A constraint can be both blocked and unsolved, for instance. @@ -40,7 +40,7 @@ struct ConstraintSolver ConstraintSolverLogger logger; - explicit ConstraintSolver(TypeArena* arena, NotNull rootScope); + explicit ConstraintSolver(TypeArena* arena, NotNull rootScope); /** * Attempts to dispatch all pending constraints and reach a type solution @@ -121,6 +121,6 @@ private: void unblock_(BlockedConstraintId progressed); }; -void dump(NotNull rootScope, struct ToStringOptions& opts); +void dump(NotNull rootScope, struct ToStringOptions& opts); } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolverLogger.h b/Analysis/include/Luau/ConstraintSolverLogger.h index 55336a23..fe2177c4 100644 --- a/Analysis/include/Luau/ConstraintSolverLogger.h +++ b/Analysis/include/Luau/ConstraintSolverLogger.h @@ -15,8 +15,8 @@ namespace Luau struct ConstraintSolverLogger { std::string compileOutput(); - void captureBoundarySnapshot(const Scope2* rootScope, std::vector>& unsolvedConstraints); - void prepareStepSnapshot(const Scope2* rootScope, NotNull current, std::vector>& unsolvedConstraints); + void captureBoundarySnapshot(const Scope* rootScope, std::vector>& unsolvedConstraints); + void prepareStepSnapshot(const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints); void commitPreparedStepSnapshot(); private: diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 27fd4d7e..3b0164c2 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -152,7 +152,7 @@ struct Frontend void registerBuiltinDefinition(const std::string& name, std::function); void applyBuiltinDefinitionToEnvironment(const std::string& environmentName, const std::string& definitionName); - NotNull getGlobalScope2(); + NotNull getGlobalScope(); private: ModulePtr check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope); @@ -169,7 +169,7 @@ private: std::unordered_map environments; std::unordered_map> builtinDefinitions; - std::unique_ptr globalScope2; + std::unique_ptr globalScope; public: FileResolver* fileResolver; diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index b3105b78..73f4c972 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -69,7 +69,6 @@ struct Module std::shared_ptr names; std::vector> scopes; // never empty - std::vector>> scope2s; // never empty DenseHashMap astTypes{nullptr}; DenseHashMap astTypePacks{nullptr}; @@ -86,7 +85,6 @@ struct Module bool timeout = false; ScopePtr getModuleScope() const; - Scope2* getModuleScope2() const; // Once a module has been typechecked, we clone its public interface into a separate arena. // This helps us to force TypeVar ownership into a DAG rather than a DCG. diff --git a/Analysis/include/Luau/Quantify.h b/Analysis/include/Luau/Quantify.h index f46f0cb5..7edf23b8 100644 --- a/Analysis/include/Luau/Quantify.h +++ b/Analysis/include/Luau/Quantify.h @@ -7,9 +7,9 @@ namespace Luau { struct TypeArena; -struct Scope2; +struct Scope; void quantify(TypeId ty, TypeLevel level); -TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope); +TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope); } // namespace Luau diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 0eaecf1d..85c1750a 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -32,10 +32,16 @@ struct Scope explicit Scope(const ScopePtr& parent, int subLevel = 0); // child scope. Parent must not be nullptr. const ScopePtr parent; // null for the root + + // All the children of this scope. + std::vector> children; std::unordered_map bindings; + std::unordered_map typeBindings; + std::unordered_map typePackBindings; TypePackId returnType; - bool breakOk = false; std::optional varargPack; + // All constraints belonging to this scope. + std::vector constraints; TypeLevel level; @@ -45,7 +51,9 @@ struct Scope std::unordered_map> importedTypeBindings; - std::optional lookup(const Symbol& name); + std::optional lookup(Symbol sym); + std::optional lookupTypeBinding(const Name& name); + std::optional lookupTypePackBinding(const Name& name); std::optional lookupType(const Name& name); std::optional lookupImportedType(const Name& moduleAlias, const Name& name); @@ -66,24 +74,4 @@ struct Scope std::unordered_map typeAliasTypePackParameters; }; -struct Scope2 -{ - // The parent scope of this scope. Null if there is no parent (i.e. this - // is the module-level scope). - Scope2* parent = nullptr; - // All the children of this scope. - std::vector> children; - std::unordered_map bindings; // TODO: I think this can be a DenseHashMap - std::unordered_map typeBindings; - std::unordered_map typePackBindings; - TypePackId returnType; - std::optional varargPack; - // All constraints belonging to this scope. - std::vector constraints; - - std::optional lookup(Symbol sym); - std::optional lookupTypeBinding(const Name& name); - std::optional lookupTypePackBinding(const Name& name); -}; - } // namespace Luau diff --git a/Analysis/include/Luau/TypeVar.h b/Analysis/include/Luau/TypeVar.h index fb6093df..052d4d88 100644 --- a/Analysis/include/Luau/TypeVar.h +++ b/Analysis/include/Luau/TypeVar.h @@ -24,7 +24,7 @@ namespace Luau { struct TypeArena; -struct Scope2; +struct Scope; /** * There are three kinds of type variables: @@ -143,7 +143,7 @@ struct ConstrainedTypeVar std::vector parts; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; }; // Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md @@ -275,7 +275,7 @@ struct FunctionTypeVar std::optional defn = {}, bool hasSelf = false); TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; /// These should all be generic std::vector generics; std::vector genericPacks; @@ -344,7 +344,7 @@ struct TableTypeVar TableState state = TableState::Unsealed; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; std::optional name; // Sometimes we throw a type on a name to make for nicer error messages, but without creating any entry in the type namespace diff --git a/Analysis/include/Luau/Unifiable.h b/Analysis/include/Luau/Unifiable.h index 4ff91714..e5eb4198 100644 --- a/Analysis/include/Luau/Unifiable.h +++ b/Analysis/include/Luau/Unifiable.h @@ -8,7 +8,7 @@ namespace Luau { -struct Scope2; +struct Scope; /** * The 'level' of a TypeVar is an indirect way to talk about the scope that it 'belongs' too. @@ -84,11 +84,11 @@ using Name = std::string; struct Free { explicit Free(TypeLevel level); - explicit Free(Scope2* scope); + explicit Free(Scope* scope); int index; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; // True if this free type variable is part of a mutually // recursive type alias whose definitions haven't been // resolved yet. @@ -115,13 +115,13 @@ struct Generic Generic(); explicit Generic(TypeLevel level); explicit Generic(const Name& name); - explicit Generic(Scope2* scope); + explicit Generic(Scope* scope); Generic(TypeLevel level, const Name& name); - Generic(Scope2* scope, const Name& name); + Generic(Scope* scope, const Name& name); int index; TypeLevel level; - Scope2* scope = nullptr; + Scope* scope = nullptr; Name name; bool explicitName = false; diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 9847fcb6..693fb3e0 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -12,7 +12,7 @@ #include #include -LUAU_FASTFLAG(LuauSelfCallAutocompleteFix2) +LUAU_FASTFLAG(LuauSelfCallAutocompleteFix3) static const std::unordered_set kStatementStartingKeywords = { "while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -149,7 +149,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ ty = follow(ty); auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); InternalErrorReporter iceReporter; UnifierSharedState unifierState(&iceReporter); @@ -168,7 +168,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ TypeId expectedType = follow(*typeAtPosition); auto checkFunctionType = [typeArena, &canUnify, &expectedType](const FunctionTypeVar* ftv) { - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) { if (std::optional firstRetTy = first(ftv->retTypes)) return checkTypeMatch(typeArena, *firstRetTy, expectedType); @@ -209,7 +209,7 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ } } - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) return checkTypeMatch(typeArena, ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; else return canUnify(ty, expectedType) ? TypeCorrectKind::Correct : TypeCorrectKind::None; @@ -226,7 +226,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId const std::vector& nodes, AutocompleteEntryMap& result, std::unordered_set& seen, std::optional containingClass = std::nullopt) { - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) rootTy = follow(rootTy); ty = follow(ty); @@ -236,7 +236,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId seen.insert(ty); auto isWrongIndexer_DEPRECATED = [indexType, useStrictFunctionIndexers = !!get(ty)](Luau::TypeId type) { - LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(!FFlag::LuauSelfCallAutocompleteFix3); if (indexType == PropIndexType::Key) return false; @@ -269,7 +269,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId } }; auto isWrongIndexer = [typeArena, rootTy, indexType](Luau::TypeId type) { - LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix2); + LUAU_ASSERT(FFlag::LuauSelfCallAutocompleteFix3); if (indexType == PropIndexType::Key) return false; @@ -277,21 +277,20 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId bool calledWithSelf = indexType == PropIndexType::Colon; auto isCompatibleCall = [typeArena, rootTy, calledWithSelf](const FunctionTypeVar* ftv) { - if (get(rootTy)) - { - // Calls on classes require strict match between how function is declared and how it's called - return calledWithSelf == ftv->hasSelf; - } + // Strong match with definition is a success + if (calledWithSelf == ftv->hasSelf) + return true; - // If a call is made with ':', it is invalid if a function has incompatible first argument or no arguments at all - // If a call is made with '.', but it was declared with 'self', it is considered invalid if first argument is compatible - if (calledWithSelf || ftv->hasSelf) + // Calls on classes require strict match between how function is declared and how it's called + if (get(rootTy)) + return false; + + // When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all + // When called with '.', but declared with 'self', it is considered invalid if first argument is compatible + if (std::optional firstArgTy = first(ftv->argTypes)) { - if (std::optional firstArgTy = first(ftv->argTypes)) - { - if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) - return calledWithSelf; - } + if (checkTypeMatch(typeArena, rootTy, *firstArgTy)) + return calledWithSelf; } return !calledWithSelf; @@ -333,7 +332,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryKind::Property, type, prop.deprecated, - FFlag::LuauSelfCallAutocompleteFix2 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), + FFlag::LuauSelfCallAutocompleteFix3 ? isWrongIndexer(type) : isWrongIndexer_DEPRECATED(type), typeCorrect, containingClass, &prop, @@ -376,7 +375,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId { autocompleteProps(module, typeArena, rootTy, mt->table, indexType, nodes, result, seen); - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) { if (auto mtable = get(mt->metatable)) fillMetatableProps(mtable); @@ -442,7 +441,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId AutocompleteEntryMap inner; std::unordered_set innerSeen; - if (!FFlag::LuauSelfCallAutocompleteFix2) + if (!FFlag::LuauSelfCallAutocompleteFix3) innerSeen = seen; if (isNil(*iter)) @@ -468,7 +467,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId ++iter; } } - else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix2) + else if (auto pt = get(ty); pt && FFlag::LuauSelfCallAutocompleteFix3) { if (pt->metatable) { @@ -476,7 +475,7 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, TypeId fillMetatableProps(mtable); } } - else if (FFlag::LuauSelfCallAutocompleteFix2 && get(get(ty))) + else if (FFlag::LuauSelfCallAutocompleteFix3 && get(get(ty))) { autocompleteProps(module, typeArena, rootTy, getSingletonTypes().stringType, indexType, nodes, result, seen); } @@ -1405,7 +1404,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M TypeId ty = follow(*it); PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point; - if (!FFlag::LuauSelfCallAutocompleteFix2 && isString(ty)) + if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty)) return { autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property}; else diff --git a/Analysis/src/ConstraintGraphBuilder.cpp b/Analysis/src/ConstraintGraphBuilder.cpp index 38895ceb..efaeff6c 100644 --- a/Analysis/src/ConstraintGraphBuilder.cpp +++ b/Analysis/src/ConstraintGraphBuilder.cpp @@ -14,7 +14,7 @@ namespace Luau const AstStat* getFallthrough(const AstStat* node); // TypeInfer.cpp ConstraintGraphBuilder::ConstraintGraphBuilder( - const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope) + const ModuleName& moduleName, TypeArena* arena, NotNull ice, NotNull globalScope) : moduleName(moduleName) , singletonTypes(getSingletonTypes()) , arena(arena) @@ -25,36 +25,34 @@ ConstraintGraphBuilder::ConstraintGraphBuilder( LUAU_ASSERT(arena); } -TypeId ConstraintGraphBuilder::freshType(NotNull scope) +TypeId ConstraintGraphBuilder::freshType(const ScopePtr& scope) { - return arena->addType(FreeTypeVar{scope}); + return arena->addType(FreeTypeVar{scope.get()}); } -TypePackId ConstraintGraphBuilder::freshTypePack(NotNull scope) +TypePackId ConstraintGraphBuilder::freshTypePack(const ScopePtr& scope) { - FreeTypePack f{scope}; + FreeTypePack f{scope.get()}; return arena->addTypePack(TypePackVar{std::move(f)}); } -NotNull ConstraintGraphBuilder::childScope(Location location, NotNull parent) +ScopePtr ConstraintGraphBuilder::childScope(Location location, const ScopePtr& parent) { - auto scope = std::make_unique(); - NotNull borrow = NotNull(scope.get()); - scopes.emplace_back(location, std::move(scope)); + auto scope = std::make_shared(parent); + scopes.emplace_back(location, scope); - borrow->parent = parent; - borrow->returnType = parent->returnType; - parent->children.push_back(borrow); + scope->returnType = parent->returnType; + parent->children.push_back(NotNull(scope.get())); - return borrow; + return scope; } -void ConstraintGraphBuilder::addConstraint(NotNull scope, ConstraintV cv) +void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, ConstraintV cv) { scope->constraints.emplace_back(new Constraint{std::move(cv)}); } -void ConstraintGraphBuilder::addConstraint(NotNull scope, std::unique_ptr c) +void ConstraintGraphBuilder::addConstraint(const ScopePtr& scope, std::unique_ptr c) { scope->constraints.emplace_back(std::move(c)); } @@ -63,13 +61,13 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block) { LUAU_ASSERT(scopes.empty()); LUAU_ASSERT(rootScope == nullptr); - scopes.emplace_back(block->location, std::make_unique()); - rootScope = scopes.back().second.get(); - NotNull borrow = NotNull(rootScope); + ScopePtr scope = std::make_shared(singletonTypes.anyTypePack); + rootScope = scope.get(); + scopes.emplace_back(block->location, scope); - rootScope->returnType = freshTypePack(borrow); + rootScope->returnType = freshTypePack(scope); - prepopulateGlobalScope(borrow, block); + prepopulateGlobalScope(scope, block); // TODO: We should share the global scope. rootScope->typeBindings["nil"] = singletonTypes.nilType; @@ -78,10 +76,10 @@ void ConstraintGraphBuilder::visit(AstStatBlock* block) rootScope->typeBindings["boolean"] = singletonTypes.booleanType; rootScope->typeBindings["thread"] = singletonTypes.threadType; - visitBlockWithoutChildScope(borrow, block); + visitBlockWithoutChildScope(scope, block); } -void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull scope, AstStatBlock* block) +void ConstraintGraphBuilder::visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block) { RecursionCounter counter{&recursionCount}; @@ -95,7 +93,7 @@ void ConstraintGraphBuilder::visitBlockWithoutChildScope(NotNull scope, visit(scope, stat); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStat* stat) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat) { RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit}; @@ -123,22 +121,24 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStat* stat) LUAU_ASSERT(0); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocal* local) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local) { std::vector varTypes; for (AstLocal* local : local->vars) { TypeId ty = freshType(scope); + Location location = local->location; if (local->annotation) { + location = local->annotation->location; TypeId annotation = resolveType(scope, local->annotation); addConstraint(scope, SubtypeConstraint{ty, annotation}); } varTypes.push_back(ty); - scope->bindings[local] = ty; + scope->bindings[local] = Binding{ty, location}; } for (size_t i = 0; i < local->values.size; ++i) @@ -169,7 +169,7 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocal* local) } } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatFor* for_) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFor* for_) { auto checkNumber = [&](AstExpr* expr) { @@ -184,24 +184,24 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFor* for_) checkNumber(for_->to); checkNumber(for_->step); - NotNull forScope = childScope(for_->location, scope); - forScope->bindings[for_->var] = singletonTypes.numberType; + ScopePtr forScope = childScope(for_->location, scope); + forScope->bindings[for_->var] = Binding{singletonTypes.numberType, for_->var->location}; visit(forScope, for_->body); } -void addConstraints(Constraint* constraint, NotNull scope) +void addConstraints(Constraint* constraint, NotNull scope) { scope->constraints.reserve(scope->constraints.size() + scope->constraints.size()); for (const auto& c : scope->constraints) constraint->dependencies.push_back(NotNull{c.get()}); - for (NotNull childScope : scope->children) + for (NotNull childScope : scope->children) addConstraints(constraint, childScope); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocalFunction* function) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFunction* function) { // Local // Global @@ -213,21 +213,21 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatLocalFunction* LUAU_ASSERT(!ty.has_value()); // The parser ensures that every local function has a distinct Symbol for its name. functionType = arena->addType(BlockedTypeVar{}); - scope->bindings[function->name] = functionType; + scope->bindings[function->name] = Binding{functionType, function->name->location}; FunctionSignature sig = checkFunctionSignature(scope, function->func); - sig.bodyScope->bindings[function->name] = sig.signature; + sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location}; checkFunctionBody(sig.bodyScope, function->func); std::unique_ptr c{ - new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}}; - addConstraints(c.get(), sig.bodyScope); + new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; + addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* function) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* function) { // Name could be AstStatLocal, AstStatGlobal, AstStatIndexName. // With or without self @@ -247,9 +247,9 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct else { functionType = arena->addType(BlockedTypeVar{}); - scope->bindings[localName->local] = functionType; + scope->bindings[localName->local] = Binding{functionType, localName->location}; } - sig.bodyScope->bindings[localName->local] = sig.signature; + sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; } else if (AstExprGlobal* globalName = function->name->as()) { @@ -262,9 +262,9 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct else { functionType = arena->addType(BlockedTypeVar{}); - rootScope->bindings[globalName->name] = functionType; + rootScope->bindings[globalName->name] = Binding{functionType, globalName->location}; } - sig.bodyScope->bindings[globalName->name] = sig.signature; + sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; } else if (AstExprIndexName* indexName = function->name->as()) { @@ -291,21 +291,21 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatFunction* funct checkFunctionBody(sig.bodyScope, function->func); std::unique_ptr c{ - new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope : sig.bodyScope}}}; - addConstraints(c.get(), sig.bodyScope); + new Constraint{GeneralizationConstraint{functionType, sig.signature, sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}}}; + addConstraints(c.get(), NotNull(sig.bodyScope.get())); addConstraint(scope, std::move(c)); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatReturn* ret) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret) { TypePackId exprTypes = checkPack(scope, ret->list); addConstraint(scope, PackSubtypeConstraint{exprTypes, scope->returnType}); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatBlock* block) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatBlock* block) { - NotNull innerScope = childScope(block->location, scope); + ScopePtr innerScope = childScope(block->location, scope); // In order to enable mutually-recursive type aliases, we need to // populate the type bindings before we actually check any of the @@ -323,7 +323,7 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatBlock* block) visitBlockWithoutChildScope(innerScope, block); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatAssign* assign) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign) { TypePackId varPackId = checkExprList(scope, assign->vars); TypePackId valuePack = checkPack(scope, assign->values); @@ -331,21 +331,21 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatAssign* assign) addConstraint(scope, PackSubtypeConstraint{valuePack, varPackId}); } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatIf* ifStatement) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement) { check(scope, ifStatement->condition); - NotNull thenScope = childScope(ifStatement->thenbody->location, scope); + ScopePtr thenScope = childScope(ifStatement->thenbody->location, scope); visit(thenScope, ifStatement->thenbody); if (ifStatement->elsebody) { - NotNull elseScope = childScope(ifStatement->elsebody->location, scope); + ScopePtr elseScope = childScope(ifStatement->elsebody->location, scope); visit(elseScope, ifStatement->elsebody); } } -void ConstraintGraphBuilder::visit(NotNull scope, AstStatTypeAlias* alias) +void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatTypeAlias* alias) { // TODO: Exported type aliases // TODO: Generic type aliases @@ -371,7 +371,7 @@ void ConstraintGraphBuilder::visit(NotNull scope, AstStatTypeAlias* alia addConstraint(scope, NameConstraint{ty, alias->name.value}); } -TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstArray exprs) +TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray exprs) { if (exprs.size == 0) return arena->addTypePack({}); @@ -392,7 +392,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstArrayaddTypePack(TypePack{std::move(types), last}); } -TypePackId ConstraintGraphBuilder::checkExprList(NotNull scope, const AstArray& exprs) +TypePackId ConstraintGraphBuilder::checkExprList(const ScopePtr& scope, const AstArray& exprs) { TypePackId result = arena->addTypePack({}); TypePack* resultPack = getMutable(result); @@ -413,7 +413,7 @@ TypePackId ConstraintGraphBuilder::checkExprList(NotNull scope, const As return result; } -TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstExpr* expr) +TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr) { RecursionCounter counter{&recursionCount}; @@ -468,7 +468,7 @@ TypePackId ConstraintGraphBuilder::checkPack(NotNull scope, AstExpr* exp return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExpr* expr) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr) { RecursionCounter counter{&recursionCount}; @@ -548,7 +548,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExpr* expr) return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexName* indexName) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexName* indexName) { TypeId obj = check(scope, indexName->expr); TypeId result = freshType(scope); @@ -564,7 +564,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexName* in return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexExpr* indexExpr) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr) { TypeId obj = check(scope, indexExpr->expr); TypeId indexType = check(scope, indexExpr->index); @@ -579,7 +579,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprIndexExpr* in return result; } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprUnary* unary) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary) { TypeId operandType = check(scope, unary->expr); @@ -599,7 +599,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprUnary* unary) return singletonTypes.errorRecoveryType(); } -TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprBinary* binary) +TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary) { TypeId leftType = check(scope, binary->left); TypeId rightType = check(scope, binary->right); @@ -624,7 +624,7 @@ TypeId ConstraintGraphBuilder::check(NotNull scope, AstExprBinary* binar return nullptr; } -TypeId ConstraintGraphBuilder::checkExprTable(NotNull scope, AstExprTable* expr) +TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTable* expr) { TypeId ty = arena->addType(TableTypeVar{}); TableTypeVar* ttv = getMutable(ty); @@ -674,10 +674,10 @@ TypeId ConstraintGraphBuilder::checkExprTable(NotNull scope, AstExprTabl return ty; } -ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(NotNull parent, AstExprFunction* fn) +ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn) { - Scope2* signatureScope = nullptr; - Scope2* bodyScope = nullptr; + ScopePtr signatureScope = nullptr; + ScopePtr bodyScope = nullptr; TypePackId returnType = nullptr; std::vector genericTypes; @@ -690,18 +690,17 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS // generics properly. if (hasGenerics) { - NotNull signatureBorrow = childScope(fn->location, parent); - signatureScope = signatureBorrow.get(); + signatureScope = childScope(fn->location, parent); // We need to assign returnType before creating bodyScope so that the // return type gets propogated to bodyScope. - returnType = freshTypePack(signatureBorrow); + returnType = freshTypePack(signatureScope); signatureScope->returnType = returnType; - bodyScope = childScope(fn->body->location, signatureBorrow).get(); + bodyScope = childScope(fn->body->location, signatureScope); - std::vector> genericDefinitions = createGenerics(signatureBorrow, fn->generics); - std::vector> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks); + std::vector> genericDefinitions = createGenerics(signatureScope, fn->generics); + std::vector> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); // We do not support default values on function generics, so we only // care about the types involved. @@ -719,11 +718,10 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS } else { - NotNull bodyBorrow = childScope(fn->body->location, parent); - bodyScope = bodyBorrow.get(); + bodyScope = childScope(fn->body->location, parent); - returnType = freshTypePack(bodyBorrow); - bodyBorrow->returnType = returnType; + returnType = freshTypePack(bodyScope); + bodyScope->returnType = returnType; // To eliminate the need to branch on hasGenerics below, we say that the // signature scope is the body scope when there is no real signature @@ -731,27 +729,24 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS signatureScope = bodyScope; } - NotNull bodyBorrow = NotNull(bodyScope); - NotNull signatureBorrow = NotNull(signatureScope); - if (fn->returnAnnotation) { - TypePackId annotatedRetType = resolveTypePack(signatureBorrow, *fn->returnAnnotation); - addConstraint(signatureBorrow, PackSubtypeConstraint{returnType, annotatedRetType}); + TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation); + addConstraint(signatureScope, PackSubtypeConstraint{returnType, annotatedRetType}); } std::vector argTypes; for (AstLocal* local : fn->args) { - TypeId t = freshType(signatureBorrow); + TypeId t = freshType(signatureScope); argTypes.push_back(t); - signatureScope->bindings[local] = t; + signatureScope->bindings[local] = Binding{t, local->location}; if (local->annotation) { - TypeId argAnnotation = resolveType(signatureBorrow, local->annotation); - addConstraint(signatureBorrow, SubtypeConstraint{t, argAnnotation}); + TypeId argAnnotation = resolveType(signatureScope, local->annotation); + addConstraint(signatureScope, SubtypeConstraint{t, argAnnotation}); } } @@ -772,11 +767,11 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS // Undo the workaround we made above: if there's no signature scope, // don't report it. /* signatureScope */ hasGenerics ? signatureScope : nullptr, - /* bodyScope */ bodyBorrow, + /* bodyScope */ bodyScope, }; } -void ConstraintGraphBuilder::checkFunctionBody(NotNull scope, AstExprFunction* fn) +void ConstraintGraphBuilder::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn) { visitBlockWithoutChildScope(scope, fn->body); @@ -789,7 +784,7 @@ void ConstraintGraphBuilder::checkFunctionBody(NotNull scope, AstExprFun } } -TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) +TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty) { TypeId result = nullptr; @@ -834,7 +829,7 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) { // TODO: Recursion limit. bool hasGenerics = fn->generics.size > 0 || fn->genericPacks.size > 0; - Scope2* signatureScope = nullptr; + ScopePtr signatureScope = nullptr; std::vector genericTypes; std::vector genericTypePacks; @@ -843,22 +838,21 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) // for the generic bindings to live on. if (hasGenerics) { - NotNull signatureBorrow = childScope(fn->location, scope); - signatureScope = signatureBorrow.get(); + signatureScope = childScope(fn->location, scope); - std::vector> genericDefinitions = createGenerics(signatureBorrow, fn->generics); - std::vector> genericPackDefinitions = createGenericPacks(signatureBorrow, fn->genericPacks); + std::vector> genericDefinitions = createGenerics(signatureScope, fn->generics); + std::vector> genericPackDefinitions = createGenericPacks(signatureScope, fn->genericPacks); for (const auto& [name, g] : genericDefinitions) { genericTypes.push_back(g.ty); - signatureBorrow->typeBindings[name] = g.ty; + signatureScope->typeBindings[name] = g.ty; } for (const auto& [name, g] : genericPackDefinitions) { genericTypePacks.push_back(g.tp); - signatureBorrow->typePackBindings[name] = g.tp; + signatureScope->typePackBindings[name] = g.tp; } } else @@ -866,13 +860,11 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) // To eliminate the need to branch on hasGenerics below, we say that // the signature scope is the parent scope if we don't have // generics. - signatureScope = scope.get(); + signatureScope = scope; } - NotNull signatureBorrow(signatureScope); - - TypePackId argTypes = resolveTypePack(signatureBorrow, fn->argTypes); - TypePackId returnTypes = resolveTypePack(signatureBorrow, fn->returnTypes); + TypePackId argTypes = resolveTypePack(signatureScope, fn->argTypes); + TypePackId returnTypes = resolveTypePack(signatureScope, fn->returnTypes); // TODO: FunctionTypeVar needs a pointer to the scope so that we know // how to quantify/instantiate it. @@ -950,7 +942,7 @@ TypeId ConstraintGraphBuilder::resolveType(NotNull scope, AstType* ty) return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTypePack* tp) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, AstTypePack* tp) { TypePackId result; if (auto expl = tp->as()) @@ -964,7 +956,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTyp } else if (auto gen = tp->as()) { - result = arena->addTypePack(TypePackVar{GenericTypePack{scope, gen->genericName.value}}); + result = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), gen->genericName.value}}); } else { @@ -976,7 +968,7 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, AstTyp return result; } -TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, const AstTypeList& list) +TypePackId ConstraintGraphBuilder::resolveTypePack(const ScopePtr& scope, const AstTypeList& list) { std::vector head; @@ -994,12 +986,12 @@ TypePackId ConstraintGraphBuilder::resolveTypePack(NotNull scope, const return arena->addTypePack(TypePack{head, tail}); } -std::vector> ConstraintGraphBuilder::createGenerics(NotNull scope, AstArray generics) +std::vector> ConstraintGraphBuilder::createGenerics(const ScopePtr& scope, AstArray generics) { std::vector> result; for (const auto& generic : generics) { - TypeId genericTy = arena->addType(GenericTypeVar{scope, generic.name.value}); + TypeId genericTy = arena->addType(GenericTypeVar{scope.get(), generic.name.value}); std::optional defaultTy = std::nullopt; if (generic.defaultValue) @@ -1015,12 +1007,12 @@ std::vector> ConstraintGraphBuilder::crea } std::vector> ConstraintGraphBuilder::createGenericPacks( - NotNull scope, AstArray generics) + const ScopePtr& scope, AstArray generics) { std::vector> result; for (const auto& generic : generics) { - TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope, generic.name.value}}); + TypePackId genericTy = arena->addTypePack(TypePackVar{GenericTypePack{scope.get(), generic.name.value}}); std::optional defaultTy = std::nullopt; if (generic.defaultValue) @@ -1035,7 +1027,7 @@ std::vector> ConstraintGraphBuilder:: return result; } -TypeId ConstraintGraphBuilder::flattenPack(NotNull scope, Location location, TypePackId tp) +TypeId ConstraintGraphBuilder::flattenPack(const ScopePtr& scope, Location location, TypePackId tp) { if (auto f = first(tp)) return *f; @@ -1061,10 +1053,10 @@ void ConstraintGraphBuilder::reportCodeTooComplex(Location location) struct GlobalPrepopulator : AstVisitor { - const NotNull globalScope; + const NotNull globalScope; const NotNull arena; - GlobalPrepopulator(NotNull globalScope, NotNull arena) + GlobalPrepopulator(NotNull globalScope, NotNull arena) : globalScope(globalScope) , arena(arena) { @@ -1073,29 +1065,29 @@ struct GlobalPrepopulator : AstVisitor bool visit(AstStatFunction* function) override { if (AstExprGlobal* g = function->name->as()) - globalScope->bindings[g->name] = arena->addType(BlockedTypeVar{}); + globalScope->bindings[g->name] = Binding{arena->addType(BlockedTypeVar{})}; return true; } }; -void ConstraintGraphBuilder::prepopulateGlobalScope(NotNull globalScope, AstStatBlock* program) +void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) { - GlobalPrepopulator gp{NotNull{globalScope}, arena}; + GlobalPrepopulator gp{NotNull{globalScope.get()}, arena}; program->visit(&gp); } -void collectConstraints(std::vector>& result, NotNull scope) +void collectConstraints(std::vector>& result, NotNull scope) { for (const auto& c : scope->constraints) result.push_back(NotNull{c.get()}); - for (NotNull child : scope->children) + for (NotNull child : scope->children) collectConstraints(result, child); } -std::vector> collectConstraints(NotNull rootScope) +std::vector> collectConstraints(NotNull rootScope) { std::vector> result; collectConstraints(result, rootScope); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 077a4e28..1cd29918 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -13,31 +13,31 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false); namespace Luau { -[[maybe_unused]] static void dumpBindings(NotNull scope, ToStringOptions& opts) +[[maybe_unused]] static void dumpBindings(NotNull scope, ToStringOptions& opts) { for (const auto& [k, v] : scope->bindings) { - auto d = toStringDetailed(v, opts); + auto d = toStringDetailed(v.typeId, opts); opts.nameMap = d.nameMap; printf("\t%s : %s\n", k.c_str(), d.name.c_str()); } - for (NotNull child : scope->children) + for (NotNull child : scope->children) dumpBindings(child, opts); } -static void dumpConstraints(NotNull scope, ToStringOptions& opts) +static void dumpConstraints(NotNull scope, ToStringOptions& opts) { for (const ConstraintPtr& c : scope->constraints) { printf("\t%s\n", toString(*c, opts).c_str()); } - for (NotNull child : scope->children) + for (NotNull child : scope->children) dumpConstraints(child, opts); } -void dump(NotNull rootScope, ToStringOptions& opts) +void dump(NotNull rootScope, ToStringOptions& opts) { printf("constraints:\n"); dumpConstraints(rootScope, opts); @@ -55,7 +55,7 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) } } -ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope) +ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull rootScope) : arena(arena) , constraints(collectConstraints(rootScope)) , rootScope(rootScope) diff --git a/Analysis/src/ConstraintSolverLogger.cpp b/Analysis/src/ConstraintSolverLogger.cpp index 2f93c280..0c2517c0 100644 --- a/Analysis/src/ConstraintSolverLogger.cpp +++ b/Analysis/src/ConstraintSolverLogger.cpp @@ -5,12 +5,12 @@ namespace Luau { -static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& opts) +static std::string dumpScopeAndChildren(const Scope* scope, ToStringOptions& opts) { std::string output = "{\"bindings\":{"; bool comma = false; - for (const auto& [name, type] : scope->bindings) + for (const auto& [name, binding] : scope->bindings) { if (comma) output += ","; @@ -19,7 +19,7 @@ static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& op output += name.c_str(); output += "\": \""; - ToStringResult result = toStringDetailed(type, opts); + ToStringResult result = toStringDetailed(binding.typeId, opts); opts.nameMap = std::move(result.nameMap); output += result.name; output += "\""; @@ -30,7 +30,7 @@ static std::string dumpScopeAndChildren(const Scope2* scope, ToStringOptions& op output += "},\"children\":["; comma = false; - for (const Scope2* child : scope->children) + for (const Scope* child : scope->children) { if (comma) output += ","; @@ -96,7 +96,7 @@ std::string ConstraintSolverLogger::compileOutput() return output; } -void ConstraintSolverLogger::captureBoundarySnapshot(const Scope2* rootScope, std::vector>& unsolvedConstraints) +void ConstraintSolverLogger::captureBoundarySnapshot(const Scope* rootScope, std::vector>& unsolvedConstraints) { std::string snapshot = "{\"type\":\"boundary\",\"rootScope\":"; @@ -109,7 +109,7 @@ void ConstraintSolverLogger::captureBoundarySnapshot(const Scope2* rootScope, st } void ConstraintSolverLogger::prepareStepSnapshot( - const Scope2* rootScope, NotNull current, std::vector>& unsolvedConstraints) + const Scope* rootScope, NotNull current, std::vector>& unsolvedConstraints) { // LUAU_ASSERT(!preparedSnapshot); diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index f93f65dd..45663531 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,7 +2,6 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAG(LuauUnknownAndNeverType) -LUAU_FASTFLAG(LuauCheckLenMT) namespace Luau { @@ -123,6 +122,7 @@ declare function tonumber(value: T, radix: number?): number? declare function rawequal(a: T1, b: T2): boolean declare function rawget(tab: {[K]: V}, k: K): V declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} +declare function rawlen(obj: {[K]: V} | string): number declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? @@ -206,10 +206,6 @@ std::string getBuiltinDefinitionSource() std::string result = kBuiltinDefinitionLuaSrc; - // TODO: move this into kBuiltinDefinitionLuaSrc - if (FFlag::LuauCheckLenMT) - result += "declare function rawlen(obj: {[K]: V} | string): number\n"; - if (FFlag::LuauUnknownAndNeverType) result += "declare function error(message: T, level: number?): never\n"; else diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index a7670de5..222a17df 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -766,35 +766,35 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons return const_cast(this)->getSourceModule(moduleName); } -NotNull Frontend::getGlobalScope2() +NotNull Frontend::getGlobalScope() { - if (!globalScope2) + if (!globalScope) { const SingletonTypes& singletonTypes = getSingletonTypes(); - globalScope2 = std::make_unique(); - globalScope2->typeBindings["nil"] = singletonTypes.nilType; - globalScope2->typeBindings["number"] = singletonTypes.numberType; - globalScope2->typeBindings["string"] = singletonTypes.stringType; - globalScope2->typeBindings["boolean"] = singletonTypes.booleanType; - globalScope2->typeBindings["thread"] = singletonTypes.threadType; + globalScope = std::make_unique(singletonTypes.anyTypePack); + globalScope->typeBindings["nil"] = singletonTypes.nilType; + globalScope->typeBindings["number"] = singletonTypes.numberType; + globalScope->typeBindings["string"] = singletonTypes.stringType; + globalScope->typeBindings["boolean"] = singletonTypes.booleanType; + globalScope->typeBindings["thread"] = singletonTypes.threadType; } - return NotNull(globalScope2.get()); + return NotNull(globalScope.get()); } ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope) { ModulePtr result = std::make_shared(); - ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope2()}; + ConstraintGraphBuilder cgb{sourceModule.name, &result->internalTypes, NotNull(&iceHandler), getGlobalScope()}; cgb.visit(sourceModule.root); result->errors = std::move(cgb.errors); ConstraintSolver cs{&result->internalTypes, NotNull(cgb.rootScope)}; cs.run(); - result->scope2s = std::move(cgb.scopes); + result->scopes = std::move(cgb.scopes); result->astTypes = std::move(cgb.astTypes); result->astTypePacks = std::move(cgb.astTypePacks); result->astOriginalCallTypes = std::move(cgb.astOriginalCallTypes); diff --git a/Analysis/src/JsonEncoder.cpp b/Analysis/src/JsonEncoder.cpp index 550c9b59..ee7dadb3 100644 --- a/Analysis/src/JsonEncoder.cpp +++ b/Analysis/src/JsonEncoder.cpp @@ -103,8 +103,8 @@ struct AstJsonEncoder : public AstVisitor void write(double d) { - char b[256]; - sprintf(b, "%.17g", d); + char b[32]; + snprintf(b, sizeof(b), "%.17g", d); writeRaw(b); } diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 0603a042..dca18027 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -100,29 +100,20 @@ void Module::clonePublicInterface(InternalErrorReporter& ice) CloneState cloneState; - ScopePtr moduleScope = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : getModuleScope(); - Scope2* moduleScope2 = FFlag::DebugLuauDeferredConstraintResolution ? getModuleScope2() : nullptr; + ScopePtr moduleScope = getModuleScope(); - TypePackId returnType = FFlag::DebugLuauDeferredConstraintResolution ? moduleScope2->returnType : moduleScope->returnType; + TypePackId returnType = moduleScope->returnType; std::optional varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack; std::unordered_map* exportedTypeBindings = FFlag::DebugLuauDeferredConstraintResolution ? nullptr : &moduleScope->exportedTypeBindings; returnType = clone(returnType, interfaceTypes, cloneState); - if (moduleScope) + moduleScope->returnType = returnType; + if (varargPack) { - moduleScope->returnType = returnType; - if (varargPack) - { - varargPack = clone(*varargPack, interfaceTypes, cloneState); - moduleScope->varargPack = varargPack; - } - } - else - { - LUAU_ASSERT(moduleScope2); - moduleScope2->returnType = returnType; // TODO varargPack + varargPack = clone(*varargPack, interfaceTypes, cloneState); + moduleScope->varargPack = varargPack; } ForceNormal forceNormal{&interfaceTypes}; @@ -201,10 +192,4 @@ ScopePtr Module::getModuleScope() const return scopes.front().second; } -Scope2* Module::getModuleScope2() const -{ - LUAU_ASSERT(!scope2s.empty()); - return scope2s.front().second.get(); -} - } // namespace Luau diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 294c479d..72eb4012 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -16,13 +16,13 @@ namespace Luau { /// @return true if outer encloses inner -static bool subsumes(Scope2* outer, Scope2* inner) +static bool subsumes(Scope* outer, Scope* inner) { while (inner) { if (inner == outer) return true; - inner = inner->parent; + inner = inner->parent.get(); } return false; @@ -33,7 +33,7 @@ struct Quantifier final : TypeVarOnceVisitor TypeLevel level; std::vector generics; std::vector genericPacks; - Scope2* scope = nullptr; + Scope* scope = nullptr; bool seenGenericType = false; bool seenMutableType = false; @@ -43,20 +43,20 @@ struct Quantifier final : TypeVarOnceVisitor LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution); } - explicit Quantifier(Scope2* scope) + explicit Quantifier(Scope* scope) : scope(scope) { LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); } /// @return true if outer encloses inner - bool subsumes(Scope2* outer, Scope2* inner) + bool subsumes(Scope* outer, Scope* inner) { while (inner) { if (inner == outer) return true; - inner = inner->parent; + inner = inner->parent.get(); } return false; @@ -216,7 +216,7 @@ void quantify(TypeId ty, TypeLevel level) } } -void quantify(TypeId ty, Scope2* scope) +void quantify(TypeId ty, Scope* scope) { Quantifier q{scope}; q.traverse(ty); @@ -240,11 +240,11 @@ void quantify(TypeId ty, Scope2* scope) struct PureQuantifier : Substitution { - Scope2* scope; + Scope* scope; std::vector insertedGenerics; std::vector insertedGenericPacks; - PureQuantifier(TypeArena* arena, Scope2* scope) + PureQuantifier(TypeArena* arena, Scope* scope) : Substitution(TxnLog::empty(), arena) , scope(scope) { @@ -322,7 +322,7 @@ struct PureQuantifier : Substitution } }; -TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope) +TypeId quantify(TypeArena* arena, TypeId ty, Scope* scope) { PureQuantifier quantifier{arena, scope}; std::optional result = quantifier.substitute(ty); diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index 247a9dd6..083aa085 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -21,22 +21,6 @@ Scope::Scope(const ScopePtr& parent, int subLevel) level.subLevel = subLevel; } -std::optional Scope::lookup(const Symbol& name) -{ - Scope* scope = this; - - while (scope) - { - auto it = scope->bindings.find(name); - if (it != scope->bindings.end()) - return it->second.typeId; - - scope = scope->parent.get(); - } - - return std::nullopt; -} - std::optional Scope::lookupType(const Name& name) { const Scope* scope = this; @@ -121,48 +105,48 @@ std::optional Scope::linearSearchForBinding(const std::string& name, bo return std::nullopt; } -std::optional Scope2::lookup(Symbol sym) +std::optional Scope::lookup(Symbol sym) { - Scope2* s = this; + Scope* s = this; while (true) { auto it = s->bindings.find(sym); if (it != s->bindings.end()) - return it->second; + return it->second.typeId; if (s->parent) - s = s->parent; + s = s->parent.get(); else return std::nullopt; } } -std::optional Scope2::lookupTypeBinding(const Name& name) +std::optional Scope::lookupTypeBinding(const Name& name) { - Scope2* s = this; + Scope* s = this; while (s) { auto it = s->typeBindings.find(name); if (it != s->typeBindings.end()) return it->second; - s = s->parent; + s = s->parent.get(); } return std::nullopt; } -std::optional Scope2::lookupTypePackBinding(const Name& name) +std::optional Scope::lookupTypePackBinding(const Name& name) { - Scope2* s = this; + Scope* s = this; while (s) { auto it = s->typePackBindings.find(name); if (it != s->typePackBindings.end()) return it->second; - s = s->parent; + s = s->parent.get(); } return std::nullopt; diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 4c606922..f1dc9211 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation) LUAU_FASTFLAG(LuauUnknownAndNeverType) +LUAU_FASTFLAGVARIABLE(LuauSpecialTypesAsterisked, false) /* * Prefix generic typenames with gen- @@ -277,7 +278,10 @@ struct TypeVarStringifier if (tv->ty.valueless_by_exception()) { state.result.error = true; - state.emit("< VALUELESS BY EXCEPTION >"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("* VALUELESS BY EXCEPTION *"); + else + state.emit("< VALUELESS BY EXCEPTION >"); return; } @@ -453,7 +457,10 @@ struct TypeVarStringifier if (state.hasSeen(&ftv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -561,7 +568,10 @@ struct TypeVarStringifier if (state.hasSeen(&ttv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -691,7 +701,10 @@ struct TypeVarStringifier if (state.hasSeen(&uv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -758,7 +771,10 @@ struct TypeVarStringifier if (state.hasSeen(&uv)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLE*"); + else + state.emit(""); return; } @@ -803,7 +819,10 @@ struct TypeVarStringifier void operator()(TypeId, const ErrorTypeVar& tv) { state.result.error = true; - state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*"); + else + state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); } void operator()(TypeId, const LazyTypeVar& ltv) @@ -857,7 +876,10 @@ struct TypePackStringifier if (tp->ty.valueless_by_exception()) { state.result.error = true; - state.emit("< VALUELESS TP BY EXCEPTION >"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("* VALUELESS TP BY EXCEPTION *"); + else + state.emit("< VALUELESS TP BY EXCEPTION >"); return; } @@ -881,7 +903,10 @@ struct TypePackStringifier if (state.hasSeen(&tp)) { state.result.cycle = true; - state.emit(""); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*CYCLETP*"); + else + state.emit(""); return; } @@ -926,14 +951,22 @@ struct TypePackStringifier void operator()(TypePackId, const Unifiable::Error& error) { state.result.error = true; - state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); + if (FFlag::LuauSpecialTypesAsterisked) + state.emit(FFlag::LuauUnknownAndNeverType ? "*error-type*" : "*unknown*"); + else + state.emit(FFlag::LuauUnknownAndNeverType ? "" : "*unknown*"); } void operator()(TypePackId, const VariadicTypePack& pack) { state.emit("..."); if (FFlag::DebugLuauVerboseTypeNames && pack.hidden) - state.emit(""); + { + if (FFlag::LuauSpecialTypesAsterisked) + state.emit("*hidden*"); + else + state.emit(""); + } stringify(pack.ty); } @@ -1128,7 +1161,11 @@ ToStringResult toStringDetailed(TypeId ty, const ToStringOptions& opts) if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) { result.truncated = true; - result.name += "... "; + + if (FFlag::LuauSpecialTypesAsterisked) + result.name += "... *TRUNCATED*"; + else + result.name += "... "; } return result; @@ -1199,7 +1236,12 @@ ToStringResult toStringDetailed(TypePackId tp, const ToStringOptions& opts) } if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength) - result.name += "... "; + { + if (FFlag::LuauSpecialTypesAsterisked) + result.name += "... *TRUNCATED*"; + else + result.name += "... "; + } return result; } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index d3b9655d..85749671 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -69,6 +69,9 @@ struct TypeChecker2 : public AstVisitor TypePackId reconstructPack(AstArray exprs, TypeArena& arena) { + if (exprs.size == 0) + return arena.addTypePack(TypePack{{}, std::nullopt}); + std::vector head; for (size_t i = 0; i < exprs.size - 1; ++i) @@ -80,14 +83,14 @@ struct TypeChecker2 : public AstVisitor return arena.addTypePack(TypePack{head, tail}); } - Scope2* findInnermostScope(Location location) + Scope* findInnermostScope(Location location) { - Scope2* bestScope = module->getModuleScope2(); - Location bestLocation = module->scope2s[0].first; + Scope* bestScope = module->getModuleScope().get(); + Location bestLocation = module->scopes[0].first; - for (size_t i = 0; i < module->scope2s.size(); ++i) + for (size_t i = 0; i < module->scopes.size(); ++i) { - auto& [scopeBounds, scope] = module->scope2s[i]; + auto& [scopeBounds, scope] = module->scopes[i]; if (scopeBounds.encloses(location)) { if (scopeBounds.begin > bestLocation.begin || scopeBounds.end < bestLocation.end) @@ -181,7 +184,7 @@ struct TypeChecker2 : public AstVisitor bool visit(AstStatReturn* ret) override { - Scope2* scope = findInnermostScope(ret->location); + Scope* scope = findInnermostScope(ret->location); TypePackId expectedRetType = scope->returnType; TypeArena arena; @@ -359,7 +362,7 @@ struct TypeChecker2 : public AstVisitor bool visit(AstTypeReference* ty) override { - Scope2* scope = findInnermostScope(ty->location); + Scope* scope = findInnermostScope(ty->location); // TODO: Imported types // TODO: Generic types diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index ea7d81e1..371ef77a 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -35,7 +35,7 @@ LUAU_FASTFLAGVARIABLE(LuauExpectedTableUnionIndexerType, false) LUAU_FASTFLAGVARIABLE(LuauIndexSilenceErrors, false) LUAU_FASTFLAGVARIABLE(LuauLowerBoundsCalculation, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) -LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix2, false) +LUAU_FASTFLAGVARIABLE(LuauSelfCallAutocompleteFix3, false) LUAU_FASTFLAGVARIABLE(LuauReduceUnionRecursion, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAG(LuauNormalizeFlagIsConservative) @@ -45,7 +45,6 @@ LUAU_FASTFLAGVARIABLE(LuauReportErrorsOnIndexerKeyMismatch, false) LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false) LUAU_FASTFLAG(LuauQuantifyConstrained) LUAU_FASTFLAGVARIABLE(LuauFalsyPredicateReturnsNilInstead, false) -LUAU_FASTFLAGVARIABLE(LuauCheckLenMT, false) LUAU_FASTFLAGVARIABLE(LuauCheckGenericHOFTypes, false) LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false) LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false) @@ -1667,7 +1666,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declar ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); - if (FFlag::LuauSelfCallAutocompleteFix2) + if (FFlag::LuauSelfCallAutocompleteFix3) ftv->hasSelf = true; } } @@ -2465,7 +2464,7 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp DenseHashSet seen{nullptr}; - if (FFlag::LuauCheckLenMT && typeCouldHaveMetatable(operandType)) + if (typeCouldHaveMetatable(operandType)) { if (auto fnt = findMetatableEntry(operandType, "__len", expr.location, /* addErrors= */ true)) { @@ -3640,6 +3639,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"); diff --git a/Analysis/src/Unifiable.cpp b/Analysis/src/Unifiable.cpp index 8d23aa49..63d8647d 100644 --- a/Analysis/src/Unifiable.cpp +++ b/Analysis/src/Unifiable.cpp @@ -12,7 +12,7 @@ Free::Free(TypeLevel level) { } -Free::Free(Scope2* scope) +Free::Free(Scope* scope) : scope(scope) { } @@ -39,7 +39,7 @@ Generic::Generic(const Name& name) { } -Generic::Generic(Scope2* scope) +Generic::Generic(Scope* scope) : index(++nextIndex) , scope(scope) { @@ -53,7 +53,7 @@ Generic::Generic(TypeLevel level, const Name& name) { } -Generic::Generic(Scope2* scope, const Name& name) +Generic::Generic(Scope* scope, const Name& name) : index(++nextIndex) , scope(scope) , name(name) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 779bd279..8d54e367 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -24,8 +24,6 @@ bool lua_telemetry_parsed_named_non_function_type = false; LUAU_FASTFLAGVARIABLE(LuauErrorParseIntegerIssues, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false) -LUAU_FASTFLAGVARIABLE(LuauAlwaysCaptureHotComments, false) - bool lua_telemetry_parsed_out_of_range_bin_integer = false; bool lua_telemetry_parsed_out_of_range_hex_integer = false; bool lua_telemetry_parsed_double_prefix_hex_integer = false; @@ -2920,39 +2918,34 @@ AstTypeError* Parser::reportTypeAnnotationError(const Location& location, const void Parser::nextLexeme() { - if (options.captureComments || FFlag::LuauAlwaysCaptureHotComments) + Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; + + while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment) { - Lexeme::Type type = lexer.next(/* skipComments= */ false, true).type; + const Lexeme& lexeme = lexer.current(); - while (type == Lexeme::BrokenComment || type == Lexeme::Comment || type == Lexeme::BlockComment) + if (options.captureComments) + commentLocations.push_back(Comment{lexeme.type, lexeme.location}); + + // Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme. + // The parser will turn this into a proper syntax error. + if (lexeme.type == Lexeme::BrokenComment) + return; + + // Comments starting with ! are called "hot comments" and contain directives for type checking / linting / compiling + if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!') { - const Lexeme& lexeme = lexer.current(); + const char* text = lexeme.data; - if (options.captureComments) - commentLocations.push_back(Comment{lexeme.type, lexeme.location}); + unsigned int end = lexeme.length; + while (end > 0 && isSpace(text[end - 1])) + --end; - // Subtlety: Broken comments are weird because we record them as comments AND pass them to the parser as a lexeme. - // The parser will turn this into a proper syntax error. - if (lexeme.type == Lexeme::BrokenComment) - return; - - // Comments starting with ! are called "hot comments" and contain directives for type checking / linting / compiling - if (lexeme.type == Lexeme::Comment && lexeme.length && lexeme.data[0] == '!') - { - const char* text = lexeme.data; - - unsigned int end = lexeme.length; - while (end > 0 && isSpace(text[end - 1])) - --end; - - hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)}); - } - - type = lexer.next(/* skipComments= */ false, /* updatePrevLocation= */ false).type; + hotcomments.push_back({hotcommentHeader, lexeme.location, std::string(text + 1, text + end)}); } + + type = lexer.next(/* skipComments= */ false, /* updatePrevLocation= */ false).type; } - else - lexer.next(); } } // namespace Luau diff --git a/CLI/ReplEntry.cpp b/CLI/ReplEntry.cpp index 75995e6a..8543e3f7 100644 --- a/CLI/ReplEntry.cpp +++ b/CLI/ReplEntry.cpp @@ -1,8 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Repl.h" - - int main(int argc, char** argv) { return replMain(argc, argv); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 8f3befad..4be54378 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -25,8 +25,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTFLAGVARIABLE(LuauCompileNoIpairs, false) -LUAU_FASTFLAGVARIABLE(LuauCompileFoldBuiltins, false) -LUAU_FASTFLAGVARIABLE(LuauCompileBetterMultret, false) LUAU_FASTFLAGVARIABLE(LuauCompileFreeReassign, false) namespace Luau @@ -276,9 +274,6 @@ struct Compiler // returns true if node can return multiple values; may conservatively return true even if expr is known to return just a single value bool isExprMultRet(AstExpr* node) { - if (!FFlag::LuauCompileBetterMultret) - return node->is() || node->is(); - AstExprCall* expr = node->as(); if (!expr) return node->is(); @@ -310,27 +305,10 @@ struct Compiler if (AstExprCall* expr = node->as()) { // Optimization: convert multret calls that always return one value to fixedret calls; this facilitates inlining/constant folding - if (options.optimizationLevel >= 2) + if (options.optimizationLevel >= 2 && !isExprMultRet(node)) { - if (FFlag::LuauCompileBetterMultret) - { - if (!isExprMultRet(node)) - { - compileExprTemp(node, target); - return false; - } - } - else - { - AstExprFunction* func = getFunctionExpr(expr->func); - Function* fi = func ? functions.find(func) : nullptr; - - if (fi && fi->returnsOne) - { - compileExprTemp(node, target); - return false; - } - } + compileExprTemp(node, target); + return false; } // We temporarily swap out regTop to have targetTop work correctly... @@ -3437,30 +3415,7 @@ struct Compiler bool visit(AstStatReturn* stat) override { - if (FFlag::LuauCompileBetterMultret) - { - returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]); - } - else if (stat->list.size == 1) - { - AstExpr* value = stat->list.data[0]; - - if (AstExprCall* expr = value->as()) - { - AstExprFunction* func = self->getFunctionExpr(expr->func); - Function* fi = func ? self->functions.find(func) : nullptr; - - returnsOne &= fi && fi->returnsOne; - } - else if (value->is()) - { - returnsOne = false; - } - } - else - { - returnsOne = false; - } + returnsOne &= stat->list.size == 1 && !self->isExprMultRet(stat->list.data[0]); return false; } @@ -3601,7 +3556,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c trackValues(compiler.globals, compiler.variables, root); // builtin folding is enabled on optimization level 2 since we can't deoptimize folding at runtime - if (options.optimizationLevel >= 2 && FFlag::LuauCompileFoldBuiltins) + if (options.optimizationLevel >= 2) compiler.builtinsFold = &compiler.builtins; if (options.optimizationLevel >= 1) diff --git a/Compiler/src/CostModel.cpp b/Compiler/src/CostModel.cpp index 56b6c36f..81cbfd7a 100644 --- a/Compiler/src/CostModel.cpp +++ b/Compiler/src/CostModel.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCompileModelBuiltins, false) - namespace Luau { namespace Compile @@ -155,7 +153,7 @@ struct CostVisitor : AstVisitor { // builtin cost modeling is different from regular calls because we use FASTCALL to compile these // thus we use a cheaper baseline, don't account for function, and assume constant/local copy is free - bool builtin = FFlag::LuauCompileModelBuiltins && builtins.find(expr) != nullptr; + bool builtin = builtins.find(expr) != nullptr; bool builtinShort = builtin && expr->args.size <= 2; // FASTCALL1/2 Cost cost = builtin ? 2 : 3; diff --git a/Sources.cmake b/Sources.cmake index a4563019..f745667f 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -267,6 +267,7 @@ if(TARGET Luau.UnitTest) tests/Error.test.cpp tests/Frontend.test.cpp tests/JsonEncoder.test.cpp + tests/Lexer.test.cpp tests/Linter.test.cpp tests/LValue.test.cpp tests/Module.test.cpp diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index e3354ea2..e59f914f 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -34,8 +34,6 @@ * therefore call luaC_checkGC before luaC_checkthreadsleep to guarantee the object is pushed to an awake thread. */ -LUAU_FASTFLAG(LuauLazyAtoms) - const char* lua_ident = "$Lua: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio $\n" "$Authors: R. Ierusalimschy, L. H. de Figueiredo & W. Celes $\n" "$URL: www.lua.org $\n"; @@ -54,7 +52,6 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n" } #define updateatom(L, ts) \ - if (FFlag::LuauLazyAtoms) \ { \ if (ts->atom == ATOM_UNDEF) \ ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \ diff --git a/VM/src/ldblib.cpp b/VM/src/ldblib.cpp index 93d8703a..8246f818 100644 --- a/VM/src/ldblib.cpp +++ b/VM/src/ldblib.cpp @@ -130,15 +130,14 @@ static int db_traceback(lua_State* L) if (ar.currentline > 0) { - char line[32]; -#ifdef _MSC_VER - _itoa(ar.currentline, line, 10); // 5x faster than sprintf -#else - sprintf(line, "%d", ar.currentline); -#endif + char line[32]; // manual conversion for performance + char* lineend = line + sizeof(line); + char* lineptr = lineend; + for (unsigned int r = ar.currentline; r > 0; r /= 10) + *--lineptr = '0' + (r % 10); luaL_addchar(&buf, ':'); - luaL_addstring(&buf, line); + luaL_addlstring(&buf, lineptr, lineend - lineptr); } if (ar.name) diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 29015565..5ef41b78 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -529,7 +529,7 @@ const char* lua_debugtrace(lua_State* L) if (ar.currentline > 0) { char line[32]; - sprintf(line, ":%d", ar.currentline); + snprintf(line, sizeof(line), ":%d", ar.currentline); offset = append(buf, sizeof(buf), offset, line); } @@ -545,7 +545,7 @@ const char* lua_debugtrace(lua_State* L) if (depth > limit1 + limit2 && level == limit1 - 1) { char skip[32]; - sprintf(skip, "... (+%d frames)\n", int(depth - limit1 - limit2)); + snprintf(skip, sizeof(skip), "... (+%d frames)\n", int(depth - limit1 - limit2)); offset = append(buf, sizeof(buf), offset, skip); diff --git a/VM/src/lstring.cpp b/VM/src/lstring.cpp index 12bd67d5..d454308c 100644 --- a/VM/src/lstring.cpp +++ b/VM/src/lstring.cpp @@ -7,8 +7,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauLazyAtoms, false) - unsigned int luaS_hash(const char* str, size_t len) { // Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash @@ -84,7 +82,7 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h) ts->memcat = L->activememcat; memcpy(ts->data, str, l); ts->data[l] = '\0'; /* ending 0 */ - ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1; + ts->atom = ATOM_UNDEF; tb = &L->global->strt; h = lmod(h, tb->size); ts->next = tb->hash[h]; /* chain new entry */ @@ -165,9 +163,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts) ts->hash = h; ts->data[ts->len] = '\0'; // ending 0 - - // Complete string object - ts->atom = FFlag::LuauLazyAtoms ? ATOM_UNDEF : L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; + ts->atom = ATOM_UNDEF; ts->next = tb->hash[bucket]; // chain new entry tb->hash[bucket] = ts; diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 74a8aa8a..b0cd4dc2 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -979,14 +979,14 @@ static int str_format(lua_State* L) { case 'c': { - sprintf(buff, form, (int)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addInt64Format(form, formatIndicator, formatItemSize); - sprintf(buff, form, (long long)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (long long)luaL_checknumber(L, arg)); break; } case 'o': @@ -997,7 +997,7 @@ static int str_format(lua_State* L) double argValue = luaL_checknumber(L, arg); addInt64Format(form, formatIndicator, formatItemSize); unsigned long long v = (argValue < 0) ? (unsigned long long)(long long)argValue : (unsigned long long)argValue; - sprintf(buff, form, v); + snprintf(buff, sizeof(buff), form, v); break; } case 'e': @@ -1006,7 +1006,7 @@ static int str_format(lua_State* L) case 'g': case 'G': { - sprintf(buff, form, (double)luaL_checknumber(L, arg)); + snprintf(buff, sizeof(buff), form, (double)luaL_checknumber(L, arg)); break; } case 'q': @@ -1028,7 +1028,7 @@ static int str_format(lua_State* L) } else { - sprintf(buff, form, s); + snprintf(buff, sizeof(buff), form, s); break; } } diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 79e65919..38693156 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -44,9 +44,6 @@ static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); -// reset cache of absent metamethods, cache is updated in luaT_gettm -#define invalidateTMcache(t) t->tmcache = 0 - // empty hash data points to dummynode so that we can always dereference it const LuaNode luaH_dummynode = { {{NULL}, {0}, LUA_TNIL}, /* value */ @@ -667,15 +664,18 @@ TValue* luaH_set(lua_State* L, Table* t, const TValue* key) if (p != luaO_nilobject) return cast_to(TValue*, p); else - { - if (ttisnil(key)) - luaG_runerror(L, "table index is nil"); - else if (ttisnumber(key) && luai_numisnan(nvalue(key))) - luaG_runerror(L, "table index is NaN"); - else if (ttisvector(key) && luai_vecisnan(vvalue(key))) - luaG_runerror(L, "table index contains NaN"); - return newkey(L, t, key); - } + return luaH_newkey(L, t, key); +} + +TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) +{ + if (ttisnil(key)) + luaG_runerror(L, "table index is nil"); + else if (ttisnumber(key) && luai_numisnan(nvalue(key))) + luaG_runerror(L, "table index is NaN"); + else if (ttisvector(key) && luai_vecisnan(vvalue(key))) + luaG_runerror(L, "table index contains NaN"); + return newkey(L, t, key); } TValue* luaH_setnum(lua_State* L, Table* t, int key) diff --git a/VM/src/ltable.h b/VM/src/ltable.h index e8413c85..021f21bf 100644 --- a/VM/src/ltable.h +++ b/VM/src/ltable.h @@ -11,12 +11,16 @@ #define gval2slot(t, v) int(cast_to(LuaNode*, static_cast(v)) - t->node) +// reset cache of absent metamethods, cache is updated in luaT_gettm +#define invalidateTMcache(t) t->tmcache = 0 + LUAI_FUNC const TValue* luaH_getnum(Table* t, int key); LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key); LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key); LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key); LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key); LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key); +LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key); LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash); LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize); LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize); @@ -26,4 +30,6 @@ LUAI_FUNC int luaH_getn(Table* t); LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt); LUAI_FUNC void luaH_clear(Table* tt); +#define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot))) + extern const LuaNode luaH_dummynode; diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 02b39313..10d89aaf 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -33,7 +33,7 @@ LUAU_FASTFLAGVARIABLE(LuauLenTM, false) // 3. VM_PROTECT macro saves savedpc and restores base for you; most external calls need to be wrapped into that. However, it does NOT restore // ra/rb/rc! // 4. When copying an object to any existing object as a field, generally speaking you need to call luaC_barrier! Be careful with all setobj calls -// 5. To make 4 easier to follow, please use setobj2s for copies to stack and setobj for other copies. +// 5. To make 4 easier to follow, please use setobj2s for copies to stack, setobj2t for writes to tables, and setobj for other copies. // 6. You can define HARDSTACKTESTS in llimits.h which will aggressively realloc stack; with address sanitizer this should be effective at finding // stack corruption bugs // 7. Many external Lua functions can call GC! GC will *not* traverse pointers to new objects that aren't reachable from Lua root. Be careful when @@ -458,7 +458,7 @@ static void luau_execute(lua_State* L) if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly)) { - setobj(L, gval(n), ra); + setobj2t(L, gval(n), ra); luaC_barriert(L, h, ra); VM_NEXT(); } @@ -672,7 +672,7 @@ static void luau_execute(lua_State* L) // fast-path: value is in expected slot if (LUAU_LIKELY(ttisstring(gkey(n)) && tsvalue(gkey(n)) == tsvalue(kv) && !ttisnil(gval(n)) && !h->readonly)) { - setobj(L, gval(n), ra); + setobj2t(L, gval(n), ra); luaC_barriert(L, h, ra); VM_NEXT(); } @@ -684,7 +684,7 @@ static void luau_execute(lua_State* L) int cachedslot = gval2slot(h, res); // save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++ VM_PATCH_C(pc - 2, cachedslot); - setobj(L, res, ra); + setobj2t(L, res, ra); luaC_barriert(L, h, ra); VM_NEXT(); } diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index be4e99a9..b9267fb8 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauLenTM) +LUAU_FASTFLAGVARIABLE(LuauBetterNewindex, false) + /* limit for table tag-method chains (to avoid loops) */ #define MAXTAGLOOP 100 @@ -142,24 +144,50 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) { /* `t' is a table? */ Table* h = hvalue(t); - if (h->readonly) - luaG_readonlyerror(L); + if (FFlag::LuauBetterNewindex) + { + const TValue* oldval = luaH_get(h, key); - TValue* oldval = luaH_set(L, h, key); /* do a primitive set */ + /* should we assign the key? (if key is valid or __newindex is not set) */ + if (!ttisnil(oldval) || (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) + { + if (h->readonly) + luaG_readonlyerror(L); - L->cachedslot = gval2slot(h, oldval); /* remember slot to accelerate future lookups */ + /* luaH_set would work but would repeat the lookup so we use luaH_setslot that can reuse oldval if it's safe */ + TValue* newval = luaH_setslot(L, h, oldval, key); - if (!ttisnil(oldval) || /* result is no nil? */ - (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) - { /* or no TM? */ - setobj2t(L, oldval, val); - luaC_barriert(L, h, val); - return; + L->cachedslot = gval2slot(h, newval); /* remember slot to accelerate future lookups */ + + setobj2t(L, newval, val); + luaC_barriert(L, h, val); + return; + } + + /* fallthrough to metamethod */ + } + else + { + if (h->readonly) + luaG_readonlyerror(L); + + TValue* oldval = luaH_set(L, h, key); /* do a primitive set */ + + L->cachedslot = gval2slot(h, oldval); /* remember slot to accelerate future lookups */ + + if (!ttisnil(oldval) || /* result is no nil? */ + (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) + { /* or no TM? */ + setobj2t(L, oldval, val); + luaC_barriert(L, h, val); + return; + } + /* else will try the tag method */ } - /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_indexerror(L, t, key); + if (ttisfunction(tm)) { callTM(L, tm, t, key, val); 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 00000000..d9c5c7d7 --- /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") diff --git a/rfcs/syntax-string-interpolation.md b/rfcs/syntax-string-interpolation.md index 208143a0..ad182620 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. diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index e4b375b7..396b437d 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -2946,7 +2946,7 @@ local abc = b@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_on_class") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; loadDefinition(R"( declare class Foo @@ -2984,9 +2984,25 @@ t.@1 } } +TEST_CASE_FIXTURE(ACFixture, "do_compatible_self_calls") +{ + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; + + check(R"( +local t = {} +function t:m() end +t:@1 + )"); + + auto ac = autocomplete('1'); + + REQUIRE(ac.entryMap.count("m")); + CHECK(!ac.entryMap["m"].wrongIndexType); +} + TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local t = {} @@ -3002,7 +3018,7 @@ t:@1 TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_2") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local f: (() -> number) & ((number) -> number) = function(x: number?) return 2 end @@ -3017,7 +3033,7 @@ t:@1 CHECK(ac.entryMap["f"].wrongIndexType); } -TEST_CASE_FIXTURE(ACFixture, "no_incompatible_self_calls_provisional") +TEST_CASE_FIXTURE(ACFixture, "do_wrong_compatible_self_calls") { check(R"( local t = {} @@ -3032,9 +3048,26 @@ t:@1 CHECK(!ac.entryMap["m"].wrongIndexType); } +TEST_CASE_FIXTURE(ACFixture, "no_wrong_compatible_self_calls_with_generics") +{ + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; + + check(R"( +local t = {} +function t.m(a: T) end +t:@1 + )"); + + auto ac = autocomplete('1'); + + REQUIRE(ac.entryMap.count("m")); + // While this call is compatible with the type, this requires instantiation of a generic type which we don't perform + CHECK(ac.entryMap["m"].wrongIndexType); +} + TEST_CASE_FIXTURE(ACFixture, "string_prim_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local s = "hello" @@ -3053,7 +3086,7 @@ s:@1 TEST_CASE_FIXTURE(ACFixture, "string_prim_non_self_calls_are_avoided") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( local s = "hello" @@ -3070,7 +3103,7 @@ s.@1 TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_non_self_calls_are_fine") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( string.@1 @@ -3101,7 +3134,7 @@ table.@1 TEST_CASE_FIXTURE(ACBuiltinsFixture, "library_self_calls_are_invalid") { - ScopedFastFlag selfCallAutocompleteFix2{"LuauSelfCallAutocompleteFix2", true}; + ScopedFastFlag selfCallAutocompleteFix3{"LuauSelfCallAutocompleteFix3", true}; check(R"( string:@1 @@ -3113,8 +3146,11 @@ string:@1 CHECK(ac.entryMap["byte"].wrongIndexType == true); REQUIRE(ac.entryMap.count("char")); CHECK(ac.entryMap["char"].wrongIndexType == true); + + // We want the next test to evaluate to 'true', but we have to allow function defined with 'self' to be callable with ':' + // We may change the definition of the string metatable to not use 'self' types in the future (like byte/char/pack/unpack) REQUIRE(ac.entryMap.count("sub")); - CHECK(ac.entryMap["sub"].wrongIndexType == true); + CHECK(ac.entryMap["sub"].wrongIndexType == false); } TEST_CASE_FIXTURE(ACFixture, "source_module_preservation_and_invalidation") diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index c1917638..4b87c003 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -4352,8 +4352,6 @@ TEST_CASE("LoopUnrollControlFlow") {"LuauCompileLoopUnrollThresholdMaxBoost", 300}, }; - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - // break jumps to the end CHECK_EQ("\n" + compileFunction(R"( for i=1,3 do @@ -4669,8 +4667,6 @@ TEST_CASE("LoopUnrollCostBuiltins") {"LuauCompileLoopUnrollThresholdMaxBoost", 300}, }; - ScopedFastFlag sff("LuauCompileModelBuiltins", true); - // this loop uses builtins and is close to the cost budget so it's important that we model builtins as cheaper than regular calls CHECK_EQ("\n" + compileFunction(R"( function cipher(block, nonce) @@ -5893,8 +5889,6 @@ RETURN R0 2 TEST_CASE("OptimizationLevel") { - ScopedFastFlag sff("LuauAlwaysCaptureHotComments", true); - // at optimization level 1, no inlining is performed CHECK_EQ("\n" + compileFunction(R"( local function foo(a) @@ -5964,8 +5958,6 @@ RETURN R1 -1 TEST_CASE("BuiltinFolding") { - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - CHECK_EQ("\n" + compileFunction(R"( return math.abs(-42), @@ -6073,8 +6065,6 @@ RETURN R0 48 TEST_CASE("BuiltinFoldingProhibited") { - ScopedFastFlag sff("LuauCompileFoldBuiltins", true); - CHECK_EQ("\n" + compileFunction(R"( return math.abs(), @@ -6108,9 +6098,6 @@ L3: RETURN R0 -1 TEST_CASE("BuiltinFoldingMultret") { - ScopedFastFlag sff1("LuauCompileFoldBuiltins", true); - ScopedFastFlag sff2("LuauCompileBetterMultret", true); - CHECK_EQ("\n" + compileFunction(R"( local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000 local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index fc8ab2f9..1339fa0c 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -316,7 +316,8 @@ TEST_CASE("Errors") TEST_CASE("Events") { - ScopedFastFlag sff("LuauLenTM", true); + ScopedFastFlag sff1("LuauLenTM", true); + ScopedFastFlag sff2("LuauBetterNewindex", true); runConformance("events.lua"); } @@ -490,8 +491,6 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { - ScopedFastFlag sff("LuauCheckLenMT", true); - runConformance("types.lua", [](lua_State* L) { Luau::NullModuleResolver moduleResolver; Luau::InternalErrorReporter iceHandler; @@ -862,8 +861,6 @@ TEST_CASE("ApiCalls") TEST_CASE("ApiAtoms") { - ScopedFastFlag sff("LuauLazyAtoms", true); - StateRef globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); diff --git a/tests/ConstraintSolver.test.cpp b/tests/ConstraintSolver.test.cpp index f521c667..e33f6570 100644 --- a/tests/ConstraintSolver.test.cpp +++ b/tests/ConstraintSolver.test.cpp @@ -9,7 +9,7 @@ using namespace Luau; -static TypeId requireBinding(NotNull scope, const char* name) +static TypeId requireBinding(NotNull scope, const char* name) { auto b = linearSearchForBinding(scope, name); LUAU_ASSERT(b.has_value()); @@ -26,7 +26,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "hello") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ConstraintSolver cs{&arena, rootScope}; @@ -46,7 +46,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "generic_function") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ConstraintSolver cs{&arena, rootScope}; @@ -73,7 +73,7 @@ TEST_CASE_FIXTURE(ConstraintGraphBuilderFixture, "proper_let_generalization") )"); cgb.visit(block); - NotNull rootScope = NotNull(cgb.rootScope); + NotNull rootScope = NotNull(cgb.rootScope); ToStringOptions opts; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index c92c4457..90aa6236 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -258,7 +258,7 @@ std::optional Fixture::getType(const std::string& name) REQUIRE(module); if (FFlag::DebugLuauDeferredConstraintResolution) - return linearSearchForBinding(module->getModuleScope2(), name.c_str()); + return linearSearchForBinding(module->getModuleScope().get(), name.c_str()); else return lookupName(module->getModuleScope(), name); } @@ -434,7 +434,7 @@ BuiltinsFixture::BuiltinsFixture(bool freeze, bool prepareAutocomplete) ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture() : Fixture() - , cgb(mainModuleName, &arena, NotNull(&ice), frontend.getGlobalScope2()) + , cgb(mainModuleName, &arena, NotNull(&ice), frontend.getGlobalScope()) , forceTheFlag{"DebugLuauDeferredConstraintResolution", true} { BlockedTypeVar::nextIndex = 0; @@ -479,17 +479,17 @@ std::optional lookupName(ScopePtr scope, const std::string& name) return std::nullopt; } -std::optional linearSearchForBinding(Scope2* scope, const char* name) +std::optional linearSearchForBinding(Scope* scope, const char* name) { while (scope) { for (const auto& [n, ty] : scope->bindings) { if (n.astName() == name) - return ty; + return ty.typeId; } - scope = scope->parent; + scope = scope->parent.get(); } return std::nullopt; diff --git a/tests/Fixture.h b/tests/Fixture.h index 4bd6f1ea..a716fe9b 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -192,7 +192,7 @@ void dump(const std::vector& constraints); std::optional lookupName(ScopePtr scope, const std::string& name); // Warning: This function runs in O(n**2) -std::optional linearSearchForBinding(Scope2* scope, const char* name); +std::optional linearSearchForBinding(Scope* scope, const char* name); } // namespace Luau diff --git a/tests/Lexer.test.cpp b/tests/Lexer.test.cpp new file mode 100644 index 00000000..20d8d0d5 --- /dev/null +++ b/tests/Lexer.test.cpp @@ -0,0 +1,141 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/Lexer.h" + +#include "Fixture.h" +#include "ScopedFlags.h" + +#include "doctest.h" + +using namespace Luau; + +TEST_SUITE_BEGIN("LexerTests"); + +TEST_CASE("broken_string_works") +{ + const std::string testInput = "[["; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme lexeme = lexer.next(); + CHECK_EQ(lexeme.type, Lexeme::Type::BrokenString); + CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 2))); +} + +TEST_CASE("broken_comment") +{ + const std::string testInput = "--[[ "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme lexeme = lexer.next(); + CHECK_EQ(lexeme.type, Lexeme::Type::BrokenComment); + CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 6))); +} + +TEST_CASE("broken_comment_kept") +{ + const std::string testInput = "--[[ "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + CHECK_EQ(lexer.next().type, Lexeme::Type::BrokenComment); +} + +TEST_CASE("comment_skipped") +{ + const std::string testInput = "-- "; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + CHECK_EQ(lexer.next().type, Lexeme::Type::Eof); +} + +TEST_CASE("multilineCommentWithLexemeInAndAfter") +{ + const std::string testInput = "--[[ function \n" + "]] end"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme comment = lexer.next(); + Lexeme end = lexer.next(); + + CHECK_EQ(comment.type, Lexeme::Type::BlockComment); + CHECK_EQ(comment.location, Luau::Location(Luau::Position(0, 0), Luau::Position(1, 2))); + CHECK_EQ(end.type, Lexeme::Type::ReservedEnd); + CHECK_EQ(end.location, Luau::Location(Luau::Position(1, 3), Luau::Position(1, 6))); +} + +TEST_CASE("testBrokenEscapeTolerant") +{ + const std::string testInput = "'\\3729472897292378'"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme item = lexer.next(); + + CHECK_EQ(item.type, Lexeme::QuotedString); + CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, int(testInput.size())))); +} + +TEST_CASE("testBigDelimiters") +{ + const std::string testInput = "--[===[\n" + "\n" + "\n" + "\n" + "]===]"; + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + Lexeme item = lexer.next(); + + CHECK_EQ(item.type, Lexeme::Type::BlockComment); + CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(4, 5))); +} + +TEST_CASE("lookahead") +{ + const std::string testInput = "foo --[[ comment ]] bar : nil end"; + + Luau::Allocator alloc; + AstNameTable table(alloc); + Lexer lexer(testInput.c_str(), testInput.size(), table); + lexer.setSkipComments(true); + lexer.next(); // must call next() before reading data from lexer at least once + + CHECK_EQ(lexer.current().type, Lexeme::Name); + CHECK_EQ(lexer.current().name, std::string("foo")); + CHECK_EQ(lexer.lookahead().type, Lexeme::Name); + CHECK_EQ(lexer.lookahead().name, std::string("bar")); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::Name); + CHECK_EQ(lexer.current().name, std::string("bar")); + CHECK_EQ(lexer.lookahead().type, ':'); + + lexer.next(); + + CHECK_EQ(lexer.current().type, ':'); + CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedNil); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::ReservedNil); + CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedEnd); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::ReservedEnd); + CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); + + lexer.next(); + + CHECK_EQ(lexer.current().type, Lexeme::Eof); + CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); +} + +TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c517853f..c5c019aa 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -97,138 +97,6 @@ TEST_CASE("initial_double_is_aligned") TEST_SUITE_END(); -TEST_SUITE_BEGIN("LexerTests"); - -TEST_CASE("broken_string_works") -{ - const std::string testInput = "[["; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme lexeme = lexer.next(); - CHECK_EQ(lexeme.type, Lexeme::Type::BrokenString); - CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 2))); -} - -TEST_CASE("broken_comment") -{ - const std::string testInput = "--[[ "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme lexeme = lexer.next(); - CHECK_EQ(lexeme.type, Lexeme::Type::BrokenComment); - CHECK_EQ(lexeme.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, 6))); -} - -TEST_CASE("broken_comment_kept") -{ - const std::string testInput = "--[[ "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - CHECK_EQ(lexer.next().type, Lexeme::Type::BrokenComment); -} - -TEST_CASE("comment_skipped") -{ - const std::string testInput = "-- "; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - CHECK_EQ(lexer.next().type, Lexeme::Type::Eof); -} - -TEST_CASE("multilineCommentWithLexemeInAndAfter") -{ - const std::string testInput = "--[[ function \n" - "]] end"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme comment = lexer.next(); - Lexeme end = lexer.next(); - - CHECK_EQ(comment.type, Lexeme::Type::BlockComment); - CHECK_EQ(comment.location, Luau::Location(Luau::Position(0, 0), Luau::Position(1, 2))); - CHECK_EQ(end.type, Lexeme::Type::ReservedEnd); - CHECK_EQ(end.location, Luau::Location(Luau::Position(1, 3), Luau::Position(1, 6))); -} - -TEST_CASE("testBrokenEscapeTolerant") -{ - const std::string testInput = "'\\3729472897292378'"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme item = lexer.next(); - - CHECK_EQ(item.type, Lexeme::QuotedString); - CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(0, int(testInput.size())))); -} - -TEST_CASE("testBigDelimiters") -{ - const std::string testInput = "--[===[\n" - "\n" - "\n" - "\n" - "]===]"; - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - Lexeme item = lexer.next(); - - CHECK_EQ(item.type, Lexeme::Type::BlockComment); - CHECK_EQ(item.location, Luau::Location(Luau::Position(0, 0), Luau::Position(4, 5))); -} - -TEST_CASE("lookahead") -{ - const std::string testInput = "foo --[[ comment ]] bar : nil end"; - - Luau::Allocator alloc; - AstNameTable table(alloc); - Lexer lexer(testInput.c_str(), testInput.size(), table); - lexer.setSkipComments(true); - lexer.next(); // must call next() before reading data from lexer at least once - - CHECK_EQ(lexer.current().type, Lexeme::Name); - CHECK_EQ(lexer.current().name, std::string("foo")); - CHECK_EQ(lexer.lookahead().type, Lexeme::Name); - CHECK_EQ(lexer.lookahead().name, std::string("bar")); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::Name); - CHECK_EQ(lexer.current().name, std::string("bar")); - CHECK_EQ(lexer.lookahead().type, ':'); - - lexer.next(); - - CHECK_EQ(lexer.current().type, ':'); - CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedNil); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::ReservedNil); - CHECK_EQ(lexer.lookahead().type, Lexeme::ReservedEnd); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::ReservedEnd); - CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); - - lexer.next(); - - CHECK_EQ(lexer.current().type, Lexeme::Eof); - CHECK_EQ(lexer.lookahead().type, Lexeme::Eof); -} - -TEST_SUITE_END(); - TEST_SUITE_BEGIN("ParserTests"); TEST_CASE_FIXTURE(Fixture, "basic_parse") diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 288af8da..8c03fe55 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("ToString"); @@ -267,8 +268,16 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded") o.maxTypeLength = 40; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()"); - CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); - CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + } + else + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + } } TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") @@ -286,8 +295,17 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") o.maxTypeLength = 40; CHECK_EQ(toString(requireType("f0"), o), "() -> ()"); CHECK_EQ(toString(requireType("f1"), o), "(() -> ()) -> () -> ()"); - CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); - CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED*"); + } + else + { + CHECK_EQ(toString(requireType("f2"), o), "((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + CHECK_EQ(toString(requireType("f3"), o), "(((() -> ()) -> () -> ()) -> (() -> ()) -> ... "); + } + } TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table_state_braces") @@ -497,7 +515,10 @@ local function target(callback: nil) return callback(4, "hello") end )"); LUAU_REQUIRE_ERRORS(result); - CHECK_EQ("(nil) -> ()", toString(requireType("target"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(nil) -> (*error-type*)", toString(requireType("target"))); + else + CHECK_EQ("(nil) -> ()", toString(requireType("target"))); } TEST_CASE_FIXTURE(Fixture, "toStringGenericPack") diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index 4c5309e0..f4766104 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -13,6 +13,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferAnyError"); TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any") @@ -94,7 +96,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2") @@ -110,7 +115,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error2") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "length_of_error_type_does_not_produce_an_error") @@ -225,7 +233,10 @@ TEST_CASE_FIXTURE(Fixture, "calling_error_type_yields_error") CHECK_EQ("unknown", err->name); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") @@ -234,7 +245,10 @@ TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") local a = Utility.Create "Foo" {} )"); - CHECK_EQ("", toString(requireType("a"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("a"))); + else + CHECK_EQ("", toString(requireType("a"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_complex_function_with_any") diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 4f6adf97..0c878023 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -9,6 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauLowerBoundsCalculation); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("BuiltinTests"); @@ -952,7 +953,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic") CHECK_EQ("number", toString(requireType("a"))); CHECK_EQ("string", toString(requireType("b"))); CHECK_EQ("boolean", toString(requireType("c"))); - CHECK_EQ("", toString(requireType("d"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("d"))); + else + CHECK_EQ("", toString(requireType("d"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index a634ba09..70773d95 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauLowerBoundsCalculation); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -907,13 +908,19 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language") REQUIRE(tm1); CHECK_EQ("(string) -> number", toString(tm1->wantedType)); - CHECK_EQ("(string, ) -> number", toString(tm1->givenType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(string, *error-type*) -> number", toString(tm1->givenType)); + else + CHECK_EQ("(string, ) -> number", toString(tm1->givenType)); auto tm2 = get(result.errors[1]); REQUIRE(tm2); CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType)); - CHECK_EQ("(string, ) -> number", toString(tm2->givenType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("(string, *error-type*) -> number", toString(tm2->givenType)); + else + CHECK_EQ("(string, ) -> number", toString(tm2->givenType)); } TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type") @@ -1526,10 +1533,21 @@ function t:b() return 2 end -- not OK )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(R"(Type '() -> number' could not be converted into '() -> number' + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ(R"(Type '(*error-type*) -> number' could not be converted into '() -> number' caused by: Argument count mismatch. Function expects 1 argument, but none are specified)", - toString(result.errors[0])); + toString(result.errors[0])); + } + else + { + CHECK_EQ(R"(Type '() -> number' could not be converted into '() -> number' +caused by: + Argument count mismatch. Function expects 1 argument, but none are specified)", + toString(result.errors[0])); + } + } TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 46258072..7a1af164 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -10,6 +10,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauCheckGenericHOFTypes) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -1003,7 +1004,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") std::optional t0 = getMainModule()->getModuleScope()->lookupType("t0"); REQUIRE(t0); - CHECK_EQ("", toString(t0->type)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(t0->type)); + else + CHECK_EQ("", toString(t0->type)); auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { return get(err); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 354b3996..9b10092c 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -13,6 +13,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferLoops"); TEST_CASE_FIXTURE(Fixture, "for_loop") @@ -142,7 +144,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error") CHECK_EQ(2, result.errors.size()); TypeId p = requireType("p"); - CHECK_EQ("", toString(p)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(p)); + else + CHECK_EQ("", toString(p)); } TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function") diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 2343a7fa..a1d41339 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -12,6 +12,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + TEST_SUITE_BEGIN("TypeInferModules"); TEST_CASE_FIXTURE(BuiltinsFixture, "require") @@ -143,7 +145,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require_module_that_does_not_export") auto hootyType = requireType(bModule, "Hooty"); - CHECK_EQ("", toString(hootyType)); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(hootyType)); + else + CHECK_EQ("", toString(hootyType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "warn_if_you_try_to_require_a_non_modulescript") @@ -244,7 +249,11 @@ local ModuleA = require(game.A) LUAU_REQUIRE_NO_ERRORS(result); std::optional oty = requireType("ModuleA"); - CHECK_EQ("", toString(*oty)); + + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(*oty)); + else + CHECK_EQ("", toString(*oty)); } TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_modify_imported_types") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index c90f0a4d..3d6c0193 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -490,8 +490,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error") { - ScopedFastFlag sff("LuauCheckLenMT", true); - CheckResult result = check(R"( --!strict local foo = { diff --git a/tests/TypeInfer.primitives.test.cpp b/tests/TypeInfer.primitives.test.cpp index d60c96e0..a06fd749 100644 --- a/tests/TypeInfer.primitives.test.cpp +++ b/tests/TypeInfer.primitives.test.cpp @@ -12,6 +12,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauDeduceFindMatchReturnTypes) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -49,7 +50,10 @@ TEST_CASE_FIXTURE(Fixture, "string_index") REQUIRE(nat); CHECK_EQ("string", toString(nat->ty)); - CHECK_EQ("", toString(requireType("t"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("t"))); + else + CHECK_EQ("", toString(requireType("t"))); } TEST_CASE_FIXTURE(Fixture, "string_method") diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index cc8cdee3..8a1cadcf 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -8,6 +8,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauLowerBoundsCalculation) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -526,7 +527,10 @@ TEST_CASE_FIXTURE(Fixture, "type_narrow_to_vector") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("", toString(requireTypeAtPosition({3, 28}))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireTypeAtPosition({3, 28}))); + else + CHECK_EQ("", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(Fixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 858e8ac0..af6185ef 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation); LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); +LUAU_FASTFLAG(LuauSpecialTypesAsterisked); using namespace Luau; @@ -237,10 +238,21 @@ TEST_CASE_FIXTURE(Fixture, "type_errors_infer_types") // TODO: Should we assert anything about these tests when DCR is being used? if (!FFlag::DebugLuauDeferredConstraintResolution) { - CHECK_EQ("", toString(requireType("c"))); - CHECK_EQ("", toString(requireType("d"))); - CHECK_EQ("", toString(requireType("e"))); - CHECK_EQ("", toString(requireType("f"))); + if (FFlag::LuauSpecialTypesAsterisked) + { + CHECK_EQ("*error-type*", toString(requireType("c"))); + CHECK_EQ("*error-type*", toString(requireType("d"))); + CHECK_EQ("*error-type*", toString(requireType("e"))); + CHECK_EQ("*error-type*", toString(requireType("f"))); + } + else + { + CHECK_EQ("", toString(requireType("c"))); + CHECK_EQ("", toString(requireType("d"))); + CHECK_EQ("", toString(requireType("e"))); + CHECK_EQ("", toString(requireType("f"))); + } + } } @@ -650,7 +662,11 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional") std::optional t0 = getMainModule()->getModuleScope()->lookupType("t0"); REQUIRE(t0); - CHECK_EQ("", toString(t0->type)); + + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(t0->type)); + else + CHECK_EQ("", toString(t0->type)); auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) { return get(err); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index d51a38f8..e0a0e5b5 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -9,6 +9,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) + struct TryUnifyFixture : Fixture { TypeArena arena; @@ -121,7 +123,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "members_of_failed_typepack_unification_are_u LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ("a", toString(requireType("a"))); - CHECK_EQ("", toString(requireType("b"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("b"))); + else + CHECK_EQ("", toString(requireType("b"))); } TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_constrained") @@ -136,7 +141,10 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "result_of_failed_typepack_unification_is_con LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ("a", toString(requireType("a"))); - CHECK_EQ("", toString(requireType("b"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("b"))); + else + CHECK_EQ("", toString(requireType("b"))); CHECK_EQ("number", toString(requireType("c"))); } diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 94918692..8eb485e9 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -7,6 +7,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauLowerBoundsCalculation) +LUAU_FASTFLAG(LuauSpecialTypesAsterisked) using namespace Luau; @@ -199,7 +200,10 @@ TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_missing_property") CHECK_EQ(mup->missing[0], *bTy); CHECK_EQ(mup->key, "x"); - CHECK_EQ("", toString(requireType("r"))); + if (FFlag::LuauSpecialTypesAsterisked) + CHECK_EQ("*error-type*", toString(requireType("r"))); + else + CHECK_EQ("", toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "index_on_a_union_type_with_one_property_of_type_any") diff --git a/tests/TypePack.test.cpp b/tests/TypePack.test.cpp index 35852c05..1087a24c 100644 --- a/tests/TypePack.test.cpp +++ b/tests/TypePack.test.cpp @@ -25,7 +25,7 @@ struct TypePackFixture TypePackId freshTypePack() { - typePacks.emplace_back(new TypePackVar{Unifiable::Free{{}}}); + typePacks.emplace_back(new TypePackVar{Unifiable::Free{TypeLevel{}}}); return typePacks.back().get(); } diff --git a/tests/conformance/errors.lua b/tests/conformance/errors.lua index 0b5aafed..6c0ac5e6 100644 --- a/tests/conformance/errors.lua +++ b/tests/conformance/errors.lua @@ -380,7 +380,8 @@ assert(ecall(function() return "a" + "b" end) == "attempt to perform arithmetic assert(ecall(function() return 1 > nil end) == "attempt to compare nil < number") -- note reversed order (by design) assert(ecall(function() return "a" <= 5 end) == "attempt to compare string <= number") -assert(ecall(function() local t = {} setmetatable(t, { __newindex = function(t,i,v) end }) t[nil] = 2 end) == "table index is nil") +assert(ecall(function() local t = {} t[nil] = 2 end) == "table index is nil") +assert(ecall(function() local t = {} t[0/0] = 2 end) == "table index is NaN") -- for loop type errors assert(ecall(function() for i='a',2 do end end) == "invalid 'for' initial value (number expected, got string)") diff --git a/tests/conformance/events.lua b/tests/conformance/events.lua index 42f1beda..6dcdbf0e 100644 --- a/tests/conformance/events.lua +++ b/tests/conformance/events.lua @@ -424,4 +424,57 @@ do assert(not ok and err:match("table or string expected")) end +-- verify that NaN/nil keys are passed to __newindex even though table assignment with them anywhere in the chain fails +do + assert(pcall(function() local t = {} t[nil] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[nil] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[nil] = 5 end) == true) + + assert(pcall(function() local t = {} t[0/0] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = {} }) t[0/0] = 5 end) == false) + assert(pcall(function() local t = {} setmetatable(t, { __newindex = function() end }) t[0/0] = 5 end) == true) +end + +-- verify that __newindex gets called for frozen tables but only if the assignment is to a key absent from the table +do + local ni = {} + local t = table.create(2) + + t[1] = 42 + -- t[2] is semantically absent with storage allocated for it + + t.a = 1 + t.b = 2 + t.b = nil -- this sets 'b' value to nil but leaves key as is to exercise more internal paths -- no observable behavior change expected between b and other absent keys + + setmetatable(t, { __newindex = function(_, k, v) + assert(v == 42) + table.insert(ni, k) + end }) + table.freeze(t) + + -- "redundant" combinations are there to test all three of SETTABLEN/SETTABLEKS/SETTABLE + assert(pcall(function() t.a = 42 end) == false) + assert(pcall(function() t[1] = 42 end) == false) + assert(pcall(function() local key key = "a" t[key] = 42 end) == false) + assert(pcall(function() local key key = 1 t[key] = 42 end) == false) + + -- now repeat the same for keys absent from the table: b (semantically absent), c (physically absent), 2 (semantically absent), 3 (physically absent) + assert(pcall(function() t.b = 42 end) == true) + assert(pcall(function() t.c = 42 end) == true) + assert(pcall(function() local key key = "b" t[key] = 42 end) == true) + assert(pcall(function() local key key = "c" t[key] = 42 end) == true) + assert(pcall(function() t[2] = 42 end) == true) + assert(pcall(function() t[3] = 42 end) == true) + assert(pcall(function() local key key = 2 t[key] = 42 end) == true) + assert(pcall(function() local key key = 3 t[key] = 42 end) == true) + + -- validate the assignment sequence + local ei = { "b", "c", "b", "c", 2, 3, 2, 3 } + assert(#ni == #ei) + for k,v in ni do + assert(ei[k] == v) + end +end + return 'OK' diff --git a/tests/main.cpp b/tests/main.cpp index e7c4aed6..40ccd0b3 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -61,7 +61,7 @@ static bool debuggerPresent() static int testAssertionHandler(const char* expr, const char* file, int line, const char* function) { if (debuggerPresent()) - LUAU_DEBUGBREAK(); + LUAU_DEBUGBREAK(); ADD_FAIL_AT(file, line, "Assertion failed: ", std::string(expr)); return 1; @@ -298,5 +298,3 @@ int main(int argc, char** argv) } return result; } - - diff --git a/tools/faillist.txt b/tools/faillist.txt index dc74a6a9..40fc3c06 100644 --- a/tools/faillist.txt +++ b/tools/faillist.txt @@ -44,7 +44,6 @@ 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 @@ -65,47 +64,34 @@ 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_compatible_self_calls 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.do_wrong_compatible_self_calls 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 @@ -114,16 +100,13 @@ 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.no_wrong_compatible_self_calls_with_generics 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 @@ -154,9 +137,7 @@ 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 @@ -231,16 +212,10 @@ 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 @@ -256,12 +231,9 @@ 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 @@ -269,29 +241,97 @@ 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.reexport_cyclic_type +FrontendTest.reexport_type_alias 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 +GenericsTests.apply_type_function_nested_generics1 +GenericsTests.apply_type_function_nested_generics2 +GenericsTests.better_mismatch_error_messages +GenericsTests.bound_tables_do_not_clone_original_fields +GenericsTests.check_generic_typepack_function +GenericsTests.check_mutual_generic_functions +GenericsTests.correctly_instantiate_polymorphic_member_functions +GenericsTests.do_not_always_instantiate_generic_intersection_types +GenericsTests.do_not_infer_generic_functions +GenericsTests.dont_substitute_bound_types +GenericsTests.dont_unify_bound_types +GenericsTests.duplicate_generic_type_packs +GenericsTests.duplicate_generic_types +GenericsTests.error_detailed_function_mismatch_generic_pack +GenericsTests.error_detailed_function_mismatch_generic_types +GenericsTests.factories_of_generics +GenericsTests.function_arguments_can_be_polytypes +GenericsTests.function_results_can_be_polytypes +GenericsTests.generic_argument_count_too_few +GenericsTests.generic_argument_count_too_many +GenericsTests.generic_factories +GenericsTests.generic_functions_dont_cache_type_parameters +GenericsTests.generic_functions_in_types +GenericsTests.generic_functions_should_be_memory_safe +GenericsTests.generic_table_method +GenericsTests.generic_type_pack_syntax +GenericsTests.generic_type_pack_unification1 +GenericsTests.generic_type_pack_unification2 +GenericsTests.generic_type_pack_unification3 +GenericsTests.infer_generic_function_function_argument +GenericsTests.infer_generic_function_function_argument_overloaded +GenericsTests.infer_generic_lib_function_function_argument +GenericsTests.infer_generic_property +GenericsTests.inferred_local_vars_can_be_polytypes +GenericsTests.instantiate_cyclic_generic_function +GenericsTests.instantiate_generic_function_in_assignments +GenericsTests.instantiate_generic_function_in_assignments2 +GenericsTests.instantiated_function_argument_names +GenericsTests.instantiation_sharing_types +GenericsTests.local_vars_can_be_instantiated_polytypes +GenericsTests.mutable_state_polymorphism +GenericsTests.no_stack_overflow_from_quantifying +GenericsTests.properties_can_be_instantiated_polytypes +GenericsTests.properties_can_be_polytypes +GenericsTests.rank_N_types_via_typeof +GenericsTests.reject_clashing_generic_and_pack_names +GenericsTests.self_recursive_instantiated_param +GenericsTests.substitution_with_bound_table +GenericsTests.typefuns_sharing_types +GenericsTests.variadic_generics +IntersectionTypes.argument_is_intersection +IntersectionTypes.error_detailed_intersection_all +IntersectionTypes.error_detailed_intersection_part +IntersectionTypes.fx_intersection_as_argument +IntersectionTypes.fx_union_as_argument_fails +IntersectionTypes.index_on_an_intersection_type_with_all_parts_missing_the_property +IntersectionTypes.index_on_an_intersection_type_with_mixed_types +IntersectionTypes.index_on_an_intersection_type_with_one_part_missing_the_property +IntersectionTypes.index_on_an_intersection_type_with_one_property_of_type_any +IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist +IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth +IntersectionTypes.no_stack_overflow_from_flattenintersection +IntersectionTypes.overload_is_not_a_function +IntersectionTypes.propagates_name +IntersectionTypes.select_correct_union_fn +IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions +IntersectionTypes.table_combines +IntersectionTypes.table_combines_missing +IntersectionTypes.table_extra_ok +IntersectionTypes.table_intersection_setmetatable +IntersectionTypes.table_intersection_write +IntersectionTypes.table_intersection_write_sealed +IntersectionTypes.table_intersection_write_sealed_indirect +IntersectionTypes.table_write_sealed_indirect 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 @@ -342,45 +382,237 @@ 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 +ProvisionalTests.bail_early_if_unification_is_too_complicated +ProvisionalTests.choose_the_right_overload_for_pcall +ProvisionalTests.constrained_is_level_dependent +ProvisionalTests.discriminate_from_x_not_equal_to_nil +ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack +ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean +ProvisionalTests.free_is_not_bound_to_any +ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten +ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns +ProvisionalTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound +ProvisionalTests.it_should_be_agnostic_of_actual_size +ProvisionalTests.lower_bounds_calculation_is_too_permissive_with_overloaded_higher_order_functions +ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap +ProvisionalTests.normalization_fails_on_certain_kinds_of_cyclic_tables +ProvisionalTests.operator_eq_completely_incompatible +ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing +ProvisionalTests.setmetatable_constrains_free_type_into_free_table +ProvisionalTests.typeguard_inference_incomplete +ProvisionalTests.weird_fail_to_unify_type_pack +ProvisionalTests.weirditer_should_not_loop_forever +ProvisionalTests.while_body_are_also_refined +ProvisionalTests.xpcall_returns_what_f_returns +RefinementTest.and_constraint +RefinementTest.and_or_peephole_refinement +RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string +RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number +RefinementTest.assert_non_binary_expressions_actually_resolve_constraints +RefinementTest.assign_table_with_refined_property_with_a_similar_type_is_illegal +RefinementTest.call_a_more_specific_function_using_typeguard +RefinementTest.correctly_lookup_a_shadowed_local_that_which_was_previously_refined +RefinementTest.correctly_lookup_property_whose_base_was_previously_refined +RefinementTest.correctly_lookup_property_whose_base_was_previously_refined2 +RefinementTest.discriminate_from_isa_of_x +RefinementTest.discriminate_from_truthiness_of_x +RefinementTest.discriminate_on_properties_of_disjoint_tables_where_that_property_is_true_or_false +RefinementTest.discriminate_tag +RefinementTest.either_number_or_string +RefinementTest.eliminate_subclasses_of_instance +RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil +RefinementTest.free_type_is_equal_to_an_lvalue +RefinementTest.impossible_type_narrow_is_not_an_error +RefinementTest.index_on_a_refined_property +RefinementTest.invert_is_truthy_constraint +RefinementTest.invert_is_truthy_constraint_ifelse_expression +RefinementTest.is_truthy_constraint +RefinementTest.is_truthy_constraint_ifelse_expression +RefinementTest.lvalue_is_equal_to_a_term +RefinementTest.lvalue_is_equal_to_another_lvalue +RefinementTest.lvalue_is_not_nil +RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering +RefinementTest.narrow_property_of_a_bounded_variable +RefinementTest.narrow_this_large_union +RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true +RefinementTest.not_a_and_not_b +RefinementTest.not_a_and_not_b2 +RefinementTest.not_a_or_not_b +RefinementTest.not_a_or_not_b2 +RefinementTest.not_and_constraint +RefinementTest.not_t_or_some_prop_of_t +RefinementTest.or_predicate_with_truthy_predicates +RefinementTest.parenthesized_expressions_are_followed_through +RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table +RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string +RefinementTest.refine_unknowns +RefinementTest.string_not_equal_to_string_or_nil +RefinementTest.term_is_equal_to_an_lvalue +RefinementTest.truthy_constraint_on_properties +RefinementTest.type_assertion_expr_carry_its_constraints +RefinementTest.type_comparison_ifelse_expression +RefinementTest.type_guard_can_filter_for_intersection_of_tables +RefinementTest.type_guard_can_filter_for_overloaded_function +RefinementTest.type_guard_narrowed_into_nothingness +RefinementTest.type_narrow_for_all_the_userdata +RefinementTest.type_narrow_to_vector +RefinementTest.typeguard_cast_free_table_to_vector +RefinementTest.typeguard_cast_instance_or_vector3_to_vector +RefinementTest.typeguard_doesnt_leak_to_elseif +RefinementTest.typeguard_in_assert_position +RefinementTest.typeguard_in_if_condition_position +RefinementTest.typeguard_narrows_for_functions +RefinementTest.typeguard_narrows_for_table +RefinementTest.typeguard_not_to_be_string +RefinementTest.typeguard_only_look_up_types_from_global_scope +RefinementTest.unknown_lvalue_is_not_synonymous_with_other_on_not_equal +RefinementTest.what_nonsensical_condition +RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table +RefinementTest.x_is_not_instance_or_else_not_part RuntimeLimits.typescript_port_of_Result_type +TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible +TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible +TableTests.access_index_metamethod_that_returns_variadic +TableTests.accidentally_checked_prop_in_opposite_branch +TableTests.assigning_to_an_unsealed_table_with_string_literal_should_infer_new_properties_over_indexer +TableTests.augment_nested_table +TableTests.augment_table +TableTests.builtin_table_names +TableTests.call_method +TableTests.call_method_with_explicit_self_argument +TableTests.cannot_augment_sealed_table +TableTests.cannot_call_tables +TableTests.cannot_change_type_of_unsealed_table_prop +TableTests.casting_sealed_tables_with_props_into_table_with_indexer +TableTests.casting_tables_with_props_into_table_with_indexer3 +TableTests.casting_tables_with_props_into_table_with_indexer4 +TableTests.casting_unsealed_tables_with_props_into_table_with_indexer +TableTests.checked_prop_too_early +TableTests.common_table_element_general +TableTests.common_table_element_inner_index +TableTests.common_table_element_inner_prop +TableTests.common_table_element_list +TableTests.common_table_element_union_assignment +TableTests.common_table_element_union_in_call +TableTests.common_table_element_union_in_call_tail +TableTests.common_table_element_union_in_prop +TableTests.confusing_indexing +TableTests.defining_a_method_for_a_builtin_sealed_table_must_fail +TableTests.defining_a_method_for_a_local_sealed_table_must_fail +TableTests.defining_a_method_for_a_local_unsealed_table_is_ok +TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail +TableTests.defining_a_self_method_for_a_local_sealed_table_must_fail +TableTests.defining_a_self_method_for_a_local_unsealed_table_is_ok +TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar +TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index +TableTests.dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back +TableTests.dont_leak_free_table_props +TableTests.dont_quantify_table_that_belongs_to_outer_scope +TableTests.dont_seal_an_unsealed_table_by_passing_it_to_a_function_that_takes_a_sealed_table +TableTests.dont_suggest_exact_match_keys +TableTests.error_detailed_indexer_key +TableTests.error_detailed_indexer_value +TableTests.error_detailed_metatable_prop +TableTests.error_detailed_prop +TableTests.error_detailed_prop_nested +TableTests.expected_indexer_from_table_union +TableTests.expected_indexer_value_type_extra +TableTests.expected_indexer_value_type_extra_2 +TableTests.explicitly_typed_table +TableTests.explicitly_typed_table_error +TableTests.explicitly_typed_table_with_indexer +TableTests.found_like_key_in_table_function_call +TableTests.found_like_key_in_table_property_access +TableTests.found_multiple_like_keys +TableTests.function_calls_produces_sealed_table_given_unsealed_table +TableTests.generalize_table_argument +TableTests.getmetatable_returns_pointer_to_metatable +TableTests.give_up_after_one_metatable_index_look_up +TableTests.hide_table_error_properties +TableTests.indexer_fn +TableTests.indexer_on_sealed_table_must_unify_with_free_table +TableTests.indexer_table +TableTests.indexing_from_a_table_should_prefer_properties_when_possible +TableTests.inequality_operators_imply_exactly_matching_types +TableTests.infer_array_2 +TableTests.infer_indexer_from_value_property_in_literal +TableTests.inferred_return_type_of_free_table +TableTests.inferring_crazy_table_should_also_be_quick +TableTests.instantiate_table_cloning +TableTests.instantiate_table_cloning_2 +TableTests.instantiate_table_cloning_3 +TableTests.instantiate_tables_at_scope_level +TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound +TableTests.leaking_bad_metatable_errors +TableTests.length_operator_intersection +TableTests.length_operator_non_table_union +TableTests.length_operator_union +TableTests.length_operator_union_errors +TableTests.less_exponential_blowup_please +TableTests.meta_add +TableTests.meta_add_both_ways +TableTests.meta_add_inferred +TableTests.metatable_mismatch_should_fail +TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred +TableTests.mixed_tables_with_implicit_numbered_keys +TableTests.MixedPropertiesAndIndexers +TableTests.nil_assign_doesnt_hit_indexer +TableTests.okay_to_add_property_to_unsealed_tables_by_function_call +TableTests.only_ascribe_synthetic_names_at_module_scope +TableTests.oop_indexer_works +TableTests.oop_polymorphic +TableTests.open_table_unification_2 +TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table +TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2 +TableTests.pass_incompatible_union_to_a_generic_table_without_crashing +TableTests.passing_compatible_unions_to_a_generic_table_without_crashing +TableTests.persistent_sealed_table_is_immutable +TableTests.property_lookup_through_tabletypevar_metatable +TableTests.quantify_even_that_table_was_never_exported_at_all +TableTests.quantify_metatables_of_metatables_of_table +TableTests.quantifying_a_bound_var_works +TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table +TableTests.recursive_metatable_type_call +TableTests.result_is_always_any_if_lhs_is_any +TableTests.result_is_bool_for_equality_operators_if_lhs_is_any +TableTests.right_table_missing_key +TableTests.right_table_missing_key2 +TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type +TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type +TableTests.setmetatable_cant_be_used_to_mutate_global_types +TableTests.shared_selfs +TableTests.shared_selfs_from_free_param +TableTests.shared_selfs_through_metatables +TableTests.table_function_check_use_after_free +TableTests.table_indexing_error_location +TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict +TableTests.table_insert_should_cope_with_optional_properties_in_strict +TableTests.table_length +TableTests.table_param_row_polymorphism_2 +TableTests.table_param_row_polymorphism_3 +TableTests.table_simple_call +TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors +TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors +TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2 +TableTests.table_unifies_into_map +TableTests.tables_get_names_from_their_locals +TableTests.tc_member_function +TableTests.tc_member_function_2 +TableTests.top_table_type +TableTests.type_mismatch_on_massive_table_is_cut_short +TableTests.unification_of_unions_in_a_self_referential_type +TableTests.unifying_tables_shouldnt_uaf1 +TableTests.unifying_tables_shouldnt_uaf2 +TableTests.used_colon_correctly +TableTests.used_colon_instead_of_dot +TableTests.used_dot_instead_of_colon +TableTests.used_dot_instead_of_colon_but_correctly +TableTests.user_defined_table_types_are_named +TableTests.width_subtyping ToDot.bound_table ToDot.class ToDot.function @@ -401,9 +633,13 @@ 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 +TryUnifyTests.cli_41095_concat_log_in_sealed_table_unification +TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType +TryUnifyTests.result_of_failed_typepack_unification_is_constrained +TryUnifyTests.typepack_unification_should_trim_free_tails +TryUnifyTests.variadics_should_use_reversed_properly TypeAliases.basic_alias TypeAliases.cannot_steal_hoisted_type_alias TypeAliases.cli_38393_recursive_intersection_oom @@ -446,6 +682,37 @@ 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 +TypeInfer.check_expr_recursion_limit +TypeInfer.checking_should_not_ice +TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error +TypeInfer.cyclic_follow +TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table +TypeInfer.dont_report_type_errors_within_an_AstStatError +TypeInfer.follow_on_new_types_in_substitution +TypeInfer.free_typevars_introduced_within_control_flow_constructs_do_not_get_an_elevated_TypeLevel +TypeInfer.globals +TypeInfer.globals2 +TypeInfer.index_expr_should_be_checked +TypeInfer.infer_assignment_value_types +TypeInfer.infer_assignment_value_types_mutable_lval +TypeInfer.infer_through_group_expr +TypeInfer.infer_type_assertion_value_type +TypeInfer.no_heap_use_after_free_error +TypeInfer.no_infinite_loop_when_trying_to_unify_uh_this +TypeInfer.no_stack_overflow_from_isoptional +TypeInfer.no_stack_overflow_from_isoptional2 +TypeInfer.recursive_metatable_crash +TypeInfer.tc_after_error_recovery_no_replacement_name_in_error +TypeInfer.tc_if_else_expressions1 +TypeInfer.tc_if_else_expressions2 +TypeInfer.tc_if_else_expressions_expected_type_1 +TypeInfer.tc_if_else_expressions_expected_type_2 +TypeInfer.tc_if_else_expressions_expected_type_3 +TypeInfer.tc_if_else_expressions_type_union +TypeInfer.type_infer_recursion_limit_no_ice +TypeInfer.types stored in astResolvedTypes +TypeInfer.warn_on_lowercase_parent_property +TypeInfer.weird_case TypeInferAnyError.any_type_propagates TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any TypeInferAnyError.call_to_any_yields_any @@ -496,26 +763,289 @@ 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.calling_function_with_anytypepack_doesnt_leak_free_types +TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument TypeInferFunctions.cannot_hoist_interior_defns_into_signature TypeInferFunctions.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.dont_infer_parameter_types_for_functions_from_their_call_site +TypeInferFunctions.dont_mutate_the_underlying_head_of_typepack_when_calling_with_self TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict +TypeInferFunctions.error_detailed_function_mismatch_arg +TypeInferFunctions.error_detailed_function_mismatch_arg_count +TypeInferFunctions.error_detailed_function_mismatch_ret +TypeInferFunctions.error_detailed_function_mismatch_ret_count +TypeInferFunctions.error_detailed_function_mismatch_ret_mult TypeInferFunctions.first_argument_can_be_optional +TypeInferFunctions.free_is_not_bound_to_unknown TypeInferFunctions.func_expr_doesnt_leak_free +TypeInferFunctions.function_cast_error_uses_correct_language +TypeInferFunctions.function_decl_non_self_sealed_overwrite +TypeInferFunctions.function_decl_non_self_sealed_overwrite_2 +TypeInferFunctions.function_decl_non_self_unsealed_overwrite +TypeInferFunctions.function_decl_quantify_right_type +TypeInferFunctions.function_does_not_return_enough_values +TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer TypeInferFunctions.higher_order_function_2 TypeInferFunctions.higher_order_function_4 +TypeInferFunctions.ignored_return_values +TypeInferFunctions.inconsistent_higher_order_function +TypeInferFunctions.inconsistent_return_types +TypeInferFunctions.infer_anonymous_function_arguments +TypeInferFunctions.infer_anonymous_function_arguments_outside_call TypeInferFunctions.infer_return_type_from_selected_overload TypeInferFunctions.infer_that_function_does_not_return_a_table +TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time +TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time2 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.no_lossy_function_type +TypeInferFunctions.occurs_check_failure_in_function_return_type +TypeInferFunctions.quantify_constrained_types +TypeInferFunctions.record_matching_overload TypeInferFunctions.recursive_function TypeInferFunctions.recursive_local_function +TypeInferFunctions.report_exiting_without_return_nonstrict +TypeInferFunctions.report_exiting_without_return_strict +TypeInferFunctions.return_type_by_overload +TypeInferFunctions.strict_mode_ok_with_missing_arguments +TypeInferFunctions.too_few_arguments_variadic +TypeInferFunctions.too_few_arguments_variadic_generic +TypeInferFunctions.too_few_arguments_variadic_generic2 TypeInferFunctions.too_many_arguments +TypeInferFunctions.too_many_return_values TypeInferFunctions.toposort_doesnt_break_mutual_recursion TypeInferFunctions.vararg_function_is_quantified TypeInferFunctions.vararg_functions_should_allow_calls_of_any_types_and_size +TypeInferLoops.correctly_scope_locals_while +TypeInferLoops.for_in_loop +TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values +TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given +TypeInferLoops.for_in_loop_on_error +TypeInferLoops.for_in_loop_on_non_function +TypeInferLoops.for_in_loop_should_fail_with_non_function_iterator +TypeInferLoops.for_in_loop_where_iteratee_is_free +TypeInferLoops.for_in_loop_with_custom_iterator +TypeInferLoops.for_in_loop_with_next +TypeInferLoops.for_in_with_a_custom_iterator_should_type_check +TypeInferLoops.for_in_with_an_iterator_of_type_any +TypeInferLoops.for_in_with_just_one_iterator_is_ok +TypeInferLoops.fuzz_fail_missing_instantitation_follow +TypeInferLoops.ipairs_produces_integral_indices +TypeInferLoops.loop_iter_basic +TypeInferLoops.loop_iter_iter_metamethod +TypeInferLoops.loop_iter_no_indexer_nonstrict +TypeInferLoops.loop_iter_no_indexer_strict +TypeInferLoops.loop_iter_trailing_nil +TypeInferLoops.loop_typecheck_crash_on_empty_optional +TypeInferLoops.properly_infer_iteratee_is_a_free_table +TypeInferLoops.repeat_loop +TypeInferLoops.repeat_loop_condition_binds_to_its_block +TypeInferLoops.symbols_in_repeat_block_should_not_be_visible_beyond_until_condition +TypeInferLoops.unreachable_code_after_infinite_loop +TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free +TypeInferLoops.while_loop +TypeInferModules.bound_free_table_export_is_ok +TypeInferModules.constrained_anyification_clone_immutable_types +TypeInferModules.custom_require_global +TypeInferModules.do_not_modify_imported_types +TypeInferModules.do_not_modify_imported_types_2 +TypeInferModules.do_not_modify_imported_types_3 +TypeInferModules.do_not_modify_imported_types_4 +TypeInferModules.general_require_call_expression +TypeInferModules.general_require_type_mismatch +TypeInferModules.module_type_conflict +TypeInferModules.module_type_conflict_instantiated +TypeInferModules.require +TypeInferModules.require_a_variadic_function +TypeInferModules.require_failed_module +TypeInferModules.require_module_that_does_not_export +TypeInferModules.require_types +TypeInferModules.type_error_of_unknown_qualified_type +TypeInferModules.warn_if_you_try_to_require_a_non_modulescript +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2 +TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon +TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table +TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory +TypeInferOOP.method_depends_on_table +TypeInferOOP.methods_are_topologically_sorted +TypeInferOOP.nonstrict_self_mismatch_tail +TypeInferOOP.object_constructor_can_refer_to_method_of_self +TypeInferOOP.table_oop +TypeInferOperators.and_adds_boolean +TypeInferOperators.and_adds_boolean_no_superfluous_union +TypeInferOperators.and_binexps_dont_unify +TypeInferOperators.and_or_ternary +TypeInferOperators.CallAndOrOfFunctions +TypeInferOperators.cannot_compare_tables_that_do_not_have_the_same_metatable +TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable +TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators +TypeInferOperators.cli_38355_recursive_union +TypeInferOperators.compare_numbers +TypeInferOperators.compare_strings +TypeInferOperators.compound_assign_basic +TypeInferOperators.compound_assign_metatable +TypeInferOperators.compound_assign_mismatch_metatable +TypeInferOperators.compound_assign_mismatch_op +TypeInferOperators.compound_assign_mismatch_result +TypeInferOperators.concat_op_on_free_lhs_and_string_rhs +TypeInferOperators.concat_op_on_string_lhs_and_free_rhs +TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops +TypeInferOperators.dont_strip_nil_from_rhs_or_operator +TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds +TypeInferOperators.error_on_invalid_operand_types_to_relational_operators +TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2 +TypeInferOperators.expected_types_through_binary_and +TypeInferOperators.expected_types_through_binary_or +TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators +TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown +TypeInferOperators.operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap +TypeInferOperators.operator_eq_verifies_types_do_intersect +TypeInferOperators.or_joins_types +TypeInferOperators.or_joins_types_with_no_extras +TypeInferOperators.primitive_arith_no_metatable +TypeInferOperators.primitive_arith_no_metatable_with_follows +TypeInferOperators.primitive_arith_possible_metatable +TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not +TypeInferOperators.refine_and_or +TypeInferOperators.some_primitive_binary_ops +TypeInferOperators.strict_binary_op_where_lhs_unknown +TypeInferOperators.strip_nil_from_lhs_or_operator +TypeInferOperators.strip_nil_from_lhs_or_operator2 +TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection +TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs +TypeInferOperators.typecheck_unary_len_error +TypeInferOperators.typecheck_unary_minus +TypeInferOperators.typecheck_unary_minus_error +TypeInferOperators.unary_not_is_boolean +TypeInferOperators.unknown_type_in_comparison +TypeInferOperators.UnknownGlobalCompoundAssign +TypeInferPrimitives.cannot_call_primitives +TypeInferPrimitives.CheckMethodsOfNumber +TypeInferPrimitives.string_function_other +TypeInferPrimitives.string_index +TypeInferPrimitives.string_length +TypeInferPrimitives.string_method +TypeInferUnknownNever.array_like_table_of_never_is_inhabitable +TypeInferUnknownNever.assign_to_global_which_is_never +TypeInferUnknownNever.assign_to_local_which_is_never +TypeInferUnknownNever.assign_to_prop_which_is_never +TypeInferUnknownNever.assign_to_subscript_which_is_never +TypeInferUnknownNever.call_never +TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators +TypeInferUnknownNever.index_on_never +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never +TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never +TypeInferUnknownNever.length_of_never +TypeInferUnknownNever.math_operators_and_never +TypeInferUnknownNever.never_is_reflexive +TypeInferUnknownNever.never_subtype_and_string_supertype +TypeInferUnknownNever.string_subtype_and_unknown_supertype +TypeInferUnknownNever.table_with_prop_of_type_never_is_also_reflexive +TypeInferUnknownNever.table_with_prop_of_type_never_is_uninhabitable +TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable +TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2 +TypeInferUnknownNever.unary_minus_of_never +TypeInferUnknownNever.unknown_is_reflexive +TypePackTests.cyclic_type_packs +TypePackTests.higher_order_function +TypePackTests.multiple_varargs_inference_are_not_confused +TypePackTests.no_return_size_should_be_zero +TypePackTests.pack_tail_unification_check +TypePackTests.parenthesized_varargs_returns_any +TypePackTests.self_and_varargs_should_work +TypePackTests.type_alias_backwards_compatible +TypePackTests.type_alias_default_export +TypePackTests.type_alias_default_mixed_self +TypePackTests.type_alias_default_type_chained +TypePackTests.type_alias_default_type_errors +TypePackTests.type_alias_default_type_explicit +TypePackTests.type_alias_default_type_pack_explicit +TypePackTests.type_alias_default_type_pack_self_chained_tp +TypePackTests.type_alias_default_type_pack_self_tp +TypePackTests.type_alias_default_type_pack_self_ty +TypePackTests.type_alias_default_type_self +TypePackTests.type_alias_default_type_skip_brackets +TypePackTests.type_alias_defaults_confusing_types +TypePackTests.type_alias_defaults_recursive_type +TypePackTests.type_alias_type_pack_explicit +TypePackTests.type_alias_type_pack_explicit_multi +TypePackTests.type_alias_type_pack_explicit_multi_tostring +TypePackTests.type_alias_type_pack_multi +TypePackTests.type_alias_type_pack_variadic +TypePackTests.type_alias_type_packs +TypePackTests.type_alias_type_packs_errors +TypePackTests.type_alias_type_packs_import +TypePackTests.type_alias_type_packs_nested +TypePackTests.type_pack_hidden_free_tail_infinite_growth +TypePackTests.type_pack_type_parameters +TypePackTests.varargs_inference_through_multiple_scopes +TypePackTests.variadic_argument_tail +TypePackTests.variadic_pack_syntax +TypePackTests.variadic_packs +TypeSingletons.bool_singleton_subtype +TypeSingletons.bool_singletons +TypeSingletons.bool_singletons_mismatch +TypeSingletons.enums_using_singletons +TypeSingletons.enums_using_singletons_mismatch +TypeSingletons.enums_using_singletons_subtyping +TypeSingletons.error_detailed_tagged_union_mismatch_bool +TypeSingletons.error_detailed_tagged_union_mismatch_string +TypeSingletons.function_call_with_singletons_mismatch +TypeSingletons.if_then_else_expression_singleton_options +TypeSingletons.indexing_on_string_singletons +TypeSingletons.indexing_on_union_of_string_singletons +TypeSingletons.no_widening_from_callsites +TypeSingletons.overloaded_function_call_with_singletons +TypeSingletons.overloaded_function_call_with_singletons_mismatch +TypeSingletons.return_type_of_f_is_not_widened +TypeSingletons.string_singleton_subtype +TypeSingletons.string_singletons +TypeSingletons.string_singletons_escape_chars +TypeSingletons.string_singletons_mismatch +TypeSingletons.table_insert_with_a_singleton_argument +TypeSingletons.table_properties_alias_or_parens_is_indexer +TypeSingletons.table_properties_singleton_strings +TypeSingletons.table_properties_singleton_strings_mismatch +TypeSingletons.table_properties_type_error_escapes +TypeSingletons.tagged_unions_immutable_tag +TypeSingletons.tagged_unions_using_singletons +TypeSingletons.tagged_unions_using_singletons_mismatch +TypeSingletons.taking_the_length_of_string_singleton +TypeSingletons.taking_the_length_of_union_of_string_singleton +TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton +TypeSingletons.widening_happens_almost_everywhere +TypeSingletons.widening_happens_almost_everywhere_except_for_tables +TypeVarTests.visit_once +UnionTypes.error_detailed_optional +UnionTypes.error_detailed_union_all +UnionTypes.error_detailed_union_part +UnionTypes.error_takes_optional_arguments +UnionTypes.index_on_a_union_type_with_missing_property +UnionTypes.index_on_a_union_type_with_mixed_types +UnionTypes.index_on_a_union_type_with_one_optional_property +UnionTypes.index_on_a_union_type_with_one_property_of_type_any +UnionTypes.index_on_a_union_type_with_property_guaranteed_to_exist +UnionTypes.index_on_a_union_type_works_at_arbitrary_depth +UnionTypes.optional_arguments +UnionTypes.optional_assignment_errors +UnionTypes.optional_call_error +UnionTypes.optional_field_access_error +UnionTypes.optional_index_error +UnionTypes.optional_length_error +UnionTypes.optional_missing_key_error_details +UnionTypes.optional_union_follow +UnionTypes.optional_union_functions +UnionTypes.optional_union_members +UnionTypes.optional_union_methods +UnionTypes.return_types_can_be_disjoint +UnionTypes.table_union_write_indirect +UnionTypes.unify_sealed_table_union_check +UnionTypes.unify_unsealed_table_union_check +UnionTypes.union_equality_comparisons diff --git a/tools/test_dcr.py b/tools/test_dcr.py index 8b090bcd..94ff5ca2 100644 --- a/tools/test_dcr.py +++ b/tools/test_dcr.py @@ -14,6 +14,11 @@ def loadFailList(): with open(FAIL_LIST_PATH) as f: return set(map(str.strip, f.readlines())) +def safeParseInt(i, default=0): + try: + return int(i) + except ValueError: + return default class Handler(x.ContentHandler): def __init__(self, failList): @@ -22,6 +27,8 @@ class Handler(x.ContentHandler): self.results = {} # {DottedName: TrueIfTheTestPassed} + self.numSkippedTests = 0 + def startElement(self, name, attrs): if name == "TestSuite": self.currentTest.append(attrs["name"]) @@ -30,10 +37,7 @@ class Handler(x.ContentHandler): elif name == "OverallResultsAsserts": if self.currentTest: - try: - failed = 0 != int(attrs["failures"]) - except ValueError: - failed = False + failed = 0 != safeParseInt(attrs["failures"]) dottedName = ".".join(self.currentTest) shouldFail = dottedName in self.failList @@ -45,6 +49,9 @@ class Handler(x.ContentHandler): self.results[dottedName] = not failed + elif name == 'OverallResultsTestCases': + self.numSkippedTests = safeParseInt(attrs.get("skipped", 0)) + def endElement(self, name): if name == "TestCase": self.currentTest.pop() @@ -111,6 +118,10 @@ def main(): print(name, file=f) print("Updated faillist.txt") + if handler.numSkippedTests > 0: + print('{} test(s) were skipped! That probably means that a test segfaulted!'.format(handler.numSkippedTests), file=sys.stderr) + sys.exit(1) + sys.exit( 0 if all(