Sync to upstream/release/514 (#357)

This commit is contained in:
Arseny Kapoulkine 2022-02-11 11:02:09 -08:00 committed by GitHub
parent aecd60371b
commit 63d5423bbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 989 additions and 268 deletions

View File

@ -86,6 +86,8 @@ struct OwningAutocompleteResult
};
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);
// Deprecated, do not use in new work.
OwningAutocompleteResult autocompleteSource(Frontend& frontend, std::string_view source, Position position, StringCompletionCallback callback);
} // namespace Luau

View File

@ -49,6 +49,7 @@ struct LintWarning
Code_DeprecatedApi = 22,
Code_TableOperations = 23,
Code_DuplicateCondition = 24,
Code_MisleadingAndOr = 25,
Code__Count
};

View File

@ -93,7 +93,7 @@ struct Tarjan
// This should never be null; ensure you initialize it before calling
// substitution methods.
const TxnLog* log;
const TxnLog* log = nullptr;
std::vector<TypeId> edgesTy;
std::vector<TypePackId> edgesTp;

View File

@ -307,8 +307,8 @@ private:
//
// We can't use a DenseHashMap here because we need a non-const iterator
// over the map when we concatenate.
std::unordered_map<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
std::unordered_map<TypeId, std::unique_ptr<PendingType>, DenseHashPointer> typeVarChanges;
std::unordered_map<TypePackId, std::unique_ptr<PendingTypePack>, DenseHashPointer> typePackChanges;
TxnLog* parent = nullptr;

View File

@ -103,6 +103,11 @@ struct GenericTypeDefinitions
std::vector<GenericTypePackDefinition> genericPacks;
};
struct HashBoolNamePair
{
size_t operator()(const std::pair<bool, Name>& pair) const;
};
// All TypeVars are retained via Environment::typeVars. All TypeIds
// within a program are borrowed pointers into this set.
struct TypeChecker
@ -411,6 +416,12 @@ public:
private:
int checkRecursionCount = 0;
int recursionCount = 0;
/**
* We use this to avoid doing second-pass analysis of type aliases that are duplicates. We record a pair
* (exported, name) to properly deal with the case where the two duplicates do not have the same export status.
*/
DenseHashSet<std::pair<bool, Name>, HashBoolNamePair> duplicateTypeAliases;
};
// Unit test hook

View File

@ -54,9 +54,6 @@ struct TypePackVar
bool persistent = false;
// Pointer to the type arena that allocated this type.
// Do not depend on the value of this under any circumstances. This is for
// debugging purposes only. This is only set in debug builds; it is nullptr
// in all other environments.
TypeArena* owningArena = nullptr;
};

View File

@ -449,9 +449,6 @@ struct TypeVar final
std::optional<std::string> documentationSymbol;
// Pointer to the type arena that allocated this type.
// Do not depend on the value of this under any circumstances. This is for
// debugging purposes only. This is only set in debug builds; it is nullptr
// in all other environments.
TypeArena* owningArena = nullptr;
bool operator==(const TypeVar& rhs) const;

View File

@ -1,8 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauFixTonumberReturnType, false)
namespace Luau
{
@ -115,6 +113,7 @@ declare function gcinfo(): number
declare function error<T>(message: T, level: number?)
declare function tostring<T>(value: T): string
declare function tonumber<T>(value: T, radix: number?): number?
declare function rawequal<T1, T2>(a: T1, b: T2): boolean
declare function rawget<K, V>(tab: {[K]: V}, k: K): V
@ -200,14 +199,7 @@ declare function gcinfo(): number
std::string getBuiltinDefinitionSource()
{
std::string result = kBuiltinDefinitionLuaSrc;
if (FFlag::LuauFixTonumberReturnType)
result += "declare function tonumber<T>(value: T, radix: number?): number?\n";
else
result += "declare function tonumber<T>(value: T, radix: number?): number\n";
return result;
return kBuiltinDefinitionLuaSrc;
}
} // namespace Luau

View File

