mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Sync to upstream/release/558 (#796)
* Fixed garbage data in module scopes when type graph is not retained * LOP_MOVE with the same source and target registers is no longer generated (Fixes https://github.com/Roblox/luau/issues/793)
This commit is contained in:
parent
685ca02a30
commit
be52bd91e4
@ -71,9 +71,9 @@ struct BinaryConstraint
|
||||
|
||||
// When we dispatch this constraint, we update the key at this map to record
|
||||
// the overload that we selected.
|
||||
AstExpr* expr;
|
||||
DenseHashMap<const AstExpr*, TypeId>* astOriginalCallTypes;
|
||||
DenseHashMap<const AstExpr*, TypeId>* astOverloadResolvedTypes;
|
||||
const void* astFragment;
|
||||
DenseHashMap<const void*, TypeId>* astOriginalCallTypes;
|
||||
DenseHashMap<const void*, TypeId>* astOverloadResolvedTypes;
|
||||
};
|
||||
|
||||
// iteratee is iterable
|
||||
|
@ -82,11 +82,11 @@ struct ConstraintGraphBuilder
|
||||
|
||||
// If the node was applied as a function, this is the unspecialized type of
|
||||
// that expression.
|
||||
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
|
||||
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
|
||||
|
||||
// If overload resolution was performed on this element, this is the
|
||||
// overload that was selected.
|
||||
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
// Types resolved from type annotations. Analogous to astTypes.
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
|
@ -73,19 +73,30 @@ struct Module
|
||||
DenseHashMap<const AstExpr*, TypeId> astTypes{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypeId> astOriginalCallTypes{nullptr};
|
||||
DenseHashMap<const AstExpr*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
// Pointers are either AstExpr or AstStat.
|
||||
DenseHashMap<const void*, TypeId> astOriginalCallTypes{nullptr};
|
||||
|
||||
// Pointers are either AstExpr or AstStat.
|
||||
DenseHashMap<const void*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
|
||||
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
|
||||
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
|
||||
|
||||
std::unique_ptr<struct TypeReduction> reduction;
|
||||
|
||||
std::unordered_map<Name, TypeId> declaredGlobals;
|
||||
ErrorVec errors;
|
||||
Mode mode;
|
||||
SourceCode::Type type;
|
||||
bool timeout = false;
|
||||
|
||||
TypePackId returnType = nullptr;
|
||||
std::unordered_map<Name, TypeFun> exportedTypeBindings;
|
||||
|
||||
bool hasModuleScope() const;
|
||||
ScopePtr getModuleScope() const;
|
||||
|
||||
// Once a module has been typechecked, we clone its public interface into a separate arena.
|
||||
|
@ -32,7 +32,7 @@ struct RecursionCounter
|
||||
--(*count);
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
int* count;
|
||||
};
|
||||
|
||||
|
@ -494,13 +494,13 @@ struct AnyType
|
||||
{
|
||||
};
|
||||
|
||||
// T | U
|
||||
// `T | U`
|
||||
struct UnionType
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
};
|
||||
|
||||
// T & U
|
||||
// `T & U`
|
||||
struct IntersectionType
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
@ -519,9 +519,7 @@ struct NeverType
|
||||
{
|
||||
};
|
||||
|
||||
// ~T
|
||||
// TODO: Some simplification step that overwrites the type graph to make sure negation
|
||||
// types disappear from the user's view, and (?) a debug flag to disable that
|
||||
// `~T`
|
||||
struct NegationType
|
||||
{
|
||||
TypeId ty;
|
||||
@ -676,6 +674,8 @@ TypeLevel* getMutableLevel(TypeId ty);
|
||||
std::optional<TypeLevel> getLevel(TypePackId tp);
|
||||
|
||||
const Property* lookupClassProp(const ClassType* cls, const Name& name);
|
||||
|
||||
// Whether `cls` is a subclass of `parent`
|
||||
bool isSubclass(const ClassType* cls, const ClassType* parent);
|
||||
|
||||
Type* asMutable(TypeId ty);
|
||||
@ -767,7 +767,7 @@ struct TypeIterator
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
const TypeId& operator*()
|
||||
TypeId operator*()
|
||||
{
|
||||
descend();
|
||||
|
||||
@ -779,8 +779,8 @@ struct TypeIterator
|
||||
const std::vector<TypeId>& types = getTypes(t);
|
||||
LUAU_ASSERT(currentIndex < types.size());
|
||||
|
||||
const TypeId& ty = types[currentIndex];
|
||||
LUAU_ASSERT(!get<T>(follow(ty)));
|
||||
TypeId ty = follow(types[currentIndex]);
|
||||
LUAU_ASSERT(!get<T>(ty));
|
||||
|
||||
return ty;
|
||||
}
|
||||
|
40
Analysis/include/Luau/TypeReduction.h
Normal file
40
Analysis/include/Luau/TypeReduction.h
Normal file
@ -0,0 +1,40 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/// If it's desirable to allocate into a different arena than the TypeReduction instance you have, you will need
|
||||
/// to create a temporary TypeReduction in that case. This is because TypeReduction caches the reduced type.
|
||||
struct TypeReduction
|
||||
{
|
||||
explicit TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle);
|
||||
|
||||
std::optional<TypeId> reduce(TypeId ty);
|
||||
std::optional<TypePackId> reduce(TypePackId tp);
|
||||
std::optional<TypeFun> reduce(const TypeFun& fun);
|
||||
|
||||
private:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<struct InternalErrorReporter> handle;
|
||||
|
||||
DenseHashMap<TypeId, TypeId> cachedTypes{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> cachedTypePacks{nullptr};
|
||||
|
||||
std::optional<TypeId> reduceImpl(TypeId ty);
|
||||
std::optional<TypePackId> reduceImpl(TypePackId tp);
|
||||
|
||||
// Computes an *estimated length* of the cartesian product of the given type.
|
||||
size_t cartesianProductSize(TypeId ty) const;
|
||||
|
||||
bool hasExceededCartesianProductLimit(TypeId ty) const;
|
||||
bool hasExceededCartesianProductLimit(TypePackId tp) const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
@ -256,7 +256,8 @@ AstExpr* findExprAtPosition(const SourceModule& source, Position pos)
|
||||
|
||||
ScopePtr findScopeAtPosition(const Module& module, Position pos)
|
||||
{
|
||||
LUAU_ASSERT(!module.scopes.empty());
|
||||
if (module.scopes.empty())
|
||||
return nullptr;
|
||||
|
||||
Location scopeLocation = module.scopes.front().first;
|
||||
ScopePtr scope = module.scopes.front().second;
|
||||
@ -320,7 +321,6 @@ std::optional<Binding> findBindingAtPosition(const Module& module, const SourceM
|
||||
return std::nullopt;
|
||||
|
||||
ScopePtr currentScope = findScopeAtPosition(module, pos);
|
||||
LUAU_ASSERT(currentScope);
|
||||
|
||||
while (currentScope)
|
||||
{
|
||||
|
@ -150,6 +150,8 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
LUAU_ASSERT(module.hasModuleScope());
|
||||
|
||||
NotNull<Scope> moduleScope{module.getModuleScope().get()};
|
||||
|
||||
auto typeAtPosition = findExpectedTypeAt(module, node, position);
|
||||
@ -182,8 +184,7 @@ static TypeCorrectKind checkTypeCorrectKind(
|
||||
}
|
||||
}
|
||||
|
||||
return checkTypeMatch(ty, expectedType, NotNull{module.getModuleScope().get()}, typeArena, builtinTypes) ? TypeCorrectKind::Correct
|
||||
: TypeCorrectKind::None;
|
||||
return checkTypeMatch(ty, expectedType, moduleScope, typeArena, builtinTypes) ? TypeCorrectKind::Correct : TypeCorrectKind::None;
|
||||
}
|
||||
|
||||
enum class PropIndexType
|
||||
@ -1328,13 +1329,11 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
|
||||
}
|
||||
|
||||
static AutocompleteResult autocomplete(const SourceModule& sourceModule, const ModulePtr& module, NotNull<BuiltinTypes> builtinTypes,
|
||||
Scope* globalScope, Position position, StringCompletionCallback callback)
|
||||
TypeArena* typeArena, Scope* globalScope, Position position, StringCompletionCallback callback)
|
||||
{
|
||||
if (isWithinComment(sourceModule, position))
|
||||
return {};
|
||||
|
||||
TypeArena typeArena;
|
||||
|
||||
std::vector<AstNode*> ancestry = findAncestryAtPositionForAutocomplete(sourceModule, position);
|
||||
LUAU_ASSERT(!ancestry.empty());
|
||||
AstNode* node = ancestry.back();
|
||||
@ -1360,7 +1359,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
TypeId ty = follow(*it);
|
||||
PropIndexType indexType = indexName->op == ':' ? PropIndexType::Colon : PropIndexType::Point;
|
||||
|
||||
return {autocompleteProps(*module, &typeArena, builtinTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||
return {autocompleteProps(*module, typeArena, builtinTypes, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
|
||||
}
|
||||
else if (auto typeReference = node->as<AstTypeReference>())
|
||||
{
|
||||
@ -1378,7 +1377,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
|
||||
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
|
||||
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
@ -1392,7 +1391,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
|
||||
if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
|
||||
(statFor->step && statFor->step->location.containsClosed(position)))
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -1422,7 +1421,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];
|
||||
|
||||
if (lastExpr->location.containsClosed(position))
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
|
||||
if (position > lastExpr->location.end)
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
@ -1446,7 +1445,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
|
||||
if (!statWhile->hasDo || position < statWhile->doLocation.begin)
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
|
||||
if (statWhile->hasDo && position > statWhile->doLocation.end)
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
@ -1463,7 +1462,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
|
||||
{
|
||||
if (statIf->condition->is<AstExprError>())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
}
|
||||
@ -1471,7 +1470,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
|
||||
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
|
||||
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
else if (AstExprTable* exprTable = parent->as<AstExprTable>();
|
||||
@ -1484,7 +1483,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
{
|
||||
if (auto it = module->astExpectedTypes.find(exprTable))
|
||||
{
|
||||
auto result = autocompleteProps(*module, &typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
|
||||
auto result = autocompleteProps(*module, typeArena, builtinTypes, *it, PropIndexType::Key, ancestry);
|
||||
|
||||
if (FFlag::LuauCompleteTableKeysBetter)
|
||||
{
|
||||
@ -1518,7 +1517,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
|
||||
// If we know for sure that a key is being written, do not offer general expression suggestions
|
||||
if (!key)
|
||||
autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position, result);
|
||||
autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position, result);
|
||||
|
||||
return {result, ancestry, AutocompleteContext::Property};
|
||||
}
|
||||
@ -1546,7 +1545,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as<AstExprIndexExpr>())
|
||||
{
|
||||
if (auto it = module->astTypes.find(idxExpr->expr))
|
||||
autocompleteProps(*module, &typeArena, builtinTypes, follow(*it), PropIndexType::Point, ancestry, result);
|
||||
autocompleteProps(*module, typeArena, builtinTypes, follow(*it), PropIndexType::Point, ancestry, result);
|
||||
}
|
||||
else if (auto binExpr = ancestry.at(ancestry.size() - 2)->as<AstExprBinary>())
|
||||
{
|
||||
@ -1572,7 +1571,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
|
||||
return {};
|
||||
|
||||
if (node->asExpr())
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, &typeArena, ancestry, position);
|
||||
return autocompleteExpression(sourceModule, *module, builtinTypes, typeArena, ancestry, position);
|
||||
else if (node->asStat())
|
||||
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
|
||||
|
||||
@ -1599,9 +1598,8 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
||||
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
|
||||
Scope* globalScope = frontend.typeCheckerForAutocomplete.globalScope.get();
|
||||
|
||||
AutocompleteResult autocompleteResult = autocomplete(*sourceModule, module, builtinTypes, globalScope, position, callback);
|
||||
|
||||
return autocompleteResult;
|
||||
TypeArena typeArena;
|
||||
return autocomplete(*sourceModule, module, builtinTypes, &typeArena, globalScope, position, callback);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -18,9 +18,7 @@
|
||||
LUAU_FASTFLAGVARIABLE(LuauSetMetaTableArgsCheck, false)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBuiltInMetatableNoBadSynthetic, false)
|
||||
LUAU_FASTFLAG(LuauOptionalNextKey)
|
||||
LUAU_FASTFLAG(LuauReportShadowedTypeAlias)
|
||||
LUAU_FASTFLAG(LuauNewLibraryTypeNames)
|
||||
|
||||
/** FIXME: Many of these type definitions are not quite completely accurate.
|
||||
*
|
||||
@ -289,8 +287,6 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
|
||||
addGlobalBinding(typeChecker, "string", it->second.type, "@luau");
|
||||
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(typeChecker, arena, genericK), genericV}});
|
||||
@ -302,25 +298,7 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
typeChecker, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
else
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(typeChecker, arena, genericK)}});
|
||||
addGlobalBinding(typeChecker, "next",
|
||||
arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(
|
||||
typeChecker, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
addGlobalBinding(typeChecker, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
|
||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
||||
|
||||
@ -352,12 +330,7 @@ void registerBuiltinGlobals(TypeChecker& typeChecker)
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
{
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
else
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,8 +381,6 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
|
||||
addGlobalBinding(frontend, "string", it->second.type, "@luau");
|
||||
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
TypePackId nextRetsTypePack = arena.addTypePack(TypePack{{makeOption(frontend, arena, genericK), genericV}});
|
||||
@ -422,22 +393,6 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K?, V), Table<K, V>, nil)
|
||||
addGlobalBinding(frontend, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
else
|
||||
{
|
||||
// next<K, V>(t: Table<K, V>, i: K?) -> (K, V)
|
||||
TypePackId nextArgsTypePack = arena.addTypePack(TypePack{{mapOfKtoV, makeOption(frontend, arena, genericK)}});
|
||||
addGlobalBinding(frontend, "next",
|
||||
arena.addType(FunctionType{{genericK, genericV}, {}, nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})}), "@luau");
|
||||
|
||||
TypePackId pairsArgsTypePack = arena.addTypePack({mapOfKtoV});
|
||||
|
||||
TypeId pairsNext = arena.addType(FunctionType{nextArgsTypePack, arena.addTypePack(TypePack{{genericK, genericV}})});
|
||||
TypePackId pairsReturnTypePack = arena.addTypePack(TypePack{{pairsNext, mapOfKtoV, frontend.builtinTypes->nilType}});
|
||||
|
||||
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K?) -> (K, V), Table<K, V>, nil)
|
||||
addGlobalBinding(frontend, "pairs", arena.addType(FunctionType{{genericK, genericV}, {}, pairsArgsTypePack, pairsReturnTypePack}), "@luau");
|
||||
}
|
||||
|
||||
TypeId genericMT = arena.addType(GenericType{"MT"});
|
||||
|
||||
@ -469,12 +424,7 @@ void registerBuiltinGlobals(Frontend& frontend)
|
||||
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
|
||||
{
|
||||
if (!ttv->name)
|
||||
{
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
ttv->name = "typeof(" + toString(pair.first) + ")";
|
||||
else
|
||||
ttv->name = toString(pair.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauNegatedClassTypes);
|
||||
LUAU_FASTFLAG(LuauScopelessModule);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -520,7 +521,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
||||
const Name name{local->vars.data[i]->name.value};
|
||||
|
||||
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
|
||||
scope->importedTypeBindings[name] = module->getModuleScope()->exportedTypeBindings;
|
||||
scope->importedTypeBindings[name] =
|
||||
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -733,16 +735,15 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatCompoundAssign* assign)
|
||||
{
|
||||
// Synthesize A = A op B from A op= B and then build constraints for that instead.
|
||||
// We need to tweak the BinaryConstraint that we emit, so we cannot use the
|
||||
// strategy of falsifying an AST fragment.
|
||||
TypeId varId = checkLValue(scope, assign->var);
|
||||
Inference valueInf = check(scope, assign->value);
|
||||
|
||||
AstExprBinary exprBinary{assign->location, assign->op, assign->var, assign->value};
|
||||
AstExpr* exprBinaryPtr = &exprBinary;
|
||||
|
||||
AstArray<AstExpr*> vars{&assign->var, 1};
|
||||
AstArray<AstExpr*> values{&exprBinaryPtr, 1};
|
||||
AstStatAssign syntheticAssign{assign->location, vars, values};
|
||||
|
||||
visit(scope, &syntheticAssign);
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, assign->location,
|
||||
BinaryConstraint{assign->op, varId, valueInf.ty, resultType, assign, &astOriginalCallTypes, &astOverloadResolvedTypes});
|
||||
addConstraint(scope, assign->location, SubtypeConstraint{resultType, varId});
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
||||
LUAU_FASTFLAG(LuauScopelessModule);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -681,8 +682,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
||||
asMutable(resultType)->ty.emplace<BoundType>(mmResult);
|
||||
unblock(resultType);
|
||||
|
||||
(*c.astOriginalCallTypes)[c.expr] = *mm;
|
||||
(*c.astOverloadResolvedTypes)[c.expr] = *instantiatedMm;
|
||||
(*c.astOriginalCallTypes)[c.astFragment] = *mm;
|
||||
(*c.astOverloadResolvedTypes)[c.astFragment] = *instantiatedMm;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1895,7 +1896,7 @@ TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& l
|
||||
return errorRecoveryType();
|
||||
}
|
||||
|
||||
TypePackId modulePack = module->getModuleScope()->returnType;
|
||||
TypePackId modulePack = FFlag::LuauScopelessModule ? module->returnType : module->getModuleScope()->returnType;
|
||||
if (get<Unifiable::Error>(modulePack))
|
||||
return errorRecoveryType();
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAG(LuauOptionalNextKey)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -127,7 +126,7 @@ declare function rawlen<K, V>(obj: {[K]: V} | string): number
|
||||
|
||||
declare function setfenv<T..., R...>(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)?
|
||||
|
||||
-- TODO: place ipairs definition here with removal of FFlagLuauOptionalNextKey
|
||||
declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)
|
||||
|
||||
declare function pcall<A..., R...>(f: (A...) -> R..., ...: A...): (boolean, R...)
|
||||
|
||||
@ -208,11 +207,6 @@ std::string getBuiltinDefinitionSource()
|
||||
else
|
||||
result += "declare function error<T>(message: T, level: number?)\n";
|
||||
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
result += "declare function ipairs<V>(tab: {V}): (({V}, number) -> (number?, V), {V}, number)\n";
|
||||
else
|
||||
result += "declare function ipairs<V>(tab: {V}): (({V}, number) -> (number, V), {V}, number)\n";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -30,6 +31,7 @@ LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
|
||||
LUAU_FASTFLAG(LuauScopelessModule);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -111,7 +113,9 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
||||
CloneState cloneState;
|
||||
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
typesToPersist.reserve(
|
||||
checkedModule->declaredGlobals.size() +
|
||||
(FFlag::LuauScopelessModule ? checkedModule->exportedTypeBindings.size() : checkedModule->getModuleScope()->exportedTypeBindings.size()));
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
@ -123,7 +127,8 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(std::string_view source, c
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
for (const auto& [name, ty] :
|
||||
FFlag::LuauScopelessModule ? checkedModule->exportedTypeBindings : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
@ -168,7 +173,9 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
CloneState cloneState;
|
||||
|
||||
std::vector<TypeId> typesToPersist;
|
||||
typesToPersist.reserve(checkedModule->declaredGlobals.size() + checkedModule->getModuleScope()->exportedTypeBindings.size());
|
||||
typesToPersist.reserve(
|
||||
checkedModule->declaredGlobals.size() +
|
||||
(FFlag::LuauScopelessModule ? checkedModule->exportedTypeBindings.size() : checkedModule->getModuleScope()->exportedTypeBindings.size()));
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
@ -180,7 +187,8 @@ LoadDefinitionFileResult loadDefinitionFile(TypeChecker& typeChecker, ScopePtr t
|
||||
typesToPersist.push_back(globalTy);
|
||||
}
|
||||
|
||||
for (const auto& [name, ty] : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
for (const auto& [name, ty] :
|
||||
FFlag::LuauScopelessModule ? checkedModule->exportedTypeBindings : checkedModule->getModuleScope()->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, typeChecker.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
@ -562,6 +570,22 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
freeze(module->interfaceTypes);
|
||||
|
||||
module->internalTypes.clear();
|
||||
|
||||
if (FFlag::LuauScopelessModule)
|
||||
{
|
||||
module->astTypes.clear();
|
||||
module->astTypePacks.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
module->astOverloadResolvedTypes.clear();
|
||||
module->astResolvedTypes.clear();
|
||||
module->astResolvedTypePacks.clear();
|
||||
module->astScopes.clear();
|
||||
|
||||
module->scopes.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
module->astTypes.clear();
|
||||
module->astExpectedTypes.clear();
|
||||
module->astOriginalCallTypes.clear();
|
||||
@ -569,6 +593,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
||||
module->astResolvedTypePacks.clear();
|
||||
module->scopes.resize(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != Mode::NoCheck)
|
||||
{
|
||||
@ -852,6 +877,7 @@ ModulePtr Frontend::check(
|
||||
const SourceModule& sourceModule, Mode mode, const ScopePtr& environmentScope, std::vector<RequireCycle> requireCycles, bool forAutocomplete)
|
||||
{
|
||||
ModulePtr result = std::make_shared<Module>();
|
||||
result->reduction = std::make_unique<TypeReduction>(NotNull{&result->internalTypes}, builtinTypes, NotNull{&iceHandler});
|
||||
|
||||
std::unique_ptr<DcrLogger> logger;
|
||||
if (FFlag::DebugLuauLogSolverToJson)
|
||||
|
@ -7,9 +7,10 @@
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -17,6 +18,7 @@
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess, false);
|
||||
LUAU_FASTFLAG(LuauSubstitutionReentrant);
|
||||
LUAU_FASTFLAG(LuauScopelessModule);
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution);
|
||||
LUAU_FASTFLAG(LuauSubstitutionFixMissingFields);
|
||||
|
||||
@ -189,7 +191,6 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
||||
|
||||
TypePackId returnType = moduleScope->returnType;
|
||||
std::optional<TypePackId> varargPack = FFlag::DebugLuauDeferredConstraintResolution ? std::nullopt : moduleScope->varargPack;
|
||||
std::unordered_map<Name, TypeFun>* exportedTypeBindings = &moduleScope->exportedTypeBindings;
|
||||
|
||||
TxnLog log;
|
||||
ClonePublicInterface clonePublicInterface{&log, builtinTypes, this};
|
||||
@ -209,16 +210,13 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
||||
moduleScope->varargPack = varargPack;
|
||||
}
|
||||
|
||||
if (exportedTypeBindings)
|
||||
{
|
||||
for (auto& [name, tf] : *exportedTypeBindings)
|
||||
for (auto& [name, tf] : moduleScope->exportedTypeBindings)
|
||||
{
|
||||
if (FFlag::LuauClonePublicInterfaceLess)
|
||||
tf = clonePublicInterface.cloneTypeFun(tf);
|
||||
else
|
||||
tf = clone(tf, interfaceTypes, cloneState);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [name, ty] : declaredGlobals)
|
||||
{
|
||||
@ -228,13 +226,25 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
|
||||
ty = clone(ty, interfaceTypes, cloneState);
|
||||
}
|
||||
|
||||
// Copy external stuff over to Module itself
|
||||
if (FFlag::LuauScopelessModule)
|
||||
{
|
||||
this->returnType = moduleScope->returnType;
|
||||
this->exportedTypeBindings = std::move(moduleScope->exportedTypeBindings);
|
||||
}
|
||||
|
||||
freeze(internalTypes);
|
||||
freeze(interfaceTypes);
|
||||
}
|
||||
|
||||
bool Module::hasModuleScope() const
|
||||
{
|
||||
return !scopes.empty();
|
||||
}
|
||||
|
||||
ScopePtr Module::getModuleScope() const
|
||||
{
|
||||
LUAU_ASSERT(!scopes.empty());
|
||||
LUAU_ASSERT(hasModuleScope());
|
||||
return scopes.front().second;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ LUAU_FASTFLAGVARIABLE(LuauSerializeNilUnionAsNil, false)
|
||||
* Fair warning: Setting this will break a lot of Luau unit tests.
|
||||
*/
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -756,6 +757,7 @@ struct TypeStringifier
|
||||
|
||||
state.unsee(&uv);
|
||||
|
||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
if (optional && results.size() > 1)
|
||||
@ -821,6 +823,7 @@ struct TypeStringifier
|
||||
|
||||
state.unsee(&uv);
|
||||
|
||||
if (!FFlag::DebugLuauToStringNoLexicalSort)
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
bool first = true;
|
||||
|
@ -26,7 +26,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauUnknownAndNeverType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMaybeGenericIntersectionTypes, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewLibraryTypeNames, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
|
||||
namespace Luau
|
||||
@ -865,7 +864,7 @@ TypeId BuiltinTypes::makeStringMetatable()
|
||||
TypeId tableType = arena->addType(TableType{std::move(stringLib), std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(tableType))
|
||||
ttv->name = FFlag::LuauNewLibraryTypeNames ? "typeof(string)" : "string";
|
||||
ttv->name = "typeof(string)";
|
||||
|
||||
return arena->addType(TableType{{{{"__index", {tableType}}}}, std::nullopt, TypeLevel{}, TableState::Sealed});
|
||||
}
|
||||
|
@ -487,9 +487,12 @@ public:
|
||||
AstType* annotation = local->annotation;
|
||||
if (!annotation)
|
||||
{
|
||||
if (auto result = getScope(local->location)->lookup(local))
|
||||
if (auto scope = getScope(local->location))
|
||||
{
|
||||
if (auto result = scope->lookup(local))
|
||||
local->annotation = typeAst(*result);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -626,8 +626,11 @@ struct TypeChecker2
|
||||
|
||||
void visit(AstStatCompoundAssign* stat)
|
||||
{
|
||||
visit(stat->var);
|
||||
visit(stat->value);
|
||||
AstExprBinary fake{stat->location, stat->op, stat->var, stat->value};
|
||||
TypeId resultTy = visit(&fake, stat);
|
||||
TypeId varTy = lookupType(stat->var);
|
||||
|
||||
reportErrors(tryUnify(stack.back(), stat->location, resultTy, varTy));
|
||||
}
|
||||
|
||||
void visit(AstStatFunction* stat)
|
||||
@ -737,7 +740,10 @@ struct TypeChecker2
|
||||
else if (auto e = expr->as<AstExprUnary>())
|
||||
return visit(e);
|
||||
else if (auto e = expr->as<AstExprBinary>())
|
||||
return visit(e);
|
||||
{
|
||||
visit(e);
|
||||
return;
|
||||
}
|
||||
else if (auto e = expr->as<AstExprTypeAssertion>())
|
||||
return visit(e);
|
||||
else if (auto e = expr->as<AstExprIfElse>())
|
||||
@ -1045,7 +1051,7 @@ struct TypeChecker2
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstExprBinary* expr)
|
||||
TypeId visit(AstExprBinary* expr, void* overrideKey = nullptr)
|
||||
{
|
||||
visit(expr->left);
|
||||
visit(expr->right);
|
||||
@ -1066,8 +1072,10 @@ struct TypeChecker2
|
||||
|
||||
bool isStringOperation = isString(leftType) && isString(rightType);
|
||||
|
||||
if (get<AnyType>(leftType) || get<ErrorType>(leftType) || get<AnyType>(rightType) || get<ErrorType>(rightType))
|
||||
return;
|
||||
if (get<AnyType>(leftType) || get<ErrorType>(leftType))
|
||||
return leftType;
|
||||
else if (get<AnyType>(rightType) || get<ErrorType>(rightType))
|
||||
return rightType;
|
||||
|
||||
if ((get<BlockedType>(leftType) || get<FreeType>(leftType)) && !isEquality && !isLogical)
|
||||
{
|
||||
@ -1075,14 +1083,13 @@ struct TypeChecker2
|
||||
reportError(CannotInferBinaryOperation{expr->op, name,
|
||||
isComparison ? CannotInferBinaryOperation::OpKind::Comparison : CannotInferBinaryOperation::OpKind::Operation},
|
||||
expr->location);
|
||||
return;
|
||||
return leftType;
|
||||
}
|
||||
|
||||
if (auto it = kBinaryOpMetamethods.find(expr->op); it != kBinaryOpMetamethods.end())
|
||||
{
|
||||
std::optional<TypeId> leftMt = getMetatable(leftType, builtinTypes);
|
||||
std::optional<TypeId> rightMt = getMetatable(rightType, builtinTypes);
|
||||
|
||||
bool matches = leftMt == rightMt;
|
||||
if (isEquality && !matches)
|
||||
{
|
||||
@ -1114,7 +1121,7 @@ struct TypeChecker2
|
||||
toString(leftType).c_str(), toString(rightType).c_str(), toString(expr->op).c_str())},
|
||||
expr->location);
|
||||
|
||||
return;
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
|
||||
std::optional<TypeId> mm;
|
||||
@ -1128,7 +1135,11 @@ struct TypeChecker2
|
||||
|
||||
if (mm)
|
||||
{
|
||||
TypeId instantiatedMm = module->astOverloadResolvedTypes[expr];
|
||||
void* key = expr;
|
||||
if (overrideKey != nullptr)
|
||||
key = overrideKey;
|
||||
|
||||
TypeId instantiatedMm = module->astOverloadResolvedTypes[key];
|
||||
if (!instantiatedMm)
|
||||
reportError(CodeTooComplex{}, expr->location);
|
||||
|
||||
@ -1146,34 +1157,64 @@ struct TypeChecker2
|
||||
expectedArgs = testArena.addTypePack({leftType, rightType});
|
||||
}
|
||||
|
||||
reportErrors(tryUnify(scope, expr->location, ftv->argTypes, expectedArgs));
|
||||
|
||||
TypePackId expectedRets;
|
||||
if (expr->op == AstExprBinary::CompareEq || expr->op == AstExprBinary::CompareNe || expr->op == AstExprBinary::CompareGe ||
|
||||
expr->op == AstExprBinary::CompareGt || expr->op == AstExprBinary::Op::CompareLe || expr->op == AstExprBinary::Op::CompareLt)
|
||||
{
|
||||
TypePackId expectedRets = testArena.addTypePack({builtinTypes->booleanType});
|
||||
if (!isSubtype(ftv->retTypes, expectedRets, scope))
|
||||
expectedRets = testArena.addTypePack({builtinTypes->booleanType});
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return type 'boolean'", it->second)}, expr->location);
|
||||
expectedRets = testArena.addTypePack({testArena.freshType(scope, TypeLevel{})});
|
||||
}
|
||||
|
||||
TypeId expectedTy = testArena.addType(FunctionType(expectedArgs, expectedRets));
|
||||
|
||||
reportErrors(tryUnify(scope, expr->location, follow(*mm), expectedTy));
|
||||
|
||||
std::optional<TypeId> ret = first(ftv->retTypes);
|
||||
if (ret)
|
||||
{
|
||||
if (isComparison)
|
||||
{
|
||||
if (!isBoolean(follow(*ret)))
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return a boolean", it->second)}, expr->location);
|
||||
}
|
||||
|
||||
return builtinTypes->booleanType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return follow(*ret);
|
||||
}
|
||||
}
|
||||
else if (!first(ftv->retTypes))
|
||||
else
|
||||
{
|
||||
if (isComparison)
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return a boolean", it->second)}, expr->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(GenericError{format("Metamethod '%s' must return a value", it->second)}, expr->location);
|
||||
}
|
||||
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(CannotCallNonFunction{*mm}, expr->location);
|
||||
}
|
||||
|
||||
return;
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
// If this is a string comparison, or a concatenation of strings, we
|
||||
// want to fall through to primitive behavior.
|
||||
else if (!isEquality && !(isStringOperation && (expr->op == AstExprBinary::Op::Concat || isComparison)))
|
||||
{
|
||||
if (leftMt || rightMt)
|
||||
if ((leftMt && !isString(leftType)) || (rightMt && !isString(rightType)))
|
||||
{
|
||||
if (isComparison)
|
||||
{
|
||||
@ -1190,7 +1231,7 @@ struct TypeChecker2
|
||||
expr->location);
|
||||
}
|
||||
|
||||
return;
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else if (!leftMt && !rightMt && (get<TableType>(leftType) || get<TableType>(rightType)))
|
||||
{
|
||||
@ -1207,7 +1248,7 @@ struct TypeChecker2
|
||||
expr->location);
|
||||
}
|
||||
|
||||
return;
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1223,34 +1264,44 @@ struct TypeChecker2
|
||||
reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->numberType));
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
||||
|
||||
break;
|
||||
return builtinTypes->numberType;
|
||||
case AstExprBinary::Op::Concat:
|
||||
reportErrors(tryUnify(scope, expr->left->location, leftType, builtinTypes->stringType));
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType));
|
||||
|
||||
break;
|
||||
return builtinTypes->stringType;
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
if (isNumber(leftType))
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->numberType));
|
||||
return builtinTypes->numberType;
|
||||
}
|
||||
else if (isString(leftType))
|
||||
{
|
||||
reportErrors(tryUnify(scope, expr->right->location, rightType, builtinTypes->stringType));
|
||||
return builtinTypes->stringType;
|
||||
}
|
||||
else
|
||||
{
|
||||
reportError(GenericError{format("Types '%s' and '%s' cannot be compared with relational operator %s", toString(leftType).c_str(),
|
||||
toString(rightType).c_str(), toString(expr->op).c_str())},
|
||||
expr->location);
|
||||
|
||||
break;
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
case AstExprBinary::Op::And:
|
||||
case AstExprBinary::Op::Or:
|
||||
case AstExprBinary::Op::CompareEq:
|
||||
case AstExprBinary::Op::CompareNe:
|
||||
break;
|
||||
// Ugly case: we don't care about this possibility, because a
|
||||
// compound assignment will never exist with one of these operators.
|
||||
return builtinTypes->anyType;
|
||||
default:
|
||||
// Unhandled AstExprBinary::Op possibility.
|
||||
LUAU_ASSERT(false);
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,10 @@
|
||||
#include "Luau/TopoSortStatements.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -35,17 +36,16 @@ LUAU_FASTFLAG(LuauTypeNormalization2)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNilIterator, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnknownAndNeverType, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeInferMissingFollows, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBinaryNeedsExpectedTypesToo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNeverTypesAndOperatorsInference, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauScopelessModule, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFollowInLvalueIndexCheck, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnsFromCallsitesAreNotWidened, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompleteVisitor, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOptionalNextKey, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReportShadowedTypeAlias, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterMessagingOnCountMismatch, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauIntersectionTestForEquality, false)
|
||||
@ -276,6 +276,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo
|
||||
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
|
||||
|
||||
currentModule.reset(new Module);
|
||||
currentModule->reduction = std::make_unique<TypeReduction>(NotNull{¤tModule->internalTypes}, builtinTypes, NotNull{iceHandler});
|
||||
currentModule->type = module.type;
|
||||
currentModule->allocator = module.allocator;
|
||||
currentModule->names = module.names;
|
||||
@ -1136,7 +1137,8 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
|
||||
const Name name{local.vars.data[i]->name.value};
|
||||
|
||||
if (ModulePtr module = resolver->getModule(moduleInfo->name))
|
||||
scope->importedTypeBindings[name] = module->getModuleScope()->exportedTypeBindings;
|
||||
scope->importedTypeBindings[name] =
|
||||
FFlag::LuauScopelessModule ? module->exportedTypeBindings : module->getModuleScope()->exportedTypeBindings;
|
||||
|
||||
// In non-strict mode we force the module type on the variable, in strict mode it is already unified
|
||||
if (isNonstrictMode())
|
||||
@ -1248,7 +1250,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
|
||||
}
|
||||
|
||||
if (FFlag::LuauNilIterator)
|
||||
iterTy = stripFromNilAndReport(iterTy, firstValue->location);
|
||||
|
||||
if (std::optional<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location, /* addErrors= */ true))
|
||||
@ -1334,8 +1335,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
reportErrors(state.errors);
|
||||
}
|
||||
|
||||
if (FFlag::LuauOptionalNextKey)
|
||||
{
|
||||
TypePackId retPack = iterFunc->retTypes;
|
||||
|
||||
if (forin.values.size >= 2)
|
||||
@ -1370,25 +1369,6 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
|
||||
|
||||
unify(retPack, varPack, scope, forin.location);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
|
||||
|
||||
if (forin.values.size >= 2)
|
||||
{
|
||||
AstArray<AstExpr*> arguments{forin.values.data + 1, forin.values.size - 1};
|
||||
|
||||
Position start = firstValue->location.begin;
|
||||
Position end = values[forin.values.size - 1]->location.end;
|
||||
AstExprCall exprCall{Location(start, end), firstValue, arguments, /* self= */ false, Location()};
|
||||
|
||||
TypePackId retPack = checkExprPack(scope, exprCall).type;
|
||||
unify(retPack, varPack, scope, forin.location);
|
||||
}
|
||||
else
|
||||
unify(iterFunc->retTypes, varPack, scope, forin.location);
|
||||
}
|
||||
|
||||
check(loopScope, *forin.body);
|
||||
}
|
||||
@ -4685,7 +4665,7 @@ TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& module
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
TypePackId modulePack = module->getModuleScope()->returnType;
|
||||
TypePackId modulePack = FFlag::LuauScopelessModule ? module->returnType : module->getModuleScope()->returnType;
|
||||
|
||||
if (get<Unifiable::Error>(modulePack))
|
||||
return errorRecoveryType(scope);
|
||||
|
838
Analysis/src/TypeReduction.cpp
Normal file
838
Analysis/src/TypeReduction.cpp
Normal file
@ -0,0 +1,838 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypeReduction.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
|
||||
#include <numeric>
|
||||
#include <deque>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeReductionCartesianProductLimit, 100'000)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeReductionRecursionLimit, 700)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDontReduceTypes, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct RecursionGuard : RecursionLimiter
|
||||
{
|
||||
std::deque<const void*>* seen;
|
||||
|
||||
RecursionGuard(int* count, int limit, std::deque<const void*>* seen)
|
||||
: RecursionLimiter(count, limit)
|
||||
, seen(seen)
|
||||
{
|
||||
// count has been incremented, which should imply that seen has already had an element pushed in.
|
||||
LUAU_ASSERT(size_t(*count) == seen->size());
|
||||
}
|
||||
|
||||
~RecursionGuard()
|
||||
{
|
||||
LUAU_ASSERT(!seen->empty()); // It is UB to pop_back() on an empty deque.
|
||||
seen->pop_back();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename A, typename B, typename Thing>
|
||||
std::pair<const A*, const B*> get2(const Thing& one, const Thing& two)
|
||||
{
|
||||
const A* a = get<A>(one);
|
||||
const B* b = get<B>(two);
|
||||
return a && b ? std::make_pair(a, b) : std::make_pair(nullptr, nullptr);
|
||||
}
|
||||
|
||||
struct TypeReducer
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<InternalErrorReporter> handle;
|
||||
|
||||
TypeId reduce(TypeId ty);
|
||||
TypePackId reduce(TypePackId tp);
|
||||
|
||||
std::optional<TypeId> intersectionType(TypeId left, TypeId right);
|
||||
std::optional<TypeId> unionType(TypeId left, TypeId right);
|
||||
TypeId tableType(TypeId ty);
|
||||
TypeId functionType(TypeId ty);
|
||||
TypeId negationType(TypeId ty);
|
||||
|
||||
std::deque<const void*> seen;
|
||||
int depth = 0;
|
||||
|
||||
RecursionGuard guard(TypeId ty);
|
||||
RecursionGuard guard(TypePackId tp);
|
||||
|
||||
std::unordered_map<TypeId, TypeId> copies;
|
||||
|
||||
template<typename T>
|
||||
LUAU_NOINLINE std::pair<TypeId, T*> copy(TypeId ty, const T* t)
|
||||
{
|
||||
if (auto it = copies.find(ty); it != copies.end())
|
||||
return {it->second, getMutable<T>(it->second)};
|
||||
|
||||
TypeId copiedTy = arena->addType(*t);
|
||||
copies[ty] = copiedTy;
|
||||
return {copiedTy, getMutable<T>(copiedTy)};
|
||||
}
|
||||
|
||||
using Folder = std::optional<TypeId> (TypeReducer::*)(TypeId, TypeId);
|
||||
|
||||
template<typename T, typename Iter>
|
||||
void foldl_impl(Iter it, Iter endIt, Folder f, NotNull<std::vector<TypeId>> result)
|
||||
{
|
||||
while (it != endIt)
|
||||
{
|
||||
bool replaced = false;
|
||||
TypeId currentTy = reduce(*it);
|
||||
RecursionGuard rg = guard(*it);
|
||||
|
||||
// We're hitting a case where the `currentTy` returned a type that's the same as `T`.
|
||||
// e.g. `(string?) & ~(false | nil)` became `(string?) & (~false & ~nil)` but the current iterator we're consuming doesn't know this.
|
||||
// We will need to recurse and traverse that first.
|
||||
if (auto t = get<T>(currentTy))
|
||||
{
|
||||
foldl_impl<T>(begin(t), end(t), f, result);
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto resultIt = result->begin();
|
||||
while (resultIt != result->end())
|
||||
{
|
||||
TypeId& ty = *resultIt;
|
||||
|
||||
std::optional<TypeId> reduced = (this->*f)(ty, currentTy);
|
||||
if (reduced && replaced)
|
||||
{
|
||||
// We want to erase any other elements that occurs after the first replacement too.
|
||||
// e.g. `"a" | "b" | string` where `"a"` and `"b"` is in the `result` vector, then `string` replaces both `"a"` and `"b"`.
|
||||
// If we don't erase redundant elements, `"b"` may be kept or be replaced by `string`, leaving us with `string | string`.
|
||||
resultIt = result->erase(resultIt);
|
||||
}
|
||||
else if (reduced && !replaced)
|
||||
{
|
||||
++resultIt;
|
||||
replaced = true;
|
||||
ty = *reduced;
|
||||
}
|
||||
else
|
||||
{
|
||||
++resultIt;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!replaced)
|
||||
result->push_back(currentTy);
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename Iter>
|
||||
TypeId foldl(Iter it, Iter endIt, Folder f)
|
||||
{
|
||||
std::vector<TypeId> result;
|
||||
foldl_impl<T>(it, endIt, f, NotNull{&result});
|
||||
if (result.size() == 1)
|
||||
return result[0];
|
||||
else
|
||||
return arena->addType(T{std::move(result)});
|
||||
}
|
||||
};
|
||||
|
||||
TypeId TypeReducer::reduce(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (std::find(seen.begin(), seen.end(), ty) != seen.end())
|
||||
return ty;
|
||||
|
||||
RecursionGuard rg = guard(ty);
|
||||
|
||||
if (auto i = get<IntersectionType>(ty))
|
||||
return foldl<IntersectionType>(begin(i), end(i), &TypeReducer::intersectionType);
|
||||
else if (auto u = get<UnionType>(ty))
|
||||
return foldl<UnionType>(begin(u), end(u), &TypeReducer::unionType);
|
||||
else if (get<TableType>(ty) || get<MetatableType>(ty))
|
||||
return tableType(ty);
|
||||
else if (get<FunctionType>(ty))
|
||||
return functionType(ty);
|
||||
else if (auto n = get<NegationType>(ty))
|
||||
return negationType(follow(n->ty));
|
||||
else
|
||||
return ty;
|
||||
}
|
||||
|
||||
TypePackId TypeReducer::reduce(TypePackId tp)
|
||||
{
|
||||
tp = follow(tp);
|
||||
|
||||
if (std::find(seen.begin(), seen.end(), tp) != seen.end())
|
||||
return tp;
|
||||
|
||||
RecursionGuard rg = guard(tp);
|
||||
|
||||
TypePackIterator it = begin(tp);
|
||||
|
||||
std::vector<TypeId> head;
|
||||
while (it != end(tp))
|
||||
{
|
||||
head.push_back(reduce(*it));
|
||||
++it;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> tail = it.tail();
|
||||
if (tail)
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(follow(*it.tail())))
|
||||
tail = arena->addTypePack(VariadicTypePack{reduce(vtp->ty), vtp->hidden});
|
||||
}
|
||||
|
||||
return arena->addTypePack(TypePack{std::move(head), tail});
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReducer::intersectionType(TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(!get<IntersectionType>(left));
|
||||
LUAU_ASSERT(!get<IntersectionType>(right));
|
||||
|
||||
if (get<NeverType>(left))
|
||||
return left; // never & T ~ never
|
||||
else if (get<NeverType>(right))
|
||||
return right; // T & never ~ never
|
||||
else if (get<UnknownType>(left))
|
||||
return right; // unknown & T ~ T
|
||||
else if (get<UnknownType>(right))
|
||||
return left; // T & unknown ~ T
|
||||
else if (get<AnyType>(left))
|
||||
return right; // any & T ~ T
|
||||
else if (get<AnyType>(right))
|
||||
return left; // T & any ~ T
|
||||
else if (get<ErrorType>(left))
|
||||
return std::nullopt; // error & T ~ error & T
|
||||
else if (get<ErrorType>(right))
|
||||
return std::nullopt; // T & error ~ T & error
|
||||
else if (auto ut = get<UnionType>(left))
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
for (TypeId option : ut)
|
||||
{
|
||||
if (auto result = intersectionType(option, right))
|
||||
options.push_back(*result);
|
||||
else
|
||||
options.push_back(arena->addType(IntersectionType{{option, right}}));
|
||||
}
|
||||
|
||||
return foldl<UnionType>(begin(options), end(options), &TypeReducer::unionType); // (A | B) & T ~ (A & T) | (B & T)
|
||||
}
|
||||
else if (get<UnionType>(right))
|
||||
return intersectionType(right, left); // T & (A | B) ~ (A | B) & T
|
||||
else if (auto [p1, p2] = get2<PrimitiveType, PrimitiveType>(left, right); p1 && p2)
|
||||
{
|
||||
if (p1->type == p2->type)
|
||||
return left; // P1 & P2 ~ P1 iff P1 == P2
|
||||
else
|
||||
return builtinTypes->neverType; // P1 & P2 ~ never iff P1 != P2
|
||||
}
|
||||
else if (auto [p, s] = get2<PrimitiveType, SingletonType>(left, right); p && s)
|
||||
{
|
||||
if (p->type == PrimitiveType::String && get<StringSingleton>(s))
|
||||
return right; // string & "A" ~ "A"
|
||||
else if (p->type == PrimitiveType::Boolean && get<BooleanSingleton>(s))
|
||||
return right; // boolean & true ~ true
|
||||
else
|
||||
return builtinTypes->neverType; // string & true ~ never
|
||||
}
|
||||
else if (auto [s, p] = get2<SingletonType, PrimitiveType>(left, right); s && p)
|
||||
return intersectionType(right, left); // S & P ~ P & S
|
||||
else if (auto [p, f] = get2<PrimitiveType, FunctionType>(left, right); p && f)
|
||||
{
|
||||
if (p->type == PrimitiveType::Function)
|
||||
return right; // function & () -> () ~ () -> ()
|
||||
else
|
||||
return builtinTypes->neverType; // string & () -> () ~ never
|
||||
}
|
||||
else if (auto [f, p] = get2<FunctionType, PrimitiveType>(left, right); f && p)
|
||||
return intersectionType(right, left); // () -> () & P ~ P & () -> ()
|
||||
else if (auto [s1, s2] = get2<SingletonType, SingletonType>(left, right); s1 && s2)
|
||||
{
|
||||
if (*s1 == *s2)
|
||||
return left; // "a" & "a" ~ "a"
|
||||
else
|
||||
return builtinTypes->neverType; // "a" & "b" ~ never
|
||||
}
|
||||
else if (auto [c1, c2] = get2<ClassType, ClassType>(left, right); c1 && c2)
|
||||
{
|
||||
if (isSubclass(c1, c2))
|
||||
return left; // Derived & Base ~ Derived
|
||||
else if (isSubclass(c2, c1))
|
||||
return right; // Base & Derived ~ Derived
|
||||
else
|
||||
return builtinTypes->neverType; // Base & Unrelated ~ never
|
||||
}
|
||||
else if (auto [f1, f2] = get2<FunctionType, FunctionType>(left, right); f1 && f2)
|
||||
{
|
||||
if (std::find(seen.begin(), seen.end(), left) != seen.end())
|
||||
return std::nullopt;
|
||||
else if (std::find(seen.begin(), seen.end(), right) != seen.end())
|
||||
return std::nullopt;
|
||||
|
||||
return std::nullopt; // TODO
|
||||
}
|
||||
else if (auto [t1, t2] = get2<TableType, TableType>(left, right); t1 && t2)
|
||||
{
|
||||
if (t1->state == TableState::Free || t2->state == TableState::Free)
|
||||
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
|
||||
else if (t1->state == TableState::Generic || t2->state == TableState::Generic)
|
||||
return std::nullopt; // '{ x: T } & { x: U } ~ '{ x: T } & { x: U }
|
||||
|
||||
if (std::find(seen.begin(), seen.end(), left) != seen.end())
|
||||
return std::nullopt;
|
||||
else if (std::find(seen.begin(), seen.end(), right) != seen.end())
|
||||
return std::nullopt;
|
||||
|
||||
TypeId resultTy = arena->addType(TableType{});
|
||||
TableType* table = getMutable<TableType>(resultTy);
|
||||
table->state = t1->state == TableState::Sealed || t2->state == TableState::Sealed ? TableState::Sealed : TableState::Unsealed;
|
||||
|
||||
for (const auto& [name, prop] : t1->props)
|
||||
{
|
||||
// TODO: when t1 has properties, we should also intersect that with the indexer in t2 if it exists,
|
||||
// even if we have the corresponding property in the other one.
|
||||
if (auto other = t2->props.find(name); other != t2->props.end())
|
||||
{
|
||||
std::vector<TypeId> parts{prop.type, other->second.type};
|
||||
TypeId propTy = foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType);
|
||||
if (get<NeverType>(propTy))
|
||||
return builtinTypes->neverType; // { p : string } & { p : number } ~ { p : string & number } ~ { p : never } ~ never
|
||||
else
|
||||
table->props[name] = {propTy}; // { p : string } & { p : ~"a" } ~ { p : string & ~"a" }
|
||||
}
|
||||
else
|
||||
table->props[name] = prop; // { p : string } & {} ~ { p : string }
|
||||
}
|
||||
|
||||
for (const auto& [name, prop] : t2->props)
|
||||
{
|
||||
// TODO: And vice versa, t2 properties against t1 indexer if it exists,
|
||||
// even if we have the corresponding property in the other one.
|
||||
if (!t1->props.count(name))
|
||||
table->props[name] = prop; // {} & { p : string } ~ { p : string }
|
||||
}
|
||||
|
||||
if (t1->indexer && t2->indexer)
|
||||
{
|
||||
std::vector<TypeId> keyParts{t1->indexer->indexType, t2->indexer->indexType};
|
||||
TypeId keyTy = foldl<IntersectionType>(begin(keyParts), end(keyParts), &TypeReducer::intersectionType);
|
||||
if (get<NeverType>(keyTy))
|
||||
return builtinTypes->neverType; // { [string]: _ } & { [number]: _ } ~ { [string & number]: _ } ~ { [never]: _ } ~ never
|
||||
|
||||
std::vector<TypeId> valueParts{t1->indexer->indexResultType, t2->indexer->indexResultType};
|
||||
TypeId valueTy = foldl<IntersectionType>(begin(valueParts), end(valueParts), &TypeReducer::intersectionType);
|
||||
if (get<NeverType>(valueTy))
|
||||
return builtinTypes->neverType; // { [_]: string } & { [_]: number } ~ { [_]: string & number } ~ { [_]: never } ~ never
|
||||
|
||||
table->indexer = TableIndexer{keyTy, valueTy};
|
||||
}
|
||||
else if (t1->indexer)
|
||||
table->indexer = t1->indexer; // { [number]: boolean } & { p : string } ~ { p : string, [number]: boolean }
|
||||
else if (t2->indexer)
|
||||
table->indexer = t2->indexer; // { p : string } & { [number]: boolean } ~ { p : string, [number]: boolean }
|
||||
|
||||
return resultTy;
|
||||
}
|
||||
else if (auto [mt, tt] = get2<MetatableType, TableType>(left, right); mt && tt)
|
||||
return std::nullopt; // TODO
|
||||
else if (auto [tt, mt] = get2<TableType, MetatableType>(left, right); tt && mt)
|
||||
return intersectionType(right, left); // T & M ~ M & T
|
||||
else if (auto [m1, m2] = get2<MetatableType, MetatableType>(left, right); m1 && m2)
|
||||
return std::nullopt; // TODO
|
||||
else if (auto nl = get<NegationType>(left))
|
||||
{
|
||||
// These should've been reduced already.
|
||||
TypeId nlTy = follow(nl->ty);
|
||||
LUAU_ASSERT(!get<UnknownType>(nlTy));
|
||||
LUAU_ASSERT(!get<NeverType>(nlTy));
|
||||
LUAU_ASSERT(!get<AnyType>(nlTy));
|
||||
LUAU_ASSERT(!get<IntersectionType>(nlTy));
|
||||
LUAU_ASSERT(!get<UnionType>(nlTy));
|
||||
|
||||
if (auto [np, p] = get2<PrimitiveType, PrimitiveType>(nlTy, right); np && p)
|
||||
{
|
||||
if (np->type == p->type)
|
||||
return builtinTypes->neverType; // ~P1 & P2 ~ never iff P1 == P2
|
||||
else
|
||||
return right; // ~P1 & P2 ~ P2 iff P1 != P2
|
||||
}
|
||||
else if (auto [ns, s] = get2<SingletonType, SingletonType>(nlTy, right); ns && s)
|
||||
{
|
||||
if (*ns == *s)
|
||||
return builtinTypes->neverType; // ~"A" & "A" ~ never
|
||||
else
|
||||
return right; // ~"A" & "B" ~ "B"
|
||||
}
|
||||
else if (auto [ns, p] = get2<SingletonType, PrimitiveType>(nlTy, right); ns && p)
|
||||
{
|
||||
if (get<StringSingleton>(ns) && p->type == PrimitiveType::String)
|
||||
return std::nullopt; // ~"A" & string ~ ~"A" & string
|
||||
else if (get<BooleanSingleton>(ns) && p->type == PrimitiveType::Boolean)
|
||||
{
|
||||
// Because booleans contain a fixed amount of values (2), we can do something cooler with this one.
|
||||
const BooleanSingleton* b = get<BooleanSingleton>(ns);
|
||||
return arena->addType(SingletonType{BooleanSingleton{!b->value}}); // ~false & boolean ~ true
|
||||
}
|
||||
else
|
||||
return right; // ~"A" & number ~ number
|
||||
}
|
||||
else if (auto [np, s] = get2<PrimitiveType, SingletonType>(nlTy, right); np && s)
|
||||
{
|
||||
if (np->type == PrimitiveType::String && get<StringSingleton>(s))
|
||||
return builtinTypes->neverType; // ~string & "A" ~ never
|
||||
else if (np->type == PrimitiveType::Boolean && get<BooleanSingleton>(s))
|
||||
return builtinTypes->neverType; // ~boolean & true ~ never
|
||||
else
|
||||
return right; // ~P & "A" ~ "A"
|
||||
}
|
||||
else if (auto [np, f] = get2<PrimitiveType, FunctionType>(nlTy, right); np && f)
|
||||
{
|
||||
if (np->type == PrimitiveType::Function)
|
||||
return builtinTypes->neverType; // ~function & () -> () ~ never
|
||||
else
|
||||
return right; // ~string & () -> () ~ () -> ()
|
||||
}
|
||||
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
|
||||
{
|
||||
if (isSubclass(nc, c))
|
||||
return std::nullopt; // ~Derived & Base ~ ~Derived & Base
|
||||
else if (isSubclass(c, nc))
|
||||
return builtinTypes->neverType; // ~Base & Derived ~ never
|
||||
else
|
||||
return right; // ~Base & Unrelated ~ Unrelated
|
||||
}
|
||||
else
|
||||
return std::nullopt; // TODO
|
||||
}
|
||||
else if (get<NegationType>(right))
|
||||
return intersectionType(right, left); // T & ~U ~ ~U & T
|
||||
else
|
||||
return builtinTypes->neverType; // for all T and U except the ones handled above, T & U ~ never
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReducer::unionType(TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(!get<UnionType>(left));
|
||||
LUAU_ASSERT(!get<UnionType>(right));
|
||||
|
||||
if (get<NeverType>(left))
|
||||
return right; // never | T ~ T
|
||||
else if (get<NeverType>(right))
|
||||
return left; // T | never ~ T
|
||||
else if (get<UnknownType>(left))
|
||||
return left; // unknown | T ~ unknown
|
||||
else if (get<UnknownType>(right))
|
||||
return right; // T | unknown ~ unknown
|
||||
else if (get<AnyType>(left))
|
||||
return left; // any | T ~ any
|
||||
else if (get<AnyType>(right))
|
||||
return right; // T | any ~ any
|
||||
else if (get<ErrorType>(left))
|
||||
return std::nullopt; // error | T ~ error | T
|
||||
else if (get<ErrorType>(right))
|
||||
return std::nullopt; // T | error ~ T | error
|
||||
else if (auto [p1, p2] = get2<PrimitiveType, PrimitiveType>(left, right); p1 && p2)
|
||||
{
|
||||
if (p1->type == p2->type)
|
||||
return left; // P1 | P2 ~ P1 iff P1 == P2
|
||||
else
|
||||
return std::nullopt; // P1 | P2 ~ P1 | P2 iff P1 != P2
|
||||
}
|
||||
else if (auto [p, s] = get2<PrimitiveType, SingletonType>(left, right); p && s)
|
||||
{
|
||||
if (p->type == PrimitiveType::String && get<StringSingleton>(s))
|
||||
return left; // string | "A" ~ string
|
||||
else if (p->type == PrimitiveType::Boolean && get<BooleanSingleton>(s))
|
||||
return left; // boolean | true ~ boolean
|
||||
else
|
||||
return std::nullopt; // string | true ~ string | true
|
||||
}
|
||||
else if (auto [s, p] = get2<SingletonType, PrimitiveType>(left, right); s && p)
|
||||
return unionType(right, left); // S | P ~ P | S
|
||||
else if (auto [p, f] = get2<PrimitiveType, FunctionType>(left, right); p && f)
|
||||
{
|
||||
if (p->type == PrimitiveType::Function)
|
||||
return left; // function | () -> () ~ function
|
||||
else
|
||||
return std::nullopt; // P | () -> () ~ P | () -> ()
|
||||
}
|
||||
else if (auto [f, p] = get2<FunctionType, PrimitiveType>(left, right); f && p)
|
||||
return unionType(right, left); // () -> () | P ~ P | () -> ()
|
||||
else if (auto [s1, s2] = get2<SingletonType, SingletonType>(left, right); s1 && s2)
|
||||
{
|
||||
if (*s1 == *s2)
|
||||
return left; // "a" | "a" ~ "a"
|
||||
else
|
||||
return std::nullopt; // "a" | "b" ~ "a" | "b"
|
||||
}
|
||||
else if (auto [c1, c2] = get2<ClassType, ClassType>(left, right); c1 && c2)
|
||||
{
|
||||
if (isSubclass(c1, c2))
|
||||
return right; // Derived | Base ~ Base
|
||||
else if (isSubclass(c2, c1))
|
||||
return left; // Base | Derived ~ Base
|
||||
else
|
||||
return std::nullopt; // Base | Unrelated ~ Base | Unrelated
|
||||
}
|
||||
else if (auto [nt, it] = get2<NegationType, IntersectionType>(left, right); nt && it)
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId option : it)
|
||||
{
|
||||
if (auto result = unionType(left, option))
|
||||
parts.push_back(*result);
|
||||
else
|
||||
{
|
||||
// TODO: does there exist a reduced form such that `~T | A` hasn't already reduced it, if `A & B` is irreducible?
|
||||
// I want to say yes, but I can't generate a case that hits this code path.
|
||||
parts.push_back(arena->addType(UnionType{{left, option}}));
|
||||
}
|
||||
}
|
||||
|
||||
return foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType); // ~T | (A & B) ~ (~T | A) & (~T | B)
|
||||
}
|
||||
else if (auto [it, nt] = get2<IntersectionType, NegationType>(left, right); it && nt)
|
||||
return unionType(right, left); // (A & B) | ~T ~ ~T | (A & B)
|
||||
else if (auto [nl, nr] = get2<NegationType, NegationType>(left, right); nl && nr)
|
||||
{
|
||||
// These should've been reduced already.
|
||||
TypeId nlTy = follow(nl->ty);
|
||||
TypeId nrTy = follow(nr->ty);
|
||||
LUAU_ASSERT(!get<UnknownType>(nlTy) && !get<UnknownType>(nrTy));
|
||||
LUAU_ASSERT(!get<NeverType>(nlTy) && !get<NeverType>(nrTy));
|
||||
LUAU_ASSERT(!get<AnyType>(nlTy) && !get<AnyType>(nrTy));
|
||||
LUAU_ASSERT(!get<IntersectionType>(nlTy) && !get<IntersectionType>(nrTy));
|
||||
LUAU_ASSERT(!get<UnionType>(nlTy) && !get<UnionType>(nrTy));
|
||||
|
||||
if (auto [npl, npr] = get2<PrimitiveType, PrimitiveType>(nlTy, nrTy); npl && npr)
|
||||
{
|
||||
if (npl->type == npr->type)
|
||||
return left; // ~P1 | ~P2 ~ ~P1 iff P1 == P2
|
||||
else
|
||||
return builtinTypes->unknownType; // ~P1 | ~P2 ~ ~P1 iff P1 != P2
|
||||
}
|
||||
else if (auto [nsl, nsr] = get2<SingletonType, SingletonType>(nlTy, nrTy); nsl && nsr)
|
||||
{
|
||||
if (*nsl == *nsr)
|
||||
return left; // ~"A" | ~"A" ~ ~"A"
|
||||
else
|
||||
return builtinTypes->unknownType; // ~"A" | ~"B" ~ unknown
|
||||
}
|
||||
else if (auto [ns, np] = get2<SingletonType, PrimitiveType>(nlTy, nrTy); ns && np)
|
||||
{
|
||||
if (get<StringSingleton>(ns) && np->type == PrimitiveType::String)
|
||||
return left; // ~"A" | ~string ~ ~"A"
|
||||
else if (get<BooleanSingleton>(ns) && np->type == PrimitiveType::Boolean)
|
||||
return left; // ~false | ~boolean ~ ~false
|
||||
else
|
||||
return builtinTypes->unknownType; // ~"A" | ~P ~ unknown
|
||||
}
|
||||
else if (auto [np, ns] = get2<PrimitiveType, SingletonType>(nlTy, nrTy); np && ns)
|
||||
return unionType(right, left); // ~P | ~S ~ ~S | ~P
|
||||
else
|
||||
return std::nullopt; // TODO!
|
||||
}
|
||||
else if (auto nl = get<NegationType>(left))
|
||||
{
|
||||
// These should've been reduced already.
|
||||
TypeId nlTy = follow(nl->ty);
|
||||
LUAU_ASSERT(!get<UnknownType>(nlTy));
|
||||
LUAU_ASSERT(!get<NeverType>(nlTy));
|
||||
LUAU_ASSERT(!get<AnyType>(nlTy));
|
||||
LUAU_ASSERT(!get<IntersectionType>(nlTy));
|
||||
LUAU_ASSERT(!get<UnionType>(nlTy));
|
||||
|
||||
if (auto [np, p] = get2<PrimitiveType, PrimitiveType>(nlTy, right); np && p)
|
||||
{
|
||||
if (np->type == p->type)
|
||||
return builtinTypes->unknownType; // ~P1 | P2 ~ unknown iff P1 == P2
|
||||
else
|
||||
return left; // ~P1 | P2 ~ ~P1 iff P1 != P2
|
||||
}
|
||||
else if (auto [ns, s] = get2<SingletonType, SingletonType>(nlTy, right); ns && s)
|
||||
{
|
||||
if (*ns == *s)
|
||||
return builtinTypes->unknownType; // ~"A" | "A" ~ unknown
|
||||
else
|
||||
return left; // ~"A" | "B" ~ ~"A"
|
||||
}
|
||||
else if (auto [ns, p] = get2<SingletonType, PrimitiveType>(nlTy, right); ns && p)
|
||||
{
|
||||
if (get<StringSingleton>(ns) && p->type == PrimitiveType::String)
|
||||
return builtinTypes->unknownType; // ~"A" | string ~ unknown
|
||||
else if (get<BooleanSingleton>(ns) && p->type == PrimitiveType::Boolean)
|
||||
return builtinTypes->unknownType; // ~false | boolean ~ unknown
|
||||
else
|
||||
return left; // ~"A" | T ~ ~"A"
|
||||
}
|
||||
else if (auto [np, s] = get2<PrimitiveType, SingletonType>(nlTy, right); np && s)
|
||||
{
|
||||
if (np->type == PrimitiveType::String && get<StringSingleton>(s))
|
||||
return std::nullopt; // ~string | "A" ~ ~string | "A"
|
||||
else if (np->type == PrimitiveType::Boolean && get<BooleanSingleton>(s))
|
||||
{
|
||||
const BooleanSingleton* b = get<BooleanSingleton>(s);
|
||||
return negationType(arena->addType(SingletonType{BooleanSingleton{!b->value}})); // ~boolean | false ~ ~true
|
||||
}
|
||||
else
|
||||
return left; // ~P | "A" ~ ~P
|
||||
}
|
||||
else if (auto [nc, c] = get2<ClassType, ClassType>(nlTy, right); nc && c)
|
||||
{
|
||||
if (isSubclass(nc, c))
|
||||
return builtinTypes->unknownType; // ~Derived | Base ~ unknown
|
||||
else if (isSubclass(c, nc))
|
||||
return std::nullopt; // ~Base | Derived ~ ~Base | Derived
|
||||
else
|
||||
return left; // ~Base | Unrelated ~ ~Base
|
||||
}
|
||||
else
|
||||
return std::nullopt; // TODO
|
||||
}
|
||||
else if (get<NegationType>(right))
|
||||
return unionType(right, left); // T | ~U ~ ~U | T
|
||||
else
|
||||
return std::nullopt; // for all T and U except the ones handled above, T | U ~ T | U
|
||||
}
|
||||
|
||||
TypeId TypeReducer::tableType(TypeId ty)
|
||||
{
|
||||
RecursionGuard rg = guard(ty);
|
||||
|
||||
if (auto mt = get<MetatableType>(ty))
|
||||
{
|
||||
auto [copiedTy, copied] = copy(ty, mt);
|
||||
copied->table = reduce(mt->table);
|
||||
copied->metatable = reduce(mt->metatable);
|
||||
return copiedTy;
|
||||
}
|
||||
else if (auto tt = get<TableType>(ty))
|
||||
{
|
||||
auto [copiedTy, copied] = copy(ty, tt);
|
||||
|
||||
for (auto& [name, prop] : copied->props)
|
||||
prop.type = reduce(prop.type);
|
||||
|
||||
if (auto& indexer = copied->indexer)
|
||||
{
|
||||
indexer->indexType = reduce(indexer->indexType);
|
||||
indexer->indexResultType = reduce(indexer->indexResultType);
|
||||
}
|
||||
|
||||
for (TypeId& ty : copied->instantiatedTypeParams)
|
||||
ty = reduce(ty);
|
||||
|
||||
for (TypePackId& tp : copied->instantiatedTypePackParams)
|
||||
tp = reduce(tp);
|
||||
|
||||
return copiedTy;
|
||||
}
|
||||
else
|
||||
handle->ice("Unexpected type in TypeReducer::tableType");
|
||||
}
|
||||
|
||||
TypeId TypeReducer::functionType(TypeId ty)
|
||||
{
|
||||
RecursionGuard rg = guard(ty);
|
||||
|
||||
const FunctionType* f = get<FunctionType>(ty);
|
||||
if (!f)
|
||||
handle->ice("TypeReducer::reduce expects a FunctionType");
|
||||
|
||||
// TODO: once we have bounded quantification, we need to be able to reduce the generic bounds.
|
||||
auto [copiedTy, copied] = copy(ty, f);
|
||||
copied->argTypes = reduce(f->argTypes);
|
||||
copied->retTypes = reduce(f->retTypes);
|
||||
return copiedTy;
|
||||
}
|
||||
|
||||
TypeId TypeReducer::negationType(TypeId ty)
|
||||
{
|
||||
RecursionGuard rg = guard(ty);
|
||||
|
||||
if (auto nn = get<NegationType>(ty))
|
||||
return nn->ty; // ~~T ~ T
|
||||
else if (get<NeverType>(ty))
|
||||
return builtinTypes->unknownType; // ~never ~ unknown
|
||||
else if (get<UnknownType>(ty))
|
||||
return builtinTypes->neverType; // ~unknown ~ never
|
||||
else if (get<AnyType>(ty))
|
||||
return builtinTypes->anyType; // ~any ~ any
|
||||
else if (auto ni = get<IntersectionType>(ty))
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
for (TypeId part : ni)
|
||||
options.push_back(negationType(part));
|
||||
return foldl<UnionType>(begin(options), end(options), &TypeReducer::unionType); // ~(T & U) ~ (~T | ~U)
|
||||
}
|
||||
else if (auto nu = get<UnionType>(ty))
|
||||
{
|
||||
std::vector<TypeId> parts;
|
||||
for (TypeId option : nu)
|
||||
parts.push_back(negationType(option));
|
||||
return foldl<IntersectionType>(begin(parts), end(parts), &TypeReducer::intersectionType); // ~(T | U) ~ (~T & ~U)
|
||||
}
|
||||
else
|
||||
return arena->addType(NegationType{ty}); // for all T except the ones handled above, ~T ~ ~T
|
||||
}
|
||||
|
||||
RecursionGuard TypeReducer::guard(TypeId ty)
|
||||
{
|
||||
seen.push_back(ty);
|
||||
return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen};
|
||||
}
|
||||
|
||||
RecursionGuard TypeReducer::guard(TypePackId tp)
|
||||
{
|
||||
seen.push_back(tp);
|
||||
return RecursionGuard{&depth, FInt::LuauTypeReductionRecursionLimit, &seen};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TypeReduction::TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> handle)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, handle(handle)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReduction::reduce(TypeId ty)
|
||||
{
|
||||
if (auto found = cachedTypes.find(ty))
|
||||
return *found;
|
||||
|
||||
if (auto reduced = reduceImpl(ty))
|
||||
{
|
||||
cachedTypes[ty] = *reduced;
|
||||
return *reduced;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypeReduction::reduce(TypePackId tp)
|
||||
{
|
||||
if (auto found = cachedTypePacks.find(tp))
|
||||
return *found;
|
||||
|
||||
if (auto reduced = reduceImpl(tp))
|
||||
{
|
||||
cachedTypePacks[tp] = *reduced;
|
||||
return *reduced;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> TypeReduction::reduceImpl(TypeId ty)
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return ty;
|
||||
|
||||
if (hasExceededCartesianProductLimit(ty))
|
||||
return std::nullopt;
|
||||
|
||||
try
|
||||
{
|
||||
TypeReducer reducer{arena, builtinTypes, handle};
|
||||
return reducer.reduce(ty);
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypePackId> TypeReduction::reduceImpl(TypePackId tp)
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return tp;
|
||||
|
||||
if (hasExceededCartesianProductLimit(tp))
|
||||
return std::nullopt;
|
||||
|
||||
try
|
||||
{
|
||||
TypeReducer reducer{arena, builtinTypes, handle};
|
||||
return reducer.reduce(tp);
|
||||
}
|
||||
catch (const RecursionLimitException&)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeFun> TypeReduction::reduce(const TypeFun& fun)
|
||||
{
|
||||
if (FFlag::DebugLuauDontReduceTypes)
|
||||
return fun;
|
||||
|
||||
// TODO: once we have bounded quantification, we need to be able to reduce the generic bounds.
|
||||
if (auto reducedTy = reduce(fun.type))
|
||||
return TypeFun{fun.typeParams, fun.typePackParams, *reducedTy};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
size_t TypeReduction::cartesianProductSize(TypeId ty) const
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
auto it = get<IntersectionType>(follow(ty));
|
||||
if (!it)
|
||||
return 1;
|
||||
|
||||
return std::accumulate(begin(it), end(it), size_t(1), [](size_t acc, TypeId ty) {
|
||||
if (auto ut = get<UnionType>(ty))
|
||||
return acc * std::distance(begin(ut), end(ut));
|
||||
else if (get<NeverType>(ty))
|
||||
return acc * 0;
|
||||
else
|
||||
return acc * 1;
|
||||
});
|
||||
}
|
||||
|
||||
bool TypeReduction::hasExceededCartesianProductLimit(TypeId ty) const
|
||||
{
|
||||
return cartesianProductSize(ty) >= size_t(FInt::LuauTypeReductionCartesianProductLimit);
|
||||
}
|
||||
|
||||
bool TypeReduction::hasExceededCartesianProductLimit(TypePackId tp) const
|
||||
{
|
||||
TypePackIterator it = begin(tp);
|
||||
|
||||
while (it != end(tp))
|
||||
{
|
||||
if (hasExceededCartesianProductLimit(*it))
|
||||
return true;
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
if (auto tail = it.tail())
|
||||
{
|
||||
if (auto vtp = get<VariadicTypePack>(follow(*tail)))
|
||||
{
|
||||
if (hasExceededCartesianProductLimit(vtp->ty))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
@ -27,6 +27,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAG(LuauInterpolatedStringBaseSupport)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMultiAssignmentConflictFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSelfAssignmentSkip, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -2027,6 +2028,8 @@ struct Compiler
|
||||
// note: this can't check expr->upvalue because upvalues may be upgraded to locals during inlining
|
||||
if (int reg = getExprLocalReg(expr); reg >= 0)
|
||||
{
|
||||
// Optimization: we don't need to move if target happens to be in the same register
|
||||
if (!FFlag::LuauSelfAssignmentSkip || options.optimizationLevel == 0 || target != reg)
|
||||
bytecode.emitABC(LOP_MOVE, target, uint8_t(reg), 0);
|
||||
}
|
||||
else
|
||||
|
@ -146,6 +146,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/TypedAllocator.h
|
||||
Analysis/include/Luau/TypeInfer.h
|
||||
Analysis/include/Luau/TypePack.h
|
||||
Analysis/include/Luau/TypeReduction.h
|
||||
Analysis/include/Luau/TypeUtils.h
|
||||
Analysis/include/Luau/Type.h
|
||||
Analysis/include/Luau/Unifiable.h
|
||||
@ -195,6 +196,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/TypedAllocator.cpp
|
||||
Analysis/src/TypeInfer.cpp
|
||||
Analysis/src/TypePack.cpp
|
||||
Analysis/src/TypeReduction.cpp
|
||||
Analysis/src/TypeUtils.cpp
|
||||
Analysis/src/Type.cpp
|
||||
Analysis/src/Unifiable.cpp
|
||||
@ -364,6 +366,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.unionTypes.test.cpp
|
||||
tests/TypeInfer.unknownnever.test.cpp
|
||||
tests/TypePack.test.cpp
|
||||
tests/TypeReduction.test.cpp
|
||||
tests/TypeVar.test.cpp
|
||||
tests/Variant.test.cpp
|
||||
tests/VisitType.test.cpp
|
||||
|
@ -3331,4 +3331,36 @@ TEST_CASE_FIXTURE(ACFixture, "globals_are_order_independent")
|
||||
CHECK(ac.entryMap.count("abc1"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
|
||||
{
|
||||
check(R"(
|
||||
type T = { x: (number & string)? }
|
||||
|
||||
function f(thingamabob: T)
|
||||
thingamabob.@1
|
||||
end
|
||||
|
||||
function g(thingamabob: T)
|
||||
thingama@2
|
||||
end
|
||||
)");
|
||||
|
||||
ToStringOptions opts;
|
||||
opts.exhaustive = true;
|
||||
|
||||
auto ac1 = autocomplete('1');
|
||||
REQUIRE(ac1.entryMap.count("x"));
|
||||
std::optional<TypeId> ty1 = ac1.entryMap.at("x").type;
|
||||
REQUIRE(ty1);
|
||||
CHECK("(number & string)?" == toString(*ty1, opts));
|
||||
// CHECK("nil" == toString(*ty1, opts));
|
||||
|
||||
auto ac2 = autocomplete('2');
|
||||
REQUIRE(ac2.entryMap.count("thingamabob"));
|
||||
std::optional<TypeId> ty2 = ac2.entryMap.at("thingamabob").type;
|
||||
REQUIRE(ty2);
|
||||
CHECK("{| x: (number & string)? |}" == toString(*ty2, opts));
|
||||
// CHECK("{| x: nil |}" == toString(*ty2, opts));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1025,6 +1025,8 @@ L0: RETURN R0 0
|
||||
|
||||
TEST_CASE("AndOr")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
// codegen for constant, local, global for and
|
||||
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"(
|
||||
LOADN R0 1
|
||||
@ -1079,7 +1081,6 @@ RETURN R0 1
|
||||
// note: `a = a` assignment is to disable constant folding for testing purposes
|
||||
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a b = 2 local c = a and b return c"), R"(
|
||||
LOADN R0 1
|
||||
MOVE R0 R0
|
||||
LOADN R1 2
|
||||
SETGLOBAL R1 K0
|
||||
MOVE R1 R0
|
||||
@ -1090,7 +1091,6 @@ L0: RETURN R1 1
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a b = 2 local c = a or b return c"), R"(
|
||||
LOADN R0 1
|
||||
MOVE R0 R0
|
||||
LOADN R1 2
|
||||
SETGLOBAL R1 K0
|
||||
MOVE R1 R0
|
||||
@ -2260,6 +2260,8 @@ L1: RETURN R3 -1
|
||||
|
||||
TEST_CASE("UpvaluesLoopsBytecode")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
function test()
|
||||
for i=1,10 do
|
||||
@ -2279,7 +2281,6 @@ LOADN R0 10
|
||||
LOADN R1 1
|
||||
FORNPREP R0 L2
|
||||
L0: MOVE R3 R2
|
||||
MOVE R3 R3
|
||||
GETIMPORT R4 1
|
||||
NEWCLOSURE R5 P0
|
||||
CAPTURE REF R3
|
||||
@ -2312,8 +2313,7 @@ GETIMPORT R0 1
|
||||
GETIMPORT R1 3
|
||||
CALL R0 1 3
|
||||
FORGPREP_INEXT R0 L2
|
||||
L0: MOVE R3 R3
|
||||
GETIMPORT R5 5
|
||||
L0: GETIMPORT R5 5
|
||||
NEWCLOSURE R6 P0
|
||||
CAPTURE REF R3
|
||||
CALL R5 1 0
|
||||
@ -5159,6 +5159,8 @@ RETURN R1 1
|
||||
|
||||
TEST_CASE("InlineMutate")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
// if the argument is mutated, it gets a register even if the value is constant
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function foo(a)
|
||||
@ -5231,7 +5233,6 @@ return x
|
||||
1, 2),
|
||||
R"(
|
||||
DUPCLOSURE R0 K0
|
||||
MOVE R0 R0
|
||||
MOVE R1 R0
|
||||
LOADN R2 42
|
||||
CALL R1 1 1
|
||||
@ -6790,4 +6791,31 @@ L0: RETURN R1 -1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("SkipSelfAssignment")
|
||||
{
|
||||
ScopedFastFlag luauSelfAssignmentSkip{"LuauSelfAssignmentSkip", true};
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0("local a a = a"), R"(
|
||||
LOADNIL R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0("local a a = a :: number"), R"(
|
||||
LOADNIL R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0("local a a = (((a)))"), R"(
|
||||
LOADNIL R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
|
||||
// Keep it on optimization level 0
|
||||
CHECK_EQ("\n" + compileFunction("local a a = a", 0, 0), R"(
|
||||
LOADNIL R0
|
||||
MOVE R0 R0
|
||||
RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1,6 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "ConstraintGraphBuilderFixture.h"
|
||||
|
||||
#include "Luau/TypeReduction.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -9,6 +11,8 @@ ConstraintGraphBuilderFixture::ConstraintGraphBuilderFixture()
|
||||
, mainModule(new Module)
|
||||
, forceTheFlag{"DebugLuauDeferredConstraintResolution", true}
|
||||
{
|
||||
mainModule->reduction = std::make_unique<TypeReduction>(NotNull{&mainModule->internalTypes}, builtinTypes, NotNull{&ice});
|
||||
|
||||
BlockedType::nextIndex = 0;
|
||||
BlockedTypePack::nextIndex = 0;
|
||||
}
|
||||
|
@ -312,6 +312,9 @@ std::optional<TypeId> Fixture::getType(const std::string& name)
|
||||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
|
||||
if (!module->hasModuleScope())
|
||||
return std::nullopt;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return linearSearchForBinding(module->getModuleScope().get(), name.c_str());
|
||||
else
|
||||
@ -329,11 +332,14 @@ TypeId Fixture::requireType(const ModuleName& moduleName, const std::string& nam
|
||||
{
|
||||
ModulePtr module = frontend.moduleResolver.getModule(moduleName);
|
||||
REQUIRE(module);
|
||||
return requireType(module->getModuleScope(), name);
|
||||
return requireType(module, name);
|
||||
}
|
||||
|
||||
TypeId Fixture::requireType(const ModulePtr& module, const std::string& name)
|
||||
{
|
||||
if (!module->hasModuleScope())
|
||||
FAIL("requireType: module scope data is not available");
|
||||
|
||||
return requireType(module->getModuleScope(), name);
|
||||
}
|
||||
|
||||
@ -367,7 +373,12 @@ TypeId Fixture::requireTypeAtPosition(Position position)
|
||||
|
||||
std::optional<TypeId> Fixture::lookupType(const std::string& name)
|
||||
{
|
||||
if (auto typeFun = getMainModule()->getModuleScope()->lookupType(name))
|
||||
ModulePtr module = getMainModule();
|
||||
|
||||
if (!module->hasModuleScope())
|
||||
return std::nullopt;
|
||||
|
||||
if (auto typeFun = module->getModuleScope()->lookupType(name))
|
||||
return typeFun->type;
|
||||
|
||||
return std::nullopt;
|
||||
@ -375,12 +386,24 @@ std::optional<TypeId> Fixture::lookupType(const std::string& name)
|
||||
|
||||
std::optional<TypeId> Fixture::lookupImportedType(const std::string& moduleAlias, const std::string& name)
|
||||
{
|
||||
if (auto typeFun = getMainModule()->getModuleScope()->lookupImportedType(moduleAlias, name))
|
||||
ModulePtr module = getMainModule();
|
||||
|
||||
if (!module->hasModuleScope())
|
||||
FAIL("lookupImportedType: module scope data is not available");
|
||||
|
||||
if (auto typeFun = module->getModuleScope()->lookupImportedType(moduleAlias, name))
|
||||
return typeFun->type;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeId Fixture::requireTypeAlias(const std::string& name)
|
||||
{
|
||||
std::optional<TypeId> ty = lookupType(name);
|
||||
REQUIRE(ty);
|
||||
return *ty;
|
||||
}
|
||||
|
||||
std::string Fixture::decorateWithTypes(const std::string& code)
|
||||
{
|
||||
fileResolver.source[mainModuleName] = code;
|
||||
@ -552,15 +575,52 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void registerHiddenTypes(Fixture& fixture, TypeArena& arena)
|
||||
void registerHiddenTypes(Frontend* frontend)
|
||||
{
|
||||
TypeId t = arena.addType(GenericType{"T"});
|
||||
TypeId t = frontend->globalTypes.addType(GenericType{"T"});
|
||||
GenericTypeDefinition genericT{t};
|
||||
|
||||
ScopePtr moduleScope = fixture.frontend.getGlobalScope();
|
||||
moduleScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, arena.addType(NegationType{t})};
|
||||
moduleScope->exportedTypeBindings["fun"] = TypeFun{{}, fixture.builtinTypes->functionType};
|
||||
moduleScope->exportedTypeBindings["cls"] = TypeFun{{}, fixture.builtinTypes->classType};
|
||||
ScopePtr globalScope = frontend->getGlobalScope();
|
||||
globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, frontend->globalTypes.addType(NegationType{t})};
|
||||
globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType};
|
||||
globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType};
|
||||
globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType};
|
||||
}
|
||||
|
||||
void createSomeClasses(Frontend* frontend)
|
||||
{
|
||||
TypeArena& arena = frontend->globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
ScopePtr moduleScope = frontend->getGlobalScope();
|
||||
|
||||
TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
ClassType* parentClass = getMutable<ClassType>(parentType);
|
||||
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
|
||||
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
|
||||
addGlobalBinding(*frontend, "Parent", {parentType});
|
||||
moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
|
||||
|
||||
TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
ClassType* childClass = getMutable<ClassType>(childType);
|
||||
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
|
||||
|
||||
addGlobalBinding(*frontend, "Child", {childType});
|
||||
moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType};
|
||||
|
||||
TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
addGlobalBinding(*frontend, "Unrelated", {unrelatedType});
|
||||
moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||
|
||||
for (const auto& [name, ty] : moduleScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
|
||||
void dump(const std::vector<Constraint>& constraints)
|
||||
|
@ -91,6 +91,7 @@ struct Fixture
|
||||
|
||||
std::optional<TypeId> lookupType(const std::string& name);
|
||||
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
|
||||
TypeId requireTypeAlias(const std::string& name);
|
||||
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena;
|
||||
ScopedFastFlag sff_UnknownNever{"LuauUnknownAndNeverType", true};
|
||||
@ -151,7 +152,8 @@ std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Wa
|
||||
|
||||
std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
|
||||
|
||||
void registerHiddenTypes(Fixture& fixture, TypeArena& arena);
|
||||
void registerHiddenTypes(Frontend* frontend);
|
||||
void createSomeClasses(Frontend* frontend);
|
||||
|
||||
} // namespace Luau
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauScopelessModule)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
@ -143,6 +145,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "real_source")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
@ -157,7 +161,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
|
||||
CHECK(bModule->errors.empty());
|
||||
Luau::dumpErrors(bModule);
|
||||
|
||||
auto bExports = first(bModule->getModuleScope()->returnType);
|
||||
auto bExports = first(bModule->returnType);
|
||||
REQUIRE(!!bExports);
|
||||
|
||||
CHECK_EQ("{| b_value: number |}", toString(*bExports));
|
||||
@ -220,6 +224,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "any_annotation_breaks_cycle")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "nocheck_modules_are_typed")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!nocheck
|
||||
export type Foo = number
|
||||
@ -243,13 +249,13 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_modules_are_typed")
|
||||
ModulePtr aModule = frontend.moduleResolver.modules["game/Gui/Modules/A"];
|
||||
REQUIRE(bool(aModule));
|
||||
|
||||
std::optional<TypeId> aExports = first(aModule->getModuleScope()->returnType);
|
||||
std::optional<TypeId> aExports = first(aModule->returnType);
|
||||
REQUIRE(bool(aExports));
|
||||
|
||||
ModulePtr bModule = frontend.moduleResolver.modules["game/Gui/Modules/B"];
|
||||
REQUIRE(bool(bModule));
|
||||
|
||||
std::optional<TypeId> bExports = first(bModule->getModuleScope()->returnType);
|
||||
std::optional<TypeId> bExports = first(bModule->returnType);
|
||||
REQUIRE(bool(bExports));
|
||||
|
||||
CHECK_EQ(toString(*aExports), toString(*bExports));
|
||||
@ -275,6 +281,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_between_check_and_nocheck")
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = R"(
|
||||
--!nocheck
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
@ -300,7 +308,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
|
||||
ModulePtr cModule = frontend.moduleResolver.modules["game/Gui/Modules/C"];
|
||||
REQUIRE(bool(cModule));
|
||||
|
||||
std::optional<TypeId> cExports = first(cModule->getModuleScope()->returnType);
|
||||
std::optional<TypeId> cExports = first(cModule->returnType);
|
||||
REQUIRE(bool(cExports));
|
||||
CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
|
||||
}
|
||||
@ -493,6 +501,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "dont_recheck_script_that_hasnt_been_marked_d
|
||||
|
||||
TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||
fileResolver.source["game/Gui/Modules/B"] = R"(
|
||||
local Modules = game:GetService('Gui').Modules
|
||||
@ -511,7 +521,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty")
|
||||
CHECK(bModule->errors.empty());
|
||||
Luau::dumpErrors(bModule);
|
||||
|
||||
auto bExports = first(bModule->getModuleScope()->returnType);
|
||||
auto bExports = first(bModule->returnType);
|
||||
REQUIRE(!!bExports);
|
||||
|
||||
CHECK_EQ("{| b_value: string |}", toString(*bExports));
|
||||
|
@ -112,6 +112,8 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_cyclic_table")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
return {sign=math.sign}
|
||||
)");
|
||||
@ -119,7 +121,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ModulePtr module = frontend.moduleResolver.getModule("MainModule");
|
||||
std::optional<TypeId> exports = first(module->getModuleScope()->returnType);
|
||||
std::optional<TypeId> exports = first(module->returnType);
|
||||
REQUIRE(bool(exports));
|
||||
|
||||
REQUIRE(isInArena(*exports, module->interfaceTypes));
|
||||
@ -283,6 +285,8 @@ TEST_CASE_FIXTURE(Fixture, "clone_recursion_limit")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "any_persistance_does_not_leak")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
export type A = B
|
||||
type B = A
|
||||
@ -294,8 +298,8 @@ type B = A
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
auto mod = frontend.moduleResolver.getModule("Module/A");
|
||||
auto it = mod->getModuleScope()->exportedTypeBindings.find("A");
|
||||
REQUIRE(it != mod->getModuleScope()->exportedTypeBindings.end());
|
||||
auto it = mod->exportedTypeBindings.find("A");
|
||||
REQUIRE(it != mod->exportedTypeBindings.end());
|
||||
CHECK(toString(it->second.type) == "any");
|
||||
}
|
||||
|
||||
@ -306,6 +310,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_reexports")
|
||||
{"LuauSubstitutionReentrant", true},
|
||||
{"LuauClassTypeVarsInSubstitution", true},
|
||||
{"LuauSubstitutionFixMissingFields", true},
|
||||
{"LuauScopelessModule", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
@ -326,10 +331,10 @@ return {}
|
||||
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
||||
REQUIRE(modA);
|
||||
REQUIRE(modB);
|
||||
auto modAiter = modA->getModuleScope()->exportedTypeBindings.find("A");
|
||||
auto modBiter = modB->getModuleScope()->exportedTypeBindings.find("B");
|
||||
REQUIRE(modAiter != modA->getModuleScope()->exportedTypeBindings.end());
|
||||
REQUIRE(modBiter != modB->getModuleScope()->exportedTypeBindings.end());
|
||||
auto modAiter = modA->exportedTypeBindings.find("A");
|
||||
auto modBiter = modB->exportedTypeBindings.find("B");
|
||||
REQUIRE(modAiter != modA->exportedTypeBindings.end());
|
||||
REQUIRE(modBiter != modB->exportedTypeBindings.end());
|
||||
TypeId typeA = modAiter->second.type;
|
||||
TypeId typeB = modBiter->second.type;
|
||||
TableType* tableB = getMutable<TableType>(typeB);
|
||||
@ -344,6 +349,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_clone_types_of_reexported_values")
|
||||
{"LuauSubstitutionReentrant", true},
|
||||
{"LuauClassTypeVarsInSubstitution", true},
|
||||
{"LuauSubstitutionFixMissingFields", true},
|
||||
{"LuauScopelessModule", true},
|
||||
};
|
||||
|
||||
fileResolver.source["Module/A"] = R"(
|
||||
@ -364,8 +370,8 @@ return exports
|
||||
ModulePtr modB = frontend.moduleResolver.getModule("Module/B");
|
||||
REQUIRE(modA);
|
||||
REQUIRE(modB);
|
||||
std::optional<TypeId> typeA = first(modA->getModuleScope()->returnType);
|
||||
std::optional<TypeId> typeB = first(modB->getModuleScope()->returnType);
|
||||
std::optional<TypeId> typeA = first(modA->returnType);
|
||||
std::optional<TypeId> typeB = first(modB->returnType);
|
||||
REQUIRE(typeA);
|
||||
REQUIRE(typeB);
|
||||
TableType* tableA = getMutable<TableType>(*typeA);
|
||||
|
@ -253,6 +253,8 @@ TEST_CASE_FIXTURE(Fixture, "delay_function_does_not_require_its_argument_to_retu
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!nonstrict
|
||||
|
||||
@ -269,7 +271,7 @@ TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
REQUIRE_EQ("any", toString(getMainModule()->getModuleScope()->returnType));
|
||||
REQUIRE_EQ("any", toString(getMainModule()->returnType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "returning_insufficient_return_values")
|
||||
|
@ -17,46 +17,17 @@ struct IsSubtypeFixture : Fixture
|
||||
{
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, builtinTypes, ice);
|
||||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
|
||||
if (!module->hasModuleScope())
|
||||
FAIL("isSubtype: module scope data is not available");
|
||||
|
||||
return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, builtinTypes, ice);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void createSomeClasses(Frontend& frontend)
|
||||
{
|
||||
auto& arena = frontend.globalTypes;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend.builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
ClassType* parentClass = getMutable<ClassType>(parentType);
|
||||
parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
|
||||
parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})};
|
||||
|
||||
addGlobalBinding(frontend, "Parent", {parentType});
|
||||
frontend.getGlobalScope()->exportedTypeBindings["Parent"] = TypeFun{{}, parentType};
|
||||
|
||||
TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
ClassType* childClass = getMutable<ClassType>(childType);
|
||||
childClass->props["virtual_method"] = {makeFunction(arena, childType, {}, {})};
|
||||
|
||||
addGlobalBinding(frontend, "Child", {childType});
|
||||
frontend.getGlobalScope()->exportedTypeBindings["Child"] = TypeFun{{}, childType};
|
||||
|
||||
TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend.builtinTypes->classType, std::nullopt, {}, nullptr, "Test"});
|
||||
|
||||
addGlobalBinding(frontend, "Unrelated", {unrelatedType});
|
||||
frontend.getGlobalScope()->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType};
|
||||
|
||||
for (const auto& [name, ty] : frontend.getGlobalScope()->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
|
||||
freeze(arena);
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("isSubtype");
|
||||
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "primitives")
|
||||
@ -352,7 +323,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table")
|
||||
|
||||
TEST_CASE_FIXTURE(IsSubtypeFixture, "classes")
|
||||
{
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
|
||||
check(""); // Ensure that we have a main Module.
|
||||
|
||||
@ -403,7 +374,7 @@ struct NormalizeFixture : Fixture
|
||||
|
||||
NormalizeFixture()
|
||||
{
|
||||
registerHiddenTypes(*this, arena);
|
||||
registerHiddenTypes(&frontend);
|
||||
}
|
||||
|
||||
const NormalizedType* toNormalizedType(const std::string& annotation)
|
||||
@ -589,7 +560,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
|
||||
{
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type t0 = { a: Child }
|
||||
@ -612,7 +583,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "unions_of_classes")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("Parent | Unrelated" == toString(normal("Parent | Unrelated")));
|
||||
CHECK("Parent" == toString(normal("Parent | Child")));
|
||||
CHECK("Parent | Unrelated" == toString(normal("Parent | Child | Unrelated")));
|
||||
@ -622,7 +593,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersections_of_classes")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("Child" == toString(normal("Parent & Child")));
|
||||
CHECK("never" == toString(normal("Child & Unrelated")));
|
||||
}
|
||||
@ -631,7 +602,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("Child" == toString(normal("(Child | Unrelated) & Child")));
|
||||
}
|
||||
|
||||
@ -639,7 +610,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
|
||||
CHECK("((class & ~Child) | boolean | function | number | string | thread)?" == toString(normal("Not<Child>")));
|
||||
CHECK("Child" == toString(normal("Not<Parent> & Child")));
|
||||
@ -653,7 +624,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_unknown")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("Parent" == toString(normal("Parent & unknown")));
|
||||
}
|
||||
|
||||
@ -661,7 +632,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_never")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedClassTypes", true};
|
||||
|
||||
createSomeClasses(frontend);
|
||||
createSomeClasses(&frontend);
|
||||
CHECK("never" == toString(normal("Parent & never")));
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
LUAU_FASTFLAG(LuauNewLibraryTypeNames)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeAliases");
|
||||
|
||||
@ -506,19 +505,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "general_require_multi_assign")
|
||||
|
||||
CheckResult result = frontend.check("workspace/C");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
ModulePtr m = frontend.moduleResolver.modules["workspace/C"];
|
||||
|
||||
REQUIRE(m != nullptr);
|
||||
|
||||
std::optional<TypeId> aTypeId = lookupName(m->getModuleScope(), "a");
|
||||
REQUIRE(aTypeId);
|
||||
const Luau::TableType* aType = get<TableType>(follow(*aTypeId));
|
||||
TypeId aTypeId = requireType("workspace/C", "a");
|
||||
const Luau::TableType* aType = get<TableType>(follow(aTypeId));
|
||||
REQUIRE(aType);
|
||||
REQUIRE(aType->props.size() == 2);
|
||||
|
||||
std::optional<TypeId> bTypeId = lookupName(m->getModuleScope(), "b");
|
||||
REQUIRE(bTypeId);
|
||||
const Luau::TableType* bType = get<TableType>(follow(*bTypeId));
|
||||
TypeId bTypeId = requireType("workspace/C", "b");
|
||||
const Luau::TableType* bType = get<TableType>(follow(bTypeId));
|
||||
REQUIRE(bType);
|
||||
REQUIRE(bType->props.size() == 3);
|
||||
}
|
||||
@ -530,10 +524,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
|
||||
|
||||
TypeId ty = getGlobalBinding(frontend, "table");
|
||||
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
CHECK(toString(ty) == "typeof(table)");
|
||||
else
|
||||
CHECK(toString(ty) == "table");
|
||||
|
||||
const TableType* ttv = get<TableType>(ty);
|
||||
REQUIRE(ttv);
|
||||
|
@ -319,10 +319,10 @@ TEST_CASE_FIXTURE(Fixture, "self_referential_type_alias")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
std::optional<TypeFun> res = getMainModule()->getModuleScope()->lookupType("O");
|
||||
std::optional<TypeId> res = lookupType("O");
|
||||
REQUIRE(res);
|
||||
|
||||
TypeId oType = follow(res->type);
|
||||
TypeId oType = follow(*res);
|
||||
const TableType* oTable = get<TableType>(oType);
|
||||
REQUIRE(oTable);
|
||||
|
||||
@ -347,6 +347,8 @@ TEST_CASE_FIXTURE(Fixture, "define_generic_type_alias")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
ModulePtr mainModule = getMainModule();
|
||||
REQUIRE(mainModule);
|
||||
REQUIRE(mainModule->hasModuleScope());
|
||||
|
||||
auto it = mainModule->getModuleScope()->privateTypeBindings.find("Array");
|
||||
REQUIRE(it != mainModule->getModuleScope()->privateTypeBindings.end());
|
||||
@ -463,6 +465,8 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_always_resolve_to_a_real_type")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "interface_types_belong_to_interface_arena")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type A = {field: number}
|
||||
|
||||
@ -475,12 +479,12 @@ TEST_CASE_FIXTURE(Fixture, "interface_types_belong_to_interface_arena")
|
||||
|
||||
Module& mod = *getMainModule();
|
||||
|
||||
const TypeFun& a = mod.getModuleScope()->exportedTypeBindings["A"];
|
||||
const TypeFun& a = mod.exportedTypeBindings["A"];
|
||||
|
||||
CHECK(isInArena(a.type, mod.interfaceTypes));
|
||||
CHECK(!isInArena(a.type, typeChecker.globalTypes));
|
||||
|
||||
std::optional<TypeId> exportsType = first(mod.getModuleScope()->returnType);
|
||||
std::optional<TypeId> exportsType = first(mod.returnType);
|
||||
REQUIRE(exportsType);
|
||||
|
||||
TableType* exportsTable = getMutable<TableType>(*exportsType);
|
||||
@ -494,6 +498,8 @@ TEST_CASE_FIXTURE(Fixture, "interface_types_belong_to_interface_arena")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_aliases_are_cloned_properly")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type Array<T> = { [number]: T }
|
||||
)");
|
||||
@ -501,7 +507,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases_are_cloned_properly")
|
||||
dumpErrors(result);
|
||||
|
||||
Module& mod = *getMainModule();
|
||||
const auto& typeBindings = mod.getModuleScope()->exportedTypeBindings;
|
||||
const auto& typeBindings = mod.exportedTypeBindings;
|
||||
|
||||
auto it = typeBindings.find("Array");
|
||||
REQUIRE(typeBindings.end() != it);
|
||||
@ -521,6 +527,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases_are_cloned_properly")
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cloned_interface_maintains_pointers_between_definitions")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
export type Record = { name: string, location: string }
|
||||
local a: Record = { name="Waldo", location="?????" }
|
||||
@ -533,9 +541,9 @@ TEST_CASE_FIXTURE(Fixture, "cloned_interface_maintains_pointers_between_definiti
|
||||
|
||||
Module& mod = *getMainModule();
|
||||
|
||||
TypeId recordType = mod.getModuleScope()->exportedTypeBindings["Record"].type;
|
||||
TypeId recordType = mod.exportedTypeBindings["Record"].type;
|
||||
|
||||
std::optional<TypeId> exportsType = first(mod.getModuleScope()->returnType);
|
||||
std::optional<TypeId> exportsType = first(mod.returnType);
|
||||
REQUIRE(exportsType);
|
||||
|
||||
TableType* exportsTable = getMutable<TableType>(*exportsType);
|
||||
|
@ -109,6 +109,8 @@ TEST_CASE_FIXTURE(Fixture, "vararg_functions_should_allow_calls_of_any_types_and
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local T = {}
|
||||
function T.f(...)
|
||||
@ -129,7 +131,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
auto r = first(getMainModule()->getModuleScope()->returnType);
|
||||
auto r = first(getMainModule()->returnType);
|
||||
REQUIRE(r);
|
||||
|
||||
TableType* ttv = getMutable<TableType>(*r);
|
||||
@ -1772,7 +1774,7 @@ z = y -- Not OK, so the line is colorable
|
||||
TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(*this, frontend.globalTypes);
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(f: fun) end
|
||||
@ -1791,7 +1793,7 @@ TEST_CASE_FIXTURE(Fixture, "function_is_supertype_of_concrete_functions")
|
||||
TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(*this, frontend.globalTypes);
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: fun = function() end
|
||||
@ -1812,7 +1814,7 @@ TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
|
||||
TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauNegatedFunctionTypes", true};
|
||||
registerHiddenTypes(*this, frontend.globalTypes);
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: fun = function() end
|
||||
|
@ -1021,9 +1021,9 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
std::optional<TypeId> t0 = lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
CHECK_EQ("*error-type*", toString(t0->type));
|
||||
CHECK_EQ("*error-type*", toString(*t0));
|
||||
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||
return get<OccursCheckFailed>(err);
|
||||
|
@ -20,7 +20,7 @@ struct NegationFixture : Fixture
|
||||
|
||||
NegationFixture()
|
||||
{
|
||||
registerHiddenTypes(*this, arena);
|
||||
registerHiddenTypes(&frontend);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -405,17 +405,41 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_metatable")
|
||||
type V2B = { x: number, y: number }
|
||||
local v2b: V2B = { x = 0, y = 0 }
|
||||
local VMT = {}
|
||||
type V2 = typeof(setmetatable(v2b, VMT))
|
||||
|
||||
function VMT.__add(a: V2, b: V2): V2
|
||||
VMT.__add = function(a: V2, b: V2): V2
|
||||
return setmetatable({ x = a.x + b.x, y = a.y + b.y }, VMT)
|
||||
end
|
||||
|
||||
type V2 = typeof(setmetatable(v2b, VMT))
|
||||
|
||||
local v1: V2 = setmetatable({ x = 1, y = 2 }, VMT)
|
||||
local v2: V2 = setmetatable({ x = 3, y = 4 }, VMT)
|
||||
v1 += v2
|
||||
)");
|
||||
CHECK_EQ(0, result.errors.size());
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_result_must_be_compatible_with_var")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function __add(left, right)
|
||||
return 123
|
||||
end
|
||||
|
||||
local mt = {
|
||||
__add = __add,
|
||||
}
|
||||
|
||||
local x = setmetatable({}, mt)
|
||||
local v: number
|
||||
|
||||
v += x -- okay: number + x -> number
|
||||
x += v -- not okay: x </: number
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(result.errors[0] == TypeError{Location{{13, 8}, {13, 14}}, TypeMismatch{requireType("x"), builtinTypes->numberType}});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_mismatch_metatable")
|
||||
@ -1015,11 +1039,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mm_ops_must_return_a_value")
|
||||
local y = x + 123
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK(requireType("y") == builtinTypes->errorRecoveryType());
|
||||
|
||||
const GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
const GenericError* ge = get<GenericError>(result.errors[1]);
|
||||
REQUIRE(ge);
|
||||
CHECK(ge->message == "Metamethod '__add' must return a value");
|
||||
}
|
||||
@ -1049,13 +1073,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mm_comparisons_must_return_a_boolean")
|
||||
local v2 = o2 < o2
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
|
||||
CHECK(requireType("v1") == builtinTypes->booleanType);
|
||||
CHECK(requireType("v2") == builtinTypes->booleanType);
|
||||
|
||||
CHECK(toString(result.errors[0]) == "Metamethod '__lt' must return type 'boolean'");
|
||||
CHECK(toString(result.errors[1]) == "Metamethod '__lt' must return type 'boolean'");
|
||||
CHECK(toString(result.errors[1]) == "Metamethod '__lt' must return a boolean");
|
||||
CHECK(toString(result.errors[3]) == "Metamethod '__lt' must return a boolean");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "reworked_and")
|
||||
|
@ -516,6 +516,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
||||
// Ideally, we would not try to export a function type with generic types from incorrect scope
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
@ -548,13 +550,15 @@ return wrapStrictTable(Constants, "Constants")
|
||||
ModulePtr m = frontend.moduleResolver.modules["game/B"];
|
||||
REQUIRE(m);
|
||||
|
||||
std::optional<TypeId> result = first(m->getModuleScope()->returnType);
|
||||
std::optional<TypeId> result = first(m->returnType);
|
||||
REQUIRE(result);
|
||||
CHECK(get<AnyType>(*result));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_leak_to_module_interface_variadic")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
local wrapStrictTable
|
||||
|
||||
@ -587,7 +591,7 @@ return wrapStrictTable(Constants, "Constants")
|
||||
ModulePtr m = frontend.moduleResolver.modules["game/B"];
|
||||
REQUIRE(m);
|
||||
|
||||
std::optional<TypeId> result = first(m->getModuleScope()->returnType);
|
||||
std::optional<TypeId> result = first(m->returnType);
|
||||
REQUIRE(result);
|
||||
CHECK(get<AnyType>(*result));
|
||||
}
|
||||
@ -620,7 +624,13 @@ struct IsSubtypeFixture : Fixture
|
||||
{
|
||||
bool isSubtype(TypeId a, TypeId b)
|
||||
{
|
||||
return ::Luau::isSubtype(a, b, NotNull{getMainModule()->getModuleScope().get()}, builtinTypes, ice);
|
||||
ModulePtr module = getMainModule();
|
||||
REQUIRE(module);
|
||||
|
||||
if (!module->hasModuleScope())
|
||||
FAIL("isSubtype: module scope data is not available");
|
||||
|
||||
return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, builtinTypes, ice);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
@ -18,7 +18,6 @@ LUAU_FASTFLAG(LuauLowerBoundsCalculation);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauTypeMismatchInvarianceInError)
|
||||
LUAU_FASTFLAG(LuauNewLibraryTypeNames)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
@ -1730,16 +1729,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'h' to table 'typeof(os)'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'k' to table 'typeof(string)'", toString(result.errors[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("Cannot add property 'h' to table 'os'", toString(result.errors[0]));
|
||||
CHECK_EQ("Cannot add property 'k' to table 'string'", toString(result.errors[1]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "persistent_sealed_table_is_immutable")
|
||||
@ -1750,10 +1741,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "persistent_sealed_table_is_immutable")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
if (FFlag::LuauNewLibraryTypeNames)
|
||||
CHECK_EQ("Cannot add property 'bad' to table 'typeof(os)'", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Cannot add property 'bad' to table 'os'", toString(result.errors[0]));
|
||||
|
||||
const TableType* osType = get<TableType>(requireType("os"));
|
||||
REQUIRE(osType != nullptr);
|
||||
@ -2967,6 +2955,8 @@ TEST_CASE_FIXTURE(Fixture, "inferred_properties_of_a_table_should_start_with_the
|
||||
// The real bug here was that we weren't always uncondionally typechecking a trailing return statement last.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_leak_free_table_props")
|
||||
{
|
||||
ScopedFastFlag luauScopelessModule{"LuauScopelessModule", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function a(state)
|
||||
print(state.blah)
|
||||
@ -2988,7 +2978,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_leak_free_table_props")
|
||||
|
||||
CHECK_EQ("<a>({+ blah: a +}) -> ()", toString(requireType("a")));
|
||||
CHECK_EQ("<a>({+ gwar: a +}) -> ()", toString(requireType("b")));
|
||||
CHECK_EQ("() -> <a, b>({+ blah: a, gwar: b +}) -> ()", toString(getMainModule()->getModuleScope()->returnType));
|
||||
CHECK_EQ("() -> <a, b>({+ blah: a, gwar: b +}) -> ()", toString(getMainModule()->returnType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "inferred_return_type_of_free_table")
|
||||
@ -3230,8 +3220,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap
|
||||
TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
if (!FFlag::LuauNewLibraryTypeNames)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s)
|
||||
@ -3280,8 +3268,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
|
||||
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauScalarShapeSubtyping", true};
|
||||
if (!FFlag::LuauNewLibraryTypeNames)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(s): string
|
||||
|
@ -648,10 +648,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
std::optional<TypeFun> t0 = getMainModule()->getModuleScope()->lookupType("t0");
|
||||
std::optional<TypeId> t0 = lookupType("t0");
|
||||
REQUIRE(t0);
|
||||
|
||||
CHECK_EQ("*error-type*", toString(t0->type));
|
||||
CHECK_EQ("*error-type*", toString(*t0));
|
||||
|
||||
auto it = std::find_if(result.errors.begin(), result.errors.end(), [](TypeError& err) {
|
||||
return get<OccursCheckFailed>(err);
|
||||
|
@ -428,8 +428,12 @@ type E = X<(number, ...string)>
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(*lookupType("D")), "(...number) -> (string, ...number)");
|
||||
CHECK_EQ(toString(*lookupType("E")), "(number, ...string) -> (string, number, ...string)");
|
||||
auto d = lookupType("D");
|
||||
REQUIRE(d);
|
||||
auto e = lookupType("E");
|
||||
REQUIRE(e);
|
||||
CHECK_EQ(toString(*d), "(...number) -> (string, ...number)");
|
||||
CHECK_EQ(toString(*e), "(number, ...string) -> (string, number, ...string)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi")
|
||||
@ -887,9 +891,13 @@ TEST_CASE_FIXTURE(Fixture, "unifying_vararg_pack_with_fixed_length_pack_produces
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
REQUIRE(bool(getMainModule()->getModuleScope()->varargPack));
|
||||
ModulePtr mainModule = getMainModule();
|
||||
REQUIRE(mainModule);
|
||||
REQUIRE(mainModule->hasModuleScope());
|
||||
|
||||
TypePackId varargPack = *getMainModule()->getModuleScope()->varargPack;
|
||||
REQUIRE(bool(mainModule->getModuleScope()->varargPack));
|
||||
|
||||
TypePackId varargPack = *mainModule->getModuleScope()->varargPack;
|
||||
|
||||
auto iter = begin(varargPack);
|
||||
auto endIter = end(varargPack);
|
||||
|
@ -397,8 +397,6 @@ local e = a.z
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "optional_iteration")
|
||||
{
|
||||
ScopedFastFlag luauNilIterator{"LuauNilIterator", true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(values: {number}?)
|
||||
local s = 0
|
||||
|
1249
tests/TypeReduction.test.cpp
Normal file
1249
tests/TypeReduction.test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include "Fixture.h"
|
||||
|
@ -321,6 +321,7 @@ TypeInfer.globals2
|
||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||
TypeInfer.it_is_ok_to_have_inconsistent_number_of_return_values_in_nonstrict
|
||||
TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.no_stack_overflow_from_isoptional2
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||
TypeInfer.tc_interpolated_string_basic
|
||||
@ -408,10 +409,7 @@ 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.compound_assign_metatable
|
||||
TypeInferOperators.compound_assign_mismatch_metatable
|
||||
TypeInferOperators.compound_assign_mismatch_op
|
||||
TypeInferOperators.compound_assign_mismatch_result
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
|
||||
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
|
||||
|
Loading…
Reference in New Issue
Block a user