2024-03-08 16:47:53 -08:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
2024-07-12 10:03:36 -07:00
|
|
|
#include "Luau/TypeFunctionReductionGuesser.h"
|
2024-03-08 16:47:53 -08:00
|
|
|
|
|
|
|
#include "Luau/DenseHash.h"
|
|
|
|
#include "Luau/Normalize.h"
|
2024-09-27 11:58:21 -07:00
|
|
|
#include "Luau/ToString.h"
|
2024-07-12 10:03:36 -07:00
|
|
|
#include "Luau/TypeFunction.h"
|
2024-03-08 16:47:53 -08:00
|
|
|
#include "Luau/Type.h"
|
|
|
|
#include "Luau/TypePack.h"
|
|
|
|
#include "Luau/TypeUtils.h"
|
|
|
|
#include "Luau/VecDeque.h"
|
|
|
|
#include "Luau/VisitType.h"
|
|
|
|
|
|
|
|
#include <iostream>
|
2024-03-30 16:14:44 -07:00
|
|
|
#include <optional>
|
2024-03-08 16:47:53 -08:00
|
|
|
#include <ostream>
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
struct InstanceCollector2 : TypeOnceVisitor
|
|
|
|
{
|
|
|
|
VecDeque<TypeId> tys;
|
|
|
|
VecDeque<TypePackId> tps;
|
|
|
|
DenseHashSet<TypeId> cyclicInstance{nullptr};
|
|
|
|
DenseHashSet<TypeId> instanceArguments{nullptr};
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
bool visit(TypeId ty, const TypeFunctionInstanceType& it) override
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
|
|
|
// cycles. This means that by pushing to the front of the queue, we will
|
|
|
|
// try to reduce deeper instances first if we start with the first thing
|
|
|
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
|
|
|
// we want to reduce the innermost Add<number, number> instantiation
|
|
|
|
// first.
|
|
|
|
tys.push_front(ty);
|
|
|
|
for (auto t : it.typeArguments)
|
|
|
|
instanceArguments.insert(follow(t));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void cycle(TypeId ty) override
|
|
|
|
{
|
|
|
|
/// Detected cyclic type pack
|
|
|
|
TypeId t = follow(ty);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(t))
|
2024-03-08 16:47:53 -08:00
|
|
|
cyclicInstance.insert(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
|
|
|
// cycles. This means that by pushing to the front of the queue, we will
|
|
|
|
// try to reduce deeper instances first if we start with the first thing
|
|
|
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
|
|
|
// we want to reduce the innermost Add<number, number> instantiation
|
|
|
|
// first.
|
|
|
|
tps.push_front(tp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
TypeFunctionReductionGuesser::TypeFunctionReductionGuesser(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer)
|
2024-03-30 16:14:44 -07:00
|
|
|
: arena(arena)
|
|
|
|
, builtins(builtins)
|
2024-03-08 16:47:53 -08:00
|
|
|
, normalizer(normalizer)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet<TypeId>& argsUsed)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
bool sameSize = ftv.generics.size() == argsUsed.size();
|
|
|
|
bool allGenericsAppear = true;
|
|
|
|
for (auto gt : ftv.generics)
|
|
|
|
allGenericsAppear = allGenericsAppear || argsUsed.contains(gt);
|
|
|
|
return sameSize && allGenericsAppear;
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
void TypeFunctionReductionGuesser::dumpGuesses()
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
for (auto [tf, t] : functionReducesTo)
|
2024-03-08 16:47:53 -08:00
|
|
|
printf("Type family %s ~~> %s\n", toString(tf).c_str(), toString(t).c_str());
|
|
|
|
for (auto [t, t_] : substitutable)
|
|
|
|
printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str());
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
std::optional<TypeId> TypeFunctionReductionGuesser::guess(TypeId typ)
|
2024-03-30 16:14:44 -07:00
|
|
|
{
|
|
|
|
std::optional<TypeId> guessedType = guessType(typ);
|
|
|
|
|
|
|
|
if (!guessedType.has_value())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
TypeId guess = follow(*guessedType);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(guess))
|
2024-03-30 16:14:44 -07:00
|
|
|
return {};
|
|
|
|
|
|
|
|
return guess;
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
std::optional<TypePackId> TypeFunctionReductionGuesser::guess(TypePackId tp)
|
2024-03-30 16:14:44 -07:00
|
|
|
{
|
|
|
|
auto [head, tail] = flatten(tp);
|
|
|
|
|
|
|
|
std::vector<TypeId> guessedHead;
|
|
|
|
guessedHead.reserve(head.size());
|
|
|
|
|
|
|
|
for (auto typ : head)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> guessedType = guessType(typ);
|
|
|
|
|
|
|
|
if (!guessedType.has_value())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
TypeId guess = follow(*guessedType);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(guess))
|
2024-03-30 16:14:44 -07:00
|
|
|
return {};
|
|
|
|
|
|
|
|
guessedHead.push_back(*guessedType);
|
|
|
|
}
|
|
|
|
|
|
|
|
return arena->addTypePack(TypePack{guessedHead, tail});
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunctionReductionForFunctionExpr(
|
2024-08-02 07:30:04 -07:00
|
|
|
const AstExprFunction& expr,
|
|
|
|
const FunctionType* ftv,
|
|
|
|
TypeId retTy
|
|
|
|
)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
InstanceCollector2 collector;
|
|
|
|
collector.traverse(retTy);
|
|
|
|
toInfer = std::move(collector.tys);
|
|
|
|
cyclicInstances = std::move(collector.cyclicInstance);
|
|
|
|
|
|
|
|
if (isFunctionGenericsSaturated(*ftv, collector.instanceArguments))
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionReductionGuessResult{{}, nullptr, false};
|
2024-03-08 16:47:53 -08:00
|
|
|
infer();
|
|
|
|
|
|
|
|
std::vector<std::pair<std::string, TypeId>> results;
|
|
|
|
std::vector<TypeId> args;
|
|
|
|
for (TypeId t : ftv->argTypes)
|
|
|
|
args.push_back(t);
|
|
|
|
|
|
|
|
// Submit a guess for arg types
|
|
|
|
for (size_t i = 0; i < expr.args.size; i++)
|
|
|
|
{
|
|
|
|
TypeId argTy;
|
|
|
|
AstLocal* local = expr.args.data[i];
|
|
|
|
if (i >= args.size())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
argTy = args[i];
|
|
|
|
std::optional<TypeId> guessedType = guessType(argTy);
|
|
|
|
if (!guessedType.has_value())
|
|
|
|
continue;
|
|
|
|
TypeId guess = follow(*guessedType);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(guess))
|
2024-03-08 16:47:53 -08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
results.push_back({local->name.value, guess});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Submit a guess for return types
|
|
|
|
TypeId recommendedAnnotation;
|
|
|
|
std::optional<TypeId> guessedReturnType = guessType(retTy);
|
|
|
|
if (!guessedReturnType.has_value())
|
|
|
|
recommendedAnnotation = builtins->unknownType;
|
|
|
|
else
|
|
|
|
recommendedAnnotation = follow(*guessedReturnType);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (auto t = get<TypeFunctionInstanceType>(recommendedAnnotation))
|
2024-03-08 16:47:53 -08:00
|
|
|
recommendedAnnotation = builtins->unknownType;
|
|
|
|
|
|
|
|
toInfer.clear();
|
|
|
|
cyclicInstances.clear();
|
2024-07-19 11:20:47 -07:00
|
|
|
functionReducesTo.clear();
|
2024-03-08 16:47:53 -08:00
|
|
|
substitutable.clear();
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionReductionGuessResult{results, recommendedAnnotation};
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
std::optional<TypeId> TypeFunctionReductionGuesser::guessType(TypeId arg)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
TypeId t = follow(arg);
|
|
|
|
if (substitutable.contains(t))
|
|
|
|
{
|
|
|
|
TypeId subst = follow(substitutable[t]);
|
|
|
|
if (subst == t || substitutable.contains(subst))
|
|
|
|
return subst;
|
2024-07-12 10:03:36 -07:00
|
|
|
else if (!get<TypeFunctionInstanceType>(subst))
|
2024-03-08 16:47:53 -08:00
|
|
|
return subst;
|
|
|
|
else
|
|
|
|
return guessType(subst);
|
|
|
|
}
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(t))
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
if (functionReducesTo.contains(t))
|
|
|
|
return functionReducesTo[t];
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isNumericBinopFunction(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-08-02 07:30:04 -07:00
|
|
|
return instance.function->name == "add" || instance.function->name == "sub" || instance.function->name == "mul" ||
|
|
|
|
instance.function->name == "div" || instance.function->name == "idiv" || instance.function->name == "pow" ||
|
|
|
|
instance.function->name == "mod";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isComparisonFunction(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
return instance.function->name == "lt" || instance.function->name == "le" || instance.function->name == "eq";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isOrAndFunction(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
return instance.function->name == "or" || instance.function->name == "and";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isNotFunction(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
return instance.function->name == "not";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isLenFunction(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
return instance.function->name == "len";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
bool TypeFunctionReductionGuesser::isUnaryMinus(const TypeFunctionInstanceType& instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
return instance.function->name == "unm";
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
// Operand is assignable if it looks like a cyclic function instance, or a generic type
|
2024-07-12 10:03:36 -07:00
|
|
|
bool TypeFunctionReductionGuesser::operandIsAssignable(TypeId ty)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-12 10:03:36 -07:00
|
|
|
if (get<TypeFunctionInstanceType>(ty))
|
2024-03-08 16:47:53 -08:00
|
|
|
return true;
|
|
|
|
if (get<GenericType>(ty))
|
|
|
|
return true;
|
|
|
|
if (cyclicInstances.contains(ty))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
std::shared_ptr<const NormalizedType> TypeFunctionReductionGuesser::normalize(TypeId ty)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
return normalizer->normalize(ty);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
std::optional<TypeId> TypeFunctionReductionGuesser::tryAssignOperandType(TypeId ty)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-12 10:03:36 -07:00
|
|
|
// Because we collect innermost instances first, if we see a type function instance as an operand,
|
2024-03-08 16:47:53 -08:00
|
|
|
// We try to check if we guessed a type for it
|
2024-07-12 10:03:36 -07:00
|
|
|
if (auto tfit = get<TypeFunctionInstanceType>(ty))
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
if (functionReducesTo.contains(ty))
|
|
|
|
return {functionReducesTo[ty]};
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// If ty is a generic, we need to check if we inferred a substitution
|
|
|
|
if (auto gt = get<GenericType>(ty))
|
|
|
|
{
|
|
|
|
if (substitutable.contains(ty))
|
|
|
|
return {substitutable[ty]};
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we cannot substitute a type for this value, we return an empty optional
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
void TypeFunctionReductionGuesser::step()
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
TypeId t = toInfer.front();
|
|
|
|
toInfer.pop_front();
|
|
|
|
t = follow(t);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (auto tf = get<TypeFunctionInstanceType>(t))
|
|
|
|
inferTypeFunctionSubstitutions(t, tf);
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
void TypeFunctionReductionGuesser::infer()
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
while (!done())
|
|
|
|
step();
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
bool TypeFunctionReductionGuesser::done()
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
return toInfer.empty();
|
|
|
|
}
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
|
2024-07-12 10:03:36 -07:00
|
|
|
TypeFunctionInferenceResult result;
|
2024-03-08 16:47:53 -08:00
|
|
|
LUAU_ASSERT(instance);
|
|
|
|
// TODO: Make an inexhaustive version of this warn in the compiler?
|
2024-07-19 11:20:47 -07:00
|
|
|
if (isNumericBinopFunction(*instance))
|
|
|
|
result = inferNumericBinopFunction(instance);
|
|
|
|
else if (isComparisonFunction(*instance))
|
|
|
|
result = inferComparisonFunction(instance);
|
|
|
|
else if (isOrAndFunction(*instance))
|
|
|
|
result = inferOrAndFunction(instance);
|
|
|
|
else if (isNotFunction(*instance))
|
|
|
|
result = inferNotFunction(instance);
|
|
|
|
else if (isLenFunction(*instance))
|
|
|
|
result = inferLenFunction(instance);
|
2024-03-08 16:47:53 -08:00
|
|
|
else if (isUnaryMinus(*instance))
|
2024-07-19 11:20:47 -07:00
|
|
|
result = inferUnaryMinusFunction(instance);
|
2024-03-08 16:47:53 -08:00
|
|
|
else
|
|
|
|
result = {{}, builtins->unknownType};
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeId resultInference = follow(result.functionResultInference);
|
|
|
|
if (!functionReducesTo.contains(resultInference))
|
|
|
|
functionReducesTo[ty] = resultInference;
|
2024-03-08 16:47:53 -08:00
|
|
|
|
|
|
|
for (size_t i = 0; i < instance->typeArguments.size(); i++)
|
|
|
|
{
|
|
|
|
if (i < result.operandInference.size())
|
|
|
|
{
|
|
|
|
TypeId arg = follow(instance->typeArguments[i]);
|
|
|
|
TypeId inference = follow(result.operandInference[i]);
|
2024-07-12 10:03:36 -07:00
|
|
|
if (auto tfit = get<TypeFunctionInstanceType>(arg))
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
2024-07-19 11:20:47 -07:00
|
|
|
if (!functionReducesTo.contains(arg))
|
|
|
|
functionReducesTo.try_insert(arg, inference);
|
2024-03-08 16:47:53 -08:00
|
|
|
}
|
|
|
|
else if (auto gt = get<GenericType>(arg))
|
|
|
|
substitutable[arg] = inference;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNumericBinopFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 2);
|
2024-07-12 10:03:36 -07:00
|
|
|
TypeFunctionInferenceResult defaultNumericBinopInference{{builtins->numberType, builtins->numberType}, builtins->numberType};
|
2024-03-08 16:47:53 -08:00
|
|
|
return defaultNumericBinopInference;
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 2);
|
2024-07-19 11:20:47 -07:00
|
|
|
// Comparison functions are lt/le/eq.
|
2024-03-08 16:47:53 -08:00
|
|
|
// Heuristic: these are type functions from t -> t -> bool
|
|
|
|
|
|
|
|
TypeId lhsTy = follow(instance->typeArguments[0]);
|
|
|
|
TypeId rhsTy = follow(instance->typeArguments[1]);
|
|
|
|
|
2024-08-02 07:30:04 -07:00
|
|
|
auto comparisonInference = [&](TypeId op) -> TypeFunctionInferenceResult
|
|
|
|
{
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionInferenceResult{{op, op}, builtins->booleanType};
|
2024-03-08 16:47:53 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(lhsTy))
|
|
|
|
lhsTy = follow(*ty);
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(rhsTy))
|
|
|
|
rhsTy = follow(*ty);
|
|
|
|
if (operandIsAssignable(lhsTy) && !operandIsAssignable(rhsTy))
|
|
|
|
return comparisonInference(rhsTy);
|
|
|
|
if (operandIsAssignable(rhsTy) && !operandIsAssignable(lhsTy))
|
|
|
|
return comparisonInference(lhsTy);
|
|
|
|
return comparisonInference(builtins->numberType);
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 2);
|
|
|
|
|
|
|
|
TypeId lhsTy = follow(instance->typeArguments[0]);
|
|
|
|
TypeId rhsTy = follow(instance->typeArguments[1]);
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(lhsTy))
|
|
|
|
lhsTy = follow(*ty);
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(rhsTy))
|
|
|
|
rhsTy = follow(*ty);
|
2024-07-12 10:03:36 -07:00
|
|
|
TypeFunctionInferenceResult defaultAndOrInference{{builtins->unknownType, builtins->unknownType}, builtins->booleanType};
|
2024-03-08 16:47:53 -08:00
|
|
|
|
2024-04-12 10:18:49 -07:00
|
|
|
std::shared_ptr<const NormalizedType> lty = normalize(lhsTy);
|
|
|
|
std::shared_ptr<const NormalizedType> rty = normalize(lhsTy);
|
2024-03-08 16:47:53 -08:00
|
|
|
bool lhsTruthy = lty ? lty->isTruthy() : false;
|
|
|
|
bool rhsTruthy = rty ? rty->isTruthy() : false;
|
|
|
|
// If at the end, we still don't have good substitutions, return the default type
|
2024-07-19 11:20:47 -07:00
|
|
|
if (instance->function->name == "or")
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy))
|
|
|
|
return defaultAndOrInference;
|
|
|
|
if (operandIsAssignable(lhsTy))
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionInferenceResult{{builtins->unknownType, rhsTy}, rhsTy};
|
2024-03-08 16:47:53 -08:00
|
|
|
if (operandIsAssignable(rhsTy))
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionInferenceResult{{lhsTy, builtins->unknownType}, lhsTy};
|
2024-03-08 16:47:53 -08:00
|
|
|
if (lhsTruthy)
|
|
|
|
return {{lhsTy, rhsTy}, lhsTy};
|
|
|
|
if (rhsTruthy)
|
|
|
|
return {{builtins->unknownType, rhsTy}, rhsTy};
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
if (instance->function->name == "and")
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
|
|
|
|
if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy))
|
|
|
|
return defaultAndOrInference;
|
|
|
|
if (operandIsAssignable(lhsTy))
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionInferenceResult{{}, rhsTy};
|
2024-03-08 16:47:53 -08:00
|
|
|
if (operandIsAssignable(rhsTy))
|
2024-07-12 10:03:36 -07:00
|
|
|
return TypeFunctionInferenceResult{{}, lhsTy};
|
2024-03-08 16:47:53 -08:00
|
|
|
if (lhsTruthy)
|
|
|
|
return {{lhsTy, rhsTy}, rhsTy};
|
|
|
|
else
|
|
|
|
return {{lhsTy, rhsTy}, lhsTy};
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultAndOrInference;
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNotFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 1);
|
|
|
|
TypeId opTy = follow(instance->typeArguments[0]);
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(opTy))
|
|
|
|
opTy = follow(*ty);
|
|
|
|
return {{opTy}, builtins->booleanType};
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferLenFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 1);
|
|
|
|
TypeId opTy = follow(instance->typeArguments[0]);
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(opTy))
|
|
|
|
opTy = follow(*ty);
|
|
|
|
return {{opTy}, builtins->numberType};
|
|
|
|
}
|
|
|
|
|
2024-07-19 11:20:47 -07:00
|
|
|
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferUnaryMinusFunction(const TypeFunctionInstanceType* instance)
|
2024-03-08 16:47:53 -08:00
|
|
|
{
|
|
|
|
LUAU_ASSERT(instance->typeArguments.size() == 1);
|
|
|
|
TypeId opTy = follow(instance->typeArguments[0]);
|
|
|
|
if (std::optional<TypeId> ty = tryAssignOperandType(opTy))
|
|
|
|
opTy = follow(*ty);
|
|
|
|
if (isNumber(opTy))
|
|
|
|
return {{builtins->numberType}, builtins->numberType};
|
|
|
|
return {{builtins->unknownType}, builtins->numberType};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Luau
|