@ -43,6 +43,7 @@ static const char* kWarningNames[] = {
"DeprecatedApi",
"TableOperations",
"DuplicateCondition",
"MisleadingAndOr",
};
// clang-format on
@ -2040,18 +2041,28 @@ private:
const Property* prop = lookupClassProp(cty, node->index.value);
if (prop && prop->deprecated)
{
if (!prop->deprecatedSuggestion.empty())
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated, use '%s' instead",
cty->name.c_str(), node->index.value, prop->deprecatedSuggestion.c_str());
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, node->location, "Member '%s.%s' is deprecated", cty->name.c_str(),
node->index.value);
}
report(node->location, *prop, cty->name.c_str(), node->index.value);
}
else if (const TableTypeVar* tty = get<TableTypeVar>(follow(*ty)))
{
auto prop = tty->props.find(node->index.value);
if (prop != tty->props.end() && prop->second.deprecated)
report(node->location, prop->second, tty->name ? tty->name->c_str() : nullptr, node->index.value);
}
return true;
}
void report(const Location& location, const Property& prop, const char* container, const char* field)
{
std::string suggestion = prop.deprecatedSuggestion.empty() ? "" : format(", use '%s' instead", prop.deprecatedSuggestion.c_str());
if (container)
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s.%s' is deprecated%s", container, field, suggestion.c_str());
else
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated%s", field, suggestion.c_str());
}
};
class LintTableOperations : AstVisitor
@ -2257,6 +2268,39 @@ private:
return false;
}
bool visit(AstExprIfElse* expr) override
{
if (!expr->falseExpr->is<AstExprIfElse>())
return true;
// if..elseif chain detected, we need to unroll it
std::vector<AstExpr*> conditions;
conditions.reserve(2);
AstExprIfElse* head = expr;
while (head)
{
head->condition->visit(this);
head->trueExpr->visit(this);
conditions.push_back(head->condition);
if (head->falseExpr->is<AstExprIfElse>())
{
head = head->falseExpr->as<AstExprIfElse>();
continue;
}
head->falseExpr->visit(this);
break;
}
detectDuplicates(conditions);
// block recursive visits so that we only analyze each chain once
return false;
}
bool visit(AstExprBinary* expr) override
{
if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or)
@ -2418,6 +2462,46 @@ private:
}
};
class LintMisleadingAndOr : AstVisitor
{
public:
LUAU_NOINLINE static void process(LintContext& context)
{
LintMisleadingAndOr pass;
pass.context = &context;
context.root->visit(&pass);
}
private:
LintContext* context;
bool visit(AstExprBinary* node) override
{
if (node->op != AstExprBinary::Or)
return true;
AstExprBinary* and_ = node->left->as<AstExprBinary>();
if (!and_ || and_->op != AstExprBinary::And)
return true;
const char* alt = nullptr;
if (and_->right->is<AstExprConstantNil>())
alt = "nil";
else if (AstExprConstantBool* c = and_->right->as<AstExprConstantBool>(); c && c->value == false)
alt = "false";
if (alt)
emitWarning(*context, LintWarning::Code_MisleadingAndOr, node->location,
"The and-or expression always evaluates to the second alternative because the first alternative is %s; consider using if-then-else "
"expression instead",
alt);
return true;
}
};
static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, const ScopePtr& env)
{
ScopePtr current = env;
@ -2522,6 +2606,9 @@ std::vector<LintWarning> lint(AstStat* root, const AstNameTable& names, const Sc
if (context.warningEnabled(LintWarning::Code_DuplicateLocal))
LintDuplicateLocal::process(context);
if (context.warningEnabled(LintWarning::Code_MisleadingAndOr))
LintMisleadingAndOr::process(context);
std::sort(context.result.begin(), context.result.end(), WarningComparator());
return context.result;

View File

@ -12,10 +12,10 @@
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false)
LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuauImmutableTypes
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
LUAU_FASTFLAG(LuauTypeAliasDefaults)
LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false)
namespace Luau
@ -66,7 +66,7 @@ TypeId TypeArena::addTV(TypeVar&& tv)
{
TypeId allocated = typeVars.allocate(std::move(tv));
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -76,7 +76,7 @@ TypeId TypeArena::freshType(TypeLevel level)
{
TypeId allocated = typeVars.allocate(FreeTypeVar{level});
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -86,7 +86,7 @@ TypePackId TypeArena::addTypePack(std::initializer_list<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -96,7 +96,7 @@ TypePackId TypeArena::addTypePack(std::vector<TypeId> types)
{
TypePackId allocated = typePacks.allocate(TypePack{std::move(types)});
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -106,7 +106,7 @@ TypePackId TypeArena::addTypePack(TypePack tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -116,7 +116,7 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
{
TypePackId allocated = typePacks.allocate(std::move(tp));
if (FFlag::DebugLuauTrackOwningArena)
if (FFlag::DebugLuauTrackOwningArena || FFlag::LuauImmutableTypes)
asMutable(allocated)->owningArena = this;
return allocated;
@ -454,8 +454,16 @@ TypeId clone(TypeId typeId, TypeArena& dest, SeenTypes& seenTypes, SeenTypePacks
TypeCloner cloner{dest, typeId, seenTypes, seenTypePacks, cloneState};
Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
// TODO: Make this work when the arena of 'res' might be frozen
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
if (FFlag::LuauImmutableTypes)
{
// Persistent types are not being cloned and we get the original type back which might be read-only
if (!res->persistent)
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
else
{
asMutable(res)->documentationSymbol = typeId->documentationSymbol;
}
}
return res;

View File

@ -2,6 +2,8 @@
#include "Luau/Scope.h"
LUAU_FASTFLAG(LuauTwoPassAliasDefinitionFix);
namespace Luau
{
@ -17,6 +19,8 @@ Scope::Scope(const ScopePtr& parent, int subLevel)
, returnType(parent->returnType)
, level(parent->level.incr())
{
if (FFlag::LuauTwoPassAliasDefinitionFix)
level = level.incr();
level.subLevel = subLevel;
}

View File

@ -250,6 +250,10 @@ PendingTypePack* TxnLog::queue(TypePackId tp)
PendingType* TxnLog::pending(TypeId ty) const
{
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent)
{
if (auto it = current->typeVarChanges.find(ty); it != current->typeVarChanges.end())
@ -261,6 +265,10 @@ PendingType* TxnLog::pending(TypeId ty) const
PendingTypePack* TxnLog::pending(TypePackId tp) const
{
// This function will technically work if `this` is nullptr, but this
// indicates a bug, so we explicitly assert.
LUAU_ASSERT(static_cast<const void*>(this) != nullptr);
for (const TxnLog* current = this; current; current = current->parent)
{
if (auto it = current->typePackChanges.find(tp); it != current->typePackChanges.end())

View File

@ -32,12 +32,13 @@ LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false)
LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseBranchTypeUnion, false)
LUAU_FASTFLAGVARIABLE(LuauIfElseExpectedType2, false)
LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false)
LUAU_FASTFLAGVARIABLE(LuauLengthOnCompositeType, false)
LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false)
LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false)
LUAU_FASTFLAGVARIABLE(LuauSealExports, false)
LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions, false)
LUAU_FASTFLAGVARIABLE(LuauDiscriminableUnions2, false)
LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false)
LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false)
@ -47,7 +48,10 @@ LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false)
LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false)
LUAU_FASTFLAG(LuauUnionTagMatchFix)
LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false)
LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false)
LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false)
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false)
namespace Luau
{
@ -213,6 +217,11 @@ static bool isMetamethod(const Name& name)
name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode";
}
size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
{
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
}
TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHandler)
: resolver(resolver)
, iceHandler(iceHandler)
@ -225,6 +234,7 @@ TypeChecker::TypeChecker(ModuleResolver* resolver, InternalErrorReporter* iceHan
, anyType(getSingletonTypes().anyType)
, optionalNumberType(getSingletonTypes().optionalNumberType)
, anyTypePack(getSingletonTypes().anyTypePack)
, duplicateTypeAliases{{false, {}}}
{
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
@ -291,6 +301,9 @@ ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optiona
unifierState.skipCacheForType.clear();
}
if (FFlag::LuauTwoPassAliasDefinitionFix)
duplicateTypeAliases.clear();
return std::move(currentModule);
}
@ -496,6 +509,9 @@ LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std
{
if (const auto& typealias = stat->as<AstStatTypeAlias>())
{
if (FFlag::LuauTwoPassAliasDefinitionFix && typealias->name == Parser::errorName)
continue;
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
Name name = typealias->name.value;
@ -1176,6 +1192,10 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
// Once with forwardDeclare, and once without.
Name name = typealias.name.value;
// If the alias is missing a name, we can't do anything with it. Ignore it.
if (FFlag::LuauTwoPassAliasDefinitionFix && name == Parser::errorName)
return;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
binding = it->second;
@ -1192,6 +1212,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
bindingsMap[name] = TypeFun{binding->typeParams, binding->typePackParams, errorRecoveryType(anyType)};
if (FFlag::LuauTwoPassAliasDefinitionFix)
duplicateTypeAliases.insert({typealias.exported, name});
}
else
{
@ -1211,6 +1233,11 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
}
else
{
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
// interesting.
if (FFlag::LuauTwoPassAliasDefinitionFix && duplicateTypeAliases.find({typealias.exported, name}))
return;
if (!binding)
ice("Not predeclared");
@ -1235,7 +1262,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
if (auto ttv = getMutable<TableTypeVar>(follow(ty)))
{
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy
if (ttv->name)
// Additionally, we can't modify types that come from other modules
if (ttv->name || (FFlag::LuauImmutableTypes && follow(ty)->owningArena != &currentModule->internalTypes))
{
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
@ -1247,7 +1275,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
});
// Copy can be skipped if this is an identical alias
if (ttv->name != name || !sameTys || !sameTps)
if ((FFlag::LuauImmutableTypes && !ttv->name) || ttv->name != name || !sameTys || !sameTps)
{
// This is a shallow clone, original recursive links to self are not updated
TableTypeVar clone = TableTypeVar{ttv->props, ttv->indexer, ttv->level, ttv->state};
@ -1279,9 +1307,17 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias
}
}
else if (auto mtv = getMutable<MetatableTypeVar>(follow(ty)))
mtv->syntheticName = name;
{
// We can't modify types that come from other modules
if (!FFlag::LuauImmutableTypes || follow(ty)->owningArena == &currentModule->internalTypes)
mtv->syntheticName = name;
}
unify(ty, bindingsMap[name].type, typealias.location);
TypeId& bindingType = bindingsMap[name].type;
bool ok = unify(ty, bindingType, typealias.location);
if (FFlag::LuauTwoPassAliasDefinitionFix && ok)
bindingType = ty;
}
}
@ -1564,7 +1600,12 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCa
else if (auto vtp = get<VariadicTypePack>(retPack))
return {vtp->ty, std::move(result.predicates)};
else if (get<Unifiable::Generic>(retPack))
ice("Unexpected abstract type pack!", expr.location);
{
if (FFlag::LuauReturnAnyInsteadOfICE)
return {anyType, std::move(result.predicates)};
else
ice("Unexpected abstract type pack!", expr.location);
}
else
ice("Unknown TypePack type!", expr.location);
}
@ -1614,11 +1655,23 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromType(
tablify(type);
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
if (FFlag::LuauDiscriminableUnions2)
{
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
if (isString(type))
{
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location);
LUAU_ASSERT(mtIndex);
type = *mtIndex;
}
}
else
{
const PrimitiveTypeVar* primitiveType = get<PrimitiveTypeVar>(type);
if (primitiveType && primitiveType->type == PrimitiveTypeVar::String)
{
if (std::optional<TypeId> mtIndex = findMetatableEntry(type, "__index", location))
type = *mtIndex;
}
}
if (TableTypeVar* tableType = getMutableTableType(type))
@ -2476,7 +2529,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy),
return {checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy),
{AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
@ -2489,7 +2542,7 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right);
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
TypeId result = checkBinaryOperation(FFlag::LuauDiscriminableUnions2 ? scope : innerScope, expr, lhsTy, rhsTy, lhsPredicates);
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
@ -2497,8 +2550,8 @@ ExprResult<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBi
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions);
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions);
ExprResult<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
ExprResult<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/FFlag::LuauDiscriminableUnions2);
PredicateVec predicates;
@ -2785,12 +2838,16 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
}
else if (exprTable->state == TableState::Unsealed || exprTable->state == TableState::Free)
{
TypeId resultType = freshType(scope);
TypeId resultType = freshType(FFlag::LuauAnotherTypeLevelFix ? exprTable->level : scope->level);
exprTable->indexer = TableIndexer{anyIfNonstrict(indexType), anyIfNonstrict(resultType)};
return resultType;
}
else
{
/*
* If we use [] indexing to fetch a property from a sealed table that has no indexer, we have no idea if it will
* work, so we just mint a fresh type, return that, and hope for the best.
*/
TypeId resultType = freshType(scope);
return resultType;
}
@ -4195,6 +4252,9 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
return errorRecoveryType(scope);
}
if (FFlag::LuauImmutableTypes)
return *moduleType;
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
CloneState cloneState;
@ -5446,7 +5506,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
{
LUAU_ASSERT(FFlag::LuauDiscriminableUnions);
LUAU_ASSERT(FFlag::LuauDiscriminableUnions2);
const LValue* target = &lvalue;
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
@ -5659,7 +5719,7 @@ void TypeChecker::resolve(const TruthyPredicate& truthyP, ErrorVec& errVec, Refi
return std::nullopt;
};
if (FFlag::LuauDiscriminableUnions)
if (FFlag::LuauDiscriminableUnions2)
{
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr)
@ -5772,7 +5832,7 @@ void TypeChecker::resolve(const IsAPredicate& isaP, ErrorVec& errVec, Refinement
return res;
};
if (FFlag::LuauDiscriminableUnions)
if (FFlag::LuauDiscriminableUnions2)
{
refineLValue(isaP.lvalue, refis, scope, predicate);
}
@ -5847,7 +5907,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
if (auto it = primitives.find(typeguardP.kind); it != primitives.end())
{
if (FFlag::LuauDiscriminableUnions)
if (FFlag::LuauDiscriminableUnions2)
{
refineLValue(typeguardP.lvalue, refis, scope, it->second(sense));
return;
@ -5869,7 +5929,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, ErrorVec& errVec
}
auto fail = [&](const TypeErrorData& err) {
if (!FFlag::LuauDiscriminableUnions)
if (!FFlag::LuauDiscriminableUnions2)
errVec.push_back(TypeError{typeguardP.location, err});
addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
};
@ -5901,7 +5961,7 @@ void TypeChecker::resolve(const EqPredicate& eqP, ErrorVec& errVec, RefinementMa
return {ty};
};
if (FFlag::LuauDiscriminableUnions)
if (FFlag::LuauDiscriminableUnions2)
{
std::vector<TypeId> rhs = options(eqP.type);

View File

@ -28,6 +28,7 @@ LUAU_FASTFLAGVARIABLE(LuauMetatableAreEqualRecursion, false)
LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false)
LUAU_FASTFLAG(LuauErrorRecoveryType)
LUAU_FASTFLAG(LuauUnionTagMatchFix)
LUAU_FASTFLAG(LuauDiscriminableUnions2)
namespace Luau
{
@ -393,7 +394,8 @@ bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
if (seen.contains(ty))
return true;
if (isPrim(ty, PrimitiveTypeVar::String) || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
bool isStr = FFlag::LuauDiscriminableUnions2 ? isString(ty) : isPrim(ty, PrimitiveTypeVar::String);
if (isStr || get<AnyTypeVar>(ty) || get<TableTypeVar>(ty) || get<MetatableTypeVar>(ty))
return true;
if (auto uty = get<UnionTypeVar>(ty))

View File

@ -15,6 +15,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
LUAU_FASTFLAGVARIABLE(LuauCommittingTxnLogFreeTpPromote, false)
LUAU_FASTFLAG(LuauImmutableTypes)
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
@ -24,6 +25,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryType);
LUAU_FASTFLAG(LuauProperTypeLevels);
LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false)
LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false)
LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false)
namespace Luau
{
@ -32,11 +34,13 @@ struct PromoteTypeLevels
{
DEPRECATED_TxnLog& DEPRECATED_log;
TxnLog& log;
const TypeArena* typeArena = nullptr;
TypeLevel minLevel;
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel)
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
: DEPRECATED_log(DEPRECATED_log)
, log(log)
, typeArena(typeArena)
, minLevel(minLevel)
{
}
@ -65,8 +69,12 @@ struct PromoteTypeLevels
}
template<typename TID, typename T>
bool operator()(TID, const T&)
bool operator()(TID ty, const T&)
{
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
return true;
}
@ -83,12 +91,20 @@ struct PromoteTypeLevels
bool operator()(TypeId ty, const FunctionTypeVar&)
{
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FunctionTypeVar>(ty) : getMutable<FunctionTypeVar>(ty));
return true;
}
bool operator()(TypeId ty, const TableTypeVar& ttv)
{
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
return true;
@ -108,24 +124,33 @@ struct PromoteTypeLevels
}
};
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypeId ty)
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty)
{
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return;
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(ty, ptl, seen);
}
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypePackId tp)
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp)
{
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
// Type levels of types from other modules are already global, so we don't need to promote anything inside
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
return;
PromoteTypeLevels ptl{DEPRECATED_log, log, typeArena, minLevel};
DenseHashSet<void*> seen{nullptr};
visitTypeVarOnce(tp, ptl, seen);
}
struct SkipCacheForType
{
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType)
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
: skipCacheForType(skipCacheForType)
, typeArena(typeArena)
{
}
@ -152,6 +177,10 @@ struct SkipCacheForType
bool operator()(TypeId ty, const TableTypeVar&)
{
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
if (ttv.boundTo)
@ -172,6 +201,10 @@ struct SkipCacheForType
template<typename T>
bool operator()(TypeId ty, const T& t)
{
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
return false;
const bool* prev = skipCacheForType.find(ty);
if (prev && *prev)
@ -184,8 +217,12 @@ struct SkipCacheForType
}
template<typename T>
bool operator()(TypePackId, const T&)
bool operator()(TypePackId tp, const T&)
{
// Types from other modules don't contain mutable elements and are ok to cache
if (FFlag::LuauImmutableTypes && tp->owningArena != typeArena)
return false;
return true;
}
@ -208,6 +245,7 @@ struct SkipCacheForType
}
const DenseHashMap<TypeId, bool>& skipCacheForType;
const TypeArena* typeArena = nullptr;
bool result = false;
};
@ -422,13 +460,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
if (FFlag::LuauUseCommittingTxnLog)
{
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
log.replace(superTy, BoundTypeVar(subTy));
}
else
{
if (FFlag::LuauProperTypeLevels)
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy);
else if (auto subLevel = getMutableLevel(subTy))
{
if (!subLevel->subsumes(superFree->level))
@ -466,13 +504,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
{
if (FFlag::LuauUseCommittingTxnLog)
{
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
log.replace(subTy, BoundTypeVar(superTy));
}
else
{
if (FFlag::LuauProperTypeLevels)
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy);
else if (auto superLevel = getMutableLevel(superTy))
{
if (!superLevel->subsumes(subFree->level))
@ -849,7 +887,7 @@ void Unifier::cacheResult(TypeId subTy, TypeId superTy)
return;
auto skipCacheFor = [this](TypeId ty) {
SkipCacheForType visitor{sharedState.skipCacheForType};
SkipCacheForType visitor{sharedState.skipCacheForType, types};
visitTypeVarOnce(ty, visitor, sharedState.seenAny);
sharedState.skipCacheForType[ty] = visitor.result;
@ -1637,32 +1675,35 @@ void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCal
tryUnify_(subFunction->retType, superFunction->retType);
}
if (FFlag::LuauUseCommittingTxnLog)
if (!FFlag::LuauImmutableTypes)
{
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
if (FFlag::LuauUseCommittingTxnLog)
{
PendingType* newSubTy = log.queue(subTy);
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
LUAU_ASSERT(newSubFtv);
newSubFtv->definition = superFunction->definition;
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
{
PendingType* newSubTy = log.queue(subTy);
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
LUAU_ASSERT(newSubFtv);
newSubFtv->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
PendingType* newSuperTy = log.queue(superTy);
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
LUAU_ASSERT(newSuperFtv);
newSuperFtv->definition = subFunction->definition;
}
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
else
{
PendingType* newSuperTy = log.queue(superTy);
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
LUAU_ASSERT(newSuperFtv);
newSuperFtv->definition = subFunction->definition;
}
}
else
{
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
{
subFunction->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
superFunction->definition = subFunction->definition;
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
{
subFunction->definition = superFunction->definition;
}
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
{
superFunction->definition = subFunction->definition;
}
}
}
@ -2631,7 +2672,7 @@ static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>&
{
while (true)
{
a = follow(a);
a = FFlag::LuauFollowWithCommittingTxnLogInAnyUnification ? state.log.follow(a) : follow(a);
if (seenTypePacks.find(a))
break;
@ -2738,7 +2779,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
}
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
TypeId anyType, TypePackId anyTypePack)
const TypeArena* typeArena, TypeId anyType, TypePackId anyTypePack)
{
while (!queue.empty())
{
@ -2746,8 +2787,14 @@ static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHas
{
TypeId ty = state.log.follow(queue.back());
queue.pop_back();
// Types from other modules don't have free types
if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena)
continue;
if (seen.find(ty))
continue;
seen.insert(ty);
if (state.log.getMutable<FreeTypeVar>(ty))
@ -2853,7 +2900,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
sharedState.tempSeenTy.clear();
sharedState.tempSeenTp.clear();
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, getSingletonTypes().anyType, anyTP);
}
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
@ -2869,7 +2916,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, anyTp);
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, types, anyTy, anyTp);
}
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)

View File

@ -1133,7 +1133,7 @@ AstTypePack* Parser::parseTypeList(TempVector<AstType*>& result, TempVector<std:
resultNames.push_back({});
resultNames.push_back(AstArgumentName{AstName(lexer.current().name), lexer.current().location});
lexer.next();
nextLexeme();
expectAndConsume(':');
}

View File

@ -1,4 +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"
#include "lua.h"
#include "lualib.h"
@ -38,13 +40,14 @@ enum class CompileFormat
struct GlobalOptions
{
int optimizationLevel = 1;
int debugLevel = 1;
} globalOptions;
static Luau::CompileOptions copts()
{
Luau::CompileOptions result = {};
result.optimizationLevel = globalOptions.optimizationLevel;
result.debugLevel = 1;
result.debugLevel = globalOptions.debugLevel;
result.coverageLevel = coverageActive() ? 2 : 0;
return result;
@ -240,9 +243,8 @@ std::string runCode(lua_State* L, const std::string& source)
return std::string();
}
static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
std::string_view lookup = editBuffer;
char lastSep = 0;
@ -276,7 +278,7 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
// Add an opening paren for function calls by default.
completion += "(";
}
ic_add_completion_ex(cenv, completion.data(), key.data(), nullptr);
addCompletionCallback(completion, std::string(key));
}
}
lua_pop(L, 1);
@ -295,10 +297,11 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
{
// Replace the string object with the string class to perform further lookups of string functions
// Note: We retrieve the string class from _G to prevent issues if the user assigns to `string`.
lua_pop(L, 1); // Pop the string instance
lua_getglobal(L, "_G");
lua_pushlstring(L, "string", 6);
lua_rawget(L, -2);
lua_remove(L, -2);
lua_remove(L, -2); // Remove the global table
LUAU_ASSERT(lua_istable(L, -1));
}
else if (!lua_istable(L, -1))
@ -312,6 +315,26 @@ static void completeIndexer(ic_completion_env_t* cenv, const char* editBuffer)
lua_pop(L, 1);
}
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
{
// look the value up in current global table first
lua_pushvalue(L, LUA_GLOBALSINDEX);
completeIndexer(L, editBuffer, addCompletionCallback);
// and in actual global table after that
lua_getglobal(L, "_G");
completeIndexer(L, editBuffer, addCompletionCallback);
}
static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer)
{
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
getCompletions(L, std::string(editBuffer), [cenv](const std::string& completion, const std::string& display) {
ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr);
});
}
static bool isMethodOrFunctionChar(const char* s, long len)
{
char c = *s;
@ -320,15 +343,7 @@ static bool isMethodOrFunctionChar(const char* s, long len)
static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
{
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
// look the value up in current global table first
lua_pushvalue(L, LUA_GLOBALSINDEX);
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
// and in actual global table after that
lua_getglobal(L, "_G");
ic_complete_word(cenv, editBuffer, completeIndexer, isMethodOrFunctionChar);
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
}
struct LinenoiseScopedHistory
@ -372,19 +387,20 @@ static void runReplImpl(lua_State* L)
for (;;)
{
const char* line = ic_readline(buffer.empty() ? "" : ">");
const char* prompt = buffer.empty() ? "" : ">";
std::unique_ptr<char, void (*)(void*)> line(ic_readline(prompt), free);
if (!line)
break;
if (buffer.empty() && runCode(L, std::string("return ") + line) == std::string())
if (buffer.empty() && runCode(L, std::string("return ") + line.get()) == std::string())
{
ic_history_add(line);
ic_history_add(line.get());
continue;
}
if (!buffer.empty())
buffer += "\n";
buffer += line;
buffer += line.get();
std::string error = runCode(L, buffer);
@ -400,7 +416,6 @@ static void runReplImpl(lua_State* L)
ic_history_add(buffer.c_str());
buffer.clear();
free((void*)line);
}
}
@ -504,7 +519,7 @@ static bool compileFile(const char* name, CompileFormat format)
if (format == CompileFormat::Text)
{
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source);
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals);
bcb.setDumpSource(*source);
}
@ -549,7 +564,8 @@ static void displayHelp(const char* argv0)
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
printf(" -h, --help: Display this usage message.\n");
printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n");
printf(" -O<n>: use compiler optimization level (n=0-2).\n");
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
printf(" --timetrace: record compiler time tracing information into trace.json\n");
}
@ -620,6 +636,16 @@ int replMain(int argc, char** argv)
}
globalOptions.optimizationLevel = level