luau/Analysis/src/TypeInfer.cpp

6092 lines
216 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeInfer.h"
2022-08-04 18:35:33 -04:00
#include "Luau/ApplyTypeFunction.h"
2022-04-21 17:44:27 -04:00
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Instantiation.h"
#include "Luau/ModuleResolver.h"
2022-04-14 19:57:43 -04:00
#include "Luau/Normalize.h"
#include "Luau/Parser.h"
#include "Luau/Quantify.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/Substitution.h"
#include "Luau/TimeTrace.h"
#include "Luau/TopoSortStatements.h"
2022-04-14 19:57:43 -04:00
#include "Luau/ToString.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
#include "Luau/TypeReduction.h"
#include "Luau/TypeUtils.h"
#include "Luau/VisitType.h"
#include <algorithm>
2022-01-14 11:20:09 -05:00
#include <iterator>
LUAU_FASTFLAGVARIABLE(DebugLuauMagicTypes, false)
2022-04-28 21:24:24 -04:00
LUAU_FASTINTVARIABLE(LuauTypeInferRecursionLimit, 165)
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 20000)
LUAU_FASTINTVARIABLE(LuauTypeInferTypePackLoopLimit, 5000)
2022-04-28 21:24:24 -04:00
LUAU_FASTINTVARIABLE(LuauCheckRecursionLimit, 300)
2022-05-05 20:03:43 -04:00
LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500)
LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
2022-02-11 14:02:09 -05:00
LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false.
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAGVARIABLE(LuauTryhardAnd, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauNegatedClassTypes)
LUAU_FASTFLAGVARIABLE(LuauAllowIndexClassParameters, false)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauReducingAndOr, false)
namespace Luau
{
2022-04-07 17:29:01 -04:00
static bool typeCouldHaveMetatable(TypeId ty)
{
return get<TableType>(follow(ty)) || get<ClassType>(follow(ty)) || get<MetatableType>(follow(ty));
}
static void defaultLuauPrintLine(const std::string& s)
{
printf("%s\n", s.c_str());
}
PrintLineProc luauPrintLine = &defaultLuauPrintLine;
void setPrintLine(PrintLineProc pl)
{
luauPrintLine = pl;
}
void resetPrintLine()
{
luauPrintLine = &defaultLuauPrintLine;
}
bool doesCallError(const AstExprCall* call)
{
const AstExprGlobal* global = call->func->as<AstExprGlobal>();
if (!global)
return false;
if (global->name == "error")
return true;
else if (global->name == "assert")
{
// assert() will error because it is missing the first argument
if (call->args.size == 0)
return true;
if (AstExprConstantBool* expr = call->args.data[0]->as<AstExprConstantBool>())
if (!expr->value)
return true;
}
return false;
}
bool hasBreak(AstStat* node)
{
if (AstStatBlock* stat = node->as<AstStatBlock>())
{
for (size_t i = 0; i < stat->body.size; ++i)
{
if (hasBreak(stat->body.data[i]))
return true;
}
return false;
}
else if (node->is<AstStatBreak>())
{
return true;
}
else if (AstStatIf* stat = node->as<AstStatIf>())
{
if (hasBreak(stat->thenbody))
return true;
if (stat->elsebody && hasBreak(stat->elsebody))
return true;
return false;
}
else
{
return false;
}
}
// returns the last statement before the block exits, or nullptr if the block never exits
const AstStat* getFallthrough(const AstStat* node)
{
if (const AstStatBlock* stat = node->as<AstStatBlock>())
{
if (stat->body.size == 0)
return stat;
for (size_t i = 0; i < stat->body.size - 1; ++i)
{
if (getFallthrough(stat->body.data[i]) == nullptr)
return nullptr;
}
return getFallthrough(stat->body.data[stat->body.size - 1]);
}
else if (const AstStatIf* stat = node->as<AstStatIf>())
{
if (const AstStat* thenf = getFallthrough(stat->thenbody))
return thenf;
if (stat->elsebody)
{
if (const AstStat* elsef = getFallthrough(stat->elsebody))
return elsef;
return nullptr;
}
else
{
return stat;
}
}
else if (node->is<AstStatReturn>())
{
return nullptr;
}
else if (const AstStatExpr* stat = node->as<AstStatExpr>())
{
if (AstExprCall* call = stat->expr->as<AstExprCall>())
{
if (doesCallError(call))
return nullptr;
}
return stat;
}
else if (const AstStatWhile* stat = node->as<AstStatWhile>())
{
if (AstExprConstantBool* expr = stat->condition->as<AstExprConstantBool>())
{
if (expr->value && !hasBreak(stat->body))
return nullptr;
}
return node;
}
else if (const AstStatRepeat* stat = node->as<AstStatRepeat>())
{
if (AstExprConstantBool* expr = stat->condition->as<AstExprConstantBool>())
{
if (!expr->value && !hasBreak(stat->body))
return nullptr;
}
if (getFallthrough(stat->body) == nullptr)
return nullptr;
return node;
}
else
{
return node;
}
}
static bool isMetamethod(const Name& name)
{
return name == "__index" || name == "__newindex" || name == "__call" || name == "__concat" || name == "__unm" || name == "__add" ||
name == "__sub" || name == "__mul" || name == "__div" || name == "__mod" || name == "__pow" || name == "__tostring" ||
2022-06-30 19:52:43 -04:00
name == "__metatable" || name == "__eq" || name == "__lt" || name == "__le" || name == "__mode" || name == "__iter" || name == "__len";
}
2022-02-11 14:02:09 -05:00
size_t HashBoolNamePair::operator()(const std::pair<bool, Name>& pair) const
{
return std::hash<bool>()(pair.first) ^ std::hash<Name>()(pair.second);
}
GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes)
{
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalScope->addBuiltinTypeBinding("any", TypeFun{{}, builtinTypes->anyType});
globalScope->addBuiltinTypeBinding("nil", TypeFun{{}, builtinTypes->nilType});
globalScope->addBuiltinTypeBinding("number", TypeFun{{}, builtinTypes->numberType});
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType});
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType});
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType});
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});
}
TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver, NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter* iceHandler)
: globalScope(globalScope)
, resolver(resolver)
, builtinTypes(builtinTypes)
, iceHandler(iceHandler)
, unifierState(iceHandler)
, normalizer(nullptr, builtinTypes, NotNull{&unifierState})
, nilType(builtinTypes->nilType)
, numberType(builtinTypes->numberType)
, stringType(builtinTypes->stringType)
, booleanType(builtinTypes->booleanType)
, threadType(builtinTypes->threadType)
, anyType(builtinTypes->anyType)
, unknownType(builtinTypes->unknownType)
, neverType(builtinTypes->neverType)
, anyTypePack(builtinTypes->anyTypePack)
, neverTypePack(builtinTypes->neverTypePack)
, uninhabitableTypePack(builtinTypes->uninhabitableTypePack)
2022-02-11 14:02:09 -05:00
, duplicateTypeAliases{{false, {}}}
{
}
ModulePtr TypeChecker::check(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
2022-04-14 19:57:43 -04:00
{
2022-06-23 21:56:00 -04:00
try
2022-04-14 19:57:43 -04:00
{
2022-06-23 21:56:00 -04:00
return checkWithoutRecursionCheck(module, mode, environmentScope);
2022-04-14 19:57:43 -04:00
}
2022-06-23 21:56:00 -04:00
catch (const RecursionLimitException&)
2022-04-14 19:57:43 -04:00
{
2022-06-23 21:56:00 -04:00
reportErrorCodeTooComplex(module.root->location);
return std::move(currentModule);
2022-04-14 19:57:43 -04:00
}
}
ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mode mode, std::optional<ScopePtr> environmentScope)
{
LUAU_TIMETRACE_SCOPE("TypeChecker::check", "TypeChecker");
LUAU_TIMETRACE_ARGUMENT("module", module.name.c_str());
currentModule.reset(new Module);
currentModule->reduction = std::make_unique<TypeReduction>(NotNull{&currentModule->internalTypes}, builtinTypes, NotNull{iceHandler});
currentModule->type = module.type;
currentModule->allocator = module.allocator;
currentModule->names = module.names;
2022-04-07 17:29:01 -04:00
iceHandler->moduleName = module.name;
normalizer.arena = &currentModule->internalTypes;
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = unifierIterationLimit ? *unifierIterationLimit : FInt::LuauTypeInferIterationLimit;
2022-04-14 19:57:43 -04:00
ScopePtr parentScope = environmentScope.value_or(globalScope);
ScopePtr moduleScope = std::make_shared<Scope>(parentScope);
if (module.cyclic)
moduleScope->returnType = addTypePack(TypePack{{anyType}, std::nullopt});
else
moduleScope->returnType = freshTypePack(moduleScope);
moduleScope->varargPack = anyTypePack;
currentModule->scopes.push_back(std::make_pair(module.root->location, moduleScope));
currentModule->mode = mode;
currentModuleName = module.name;
if (prepareModuleScope)
prepareModuleScope(module.name, currentModule->getModuleScope());
try
2022-04-07 17:29:01 -04:00
{
checkBlock(moduleScope, *module.root);
2022-04-07 17:29:01 -04:00
}
catch (const TimeLimitError&)
2022-04-07 17:29:01 -04:00
{
currentModule->timeout = true;
2022-04-07 17:29:01 -04:00
}
2022-06-30 19:52:43 -04:00
if (FFlag::DebugLuauSharedSelf)
{
for (auto& [ty, scope] : deferredQuantification)
Luau::quantify(ty, scope->level);
deferredQuantification.clear();
}
if (get<FreeTypePack>(follow(moduleScope->returnType)))
moduleScope->returnType = addTypePack(TypePack{{}, std::nullopt});
else
moduleScope->returnType = anyify(moduleScope, moduleScope->returnType, Location{});
moduleScope->returnType = anyifyModuleReturnTypePackGenerics(moduleScope->returnType);
for (auto& [_, typeFun] : moduleScope->exportedTypeBindings)
typeFun.type = anyify(moduleScope, typeFun.type, Location{});
prepareErrorsForDisplay(currentModule->errors);
// Clear the normalizer caches, since they contain types from the internal type surface
normalizer.clearCaches();
normalizer.arena = nullptr;
currentModule->clonePublicInterface(builtinTypes, *iceHandler);
Sync to upstream/release/562 (#828) * Fixed rare use-after-free in analysis during table unification A lot of work these past months went into two new Luau components: * A near full rewrite of the typechecker using a new deferred constraint resolution system * Native code generation for AoT/JiT compilation of VM bytecode into x64 (avx)/arm64 instructions Both of these components are far from finished and we don't provide documentation on building and using them at this point. However, curious community members expressed interest in learning about changes that go into these components each week, so we are now listing them here in the 'sync' pull request descriptions. --- New typechecker can be enabled by setting DebugLuauDeferredConstraintResolution flag to 'true'. It is considered unstable right now, so try it at your own risk. Even though it already provides better type inference than the current one in some cases, our main goal right now is to reach feature parity with current typechecker. Features which improve over the capabilities of the current typechecker are marked as '(NEW)'. Changes to new typechecker: * Regular for loop index and parameters are now typechecked * Invalid type annotations on local variables are ignored to improve autocomplete * Fixed missing autocomplete type suggestions for function arguments * Type reduction is now performed to produce simpler types to be presented to the user (error messages, custom LSPs) * Internally, complex types like '((number | string) & ~(false?)) | string' can be produced, which is just 'string | number' when simplified * Fixed spots where support for unknown and never types was missing * (NEW) Length operator '#' is now valid to use on top table type, this type comes up when doing typeof(x) == "table" guards and isn't available in current typechecker --- Changes to native code generation: * Additional math library fast calls are now lowered to x64: math.ldexp, math.round, math.frexp, math.modf, math.sign and math.clamp
2023-02-03 14:26:13 -05:00
freeze(currentModule->internalTypes);
freeze(currentModule->interfaceTypes);
2022-02-17 20:18:01 -05:00
// Clear unifier cache since it's keyed off internal types that get deallocated
// This avoids fake cross-module cache hits and keeps cache size at bay when typechecking large module graphs.
unifierState.cachedUnify.clear();
2022-03-24 18:04:14 -04:00
unifierState.cachedUnifyError.clear();
2022-02-17 20:18:01 -05:00
unifierState.skipCacheForType.clear();
2022-01-14 11:20:09 -05:00
duplicateTypeAliases.clear();
incorrectClassDefinitions.clear();
2022-02-11 14:02:09 -05:00
return std::move(currentModule);
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program)
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (finishTime && TimeTrace::getClock() > *finishTime)
throw TimeLimitError(iceHandler->moduleName);
if (auto block = program.as<AstStatBlock>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *block);
else if (auto if_ = program.as<AstStatIf>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *if_);
else if (auto while_ = program.as<AstStatWhile>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *while_);
else if (auto repeat = program.as<AstStatRepeat>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *repeat);
else if (program.is<AstStatBreak>() || program.is<AstStatContinue>())
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
// Nothing to do
return ControlFlow::None;
}
else if (auto return_ = program.as<AstStatReturn>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *return_);
else if (auto expr = program.as<AstStatExpr>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
{
checkExprPack(scope, *expr->expr);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauTinyControlFlowAnalysis)
{
if (auto call = expr->expr->as<AstExprCall>(); call && doesCallError(call))
return ControlFlow::Throws;
}
return ControlFlow::None;
}
else if (auto local = program.as<AstStatLocal>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *local);
else if (auto for_ = program.as<AstStatFor>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *for_);
else if (auto forIn = program.as<AstStatForIn>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *forIn);
else if (auto assign = program.as<AstStatAssign>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *assign);
else if (auto assign = program.as<AstStatCompoundAssign>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *assign);
else if (program.is<AstStatFunction>())
ice("Should not be calling two-argument check() on a function statement", program.location);
else if (program.is<AstStatLocalFunction>())
ice("Should not be calling two-argument check() on a function statement", program.location);
else if (auto typealias = program.as<AstStatTypeAlias>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *typealias);
else if (auto global = program.as<AstStatDeclareGlobal>())
{
TypeId globalType = resolveType(scope, *global->type);
Name globalName(global->name.value);
currentModule->declaredGlobals[globalName] = globalType;
currentModule->getModuleScope()->bindings[global->name] = Binding{globalType, global->location};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
else if (auto global = program.as<AstStatDeclareFunction>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *global);
else if (auto global = program.as<AstStatDeclareClass>())
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return check(scope, *global);
else if (auto errorStatement = program.as<AstStatError>())
{
const size_t oldSize = currentModule->errors.size();
for (AstStat* s : errorStatement->statements)
check(scope, *s);
for (AstExpr* expr : errorStatement->expressions)
checkExpr(scope, *expr);
// HACK: We want to run typechecking on the contents of the AstStatError, but
// we don't think the type errors will be useful most of the time.
currentModule->errors.resize(oldSize);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
else
ice("Unknown AstStat");
}
// This particular overload is for do...end. If you need to not increase the scope level, use checkBlock directly.
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatBlock& block)
{
ScopePtr child = childScope(scope, block.location);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow flow = checkBlock(child, block);
scope->inheritRefinements(child);
return flow;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::checkBlock(const ScopePtr& scope, const AstStatBlock& block)
{
RecursionCounter _rc(&checkRecursionCount);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
{
reportErrorCodeTooComplex(block.location);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
2022-06-23 21:56:00 -04:00
try
2022-04-14 19:57:43 -04:00
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return checkBlockWithoutRecursionCheck(scope, block);
2022-04-14 19:57:43 -04:00
}
2022-06-23 21:56:00 -04:00
catch (const RecursionLimitException&)
2022-04-14 19:57:43 -04:00
{
2022-06-23 21:56:00 -04:00
reportErrorCodeTooComplex(block.location);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
2022-04-14 19:57:43 -04:00
}
}
struct InplaceDemoter : TypeOnceVisitor
2022-07-07 21:22:39 -04:00
{
TypeLevel newLevel;
TypeArena* arena;
InplaceDemoter(TypeLevel level, TypeArena* arena)
: TypeOnceVisitor(/* skipBoundTypes= */ true)
, newLevel(level)
2022-07-07 21:22:39 -04:00
, arena(arena)
{
}
bool demote(TypeId ty)
{
if (auto level = getMutableLevel(ty))
{
if (level->subsumesStrict(newLevel))
{
*level = newLevel;
return true;
}
}
return false;
}
bool visit(TypeId ty) override
{
if (ty->owningArena != arena)
return false;
return demote(ty);
}
bool visit(TypePackId tp, const FreeTypePack& ftpRef) override
{
if (tp->owningArena != arena)
return false;
FreeTypePack* ftp = &const_cast<FreeTypePack&>(ftpRef);
if (ftp->level.subsumesStrict(newLevel))
{
ftp->level = newLevel;
return true;
}
return false;
}
};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& block)
2022-04-14 19:57:43 -04:00
{
int subLevel = 0;
std::vector<AstStat*> sorted(block.body.data, block.body.data + block.body.size);
toposort(sorted);
for (const auto& stat : sorted)
{
if (const auto& typealias = stat->as<AstStatTypeAlias>())
{
prototype(scope, *typealias, subLevel);
++subLevel;
}
else if (const auto& declaredClass = stat->as<AstStatDeclareClass>())
{
prototype(scope, *declaredClass);
}
}
auto protoIter = sorted.begin();
auto checkIter = sorted.begin();
std::unordered_map<AstStat*, std::pair<TypeId, ScopePtr>> functionDecls;
auto checkBody = [&](AstStat* stat) {
if (auto fun = stat->as<AstStatFunction>())
{
LUAU_ASSERT(functionDecls.count(stat));
auto [funTy, funScope] = functionDecls[stat];
check(scope, funTy, funScope, *fun);
}
else if (auto fun = stat->as<AstStatLocalFunction>())
{
LUAU_ASSERT(functionDecls.count(stat));
auto [funTy, funScope] = functionDecls[stat];
check(scope, funTy, funScope, *fun);
}
};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
std::optional<ControlFlow> firstFlow;
while (protoIter != sorted.end())
{
// protoIter walks forward
// If it contains a function call (function bodies don't count), walk checkIter forward until it catches up with protoIter
// For each element checkIter sees, check function bodies and unify the computed type with the prototype
// If it is a function definition, add its prototype to the environment
// If it is anything else, check it.
// A subtlety is caused by mutually recursive functions, e.g.
// ```
// function f(x) return g(x) end
// function g(x) return f(x) end
// ```
// These both call each other, so `f` will be ordered before `g`, so the call to `g`
// is typechecked before `g` has had its body checked. For this reason, there's three
// types for each function: before its body is checked, during checking its body,
// and after its body is checked.
//
// We currently treat the before-type and the during-type as the same,
// which can result in some oddness, as the before-type is usually a monotype,
// and the after-type is often a polytype. For example:
//
// ```
// function f(x) local x: number = g(37) return x end
// function g(x) return f(x) end
// ```
// The before-type of g is `(X)->Y...` but during type-checking of `f` we will
// unify that with `(number)->number`. The types end up being
// ```
// function f<a>(x:a):a local x: number = g(37) return x end
// function g(x:number):number return f(x) end
// ```
if (containsFunctionCallOrReturn(**protoIter))
{
while (checkIter != protoIter)
{
checkBody(*checkIter);
++checkIter;
}
// We do check the current element, so advance checkIter beyond it.
++checkIter;
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow flow = check(scope, **protoIter);
if (flow != ControlFlow::None && !firstFlow)
firstFlow = flow;
}
else if (auto fun = (*protoIter)->as<AstStatFunction>())
{
2022-06-30 19:52:43 -04:00
std::optional<TypeId> selfType;
2022-03-31 17:01:51 -04:00
std::optional<TypeId> expectedType;
2022-06-30 19:52:43 -04:00
if (FFlag::DebugLuauSharedSelf)
2022-03-31 17:01:51 -04:00
{
if (auto name = fun->name->as<AstExprIndexName>())
{
2022-06-30 19:52:43 -04:00
TypeId baseTy = checkExpr(scope, *name->expr).type;
tablify(baseTy);
if (!fun->func->self)
2022-07-07 21:22:39 -04:00
expectedType = getIndexTypeFromType(scope, baseTy, name->index.value, name->indexLocation, /* addErrors= */ false);
2022-06-30 19:52:43 -04:00
else if (auto ttv = getMutableTableType(baseTy))
{
if (!baseTy->persistent && ttv->state != TableState::Sealed && !ttv->selfTy)
{
ttv->selfTy = anyIfNonstrict(freshType(ttv->level));
deferredQuantification.push_back({baseTy, scope});
}
selfType = ttv->selfTy;
}
}
}
else
{
if (!fun->func->self)
{
if (auto name = fun->name->as<AstExprIndexName>())
{
TypeId exprTy = checkExpr(scope, *name->expr).type;
2022-07-07 21:22:39 -04:00
expectedType = getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false);
2022-06-30 19:52:43 -04:00
}
2022-03-31 17:01:51 -04:00
}
}
2022-06-30 19:52:43 -04:00
auto pair = checkFunctionSignature(scope, subLevel, *fun->func, fun->name->location, selfType, expectedType);
auto [funTy, funScope] = pair;
functionDecls[*protoIter] = pair;
++subLevel;
2022-04-14 19:57:43 -04:00
TypeId leftType = follow(checkFunctionName(scope, *fun->name, funScope->level));
unify(funTy, leftType, scope, fun->location);
}
else if (auto fun = (*protoIter)->as<AstStatLocalFunction>())
{
2022-06-30 19:52:43 -04:00
auto pair = checkFunctionSignature(scope, subLevel, *fun->func, fun->name->location, std::nullopt, std::nullopt);
auto [funTy, funScope] = pair;
functionDecls[*protoIter] = pair;
++subLevel;
scope->bindings[fun->name] = {funTy, fun->name->location};
}
else
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
{
ControlFlow flow = check(scope, **protoIter);
if (flow != ControlFlow::None && !firstFlow)
firstFlow = flow;
}
++protoIter;
}
while (checkIter != sorted.end())
{
checkBody(*checkIter);
++checkIter;
}
checkBlockTypeAliases(scope, sorted);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return firstFlow.value_or(ControlFlow::None);
}
LUAU_NOINLINE void TypeChecker::checkBlockTypeAliases(const ScopePtr& scope, std::vector<AstStat*>& sorted)
{
for (const auto& stat : sorted)
{
if (const auto& typealias = stat->as<AstStatTypeAlias>())
{
if (typealias->name == kParseNameError)
2022-02-11 14:02:09 -05:00
continue;
auto& bindings = typealias->exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
Name name = typealias->name.value;
if (duplicateTypeAliases.contains({typealias->exported, name}))
continue;
TypeId type = follow(bindings[name].type);
if (get<FreeType>(type))
{
asMutable(type)->ty.emplace<BoundType>(errorRecoveryType(anyType));
reportError(TypeError{typealias->location, OccursCheckFailed{}});
}
}
}
}
static std::optional<Predicate> tryGetTypeGuardPredicate(const AstExprBinary& expr)
{
if (expr.op != AstExprBinary::Op::CompareEq && expr.op != AstExprBinary::Op::CompareNe)
return std::nullopt;
AstExpr* left = expr.left;
AstExpr* right = expr.right;
if (left->as<AstExprConstantString>())
std::swap(left, right);
AstExprConstantString* str = right->as<AstExprConstantString>();
if (!str)
return std::nullopt;
AstExprCall* call = left->as<AstExprCall>();
if (!call)
return std::nullopt;
AstExprGlobal* callee = call->func->as<AstExprGlobal>();
if (!callee)
return std::nullopt;
if (callee->name != "type" && callee->name != "typeof")
return std::nullopt;
if (call->args.size != 1)
return std::nullopt;
// If ssval is not a valid constant string, we'll find out later when resolving predicate.
Name ssval(str->value.data, str->value.size);
bool isTypeof = callee->name == "typeof";
std::optional<LValue> lvalue = tryGetLValue(*call->args.data[0]);
if (!lvalue)
return std::nullopt;
Predicate predicate{TypeGuardPredicate{std::move(*lvalue), expr.location, ssval, isTypeof}};
if (expr.op == AstExprBinary::Op::CompareNe)
return NotPredicate{{std::move(predicate)}};
return predicate;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatIf& statement)
{
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result = checkExpr(scope, *statement.condition);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ScopePtr thenScope = childScope(scope, statement.thenbody->location);
resolve(result.predicates, thenScope, true);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauTinyControlFlowAnalysis)
{
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ScopePtr elseScope = childScope(scope, statement.elsebody ? statement.elsebody->location : statement.location);
resolve(result.predicates, elseScope, false);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow thencf = check(thenScope, *statement.thenbody);
ControlFlow elsecf = ControlFlow::None;
if (statement.elsebody)
elsecf = check(elseScope, *statement.elsebody);
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && elsecf == ControlFlow::None)
scope->inheritRefinements(elseScope);
else if (thencf == ControlFlow::None && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
scope->inheritRefinements(thenScope);
if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
return ControlFlow::Returns;
else
return ControlFlow::None;
}
else
{
check(thenScope, *statement.thenbody);
if (statement.elsebody)
{
ScopePtr elseScope = childScope(scope, statement.elsebody->location);
resolve(result.predicates, elseScope, false);
check(elseScope, *statement.elsebody);
}
return ControlFlow::None;
}
}
template<typename Id>
ErrorVec TypeChecker::canUnify_(Id subTy, Id superTy, const ScopePtr& scope, const Location& location)
{
Unifier state = mkUnifier(scope, location);
return state.canUnify(subTy, superTy);
}
ErrorVec TypeChecker::canUnify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location)
{
return canUnify_(subTy, superTy, scope, location);
}
ErrorVec TypeChecker::canUnify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location)
{
return canUnify_(subTy, superTy, scope, location);
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatWhile& statement)
{
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result = checkExpr(scope, *statement.condition);
ScopePtr whileScope = childScope(scope, statement.body->location);
2022-05-19 20:02:24 -04:00
resolve(result.predicates, whileScope, true);
check(whileScope, *statement.body);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatRepeat& statement)
{
ScopePtr repScope = childScope(scope, statement.location);
checkBlock(repScope, *statement.body);
checkExpr(repScope, *statement.condition);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
2022-06-16 21:05:14 -04:00
struct Demoter : Substitution
{
Demoter(TypeArena* arena)
: Substitution(TxnLog::empty(), arena)
{
}
bool isDirty(TypeId ty) override
{
return get<FreeType>(ty);
2022-06-16 21:05:14 -04:00
}
bool isDirty(TypePackId tp) override
{
return get<FreeTypePack>(tp);
}
bool ignoreChildren(TypeId ty) override
{
if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
return true;
return false;
}
2022-06-16 21:05:14 -04:00
TypeId clean(TypeId ty) override
{
auto ftv = get<FreeType>(ty);
2022-06-16 21:05:14 -04:00
LUAU_ASSERT(ftv);
return addType(FreeType{demotedLevel(ftv->level)});
2022-06-16 21:05:14 -04:00
}
TypePackId clean(TypePackId tp) override
{
auto ftp = get<FreeTypePack>(tp);
LUAU_ASSERT(ftp);
return addTypePack(TypePackVar{FreeTypePack{demotedLevel(ftp->level)}});
}
TypeLevel demotedLevel(TypeLevel level)
{
return TypeLevel{level.level + 5000, level.subLevel};
}
void demote(std::vector<std::optional<TypeId>>& expectedTypes)
{
for (std::optional<TypeId>& ty : expectedTypes)
{
if (ty)
ty = substitute(*ty);
}
}
};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatReturn& return_)
{
std::vector<std::optional<TypeId>> expectedTypes;
expectedTypes.reserve(return_.list.size);
TypePackIterator expectedRetCurr = begin(scope->returnType);
TypePackIterator expectedRetEnd = end(scope->returnType);
for (size_t i = 0; i < return_.list.size; ++i)
{
if (expectedRetCurr != expectedRetEnd)
{
expectedTypes.push_back(*expectedRetCurr);
++expectedRetCurr;
}
else if (auto expectedArgsTail = expectedRetCurr.tail())
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*expectedArgsTail)))
expectedTypes.push_back(vtp->ty);
}
}
2022-06-16 21:05:14 -04:00
Demoter demoter{&currentModule->internalTypes};
demoter.demote(expectedTypes);
TypePackId retPack = checkExprList(scope, return_.location, return_.list, false, {}, expectedTypes).type;
// HACK: Nonstrict mode gets a bit too smart and strict for us when we
// start typechecking everything across module boundaries.
if (isNonstrictMode() && follow(scope->returnType) == follow(currentModule->getModuleScope()->returnType))
{
ErrorVec errors = tryUnify(retPack, scope->returnType, scope, return_.location);
if (!errors.empty())
currentModule->getModuleScope()->returnType = addTypePack({anyType});
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
}
unify(retPack, scope->returnType, scope, return_.location, CountMismatch::Context::Return);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return FFlag::LuauTinyControlFlowAnalysis ? ControlFlow::Returns : ControlFlow::None;
}
template<typename Id>
ErrorVec TypeChecker::tryUnify_(Id subTy, Id superTy, const ScopePtr& scope, const Location& location)
{
Unifier state = mkUnifier(scope, location);
2022-03-11 11:55:02 -05:00
if (FFlag::DebugLuauFreezeDuringUnification)
freeze(currentModule->internalTypes);
state.tryUnify(subTy, superTy);
2022-03-11 11:55:02 -05:00
if (FFlag::DebugLuauFreezeDuringUnification)
unfreeze(currentModule->internalTypes);
2022-03-11 11:55:02 -05:00
if (state.errors.empty())
state.log.commit();
return state.errors;
}
ErrorVec TypeChecker::tryUnify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location)
{
return tryUnify_(subTy, superTy, scope, location);
}
ErrorVec TypeChecker::tryUnify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location)
{
return tryUnify_(subTy, superTy, scope, location);
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatAssign& assign)
{
std::vector<std::optional<TypeId>> expectedTypes;
expectedTypes.reserve(assign.vars.size);
ScopePtr moduleScope = currentModule->getModuleScope();
for (size_t i = 0; i < assign.vars.size; ++i)
{
AstExpr* dest = assign.vars.data[i];
if (auto a = dest->as<AstExprLocal>())
{
// AstExprLocal l-values will have to be checked again because their type might have been mutated during checkExprList later
expectedTypes.push_back(scope->lookup(a->local));
}
else if (auto a = dest->as<AstExprGlobal>())
{
// AstExprGlobal l-values lookup is inlined here to avoid creating a global binding before checkExprList
if (auto it = moduleScope->bindings.find(a->name); it != moduleScope->bindings.end())
expectedTypes.push_back(it->second.typeId);
else
expectedTypes.push_back(std::nullopt);
}
else
{
expectedTypes.push_back(checkLValue(scope, *dest, ValueContext::LValue));
}
}
TypePackId valuePack = checkExprList(scope, assign.location, assign.values, false, {}, expectedTypes).type;
auto valueIter = begin(valuePack);
auto valueEnd = end(valuePack);
TypePack* growingPack = nullptr;
for (size_t i = 0; i < assign.vars.size; ++i)
{
AstExpr* dest = assign.vars.data[i];
TypeId left = nullptr;
if (dest->is<AstExprLocal>() || dest->is<AstExprGlobal>())
left = checkLValue(scope, *dest, ValueContext::LValue);
else
left = *expectedTypes[i];
TypeId right = nullptr;
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 15:21:14 -05:00
Location loc = 0 == assign.values.size ? assign.location
: i < assign.values.size ? assign.values.data[i]->location
: assign.values.data[assign.values.size - 1]->location;
if (valueIter != valueEnd)
{
right = follow(*valueIter);
++valueIter;
}
else if (growingPack)
{
growingPack->head.push_back(left);
continue;
}
else if (auto tail = valueIter.tail())
{
TypePackId tailPack = follow(*tail);
if (get<Unifiable::Error>(tailPack))
right = errorRecoveryType(scope);
else if (auto vtp = get<VariadicTypePack>(tailPack))
right = vtp->ty;
else if (get<Unifiable::Free>(tailPack))
{
*asMutable(tailPack) = TypePack{{left}};
growingPack = getMutable<TypePack>(tailPack);
}
}
if (right)
{
if (!FFlag::LuauInstantiateInSubtyping)
{
if (!maybeGeneric(left) && isGeneric(right))
right = instantiate(scope, right, loc);
}
// Setting a table entry to nil doesn't mean nil is the type of the indexer, it is just deleting the entry
const TableType* destTableTypeReceivingNil = nullptr;
if (auto indexExpr = dest->as<AstExprIndexExpr>(); isNil(right) && indexExpr)
destTableTypeReceivingNil = getTableType(checkExpr(scope, *indexExpr->expr).type);
if (!destTableTypeReceivingNil || !destTableTypeReceivingNil->indexer)
{
// In nonstrict mode, any assignments where the lhs is free and rhs isn't a function, we give it any type.
if (isNonstrictMode() && get<FreeType>(follow(left)) && !get<FunctionType>(follow(right)))
unify(anyType, left, scope, loc);
else
unify(right, left, scope, loc);
}
}
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatCompoundAssign& assign)
{
AstExprBinary expr(assign.location, assign.op, assign.var, assign.value);
TypeId left = checkExpr(scope, *expr.left).type;
TypeId right = checkExpr(scope, *expr.right).type;
TypeId result = checkBinaryOperation(scope, expr, left, right);
unify(result, left, scope, assign.location);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatLocal& local)
{
// Important subtlety: A local variable is not in scope while its initializer is being evaluated.
// For instance, you cannot do this:
// local a = function() return a end
AstLocal** vars = local.vars.data;
std::vector<std::pair<AstLocal*, Binding>> varBindings;
varBindings.reserve(local.vars.size);
std::vector<TypeId> variableTypes;
variableTypes.reserve(local.vars.size);
std::vector<std::optional<TypeId>> expectedTypes;
expectedTypes.reserve(local.vars.size);
std::vector<bool> instantiateGenerics;
for (size_t i = 0; i < local.vars.size; ++i)
{
const AstType* annotation = vars[i]->annotation;
const bool rhsIsTable = local.values.size > i && local.values.data[i]->as<AstExprTable>();
TypeId ty = nullptr;
if (annotation)
{
ty = resolveType(scope, *annotation);
// If the annotation type has an error, treat it as if there was no annotation
if (get<ErrorType>(follow(ty)))
ty = nullptr;
}
if (!ty)
ty = rhsIsTable ? freshType(scope) : isNonstrictMode() ? anyType : freshType(scope);
varBindings.emplace_back(vars[i], Binding{ty, vars[i]->location});
variableTypes.push_back(ty);
expectedTypes.push_back(ty);
// with FFlag::LuauInstantiateInSubtyping enabled, we shouldn't need to produce instantiateGenerics at all.
if (!FFlag::LuauInstantiateInSubtyping)
instantiateGenerics.push_back(annotation != nullptr && !maybeGeneric(ty));
}
if (local.values.size > 0)
{
TypePackId variablePack = addTypePack(variableTypes, freshTypePack(scope));
TypePackId valuePack =
checkExprList(scope, local.location, local.values, /* substituteFreeForNil= */ true, instantiateGenerics, expectedTypes).type;
// If the expression list only contains one expression and it's a function call or is otherwise within parentheses, use FunctionResult.
// Otherwise, we'll want to use ExprListResult to make the error messaging more general.
CountMismatch::Context ctx = CountMismatch::ExprListResult;
if (local.values.size == 1)
{
AstExpr* e = local.values.data[0];
while (auto group = e->as<AstExprGroup>())
e = group->expr;
if (e->is<AstExprCall>())
ctx = CountMismatch::FunctionResult;
}
Unifier state = mkUnifier(scope, local.location);
state.ctx = ctx;
state.tryUnify(valuePack, variablePack);
reportErrors(state.errors);
2022-03-11 11:55:02 -05:00
state.log.commit();
// In the code 'local T = {}', we wish to ascribe the name 'T' to the type of the table for error-reporting purposes.
// We also want to do this for 'local T = setmetatable(...)'.
if (local.vars.size == 1 && local.values.size == 1)
{
const AstExpr* rhs = local.values.data[0];
std::optional<TypeId> ty = first(valuePack);
if (ty)
{
if (rhs->is<AstExprTable>())
{
TableType* ttv = getMutable<TableType>(follow(*ty));
if (ttv && !ttv->name && scope == currentModule->getModuleScope())
ttv->syntheticName = vars[0]->name.value;
}
else if (const AstExprCall* call = rhs->as<AstExprCall>())
{
if (const AstExprGlobal* global = call->func->as<AstExprGlobal>(); global && global->name == "setmetatable")
{
MetatableType* mtv = getMutable<MetatableType>(follow(*ty));
if (mtv)
mtv->syntheticName = vars[0]->name.value;
}
}
}
}
// Handle 'require' calls, we need to import exported type bindings into the variable 'namespace' and to update binding type in non-strict
// mode
for (size_t i = 0; i < local.values.size && i < local.vars.size; ++i)
{
const AstExprCall* call = local.values.data[i]->as<AstExprCall>();
if (!call)
continue;
if (auto maybeRequire = matchRequire(*call))
{
AstExpr* require = *maybeRequire;
if (auto moduleInfo = resolver->resolveModuleInfo(currentModuleName, *require))
{
const Name name{local.vars.data[i]->name.value};
if (ModulePtr module = resolver->getModule(moduleInfo->name))
{
scope->importedTypeBindings[name] = module->exportedTypeBindings;
scope->importedModules[name] = moduleInfo->name;
}
// In non-strict mode we force the module type on the variable, in strict mode it is already unified
if (isNonstrictMode())
{
auto [types, tail] = flatten(valuePack);
if (i < types.size())
varBindings[i].second.typeId = types[i];
}
}
}
}
}
for (const auto& [local, binding] : varBindings)
scope->bindings[local] = binding;
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatFor& expr)
{
ScopePtr loopScope = childScope(scope, expr.location);
TypeId loopVarType = numberType;
if (expr.var->annotation)
unify(loopVarType, resolveType(scope, *expr.var->annotation), scope, expr.location);
loopScope->bindings[expr.var] = {loopVarType, expr.var->location};
if (!expr.from)
ice("Bad AstStatFor has no from expr");
if (!expr.to)
ice("Bad AstStatFor has no to expr");
unify(checkExpr(loopScope, *expr.from).type, loopVarType, scope, expr.from->location);
unify(checkExpr(loopScope, *expr.to).type, loopVarType, scope, expr.to->location);
if (expr.step)
unify(checkExpr(loopScope, *expr.step).type, loopVarType, scope, expr.step->location);
check(loopScope, *expr.body);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
{
ScopePtr loopScope = childScope(scope, forin.location);
AstLocal** vars = forin.vars.data;
std::vector<TypeId> varTypes;
varTypes.reserve(forin.vars.size);
for (size_t i = 0; i < forin.vars.size; ++i)
{
AstType* ann = vars[i]->annotation;
TypeId ty = ann ? resolveType(scope, *ann) : anyIfNonstrict(freshType(loopScope));
loopScope->bindings[vars[i]] = {ty, vars[i]->location};
varTypes.push_back(ty);
}
AstExpr** values = forin.values.data;
AstExpr* firstValue = forin.values.data[0];
// next is a function that takes Table<K, V> and an optional index of type K
// next<K, V>(t: Table<K, V>, index: K | nil) -> (K?, V)
// however, pairs and ipairs are quite messy, but they both share the same types
// pairs returns 'next, t, nil', thus the type would be
// pairs<K, V>(t: Table<K, V>) -> ((Table<K, V>, K | nil) -> (K?, V), Table<K, V>, K | nil)
// ipairs returns 'next, t, 0', thus ipairs will also share the same type as pairs, except K = number
//
// we can also define our own custom iterators by by returning a wrapped coroutine that calls coroutine.yield
// and most custom iterators does not return a table state, or returns a function that takes no additional arguments, making it optional
// so we up with this catch-all type constraint that works for all use cases
// <K, V, R>(free) -> ((free) -> R, Table<K, V> | nil, K | nil)
if (!firstValue)
ice("expected at least an iterator function value, but we parsed nothing");
TypeId iterTy = nullptr;
TypePackId callRetPack = nullptr;
if (forin.values.size == 1 && firstValue->is<AstExprCall>())
{
AstExprCall* exprCall = firstValue->as<AstExprCall>();
callRetPack = checkExprPack(scope, *exprCall).type;
callRetPack = follow(callRetPack);
if (get<Unifiable::Free>(callRetPack))
{
iterTy = freshType(scope);
unify(callRetPack, addTypePack({{iterTy}, freshTypePack(scope)}), scope, forin.location);
}
else if (get<Unifiable::Error>(callRetPack) || !first(callRetPack))
{
for (TypeId var : varTypes)
unify(errorRecoveryType(scope), var, scope, forin.location);
return check(loopScope, *forin.body);
}
else
{
iterTy = *first(callRetPack);
iterTy = instantiate(scope, iterTy, exprCall->location);
}
}
else
{
2022-05-19 20:02:24 -04:00
iterTy = instantiate(scope, checkExpr(scope, *firstValue).type, firstValue->location);
}
iterTy = stripFromNilAndReport(iterTy, firstValue->location);
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> iterMM = findMetatableEntry(iterTy, "__iter", firstValue->location, /* addErrors= */ true))
2022-05-05 20:03:43 -04:00
{
// if __iter metamethod is present, it will be called and the results are going to be called as if they are functions
// TODO: this needs to typecheck all returned values by __iter as if they were for loop arguments
// the structure of the function makes it difficult to do this especially since we don't have actual expressions, only types
for (TypeId var : varTypes)
unify(anyType, var, scope, forin.location);
2022-05-05 20:03:43 -04:00
return check(loopScope, *forin.body);
}
2022-05-05 20:03:43 -04:00
if (const TableType* iterTable = get<TableType>(iterTy))
{
// TODO: note that this doesn't cleanly handle iteration over mixed tables and tables without an indexer
// this behavior is more or less consistent with what we do for pairs(), but really both are pretty wrong and need revisiting
if (iterTable->indexer)
{
if (varTypes.size() > 0)
unify(iterTable->indexer->indexType, varTypes[0], scope, forin.location);
2022-05-05 20:03:43 -04:00
if (varTypes.size() > 1)
unify(iterTable->indexer->indexResultType, varTypes[1], scope, forin.location);
2022-05-05 20:03:43 -04:00
for (size_t i = 2; i < varTypes.size(); ++i)
unify(nilType, varTypes[i], scope, forin.location);
}
2022-07-21 17:16:54 -04:00
else if (isNonstrictMode())
{
for (TypeId var : varTypes)
unify(anyType, var, scope, forin.location);
2022-07-21 17:16:54 -04:00
}
else
{
TypeId varTy = errorRecoveryType(loopScope);
2022-05-05 20:03:43 -04:00
for (TypeId var : varTypes)
unify(varTy, var, scope, forin.location);
2022-05-05 20:03:43 -04:00
reportError(firstValue->location, GenericError{"Cannot iterate over a table without indexer"});
2022-05-05 20:03:43 -04:00
}
return check(loopScope, *forin.body);
2022-05-05 20:03:43 -04:00
}
const FunctionType* iterFunc = get<FunctionType>(iterTy);
if (!iterFunc)
{
TypeId varTy = get<AnyType>(iterTy) ? anyType : errorRecoveryType(loopScope);
for (TypeId var : varTypes)
unify(varTy, var, scope, forin.location);
if (!get<ErrorType>(iterTy) && !get<AnyType>(iterTy) && !get<FreeType>(iterTy) && !get<NeverType>(iterTy))
reportError(firstValue->location, CannotCallNonFunction{iterTy});
return check(loopScope, *forin.body);
}
if (forin.values.size == 1)
{
TypePackId argPack = nullptr;
if (firstValue->is<AstExprCall>())
{
// Extract the remaining return values of the call
// and check them against the parameter types of the iterator function.
auto [types, tail] = flatten(callRetPack);
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
else
{
// Check if iterator function accepts 0 arguments
argPack = addTypePack(TypePack{});
}
Unifier state = mkUnifier(loopScope, firstValue->location);
checkArgumentList(loopScope, *firstValue, state, argPack, iterFunc->argTypes, /*argLocations*/ {});
2022-03-11 11:55:02 -05:00
state.log.commit();
reportErrors(state.errors);
}
TypePackId retPack = iterFunc->retTypes;
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()};
retPack = checkExprPack(scope, exprCall).type;
}
// We need to remove 'nil' from the set of options of the first return value
// Because for loop stops when it gets 'nil', this result is never actually assigned to the first variable
if (std::optional<TypeId> fty = first(retPack); fty && !varTypes.empty())
{
TypeId keyTy = follow(*fty);
if (get<UnionType>(keyTy))
{
if (std::optional<TypeId> ty = tryStripUnionFromNil(keyTy))
keyTy = *ty;
}
unify(keyTy, varTypes.front(), scope, forin.location);
// We have already handled the first variable type, make it match in the pack check
varTypes.front() = *fty;
}
TypePackId varPack = addTypePack(TypePackVar{TypePack{varTypes, freshTypePack(scope)}});
unify(retPack, varPack, scope, forin.location);
check(loopScope, *forin.body);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatFunction& function)
{
if (auto exprName = function.name->as<AstExprGlobal>())
{
auto& globalBindings = currentModule->getModuleScope()->bindings;
Symbol name = exprName->name;
Name globalName = exprName->name.value;
Binding oldBinding;
bool previouslyDefined = isNonstrictMode() && globalBindings.count(name);
if (previouslyDefined)
{
oldBinding = globalBindings[name];
}
globalBindings[name] = {ty, exprName->location};
checkFunctionBody(funScope, ty, *function.func);
// If in nonstrict mode and allowing redefinition of global function, restore the previous definition type
// in case this function has a differing signature. The signature discrepancy will be caught in checkBlock.
if (previouslyDefined)
globalBindings[name] = oldBinding;
else
globalBindings[name] = {quantify(funScope, ty, exprName->location), exprName->location};
}
else if (auto name = function.name->as<AstExprLocal>())
{
scope->bindings[name->local] = {ty, name->local->location};
checkFunctionBody(funScope, ty, *function.func);
scope->bindings[name->local] = {anyIfNonstrict(quantify(funScope, ty, name->local->location)), name->local->location};
}
2022-05-13 15:36:37 -04:00
else if (auto name = function.name->as<AstExprIndexName>())
2022-03-11 11:55:02 -05:00
{
TypeId exprTy = checkExpr(scope, *name->expr).type;
TableType* ttv = getMutableTableType(exprTy);
2022-04-07 17:29:01 -04:00
2022-07-07 21:22:39 -04:00
if (!getIndexTypeFromType(scope, exprTy, name->index.value, name->indexLocation, /* addErrors= */ false))
2022-03-11 11:55:02 -05:00
{
2022-04-07 17:29:01 -04:00
if (ttv || isTableIntersection(exprTy))
2022-03-11 11:55:02 -05:00
reportError(TypeError{function.location, CannotExtendTable{exprTy, CannotExtendTable::Property, name->index.value}});
2022-04-07 17:29:01 -04:00
else
2022-03-11 11:55:02 -05:00
reportError(TypeError{function.location, OnlyTablesCanHaveMethods{exprTy}});
}
ty = follow(ty);
if (ttv && ttv->state != TableState::Sealed)
ttv->props[name->index.value] = {ty, /* deprecated */ false, {}, name->indexLocation};
if (function.func->self)
{
const FunctionType* funTy = get<FunctionType>(ty);
2022-03-11 11:55:02 -05:00
if (!funTy)
ice("Methods should be functions");
std::optional<TypeId> arg0 = first(funTy->argTypes);
if (!arg0)
ice("Methods should always have at least 1 argument (self)");
}
checkFunctionBody(funScope, ty, *function.func);
InplaceDemoter demoter{funScope->level, &currentModule->internalTypes};
demoter.traverse(ty);
2022-07-07 21:22:39 -04:00
2022-03-11 11:55:02 -05:00
if (ttv && ttv->state != TableState::Sealed)
ttv->props[name->index.value] = {follow(quantify(funScope, ty, name->indexLocation)), /* deprecated */ false, {}, name->indexLocation};
}
2022-05-13 15:36:37 -04:00
else
2022-03-11 11:55:02 -05:00
{
LUAU_ASSERT(function.name->is<AstExprError>());
ty = follow(ty);
checkFunctionBody(funScope, ty, *function.func);
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function)
{
Name name = function.name->name.value;
scope->bindings[function.name] = {ty, function.location};
checkFunctionBody(funScope, ty, *function.func);
scope->bindings[function.name] = {quantify(funScope, ty, function.name->location), function.name->location};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias)
{
Name name = typealias.name.value;
2022-02-11 14:02:09 -05:00
// If the alias is missing a name, we can't do anything with it. Ignore it.
if (name == kParseNameError)
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
2022-02-11 14:02:09 -05:00
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
binding = it->second;
else if (auto it = scope->privateTypeBindings.find(name); it != scope->privateTypeBindings.end())
binding = it->second;
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
// If the first pass failed (this should mean a duplicate definition), the second pass isn't going to be
// interesting.
if (duplicateTypeAliases.find({typealias.exported, name}))
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
// By now this alias must have been `prototype()`d first.
if (!binding)
ice("Not predeclared");
ScopePtr aliasScope = childScope(scope, typealias.location);
aliasScope->level = scope->level.incr();
for (auto param : binding->typeParams)
{
auto generic = get<GenericType>(param.ty);
LUAU_ASSERT(generic);
aliasScope->privateTypeBindings[generic->name] = TypeFun{{}, param.ty};
}
for (auto param : binding->typePackParams)
{
auto generic = get<GenericTypePack>(param.tp);
LUAU_ASSERT(generic);
aliasScope->privateTypePackBindings[generic->name] = param.tp;
}
TypeId ty = resolveType(aliasScope, *typealias.type);
if (auto ttv = getMutable<TableType>(follow(ty)))
{
// If the table is already named and we want to rename the type function, we have to bind new alias to a copy
// Additionally, we can't modify types that come from other modules
if (ttv->name || follow(ty)->owningArena != &currentModule->internalTypes)
{
bool sameTys = std::equal(ttv->instantiatedTypeParams.begin(), ttv->instantiatedTypeParams.end(), binding->typeParams.begin(),
binding->typeParams.end(), [](auto&& itp, auto&& tp) {
return itp == tp.ty;
});
bool sameTps = std::equal(ttv->instantiatedTypePackParams.begin(), ttv->instantiatedTypePackParams.end(), binding->typePackParams.begin(),
binding->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
return itpp == tpp.tp;
});
// Copy can be skipped if this is an identical alias
if (!ttv->name || ttv->name != name || !sameTys || !sameTps)
{
// This is a shallow clone, original recursive links to self are not updated
TableType clone = TableType{ttv->props, ttv->indexer, ttv->level, ttv->state};
clone.definitionModuleName = ttv->definitionModuleName;
clone.definitionLocation = ttv->definitionLocation;
clone.name = name;
for (auto param : binding->typeParams)
clone.instantiatedTypeParams.push_back(param.ty);
for (auto param : binding->typePackParams)
clone.instantiatedTypePackParams.push_back(param.tp);
ty = addType(std::move(clone));
}
}
else
{
ttv->name = name;
ttv->instantiatedTypeParams.clear();
for (auto param : binding->typeParams)
ttv->instantiatedTypeParams.push_back(param.ty);
ttv->instantiatedTypePackParams.clear();
for (auto param : binding->typePackParams)
ttv->instantiatedTypePackParams.push_back(param.tp);
}
}
else if (auto mtv = getMutable<MetatableType>(follow(ty)))
{
// We can't modify types that come from other modules
if (follow(ty)->owningArena == &currentModule->internalTypes)
mtv->syntheticName = name;
}
TypeId& bindingType = bindingsMap[name].type;
if (unify(ty, bindingType, aliasScope, typealias.location))
bindingType = ty;
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel)
{
Name name = typealias.name.value;
// If the alias is missing a name, we can't do anything with it. Ignore it.
if (name == kParseNameError)
return;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(name); it != scope->exportedTypeBindings.end())
binding = it->second;
else if (auto it = scope->privateTypeBindings.find(name); it != scope->privateTypeBindings.end())
binding = it->second;
auto& bindingsMap = typealias.exported ? scope->exportedTypeBindings : scope->privateTypeBindings;
if (binding)
{
Location location = scope->typeAliasLocations[name];
reportError(TypeError{typealias.location, DuplicateTypeDefinition{name, location}});
duplicateTypeAliases.insert({typealias.exported, name});
}
else
{
if (globalScope->builtinTypeNames.contains(name))
{
reportError(typealias.location, DuplicateTypeDefinition{name});
duplicateTypeAliases.insert({typealias.exported, name});
}
else
{
ScopePtr aliasScope = childScope(scope, typealias.location);
aliasScope->level = scope->level.incr();
2022-02-24 18:53:37 -05:00
aliasScope->level.subLevel = subLevel;
2022-02-17 20:18:01 -05:00
auto [generics, genericPacks] =
createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true);
TypeId ty = freshType(aliasScope);
FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true;
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
2022-03-11 11:55:02 -05:00
2022-04-28 21:24:24 -04:00
scope->typeAliasLocations[name] = typealias.location;
scope->typeAliasNameLocations[name] = typealias.nameLocation;
}
}
}
void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
std::optional<TypeId> superTy = FFlag::LuauNegatedClassTypes ? std::make_optional(builtinTypes->classType) : std::nullopt;
if (declaredClass.superName)
{
Name superName = Name(declaredClass.superName->value);
std::optional<TypeFun> lookupType = scope->lookupType(superName);
if (!lookupType)
{
reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type});
incorrectClassDefinitions.insert(&declaredClass);
return;
}
// We don't have generic classes, so this assertion _should_ never be hit.
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
superTy = lookupType->type;
if (!get<ClassType>(follow(*superTy)))
{
reportError(declaredClass.location,
GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)});
incorrectClassDefinitions.insert(&declaredClass);
return;
}
}
Name className(declaredClass.name.value);
TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModuleName));
ClassType* ctv = getMutable<ClassType>(classTy);
TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
ctv->metatable = metaTy;
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass)
{
Name className(declaredClass.name.value);
// Don't bother checking if the class definition was incorrect
if (incorrectClassDefinitions.find(&declaredClass))
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
std::optional<TypeFun> binding;
if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end())
binding = it->second;
// This class definition must have been `prototype()`d first.
if (!binding)
ice("Class not predeclared");
TypeId classTy = binding->type;
ClassType* ctv = getMutable<ClassType>(classTy);
if (!ctv->metatable)
ice("No metatable for declared class");
TableType* metatable = getMutable<TableType>(*ctv->metatable);
for (const AstDeclaredClassProp& prop : declaredClass.props)
{
Name propName(prop.name.value);
TypeId propTy = resolveType(scope, *prop.ty);
bool assignToMetatable = isMetamethod(propName);
Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props;
// Function types always take 'self', but this isn't reflected in the
// parsed annotation. Add it here.
if (prop.isMethod)
{
if (FunctionType* ftv = getMutable<FunctionType>(propTy))
{
ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}});
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
}
}
if (assignTo.count(propName) == 0)
{
assignTo[propName] = {propTy};
}
else
{
TypeId currentTy = assignTo[propName].type;
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = addType(IntersectionType{std::move(options)});
assignTo[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
}
else
{
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFunction& global)
{
ScopePtr funScope = childFunctionScope(scope, global.location);
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, global, global.generics, global.genericPacks);
2022-01-14 11:20:09 -05:00
std::vector<TypeId> genericTys;
genericTys.reserve(generics.size());
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
return el.ty;
});
std::vector<TypePackId> genericTps;
genericTps.reserve(genericPacks.size());
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
return el.tp;
});
TypePackId argPack = resolveTypePack(funScope, global.params);
TypePackId retPack = resolveTypePack(funScope, global.retTypes);
TypeId fnType = addType(FunctionType{funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack});
FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->argNames.reserve(global.paramNames.size);
for (const auto& el : global.paramNames)
ftv->argNames.push_back(FunctionArgument{el.first.value, el.second});
Name fnName(global.name.value);
currentModule->declaredGlobals[fnName] = fnType;
currentModule->getModuleScope()->bindings[global.name] = Binding{fnType, global.location};
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
return ControlFlow::None;
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExpr& expr, std::optional<TypeId> expectedType, bool forceSingleton)
{
RecursionCounter _rc(&checkRecursionCount);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
{
reportErrorCodeTooComplex(expr.location);
return WithPredicate{errorRecoveryType(scope)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result;
if (auto a = expr.as<AstExprGroup>())
2022-02-17 20:18:01 -05:00
result = checkExpr(scope, *a->expr, expectedType);
else if (expr.is<AstExprConstantNil>())
result = WithPredicate{nilType};
else if (const AstExprConstantBool* bexpr = expr.as<AstExprConstantBool>())
{
2022-03-24 18:04:14 -04:00
if (forceSingleton || (expectedType && maybeSingleton(*expectedType)))
result = WithPredicate{singletonType(bexpr->value)};
else
result = WithPredicate{booleanType};
}
else if (const AstExprConstantString* sexpr = expr.as<AstExprConstantString>())
{
2022-03-24 18:04:14 -04:00
if (forceSingleton || (expectedType && maybeSingleton(*expectedType)))
result = WithPredicate{singletonType(std::string(sexpr->value.data, sexpr->value.size))};
else
result = WithPredicate{stringType};
}
else if (expr.is<AstExprConstantNumber>())
result = WithPredicate{numberType};
else if (auto a = expr.as<AstExprLocal>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprGlobal>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprVarargs>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprCall>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprIndexName>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprIndexExpr>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprFunction>())
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTable>())
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprUnary>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprBinary>())
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprTypeAssertion>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprError>())
result = checkExpr(scope, *a);
else if (auto a = expr.as<AstExprIfElse>())
2022-02-17 20:18:01 -05:00
result = checkExpr(scope, *a, expectedType);
else if (auto a = expr.as<AstExprInterpString>())
result = checkExpr(scope, *a);
else
ice("Unhandled AstExpr?");
result.type = follow(result.type);
if (!currentModule->astTypes.find(&expr))
currentModule->astTypes[&expr] = result.type;
if (expectedType)
currentModule->astExpectedTypes[&expr] = *expectedType;
return result;
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprLocal& expr)
{
std::optional<LValue> lvalue = tryGetLValue(expr);
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprLocal is an LValue.
if (std::optional<TypeId> ty = resolveLValue(scope, *lvalue))
return {*ty, {TruthyPredicate{std::move(*lvalue), expr.location}}};
// TODO: tempting to ice here, but this breaks very often because our toposort doesn't enforce this constraint
// ice("AstExprLocal exists but no binding definition for it?", expr.location);
reportError(TypeError{expr.location, UnknownSymbol{expr.local->name.value, UnknownSymbol::Binding}});
return WithPredicate{errorRecoveryType(scope)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprGlobal& expr)
{
std::optional<LValue> lvalue = tryGetLValue(expr);
LUAU_ASSERT(lvalue); // Guaranteed to not be nullopt - AstExprGlobal is an LValue.
if (std::optional<TypeId> ty = resolveLValue(scope, *lvalue))
return {*ty, {TruthyPredicate{std::move(*lvalue), expr.location}}};
reportError(TypeError{expr.location, UnknownSymbol{expr.name.value, UnknownSymbol::Binding}});
return WithPredicate{errorRecoveryType(scope)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprVarargs& expr)
{
TypePackId varargPack = checkExprPack(scope, expr).type;
if (get<TypePack>(varargPack))
{
if (std::optional<TypeId> ty = first(varargPack))
return WithPredicate{*ty};
return WithPredicate{nilType};
}
else if (get<FreeTypePack>(varargPack))
{
TypeId head = freshType(scope);
TypePackId tail = freshTypePack(scope);
*asMutable(varargPack) = TypePack{{head}, tail};
return WithPredicate{head};
}
if (get<ErrorType>(varargPack))
return WithPredicate{errorRecoveryType(scope)};
else if (auto vtp = get<VariadicTypePack>(varargPack))
return WithPredicate{vtp->ty};
else if (get<Unifiable::Generic>(varargPack))
{
// TODO: Better error?
reportError(expr.location, GenericError{"Trying to get a type from a variadic type parameter"});
return WithPredicate{errorRecoveryType(scope)};
}
else
ice("Unknown TypePack type in checkExpr(AstExprVarargs)!");
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprCall& expr)
{
2022-06-16 21:05:14 -04:00
WithPredicate<TypePackId> result = checkExprPack(scope, expr);
TypePackId retPack = follow(result.type);
if (auto pack = get<TypePack>(retPack))
{
return {pack->head.empty() ? nilType : pack->head[0], std::move(result.predicates)};
}
2022-04-14 19:57:43 -04:00
else if (const FreeTypePack* ftp = get<Unifiable::Free>(retPack))
{
TypeId head = freshType(scope->level);
TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}});
unify(pack, retPack, scope, expr.location);
return {head, std::move(result.predicates)};
}
if (get<Unifiable::Error>(retPack))
return {errorRecoveryType(scope), std::move(result.predicates)};
else if (auto vtp = get<VariadicTypePack>(retPack))
return {vtp->ty, std::move(result.predicates)};
else if (get<Unifiable::Generic>(retPack))
2022-02-11 14:02:09 -05:00
{
if (FFlag::LuauReturnAnyInsteadOfICE)
return {anyType, std::move(result.predicates)};
else
ice("Unexpected abstract type pack!", expr.location);
}
else
ice("Unknown TypePack type!", expr.location);
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexName& expr)
{
Name name = expr.index.value;
// Redundant call if we find a refined lvalue, but this function must be called in order to recursively populate astTypes.
TypeId lhsType = checkExpr(scope, *expr.expr).type;
if (std::optional<LValue> lvalue = tryGetLValue(expr))
if (std::optional<TypeId> ty = resolveLValue(scope, *lvalue))
return {*ty, {TruthyPredicate{std::move(*lvalue), expr.location}}};
lhsType = stripFromNilAndReport(lhsType, expr.expr->location);
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhsType, name, expr.location, /* addErrors= */ true))
return WithPredicate{*ty};
return WithPredicate{errorRecoveryType(scope)};
}
2022-07-07 21:22:39 -04:00
std::optional<TypeId> TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location, bool addErrors)
{
ErrorVec errors;
auto result = Luau::findTablePropertyRespectingMeta(builtinTypes, errors, lhsType, name, location);
if (addErrors)
2022-07-07 21:22:39 -04:00
reportErrors(errors);
return result;
}
2022-07-07 21:22:39 -04:00
std::optional<TypeId> TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location, bool addErrors)
{
ErrorVec errors;
auto result = Luau::findMetatableEntry(builtinTypes, errors, type, entry, location);
if (addErrors)
2022-07-07 21:22:39 -04:00
reportErrors(errors);
return result;
}
std::optional<TypeId> TypeChecker::getIndexTypeFromType(
2022-07-07 21:22:39 -04:00
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors)
{
size_t errorCount = currentModule->errors.size();
std::optional<TypeId> result = getIndexTypeFromTypeImpl(scope, type, name, location, addErrors);
if (!addErrors)
2022-07-07 21:22:39 -04:00
LUAU_ASSERT(errorCount == currentModule->errors.size());
return result;
}
std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
const ScopePtr& scope, TypeId type, const Name& name, const Location& location, bool addErrors)
{
type = follow(type);
if (get<ErrorType>(type) || get<AnyType>(type) || get<NeverType>(type))
return type;
tablify(type);
2022-05-19 20:02:24 -04:00
if (isString(type))
{
2022-07-07 21:22:39 -04:00
std::optional<TypeId> mtIndex = findMetatableEntry(stringType, "__index", location, addErrors);
2022-05-19 20:02:24 -04:00
LUAU_ASSERT(mtIndex);
type = *mtIndex;
}
if (TableType* tableType = getMutableTableType(type))
{
if (auto it = tableType->props.find(name); it != tableType->props.end())
return it->second.type;
else if (auto indexer = tableType->indexer)
{
// TODO: Property lookup should work with string singletons or unions thereof as the indexer key type.
ErrorVec errors = tryUnify(stringType, indexer->indexType, scope, location);
if (errors.empty())
return indexer->indexResultType;
if (addErrors)
reportError(location, UnknownProperty{type, name});
return std::nullopt;
}
else if (tableType->state == TableState::Free)
{
2022-03-04 11:36:33 -05:00
TypeId result = freshType(tableType->level);
tableType->props[name] = {result};
return result;
}
2022-07-07 21:22:39 -04:00
if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors))
return *found;
}
else if (const ClassType* cls = get<ClassType>(type))
{
const Property* prop = lookupClassProp(cls, name);
if (prop)
return prop->type;
}
else if (const UnionType* utv = get<UnionType>(type))
{
std::vector<TypeId> goodOptions;
std::vector<TypeId> badOptions;
for (TypeId t : utv)
{
2022-06-23 21:56:00 -04:00
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
// Not needed when we normalize types.
if (get<AnyType>(follow(t)))
return t;
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
goodOptions.push_back(*ty);
else
badOptions.push_back(t);
}
if (!badOptions.empty())
{
if (addErrors)
{
if (goodOptions.empty())
reportError(location, UnknownProperty{type, name});
else
reportError(location, MissingUnionProperty{type, badOptions, name});
}
return std::nullopt;
}
std::vector<TypeId> result = reduceUnion(goodOptions);
if (result.empty())
return neverType;
2022-04-14 19:57:43 -04:00
if (result.size() == 1)
return result[0];
2022-04-14 19:57:43 -04:00
return addType(UnionType{std::move(result)});
}
else if (const IntersectionType* itv = get<IntersectionType>(type))
{
std::vector<TypeId> parts;
for (TypeId t : itv->parts)
{
2022-06-23 21:56:00 -04:00
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
parts.push_back(*ty);
}
// If no parts of the intersection had the property we looked up for, it never existed at all.
if (parts.empty())
{
if (addErrors)
reportError(location, UnknownProperty{type, name});
return std::nullopt;
}
2022-05-19 20:02:24 -04:00
if (parts.size() == 1)
return parts[0];
return addType(IntersectionType{std::move(parts)}); // Not at all correct.
}
if (addErrors)
reportError(location, UnknownProperty{type, name});
return std::nullopt;
}
std::optional<TypeId> TypeChecker::tryStripUnionFromNil(TypeId ty)
{
if (const UnionType* utv = get<UnionType>(ty))
{
2022-05-19 20:02:24 -04:00
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
std::vector<TypeId> result;
for (TypeId option : utv)
{
if (!isNil(option))
result.push_back(option);
}
if (result.empty())
return std::nullopt;
return result.size() == 1 ? result[0] : addType(UnionType{std::move(result)});
}
return std::nullopt;
}
TypeId TypeChecker::stripFromNilAndReport(TypeId ty, const Location& location)
{
2022-05-19 20:02:24 -04:00
ty = follow(ty);
2022-03-17 20:46:04 -04:00
if (auto utv = get<UnionType>(ty))
2022-05-19 20:02:24 -04:00
{
if (!std::any_of(begin(utv), end(utv), isNil))
return ty;
}
2022-05-19 20:02:24 -04:00
if (std::optional<TypeId> strippedUnion = tryStripUnionFromNil(ty))
2022-03-17 20:46:04 -04:00
{
2022-05-19 20:02:24 -04:00
reportError(location, OptionalValueAccess{ty});
return follow(*strippedUnion);
2022-03-17 20:46:04 -04:00
}
return ty;
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIndexExpr& expr)
{
TypeId ty = checkLValue(scope, expr, ValueContext::RValue);
2022-02-04 11:45:57 -05:00
if (std::optional<LValue> lvalue = tryGetLValue(expr))
if (std::optional<TypeId> refiTy = resolveLValue(scope, *lvalue))
return {*refiTy, {TruthyPredicate{std::move(*lvalue), expr.location}}};
return WithPredicate{ty};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprFunction& expr, std::optional<TypeId> expectedType)
{
2022-06-30 19:52:43 -04:00
auto [funTy, funScope] = checkFunctionSignature(scope, 0, expr, std::nullopt, std::nullopt, expectedType);
checkFunctionBody(funScope, funTy, expr);
return WithPredicate{quantify(funScope, funTy, expr.location)};
}
TypeId TypeChecker::checkExprTable(
const ScopePtr& scope, const AstExprTable& expr, const std::vector<std::pair<TypeId, TypeId>>& fieldTypes, std::optional<TypeId> expectedType)
{
TableType::Props props;
std::optional<TableIndexer> indexer;
const TableType* expectedTable = nullptr;
if (expectedType)
{
if (auto ttv = get<TableType>(follow(*expectedType)))
{
if (ttv->state == TableState::Sealed)
expectedTable = ttv;
}
}
for (size_t i = 0; i < expr.items.size; ++i)
{
const AstExprTable::Item& item = expr.items.data[i];
AstExpr* k = item.key;
AstExpr* value = item.value;
auto [keyType, valueType] = fieldTypes[i];
if (item.kind == AstExprTable::Item::List)
{
if (expectedTable && !indexer)
indexer = expectedTable->indexer;
if (indexer)
2022-04-07 17:29:01 -04:00
{
unify(numberType, indexer->indexType, scope, value->location);
unify(valueType, indexer->indexResultType, scope, value->location);
2022-04-07 17:29:01 -04:00
}
else
indexer = TableIndexer{numberType, anyIfNonstrict(valueType)};
}
else if (item.kind == AstExprTable::Item::Record || item.kind == AstExprTable::Item::General)
{
if (auto key = k->as<AstExprConstantString>())
{
TypeId exprType = follow(valueType);
if (isNonstrictMode() && !getTableType(exprType) && !get<FunctionType>(exprType))
exprType = anyType;
2022-04-28 21:24:24 -04:00
if (expectedTable)
{
auto it = expectedTable->props.find(key->value.data);
if (it != expectedTable->props.end())
{
Property expectedProp = it->second;
ErrorVec errors = tryUnify(exprType, expectedProp.type, scope, k->location);
if (errors.empty())
exprType = expectedProp.type;
}
2022-06-16 21:05:14 -04:00
else if (expectedTable->indexer && maybeString(expectedTable->indexer->indexType))
{
ErrorVec errors = tryUnify(exprType, expectedTable->indexer->indexResultType, scope, k->location);
if (errors.empty())
exprType = expectedTable->indexer->indexResultType;
}
}
props[key->value.data] = {exprType, /* deprecated */ false, {}, k->location};
}
else
{
if (expectedTable && !indexer)
indexer = expectedTable->indexer;
if (indexer)
{
unify(keyType, indexer->indexType, scope, k->location);
unify(valueType, indexer->indexResultType, scope, value->location);
}
else if (isNonstrictMode())
{
indexer = TableIndexer{anyType, anyType};
}
else
{
indexer = TableIndexer{keyType, valueType};
}
}
}
}
2022-06-23 21:56:00 -04:00
TableState state = TableState::Unsealed;
TableType table = TableType{std::move(props), indexer, scope->level, state};
table.definitionModuleName = currentModuleName;
table.definitionLocation = expr.location;
return addType(table);
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTable& expr, std::optional<TypeId> expectedType)
{
2022-05-19 20:02:24 -04:00
RecursionCounter _rc(&checkRecursionCount);
if (FInt::LuauCheckRecursionLimit > 0 && checkRecursionCount >= FInt::LuauCheckRecursionLimit)
2022-04-14 19:57:43 -04:00
{
2022-05-19 20:02:24 -04:00
reportErrorCodeTooComplex(expr.location);
return WithPredicate{errorRecoveryType(scope)};
2022-04-14 19:57:43 -04:00
}
std::vector<std::pair<TypeId, TypeId>> fieldTypes(expr.items.size);
const TableType* expectedTable = nullptr;
const UnionType* expectedUnion = nullptr;
std::optional<TypeId> expectedIndexType;
std::optional<TypeId> expectedIndexResultType;
if (expectedType)
{
if (auto ttv = get<TableType>(follow(*expectedType)))
{
if (ttv->state == TableState::Sealed)
{
expectedTable = ttv;
if (ttv->indexer)
{
expectedIndexType = ttv->indexer->indexType;
expectedIndexResultType = ttv->indexer->indexResultType;
}
}
}
else if (const UnionType* utv = get<UnionType>(follow(*expectedType)))
2022-04-28 21:24:24 -04:00
expectedUnion = utv;
}
for (size_t i = 0; i < expr.items.size; ++i)
{
AstExprTable::Item& item = expr.items.data[i];
std::optional<TypeId> expectedResultType;
bool isIndexedItem = false;
if (item.kind == AstExprTable::Item::List)
{
expectedResultType = expectedIndexResultType;
isIndexedItem = true;
}
else if (item.kind == AstExprTable::Item::Record || item.kind == AstExprTable::Item::General)
{
if (auto key = item.key->as<AstExprConstantString>())
{
if (expectedTable)
{
if (auto prop = expectedTable->props.find(key->value.data); prop != expectedTable->props.end())
expectedResultType = prop->second.type;
2022-06-16 21:05:14 -04:00
else if (expectedIndexType && maybeString(*expectedIndexType))
2022-05-19 20:02:24 -04:00
expectedResultType = expectedIndexResultType;
}
2022-04-28 21:24:24 -04:00
else if (expectedUnion)
{
std::vector<TypeId> expectedResultTypes;
for (TypeId expectedOption : expectedUnion)
2022-07-21 17:16:54 -04:00
{
if (const TableType* ttv = get<TableType>(follow(expectedOption)))
2022-07-21 17:16:54 -04:00
{
if (auto prop = ttv->props.find(key->value.data); prop != ttv->props.end())
expectedResultTypes.push_back(prop->second.type);
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
2022-07-21 17:16:54 -04:00
expectedResultTypes.push_back(ttv->indexer->indexResultType);
}
}
if (expectedResultTypes.size() == 1)
expectedResultType = expectedResultTypes[0];
else if (expectedResultTypes.size() > 1)
expectedResultType = addType(UnionType{expectedResultTypes});
}
}
else
{
expectedResultType = expectedIndexResultType;
isIndexedItem = true;
}
}
fieldTypes[i].first = item.key ? checkExpr(scope, *item.key, expectedIndexType).type : nullptr;
fieldTypes[i].second = checkExpr(scope, *item.value, expectedResultType).type;
// Indexer keys after the first are unified with the first one
// If we don't have an expected indexer type yet, take this first item type
if (isIndexedItem && !expectedIndexResultType)
expectedIndexResultType = fieldTypes[i].second;
}
return WithPredicate{checkExprTable(scope, expr, fieldTypes, expectedType)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprUnary& expr)
{
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result = checkExpr(scope, *expr.expr);
TypeId operandType = follow(result.type);
switch (expr.op)
{
case AstExprUnary::Not:
return {booleanType, {NotPredicate{std::move(result.predicates)}}};
case AstExprUnary::Minus:
{
const bool operandIsAny = get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType);
if (operandIsAny)
return WithPredicate{operandType};
if (typeCouldHaveMetatable(operandType))
{
2022-07-07 21:22:39 -04:00
if (auto fnt = findMetatableEntry(operandType, "__unm", expr.location, /* addErrors= */ true))
{
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
TypePackId arguments = addTypePack({operandType});
TypePackId retTypePack = freshTypePack(scope);
TypeId expectedFunctionType = addType(FunctionType(scope->level, arguments, retTypePack));
Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(actualFunctionType, expectedFunctionType, /*isFunctionCall*/ true);
2022-03-11 11:55:02 -05:00
state.log.commit();
2022-06-30 19:52:43 -04:00
reportErrors(state.errors);
TypeId retType = first(retTypePack).value_or(nilType);
if (!state.errors.empty())
retType = errorRecoveryType(retType);
return WithPredicate{retType};
}
reportError(expr.location,
GenericError{format("Unary operator '%s' not supported by type '%s'", toString(expr.op).c_str(), toString(operandType).c_str())});
return WithPredicate{errorRecoveryType(scope)};
}
reportErrors(tryUnify(operandType, numberType, scope, expr.location));
return WithPredicate{numberType};
}
case AstExprUnary::Len:
2022-02-17 20:18:01 -05:00
{
tablify(operandType);
operandType = stripFromNilAndReport(operandType, expr.location);
2022-07-14 18:52:26 -04:00
// # operator is guaranteed to return number
if (get<AnyType>(operandType) || get<ErrorType>(operandType) || get<NeverType>(operandType))
return WithPredicate{numberType};
2022-02-17 20:18:01 -05:00
DenseHashSet<TypeId> seen{nullptr};
2022-07-29 00:24:07 -04:00
if (typeCouldHaveMetatable(operandType))
2022-06-30 19:52:43 -04:00
{
2022-07-07 21:22:39 -04:00
if (auto fnt = findMetatableEntry(operandType, "__len", expr.location, /* addErrors= */ true))
2022-06-30 19:52:43 -04:00
{
TypeId actualFunctionType = instantiate(scope, *fnt, expr.location);
TypePackId arguments = addTypePack({operandType});
TypePackId retTypePack = addTypePack({numberType});
TypeId expectedFunctionType = addType(FunctionType(scope->level, arguments, retTypePack));
2022-06-30 19:52:43 -04:00
Unifier state = mkUnifier(scope, expr.location);
2022-06-30 19:52:43 -04:00
state.tryUnify(actualFunctionType, expectedFunctionType, /*isFunctionCall*/ true);
state.log.commit();
reportErrors(state.errors);
}
}
2022-02-17 20:18:01 -05:00
if (!hasLength(operandType, seen, &recursionCount))
reportError(TypeError{expr.location, NotATable{operandType}});
return WithPredicate{numberType};
2022-02-17 20:18:01 -05:00
}
default:
ice("Unknown AstExprUnary " + std::to_string(int(expr.op)));
}
}
std::string opToMetaTableEntry(const AstExprBinary::Op& op)
{
switch (op)
{
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
return "__eq";
case AstExprBinary::CompareLt:
case AstExprBinary::CompareGe:
return "__lt";
case AstExprBinary::CompareLe:
case AstExprBinary::CompareGt:
return "__le";
case AstExprBinary::Add:
return "__add";
case AstExprBinary::Sub:
return "__sub";
case AstExprBinary::Mul:
return "__mul";
case AstExprBinary::Div:
return "__div";
case AstExprBinary::Mod:
return "__mod";
case AstExprBinary::Pow:
return "__pow";
case AstExprBinary::Concat:
return "__concat";
default:
return "";
}
}
TypeId TypeChecker::unionOfTypes(TypeId a, TypeId b, const ScopePtr& scope, const Location& location, bool unifyFreeTypes)
{
a = follow(a);
b = follow(b);
if (unifyFreeTypes && (get<FreeType>(a) || get<FreeType>(b)))
{
if (unify(b, a, scope, location))
return a;
return errorRecoveryType(anyType);
}
if (*a == *b)
return a;
std::vector<TypeId> types = reduceUnion({a, b});
if (types.empty())
2022-07-07 21:22:39 -04:00
return neverType;
if (types.size() == 1)
return types[0];
return addType(UnionType{types});
}
static std::optional<std::string> getIdentifierOfBaseVar(AstExpr* node)
{
if (AstExprGlobal* expr = node->as<AstExprGlobal>())
return expr->name.value;
if (AstExprLocal* expr = node->as<AstExprLocal>())
return expr->local->name.value;
if (AstExprIndexExpr* expr = node->as<AstExprIndexExpr>())
return getIdentifierOfBaseVar(expr->expr);
if (AstExprIndexName* expr = node->as<AstExprIndexName>())
return getIdentifierOfBaseVar(expr->expr);
return std::nullopt;
}
/** Return true if comparison between the types a and b should be permitted with
* the == or ~= operators.
*
* Two types are considered eligible for equality testing if it is possible for
* the test to ever succeed. In other words, we test to see whether the two
* types have any overlap at all.
*
* In order to make things work smoothly with the greedy solver, this function
* exempts any and FreeTypes from this requirement.
*
* This function does not (yet?) take into account extra Lua restrictions like
* that two tables can only be compared if they have the same metatable. That
* is presently handled by the caller.
*
* @return True if the types are comparable. False if they are not.
*
* If an internal recursion limit is reached while performing this test, the
* function returns std::nullopt.
*/
static std::optional<bool> areEqComparable(NotNull<TypeArena> arena, NotNull<Normalizer> normalizer, TypeId a, TypeId b)
{
a = follow(a);
b = follow(b);
auto isExempt = [](TypeId t) {
return isNil(t) || get<FreeType>(t);
};
if (isExempt(a) || isExempt(b))
return true;
TypeId c = arena->addType(IntersectionType{{a, b}});
const NormalizedType* n = normalizer->normalize(c);
if (!n)
return std::nullopt;
if (FFlag::LuauUninhabitedSubAnything2)
return normalizer->isInhabited(n);
else
return isInhabited_DEPRECATED(*n);
}
TypeId TypeChecker::checkRelationalOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates)
{
auto stripNil = [this](TypeId ty, bool isOrOp = false) {
ty = follow(ty);
if (!isNonstrictMode() && !isOrOp)
return ty;
if (get<UnionType>(ty))
{
std::optional<TypeId> cleaned = tryStripUnionFromNil(ty);
// If there is no union option without 'nil'
if (!cleaned)
return nilType;
return follow(*cleaned);
}
return follow(ty);
};
bool isEquality = expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe;
lhsType = stripNil(lhsType, expr.op == AstExprBinary::Or);
rhsType = stripNil(rhsType);
// If we know nothing at all about the lhs type, we can usually say nothing about the result.
// The notable exception to this is the equality and inequality operators, which always produce a boolean.
const bool lhsIsAny = get<AnyType>(lhsType) || get<ErrorType>(lhsType) || get<NeverType>(lhsType);
// Peephole check for `cond and a or b -> type(a)|type(b)`
// TODO: Kill this when singleton types arrive. :(
if (AstExprBinary* subexp = expr.left->as<AstExprBinary>())
{
if (expr.op == AstExprBinary::Or && subexp->op == AstExprBinary::And)
{
ScopePtr subScope = childScope(scope, subexp->location);
2022-05-19 20:02:24 -04:00
resolve(predicates, subScope, true);
return unionOfTypes(rhsType, stripNil(checkExpr(subScope, *subexp->right).type, true), subScope, expr.location);
}
}
// Lua casts the results of these to boolean
switch (expr.op)
{
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
{
if (isNonstrictMode() && (isNil(lhsType) || isNil(rhsType)))
return booleanType;
const bool rhsIsAny = get<AnyType>(rhsType) || get<ErrorType>(rhsType) || get<NeverType>(rhsType);
if (lhsIsAny || rhsIsAny)
return booleanType;
// Fallthrough here is intentional
}
case AstExprBinary::CompareLt:
case AstExprBinary::CompareGt:
case AstExprBinary::CompareGe:
case AstExprBinary::CompareLe:
{
// If one of the operand is never, it doesn't make sense to unify these.
if (get<NeverType>(lhsType) || get<NeverType>(rhsType))
return booleanType;
2022-07-14 18:52:26 -04:00
if (isEquality)
{
// Unless either type is free or any, an equality comparison is only
// valid when the intersection of the two operands is non-empty.
//
// eg it is okay to compare string? == number? because the two types
// have nil in common, but string == number is not allowed.
std::optional<bool> eqTestResult = areEqComparable(NotNull{&currentModule->internalTypes}, NotNull{&normalizer}, lhsType, rhsType);
if (!eqTestResult)
{
reportErrorCodeTooComplex(expr.location);
return errorRecoveryType(booleanType);
}
if (!*eqTestResult)
{
reportError(
expr.location, GenericError{format("Type %s cannot be compared with %s", toString(lhsType).c_str(), toString(rhsType).c_str())});
return errorRecoveryType(booleanType);
}
}
/* Subtlety here:
* We need to do this unification first, but there are situations where we don't actually want to
* report any problems that might have been surfaced as a result of this step because we might already
* have a better, more descriptive error teed up.
*/
Unifier state = mkUnifier(scope, expr.location);
if (!isEquality)
{
state.tryUnify(rhsType, lhsType);
2022-03-11 11:55:02 -05:00
state.log.commit();
}
const bool needsMetamethod = !isEquality;
TypeId leftType = follow(lhsType);
if (get<PrimitiveType>(leftType) || get<AnyType>(leftType) || get<ErrorType>(leftType) || get<UnionType>(leftType))
{
reportErrors(state.errors);
if (!isEquality && state.errors.empty() && (get<UnionType>(leftType) || isBoolean(leftType)))
{
reportError(expr.location, GenericError{format("Type '%s' cannot be compared with relational operator %s", toString(leftType).c_str(),
toString(expr.op).c_str())});
}
return booleanType;
}
std::string metamethodName = opToMetaTableEntry(expr.op);
std::optional<TypeId> leftMetatable = isString(lhsType) ? std::nullopt : getMetatable(follow(lhsType), builtinTypes);
std::optional<TypeId> rightMetatable = isString(rhsType) ? std::nullopt : getMetatable(follow(rhsType), builtinTypes);
2022-06-16 21:05:14 -04:00
if (leftMetatable != rightMetatable)
{
2022-06-16 21:05:14 -04:00
bool matches = false;
if (isEquality)
2022-05-13 15:36:37 -04:00
{
if (const UnionType* utv = get<UnionType>(leftType); utv && rightMetatable)
2022-05-13 15:36:37 -04:00
{
2022-06-16 21:05:14 -04:00
for (TypeId leftOption : utv)
2022-05-13 15:36:37 -04:00
{
if (getMetatable(follow(leftOption), builtinTypes) == rightMetatable)
2022-05-13 15:36:37 -04:00
{
2022-06-16 21:05:14 -04:00
matches = true;
break;
2022-05-13 15:36:37 -04:00
}
}
2022-06-16 21:05:14 -04:00
}
2022-05-13 15:36:37 -04:00
2022-06-16 21:05:14 -04:00
if (!matches)
{
if (const UnionType* utv = get<UnionType>(rhsType); utv && leftMetatable)
2022-05-13 15:36:37 -04:00
{
2022-06-16 21:05:14 -04:00
for (TypeId rightOption : utv)
2022-05-13 15:36:37 -04:00
{
if (getMetatable(follow(rightOption), builtinTypes) == leftMetatable)
2022-05-13 15:36:37 -04:00
{
2022-06-16 21:05:14 -04:00
matches = true;
break;
2022-05-13 15:36:37 -04:00
}
}
}
}
2022-06-16 21:05:14 -04:00
}
2022-05-13 15:36:37 -04:00
2022-06-16 21:05:14 -04:00
if (!matches)
2022-05-13 15:36:37 -04:00
{
reportError(
expr.location, GenericError{format("Types %s and %s cannot be compared with %s because they do not have the same metatable",
2022-06-30 19:52:43 -04:00
toString(lhsType).c_str(), toString(rhsType).c_str(), toString(expr.op).c_str())});
2022-05-13 15:36:37 -04:00
return errorRecoveryType(booleanType);
}
}
if (leftMetatable)
{
2022-07-07 21:22:39 -04:00
std::optional<TypeId> metamethod = findMetatableEntry(lhsType, metamethodName, expr.location, /* addErrors= */ true);
if (metamethod)
{
if (const FunctionType* ftv = get<FunctionType>(*metamethod))
{
if (isEquality)
{
Unifier state = mkUnifier(scope, expr.location);
2022-06-16 21:05:14 -04:00
state.tryUnify(addTypePack({booleanType}), ftv->retTypes);
if (!state.errors.empty())
{
reportError(expr.location, GenericError{format("Metamethod '%s' must return type 'boolean'", metamethodName.c_str())});
return errorRecoveryType(booleanType);
}
2022-03-11 11:55:02 -05:00
state.log.commit();
}
}
reportErrors(state.errors);
TypeId actualFunctionType = addType(FunctionType(scope->level, addTypePack({lhsType, rhsType}), addTypePack({booleanType})));
state.tryUnify(
instantiate(scope, actualFunctionType, expr.location), instantiate(scope, *metamethod, expr.location), /*isFunctionCall*/ true);
2022-03-11 11:55:02 -05:00
state.log.commit();
reportErrors(state.errors);
return booleanType;
}
else if (needsMetamethod)
{
reportError(
expr.location, GenericError{format("Table %s does not offer metamethod %s", toString(lhsType).c_str(), metamethodName.c_str())});
return errorRecoveryType(booleanType);
}
}
if (get<FreeType>(follow(lhsType)) && !isEquality)
{
auto name = getIdentifierOfBaseVar(expr.left);
reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Comparison});
return errorRecoveryType(booleanType);
}
if (needsMetamethod)
{
reportError(expr.location, GenericError{format("Type %s cannot be compared with %s because it has no metatable",
toString(lhsType).c_str(), toString(expr.op).c_str())});
return errorRecoveryType(booleanType);
}
return booleanType;
}
case AstExprBinary::And:
if (lhsIsAny)
{
return lhsType;
}
else if (FFlag::LuauTryhardAnd)
{
// If lhs is free, we can't tell which 'falsy' components it has, if any
if (get<FreeType>(lhsType))
return unionOfTypes(addType(UnionType{{nilType, singletonType(false)}}), rhsType, scope, expr.location, false);
auto [oty, notNever] = pickTypesFromSense(lhsType, false, neverType); // Filter out falsy types
if (notNever)
{
LUAU_ASSERT(oty);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauReducingAndOr)
{
// Perform a limited form of type reduction for booleans
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
return booleanType;
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
return booleanType;
}
return unionOfTypes(*oty, rhsType, scope, expr.location, false);
}
else
{
return rhsType;
}
}
else
{
return unionOfTypes(rhsType, booleanType, scope, expr.location, false);
}
case AstExprBinary::Or:
if (lhsIsAny)
{
return lhsType;
}
else if (FFlag::LuauTryhardAnd)
{
auto [oty, notNever] = pickTypesFromSense(lhsType, true, neverType); // Filter out truthy types
if (notNever)
{
LUAU_ASSERT(oty);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 15:20:37 -04:00
if (FFlag::LuauReducingAndOr)
{
// Perform a limited form of type reduction for booleans
if (isPrim(*oty, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(rhsType))))
return booleanType;
if (isPrim(rhsType, PrimitiveType::Boolean) && get<BooleanSingleton>(get<SingletonType>(follow(*oty))))
return booleanType;
}
return unionOfTypes(*oty, rhsType, scope, expr.location);
}
else
{
return rhsType;
}
}
else
{
return unionOfTypes(lhsType, rhsType, scope, expr.location);
}
default:
LUAU_ASSERT(0);
ice(format("checkRelationalOperation called with incorrect binary expression '%s'", toString(expr.op).c_str()), expr.location);
}
}
TypeId TypeChecker::checkBinaryOperation(
const ScopePtr& scope, const AstExprBinary& expr, TypeId lhsType, TypeId rhsType, const PredicateVec& predicates)
{
switch (expr.op)
{
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
case AstExprBinary::CompareLt:
case AstExprBinary::CompareGt:
case AstExprBinary::CompareGe:
case AstExprBinary::CompareLe:
case AstExprBinary::And:
case AstExprBinary::Or:
return checkRelationalOperation(scope, expr, lhsType, rhsType, predicates);
default:
break;
}
lhsType = follow(lhsType);
rhsType = follow(rhsType);
if (!isNonstrictMode() && get<FreeType>(lhsType))
{
2022-05-13 15:36:37 -04:00
auto name = getIdentifierOfBaseVar(expr.left);
reportError(expr.location, CannotInferBinaryOperation{expr.op, name, CannotInferBinaryOperation::Operation});
// We will fall-through to the `return anyType` check below.
}
// If we know nothing at all about the lhs type, we can usually say nothing about the result.
// The notable exception to this is the equality and inequality operators, which always produce a boolean.
const bool lhsIsAny = get<AnyType>(lhsType) || get<ErrorType>(lhsType) || get<NeverType>(lhsType);
const bool rhsIsAny = get<AnyType>(rhsType) || get<ErrorType>(rhsType) || get<NeverType>(rhsType);
if (lhsIsAny)
return lhsType;
if (rhsIsAny)
return rhsType;
if (get<FreeType>(lhsType))
{
// Inferring this accurately will get a bit weird.
// If the lhs type is not known, it could be assumed that it is a table or class that has a metatable
// that defines the required method, but we don't know which.
// For now, we'll give up and hope for the best.
return anyType;
}
if (get<FreeType>(rhsType))
unify(rhsType, lhsType, scope, expr.location);
if (typeCouldHaveMetatable(lhsType) || typeCouldHaveMetatable(rhsType))
{
auto checkMetatableCall = [this, &scope, &expr](TypeId fnt, TypeId lhst, TypeId rhst) -> TypeId {
TypeId actualFunctionType = instantiate(scope, fnt, expr.location);
TypePackId arguments = addTypePack({lhst, rhst});
TypePackId retTypePack = freshTypePack(scope);
TypeId expectedFunctionType = addType(FunctionType(scope->level, arguments, retTypePack));
Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(actualFunctionType, expectedFunctionType, /*isFunctionCall*/ true);
reportErrors(state.errors);
bool hasErrors = !state.errors.empty();
2022-04-28 21:24:24 -04:00
if (hasErrors)
{
// If there are unification errors, the return type may still be unknown
// so we loosen the argument types to see if that helps.
TypePackId fallbackArguments = freshTypePack(scope);
TypeId fallbackFunctionType = addType(FunctionType(scope->level, fallbackArguments, retTypePack));
state.errors.clear();
2022-03-11 11:55:02 -05:00
state.log.clear();
state.tryUnify(actualFunctionType, fallbackFunctionType, /*isFunctionCall*/ true);
2022-03-11 11:55:02 -05:00
if (state.errors.empty())
state.log.commit();
}
2022-04-28 21:24:24 -04:00
else
{
state.log.commit();
}
TypeId retType = first(retTypePack).value_or(nilType);
if (hasErrors)
retType = errorRecoveryType(retType);
return retType;
};
std::string op = opToMetaTableEntry(expr.op);
2022-07-07 21:22:39 -04:00
if (auto fnt = findMetatableEntry(lhsType, op, expr.location, /* addErrors= */ true))
return checkMetatableCall(*fnt, lhsType, rhsType);
2022-07-07 21:22:39 -04:00
if (auto fnt = findMetatableEntry(rhsType, op, expr.location, /* addErrors= */ true))
{
// Note the intentionally reversed arguments here.
return checkMetatableCall(*fnt, rhsType, lhsType);
}
reportError(expr.location, GenericError{format("Binary operator '%s' not supported by types '%s' and '%s'", toString(expr.op).c_str(),
toString(lhsType).c_str(), toString(rhsType).c_str())});
return errorRecoveryType(scope);
}
switch (expr.op)
{
case AstExprBinary::Concat:
reportErrors(tryUnify(lhsType, addType(UnionType{{stringType, numberType}}), scope, expr.left->location));
reportErrors(tryUnify(rhsType, addType(UnionType{{stringType, numberType}}), scope, expr.right->location));
return stringType;
case AstExprBinary::Add:
case AstExprBinary::Sub:
case AstExprBinary::Mul:
case AstExprBinary::Div:
case AstExprBinary::Mod:
case AstExprBinary::Pow:
reportErrors(tryUnify(lhsType, numberType, scope, expr.left->location));
reportErrors(tryUnify(rhsType, numberType, scope, expr.right->location));
return numberType;
default:
// These should have been handled with checkRelationalOperation
LUAU_ASSERT(0);
return anyType;
}
}
2022-07-07 21:22:39 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional<TypeId> expectedType)
{
if (expr.op == AstExprBinary::And)
{
2022-07-07 21:22:39 -04:00
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
2022-02-04 11:45:57 -05:00
resolve(lhsPredicates, innerScope, true);
2022-07-07 21:22:39 -04:00
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
2022-05-19 20:02:24 -04:00
return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::Or)
{
2022-07-07 21:22:39 -04:00
auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType);
ScopePtr innerScope = childScope(scope, expr.location);
2022-02-04 11:45:57 -05:00
resolve(lhsPredicates, innerScope, false);
2022-07-07 21:22:39 -04:00
auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType);
2022-02-04 11:45:57 -05:00
// Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation.
2022-05-19 20:02:24 -04:00
TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates);
2022-02-04 11:45:57 -05:00
return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}};
}
else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe)
{
if (!FFlag::LuauTypecheckTypeguards)
{
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
}
2022-07-07 21:22:39 -04:00
// For these, passing expectedType is worse than simply forcing them, because their implementation
// may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first.
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true);
if (FFlag::LuauTypecheckTypeguards)
{
if (auto predicate = tryGetTypeGuardPredicate(expr))
return {booleanType, {std::move(*predicate)}};
}
PredicateVec predicates;
if (auto lvalue = tryGetLValue(*expr.left))
predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location});
if (auto lvalue = tryGetLValue(*expr.right))
predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location});
if (!predicates.empty() && expr.op == AstExprBinary::CompareNe)
predicates = {NotPredicate{std::move(predicates)}};
return {checkBinaryOperation(scope, expr, lhs.type, rhs.type), std::move(predicates)};
}
else
{
2022-07-07 21:22:39 -04:00
// Expected types are not useful for other binary operators.
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> lhs = checkExpr(scope, *expr.left);
WithPredicate<TypeId> rhs = checkExpr(scope, *expr.right);
// Intentionally discarding predicates with other operators.
return WithPredicate{checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)};
}
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr)
{
TypeId annotationType = resolveType(scope, *expr.annotation);
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result = checkExpr(scope, *expr.expr, annotationType);
2022-02-04 11:45:57 -05:00
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
if (canUnify(annotationType, result.type, scope, expr.location).empty())
2022-02-04 11:45:57 -05:00
return {annotationType, std::move(result.predicates)};
if (canUnify(result.type, annotationType, scope, expr.location).empty())
return {annotationType, std::move(result.predicates)};
2022-02-04 11:45:57 -05:00
reportError(expr.location, TypesAreUnrelated{result.type, annotationType});
return {errorRecoveryType(annotationType), std::move(result.predicates)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprError& expr)
{
const size_t oldSize = currentModule->errors.size();
for (AstExpr* expr : expr.expressions)
checkExpr(scope, *expr);
// HACK: We want to check the contents of the AstExprError, but
// any type errors that may arise from it are going to be useless.
currentModule->errors.resize(oldSize);
return WithPredicate{errorRecoveryType(scope)};
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional<TypeId> expectedType)
{
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> result = checkExpr(scope, *expr.condition);
2022-05-19 20:02:24 -04:00
ScopePtr trueScope = childScope(scope, expr.trueExpr->location);
2022-05-19 20:02:24 -04:00
resolve(result.predicates, trueScope, true);
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> trueType = checkExpr(trueScope, *expr.trueExpr, expectedType);
ScopePtr falseScope = childScope(scope, expr.falseExpr->location);
resolve(result.predicates, falseScope, false);
2022-06-16 21:05:14 -04:00
WithPredicate<TypeId> falseType = checkExpr(falseScope, *expr.falseExpr, expectedType);
2022-02-17 20:18:01 -05:00
if (falseType.type == trueType.type)
return WithPredicate{trueType.type};
2022-02-17 20:18:01 -05:00
std::vector<TypeId> types = reduceUnion({trueType.type, falseType.type});
if (types.empty())
return WithPredicate{neverType};
return WithPredicate{types.size() == 1 ? types[0] : addType(UnionType{std::move(types)})};
}
WithPredicate<TypeId> TypeChecker::checkExpr(const ScopePtr& scope, const AstExprInterpString& expr)
{
for (AstExpr* expr : expr.expressions)
checkExpr(scope, *expr);
return WithPredicate{stringType};
}
TypeId TypeChecker::checkLValue(const ScopePtr& scope, const AstExpr& expr, ValueContext ctx)
{
return checkLValueBinding(scope, expr, ctx);
}
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExpr& expr, ValueContext ctx)
{
if (auto a = expr.as<AstExprLocal>())
return checkLValueBinding(scope, *a);
else if (auto a = expr.as<AstExprGlobal>())
return checkLValueBinding(scope, *a);
else if (auto a = expr.as<AstExprIndexName>())
return checkLValueBinding(scope, *a, ctx);
else if (auto a = expr.as<AstExprIndexExpr>())
return checkLValueBinding(scope, *a, ctx);
else if (auto a = expr.as<AstExprError>())
{
for (AstExpr* expr : a->expressions)
checkExpr(scope, *expr);
return errorRecoveryType(scope);
}
else
ice("Unexpected AST node in checkLValue", expr.location);
}
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprLocal& expr)
{
if (std::optional<TypeId> ty = scope->lookup(expr.local))
2022-07-07 21:22:39 -04:00
{
ty = follow(*ty);
return get<NeverType>(*ty) ? unknownType : *ty;
2022-07-07 21:22:39 -04:00
}
reportError(expr.location, UnknownSymbol{expr.local->name.value, UnknownSymbol::Binding});
return errorRecoveryType(scope);
}
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprGlobal& expr)
{
Name name = expr.name.value;
ScopePtr moduleScope = currentModule->getModuleScope();
const auto it = moduleScope->bindings.find(expr.name);
if (it != moduleScope->bindings.end())
2022-07-07 21:22:39 -04:00
{
TypeId ty = follow(it->second.typeId);
return get<NeverType>(ty) ? unknownType : ty;
2022-07-07 21:22:39 -04:00
}
TypeId result = freshType(scope);
Binding& binding = moduleScope->bindings[expr.name];
binding = {result, expr.location};
// If we're in strict mode, we want to report defining a global as an error,
// but still add it to the bindings, so that autocomplete includes it in completions.
if (!isNonstrictMode())
reportError(TypeError{expr.location, UnknownSymbol{name, UnknownSymbol::Binding}});
return result;
}
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndexName& expr, ValueContext ctx)
{
TypeId lhs = checkExpr(scope, *expr.expr).type;
if (get<ErrorType>(lhs) || get<AnyType>(lhs))
return lhs;
if (get<NeverType>(lhs))
2022-07-07 21:22:39 -04:00
return unknownType;
tablify(lhs);
Name name = expr.index.value;
lhs = stripFromNilAndReport(lhs, expr.expr->location);
if (TableType* lhsTable = getMutableTableType(lhs))
{
const auto& it = lhsTable->props.find(name);
if (it != lhsTable->props.end())
{
return it->second.type;
}
else if ((ctx == ValueContext::LValue && lhsTable->state == TableState::Unsealed) || lhsTable->state == TableState::Free)
{
TypeId theType = freshType(scope);
Property& property = lhsTable->props[name];
property.type = theType;
property.location = expr.indexLocation;
return theType;
}
else if (auto indexer = lhsTable->indexer)
{
Unifier state = mkUnifier(scope, expr.location);
state.tryUnify(stringType, indexer->indexType);
TypeId retType = indexer->indexResultType;
if (!state.errors.empty())
{
reportError(expr.location, UnknownProperty{lhs, name});
retType = errorRecoveryType(retType);
}
2022-03-11 11:55:02 -05:00
else
state.log.commit();
return retType;
}
else if (lhsTable->state == TableState::Sealed)
{
reportError(TypeError{expr.location, CannotExtendTable{lhs, CannotExtendTable::Property, name}});
return errorRecoveryType(scope);
}
else
{
reportError(TypeError{expr.location, GenericError{"Internal error: generic tables are not lvalues"}});
return errorRecoveryType(scope);
}
}
else if (const ClassType* lhsClass = get<ClassType>(lhs))
{
const Property* prop = lookupClassProp(lhsClass, name);
if (!prop)
{
reportError(TypeError{expr.location, UnknownProperty{lhs, name}});
return errorRecoveryType(scope);
}
return prop->type;
}
else if (get<IntersectionType>(lhs))
{
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, lhs, name, expr.location, /* addErrors= */ false))
return *ty;
// If intersection has a table part, report that it cannot be extended just as a sealed table
if (isTableIntersection(lhs))
{
reportError(TypeError{expr.location, CannotExtendTable{lhs, CannotExtendTable::Property, name}});
return errorRecoveryType(scope);
}
}
reportError(TypeError{expr.location, NotATable{lhs}});
return errorRecoveryType(scope);
}
TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndexExpr& expr, ValueContext ctx)
{
TypeId exprType = checkExpr(scope, *expr.expr).type;
tablify(exprType);
exprType = stripFromNilAndReport(exprType, expr.expr->location);
TypeId indexType = checkExpr(scope, *expr.index).type;
exprType = follow(exprType);
if (get<AnyType>(exprType) || get<ErrorType>(exprType))
return exprType;
if (get<NeverType>(exprType))
2022-07-07 21:22:39 -04:00
return unknownType;
AstExprConstantString* value = expr.index->as<AstExprConstantString>();
if (value)
{
if (const ClassType* exprClass = get<ClassType>(exprType))
{
const Property* prop = lookupClassProp(exprClass, value->value.data);
if (!prop)
{
reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}});
return errorRecoveryType(scope);
}
return prop->type;
}
}
else if (FFlag::LuauAllowIndexClassParameters)
{
if (const ClassType* exprClass = get<ClassType>(exprType))
{
if (isNonstrictMode())
return unknownType;
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
return errorRecoveryType(scope);
}
}
TableType* exprTable = getMutableTableType(exprType);
if (!exprTable)
{
reportError(TypeError{expr.expr->location, NotATable{exprType}});
return errorRecoveryType(scope);
}
if (value)
{
const auto& it = exprTable->props.find(value->value.data);
if (it != exprTable->props.end())
{
return it->second.type;
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
{
TypeId resultType = freshType(scope);
Property& property = exprTable->props[value->value.data];
property.type = resultType;
property.location = expr.index->location;
return resultType;
}
}
if (exprTable->indexer)
{
const TableIndexer& indexer = *exprTable->indexer;
unify(indexType, indexer.indexType, scope, expr.index->location);
return indexer.indexResultType;
}
else if ((ctx == ValueContext::LValue && exprTable->state == TableState::Unsealed) || exprTable->state == TableState::Free)
{
TypeId indexerType = freshType(exprTable->level);
unify(indexType, indexerType, scope, expr.location);
TypeId indexResultType = freshType(exprTable->level);
exprTable->indexer = TableIndexer{anyIfNonstrict(indexerType), anyIfNonstrict(indexResultType)};
return indexResultType;
}
else
{
2022-02-11 14:02:09 -05:00
/*
* If we use [] indexing to fetch a property from a sealed table that
* has no indexer, we have no idea if it will work so we just return any
* and hope for the best.
2022-02-11 14:02:09 -05:00
*/
return anyType;
}
}
// Answers the question: "Can I define another function with this name?"
// Primarily about detecting duplicates.
TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level)
{
auto freshTy = [&]() {
2022-02-24 18:53:37 -05:00
return freshType(level);
};
if (auto globalName = funName.as<AstExprGlobal>())
{
const ScopePtr& moduleScope = currentModule->getModuleScope();
Symbol name = globalName->name;
if (moduleScope->bindings.count(name))
{
if (isNonstrictMode())
return moduleScope->bindings[name].typeId;
return errorRecoveryType(scope);
}
else
{
TypeId ty = freshTy();
moduleScope->bindings[name] = {ty, funName.location};
return ty;
}
}
else if (auto localName = funName.as<AstExprLocal>())
{
Symbol name = localName->local;
Binding& binding = scope->bindings[name];
if (binding.typeId == nullptr)
binding = {freshTy(), funName.location};
return binding.typeId;
}
else if (auto indexName = funName.as<AstExprIndexName>())
{
TypeId lhsType = checkExpr(scope, *indexName->expr).type;
TableType* ttv = getMutableTableType(lhsType);
2022-05-13 15:36:37 -04:00
if (!ttv || ttv->state == TableState::Sealed)
2022-03-17 20:46:04 -04:00
{
2022-07-07 21:22:39 -04:00
if (auto ty = getIndexTypeFromType(scope, lhsType, indexName->index.value, indexName->indexLocation, /* addErrors= */ false))
2022-05-13 15:36:37 -04:00
return *ty;
2022-04-07 17:29:01 -04:00
2022-05-13 15:36:37 -04:00
return errorRecoveryType(scope);
2022-03-17 20:46:04 -04:00
}
Name name = indexName->index.value;
if (ttv->props.count(name))
2022-05-13 15:36:37 -04:00
return ttv->props[name].type;
Property& property = ttv->props[name];
property.type = freshTy();
property.location = indexName->indexLocation;
return property.type;
}
else if (funName.is<AstExprError>())
return errorRecoveryType(scope);
else
{
ice("Unexpected AST node type", funName.location);
}
}
// This returns a pair `[funType, funScope]` where
// - funType is the prototype type of the function
// - funScope is the scope for the function, which is a child scope with bindings added for
// parameters (and generic types if there were explicit generic annotations).
//
// The function type is a prototype, in that it may be missing some generic types which
// can only be inferred from type inference after typechecking the function body.
// For example the function `function id(x) return x end` has prototype
// `(X) -> Y...`, but after typechecking the body, we cam unify `Y...` with `X`
// to get type `(X) -> X`, then we quantify the free types to get the final
// generic type `<a>(a) -> a`.
2022-06-30 19:52:43 -04:00
std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(const ScopePtr& scope, int subLevel, const AstExprFunction& expr,
std::optional<Location> originalName, std::optional<TypeId> selfType, std::optional<TypeId> expectedType)
{
ScopePtr funScope = childFunctionScope(scope, expr.location, subLevel);
const FunctionType* expectedFunctionType = nullptr;
if (expectedType)
{
LUAU_ASSERT(!expr.self);
if (auto ftv = get<FunctionType>(follow(*expectedType)))
{
expectedFunctionType = ftv;
}
else if (auto utv = get<UnionType>(follow(*expectedType)))
{
// Look for function type in a union. Other types can be ignored since current expression is a function
for (auto option : utv)
{
if (auto ftv = get<FunctionType>(follow(option)))
{
if (!expectedFunctionType)
{
expectedFunctionType = ftv;
}
else
{
// Do not infer argument types when multiple overloads are expected
expectedFunctionType = nullptr;
break;
}
}
}
}
}
auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks);
TypePackId retPack;
2022-02-17 20:18:01 -05:00
if (expr.returnAnnotation)
retPack = resolveTypePack(funScope, *expr.returnAnnotation);
2022-07-21 17:16:54 -04:00
else if (isNonstrictMode())
retPack = anyTypePack;
else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty())
{
2022-06-16 21:05:14 -04:00
auto [head, tail] = flatten(expectedFunctionType->retTypes);
// Do not infer 'nil' as function return type
if (!tail && head.size() == 1 && isNil(head[0]))
retPack = freshTypePack(funScope);
else
retPack = addTypePack(head, tail);
}
else
retPack = freshTypePack(funScope);
if (expr.vararg)
{
if (expr.varargAnnotation)
funScope->varargPack = resolveTypePack(funScope, *expr.varargAnnotation);
else
{
if (expectedFunctionType && !isNonstrictMode())
{
auto [head, tail] = flatten(expectedFunctionType->argTypes);
if (expr.args.size <= head.size())
{
head.erase(head.begin(), head.begin() + expr.args.size);
funScope->varargPack = addTypePack(head, tail);
}
else if (tail)
{
if (get<VariadicTypePack>(follow(*tail)))
funScope->varargPack = addTypePack({}, tail);
}
else
{
funScope->varargPack = addTypePack({});
}
}
// TODO: should this be a free type pack? CLI-39910
if (!funScope->varargPack)
funScope->varargPack = anyTypePack;
}
}
std::vector<TypeId> argTypes;
funScope->returnType = retPack;
2022-06-30 19:52:43 -04:00
if (FFlag::DebugLuauSharedSelf)
{
2022-06-30 19:52:43 -04:00
if (expr.self)
{
// TODO: generic self types: CLI-39906
TypeId selfTy = anyIfNonstrict(selfType ? *selfType : freshType(funScope));
funScope->bindings[expr.self] = {selfTy, expr.self->location};
argTypes.push_back(selfTy);
}
}
else
{
if (expr.self)
{
// TODO: generic self types: CLI-39906
TypeId selfType = anyIfNonstrict(freshType(funScope));
funScope->bindings[expr.self] = {selfType, expr.self->location};
argTypes.push_back(selfType);
}
}
// Prepare expected argument type iterators if we have an expected function type
TypePackIterator expectedArgsCurr, expectedArgsEnd;
if (expectedFunctionType && !isNonstrictMode())
{
expectedArgsCurr = begin(expectedFunctionType->argTypes);
expectedArgsEnd = end(expectedFunctionType->argTypes);
}
for (AstLocal* local : expr.args)
{
TypeId argType = nullptr;
if (local->annotation)
{
argType = resolveType(funScope, *local->annotation);
// If the annotation type has an error, treat it as if there was no annotation
if (get<ErrorType>(follow(argType)))
argType = anyIfNonstrict(freshType(funScope));
}
else
{
if (expectedFunctionType && !isNonstrictMode())
{
if (expectedArgsCurr != expectedArgsEnd)
{
argType = *expectedArgsCurr;
}
else if (auto expectedArgsTail = expectedArgsCurr.tail())
{
if (const VariadicTypePack* vtp = get<VariadicTypePack>(follow(*expectedArgsTail)))
argType = vtp->ty;
}
}
if (!argType)
argType = anyIfNonstrict(freshType(funScope));
}
funScope->bindings[local] = {argType, local->location};
argTypes.push_back(argType);
if (expectedArgsCurr != expectedArgsEnd)
++expectedArgsCurr;
}
TypePackId argPack = addTypePack(TypePackVar(TypePack{argTypes, funScope->varargPack}));
FunctionDefinition defn;
defn.definitionModuleName = currentModuleName;
defn.definitionLocation = expr.location;
defn.varargLocation = expr.vararg ? std::make_optional(expr.varargLocation) : std::nullopt;
defn.originalNameLocation = originalName.value_or(Location(expr.location.begin, 0));
2022-01-14 11:20:09 -05:00
std::vector<TypeId> genericTys;
2022-07-07 21:22:39 -04:00
// if we have a generic expected function type and no generics, we should use the expected ones.
if (expectedFunctionType && generics.empty())
2022-07-07 21:22:39 -04:00
{
genericTys = expectedFunctionType->generics;
2022-07-07 21:22:39 -04:00
}
else
{
genericTys.reserve(generics.size());
for (const GenericTypeDefinition& generic : generics)
genericTys.push_back(generic.ty);
2022-07-07 21:22:39 -04:00
}
2022-01-14 11:20:09 -05:00
std::vector<TypePackId> genericTps;
2022-07-07 21:22:39 -04:00
// if we have a generic expected function type and no generic typepacks, we should use the expected ones.
if (expectedFunctionType && genericPacks.empty())
2022-07-07 21:22:39 -04:00
{
genericTps = expectedFunctionType->genericPacks;
2022-07-07 21:22:39 -04:00
}
else
{
genericTps.reserve(genericPacks.size());
for (const GenericTypePackDefinition& generic : genericPacks)
genericTps.push_back(generic.tp);
2022-07-07 21:22:39 -04:00
}
2022-01-14 11:20:09 -05:00
TypeId funTy =
addType(FunctionType(funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack, std::move(defn), bool(expr.self)));
FunctionType* ftv = getMutable<FunctionType>(funTy);
ftv->argNames.reserve(expr.args.size + (expr.self ? 1 : 0));
if (expr.self)
ftv->argNames.push_back(FunctionArgument{"self", {}});
for (AstLocal* local : expr.args)
ftv->argNames.push_back(FunctionArgument{local->name.value, local->location});
return std::make_pair(funTy, funScope);
}
static bool allowsNoReturnValues(const TypePackId tp)
{
for (TypeId ty : tp)
{
if (!get<ErrorType>(follow(ty)))
{
return false;
}
}
return true;
}
static Location getEndLocation(const AstExprFunction& function)
{
Location loc = function.location;
if (loc.begin.line != loc.end.line)
{
Position begin = loc.end;
begin.column = std::max(0u, begin.column - 3);
loc = Location(begin, 3);
}
return loc;
}
void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstExprFunction& function)
{
LUAU_TIMETRACE_SCOPE("TypeChecker::checkFunctionBody", "TypeChecker");
if (function.debugname.value)
LUAU_TIMETRACE_ARGUMENT("name", function.debugname.value);
else
LUAU_TIMETRACE_ARGUMENT("line", std::to_string(function.location.begin.line).c_str());
if (FunctionType* funTy = getMutable<FunctionType>(ty))
{
check(scope, *function.body);
// We explicitly don't follow here to check if we have a 'true' free type instead of bound one
if (get_if<FreeTypePack>(&funTy->retTypes->ty))
*asMutable(funTy->retTypes) = TypePack{{}, std::nullopt};
bool reachesImplicitReturn = getFallthrough(function.body) != nullptr;
2022-06-16 21:05:14 -04:00
if (reachesImplicitReturn && !allowsNoReturnValues(follow(funTy->retTypes)))
{
// If we're in nonstrict mode we want to only report this missing return
// statement if there are type annotations on the function. In strict mode
// we report it regardless.
2022-02-17 20:18:01 -05:00
if (!isNonstrictMode() || function.returnAnnotation)
{
2022-06-16 21:05:14 -04:00
reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes});
}
}
if (!currentModule->astTypes.find(&function))
currentModule->astTypes[&function] = ty;
}
else
ice("Checking non functional type");
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypePackId> TypeChecker::checkExprPack(const ScopePtr& scope, const AstExpr& expr)
2022-07-07 21:22:39 -04:00
{
WithPredicate<TypePackId> result = checkExprPackHelper(scope, expr);
if (containsNever(result.type))
return WithPredicate{uninhabitableTypePack};
return result;
2022-07-07 21:22:39 -04:00
}
WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope, const AstExpr& expr)
{
if (auto a = expr.as<AstExprCall>())
2022-07-07 21:22:39 -04:00
return checkExprPackHelper(scope, *a);
else if (expr.is<AstExprVarargs>())
{
if (!scope->varargPack)
return WithPredicate{errorRecoveryTypePack(scope)};
return WithPredicate{*scope->varargPack};
}
else
{
TypeId type = checkExpr(scope, expr).type;
return WithPredicate{addTypePack({type})};
}
}
void TypeChecker::checkArgumentList(const ScopePtr& scope, const AstExpr& funName, Unifier& state, TypePackId argPack, TypePackId paramPack,
const std::vector<Location>& argLocations)
{
/* Important terminology refresher:
* A function requires parameters.
* To call a function, you supply arguments.
*/
TypePackIterator argIter = begin(argPack, &state.log);
TypePackIterator paramIter = begin(paramPack, &state.log);
TypePackIterator endIter = end(argPack); // Important subtlety: All end TypePackIterators are equivalent
size_t paramIndex = 0;
auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() {
2022-04-14 19:57:43 -04:00
// For this case, we want the error span to cover every errant extra parameter
Location location = state.location;
if (!argLocations.empty())
location = {state.location.begin, argLocations.back().end};
std::string namePath;
if (std::optional<std::string> path = getFunctionNameAsString(funName))
namePath = *path;
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
state.reportError(TypeError{location,
CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, namePath}});
2022-04-14 19:57:43 -04:00
};
2022-03-11 11:55:02 -05:00
while (true)
{
2022-03-11 11:55:02 -05:00
state.location = paramIndex < argLocations.size() ? argLocations[paramIndex] : state.location;
2022-03-11 11:55:02 -05:00
if (argIter == endIter && paramIter == endIter)
{
std::optional<TypePackId> argTail = argIter.tail();
std::optional<TypePackId> paramTail = paramIter.tail();
// If we hit the end of both type packs simultaneously, we have to unify them.
// But if one side has a free tail and the other has none at all, we create an empty pack and bind the free tail to that.
2022-03-11 11:55:02 -05:00
if (argTail)
{
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*argTail)))
{
2022-03-11 11:55:02 -05:00
if (paramTail)
state.tryUnify(*paramTail, *argTail);
else
state.log.replace(*argTail, TypePackVar(TypePack{{}}));
}
else if (paramTail)
{
state.tryUnify(*argTail, *paramTail);
}
}
2022-03-11 11:55:02 -05:00
else if (paramTail)
{
2022-03-11 11:55:02 -05:00
// argTail is definitely empty
if (state.log.getMutable<Unifiable::Free>(state.log.follow(*paramTail)))
state.log.replace(*paramTail, TypePackVar(TypePack{{}}));
}
2022-03-11 11:55:02 -05:00
return;
}
else if (argIter == endIter)
{
// Not enough arguments.
2022-03-11 11:55:02 -05:00
// Might be ok if we are forwarding a vararg along. This is a common thing to occur in nonstrict mode.
if (argIter.tail())
{
2022-03-11 11:55:02 -05:00
TypePackId tail = *argIter.tail();
if (state.log.getMutable<Unifiable::Error>(tail))
{
2022-03-11 11:55:02 -05:00
// Unify remaining parameters so we don't leave any free-types hanging around.
while (paramIter != endIter)
{
2022-03-11 11:55:02 -05:00
state.tryUnify(errorRecoveryType(anyType), *paramIter);
++paramIter;
}
return;
}
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
2022-04-14 19:57:43 -04:00
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
2022-03-11 11:55:02 -05:00
while (paramIter != endIter)
{
2022-03-11 11:55:02 -05:00
state.tryUnify(vtp->ty, *paramIter);
++paramIter;
}
return;
}
else if (state.log.getMutable<FreeTypePack>(tail))
{
std::vector<TypeId> rest;
2022-03-11 11:55:02 -05:00
rest.reserve(std::distance(paramIter, endIter));
while (paramIter != endIter)
{
2022-03-11 11:55:02 -05:00
rest.push_back(*paramIter);
++paramIter;
}
2022-03-11 11:55:02 -05:00
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}});
state.tryUnify(tail, varPack);
return;
}
2022-03-11 11:55:02 -05:00
}
// If any remaining unfulfilled parameters are nonoptional, this is a problem.
while (paramIter != endIter)
{
TypeId t = state.log.follow(*paramIter);
if (isOptional(t))
{
2022-03-11 11:55:02 -05:00
} // ok
else if (state.log.getMutable<ErrorType>(t))
{
2022-03-11 11:55:02 -05:00
} // ok
else
{
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
2022-04-14 19:57:43 -04:00
2022-04-28 21:24:24 -04:00
std::optional<TypePackId> tail = flatten(paramPack, state.log).second;
bool isVariadic = tail && Luau::isVariadic(*tail);
2022-04-14 19:57:43 -04:00
std::string namePath;
if (std::optional<std::string> path = getFunctionNameAsString(funName))
namePath = *path;
state.reportError(TypeError{
funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, namePath}});
return;
}
++paramIter;
}
}
2022-03-11 11:55:02 -05:00
else if (paramIter == endIter)
{
2022-03-11 11:55:02 -05:00
// too many parameters passed
if (!paramIter.tail())
{
2022-03-11 11:55:02 -05:00
while (argIter != endIter)
{
2022-03-11 11:55:02 -05:00
// The use of unify here is deliberate. We don't want this unification
// to be undoable.
unify(errorRecoveryType(scope), *argIter, scope, state.location);
2022-03-11 11:55:02 -05:00
++argIter;
}
2022-04-14 19:57:43 -04:00
reportCountMismatchError();
2022-03-11 11:55:02 -05:00
return;
}
TypePackId tail = state.log.follow(*paramIter.tail());
2022-03-11 11:55:02 -05:00
if (state.log.getMutable<Unifiable::Error>(tail))
{
// Function is variadic. Ok.
return;
}
2022-03-11 11:55:02 -05:00
else if (auto vtp = state.log.getMutable<VariadicTypePack>(tail))
{
2022-03-11 11:55:02 -05:00
// Function is variadic and requires that all subsequent parameters
// be compatible with a type.
size_t argIndex = paramIndex;
while (argIter != endIter)
{
2022-03-11 11:55:02 -05:00
Location location = state.location;
2022-03-11 11:55:02 -05:00
if (argIndex < argLocations.size())
location = argLocations[argIndex];
unify(*argIter, vtp->ty, scope, location);
2022-03-11 11:55:02 -05:00
++argIter;
++argIndex;
}
2022-03-11 11:55:02 -05:00
return;
}
2022-03-11 11:55:02 -05:00
else if (state.log.getMutable<FreeTypePack>(tail))
{
2022-03-11 11:55:02 -05:00
// Create a type pack out of the remaining argument types
// and unify it with the tail.
std::vector<TypeId> rest;
rest.reserve(std::distance(argIter, endIter));
while (argIter != endIter)
{
2022-03-11 11:55:02 -05:00
rest.push_back(*argIter);
++argIter;
}
2022-03-11 11:55:02 -05:00
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
state.tryUnify(varPack, tail);
2022-03-17 20:46:04 -04:00
2022-03-11 11:55:02 -05:00
return;
}
2022-03-11 11:55:02 -05:00
else if (state.log.getMutable<FreeTypePack>(tail))
{
2022-03-11 11:55:02 -05:00
state.log.replace(tail, TypePackVar(TypePack{{}}));
return;
}
else if (state.log.getMutable<GenericTypePack>(tail))
{
2022-04-14 19:57:43 -04:00
reportCountMismatchError();
2022-03-11 11:55:02 -05:00
return;
}
}
2022-03-11 11:55:02 -05:00
else
{
if (FFlag::LuauInstantiateInSubtyping)
state.tryUnify(*argIter, *paramIter, /*isFunctionCall*/ false);
else
unifyWithInstantiationIfNeeded(*argIter, *paramIter, scope, state);
2022-03-11 11:55:02 -05:00
++argIter;
++paramIter;
}
++paramIndex;
}
}
2022-07-07 21:22:39 -04:00
WithPredicate<TypePackId> TypeChecker::checkExprPackHelper(const ScopePtr& scope, const AstExprCall& expr)
{
// evaluate type of function
// decompose an intersection into its component overloads
// Compute types of parameters
// For each overload
// Compare parameter and argument types
// Report any errors (also speculate dot vs colon warnings!)
// Return the resulting return type (even if there are errors)
// If there are no matching overloads, unify with (a...) -> (b...) and return b...
TypeId selfType = nullptr;
TypeId functionType = nullptr;
TypeId actualFunctionType = nullptr;
if (expr.self)
{
AstExprIndexName* indexExpr = expr.func->as<AstExprIndexName>();
if (!indexExpr)
ice("method call expression has no 'self'");
selfType = checkExpr(scope, *indexExpr->expr).type;
selfType = stripFromNilAndReport(selfType, expr.func->location);
2022-07-07 21:22:39 -04:00
if (std::optional<TypeId> propTy = getIndexTypeFromType(scope, selfType, indexExpr->index.value, expr.location, /* addErrors= */ true))
{
functionType = *propTy;
actualFunctionType = instantiate(scope, functionType, expr.func->location);
}
else
{
functionType = errorRecoveryType(scope);
actualFunctionType = functionType;
}
}
else
{
functionType = checkExpr(scope, *expr.func).type;
actualFunctionType = instantiate(scope, functionType, expr.func->location);
}
2022-03-17 20:46:04 -04:00
TypePackId retPack;
if (auto free = get<FreeType>(actualFunctionType))
2022-03-17 20:46:04 -04:00
{
retPack = freshTypePack(free->level);
TypePackId freshArgPack = freshTypePack(free->level);
emplaceType<FunctionType>(asMutable(actualFunctionType), free->level, freshArgPack, retPack);
2022-03-17 20:46:04 -04:00
}
else
retPack = freshTypePack(scope->level);
2022-03-17 20:46:04 -04:00
// We break this function up into a lambda here to limit our stack footprint.
// The vectors used by this function aren't allocated until the lambda is actually called.
auto the_rest = [&]() -> WithPredicate<TypePackId> {
// checkExpr will log the pre-instantiated type of the function.
// That's not nearly as interesting as the instantiated type, which will include details about how
// generic functions are being instantiated for this particular callsite.
currentModule->astOriginalCallTypes[expr.func] = follow(functionType);
currentModule->astTypes[expr.func] = actualFunctionType;
std::vector<TypeId> overloads = flattenIntersection(actualFunctionType);
std::vector<std::optional<TypeId>> expectedTypes = getExpectedTypesForCall(overloads, expr.args.size, expr.self);
WithPredicate<TypePackId> argListResult = checkExprList(scope, expr.location, expr.args, false, {}, expectedTypes);
TypePackId argPack = argListResult.type;
if (get<Unifiable::Error>(argPack))
return WithPredicate{errorRecoveryTypePack(scope)};
TypePack* args = nullptr;
if (expr.self)
{
argPack = addTypePack(TypePack{{selfType}, argPack});
argListResult.type = argPack;
}
args = getMutable<TypePack>(argPack);
LUAU_ASSERT(args);
std::vector<Location> argLocations;
argLocations.reserve(expr.args.size + 1);
if (expr.self)
argLocations.push_back(expr.func->as<AstExprIndexName>()->expr->location);
for (AstExpr* arg : expr.args)
argLocations.push_back(arg->location);
std::vector<OverloadErrorEntry> errors; // errors encountered for each overload
std::vector<TypeId> overloadsThatMatchArgCount;
std::vector<TypeId> overloadsThatDont;
for (TypeId fn : overloads)
{
fn = follow(fn);
if (auto ret = checkCallOverload(
scope, expr, fn, retPack, argPack, args, &argLocations, argListResult, overloadsThatMatchArgCount, overloadsThatDont, errors))
return *ret;
}
if (handleSelfCallMismatch(scope, expr, args, argLocations, errors))
return WithPredicate{retPack};
reportOverloadResolutionError(scope, expr, retPack, argPack, argLocations, overloads, overloadsThatMatchArgCount, errors);
const FunctionType* overload = nullptr;
if (!overloadsThatMatchArgCount.empty())
overload = get<FunctionType>(overloadsThatMatchArgCount[0]);
if (!overload && !overloadsThatDont.empty())
overload = get<FunctionType>(overloadsThatDont[0]);
if (overload)
return WithPredicate{errorRecoveryTypePack(overload->retTypes)};
return WithPredicate{errorRecoveryTypePack(retPack)};
};
return the_rest();
}
std::vector<std::optional<TypeId>> TypeChecker::getExpectedTypesForCall(const std::vector<TypeId>& overloads, size_t argumentCount, bool selfCall)
{
std::vector<std::optional<TypeId>> expectedTypes;
2022-06-16 21:05:14 -04:00
auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) {
if (index == expectedTypes.size())
{
expectedTypes.push_back(ty);
}
else if (ty)
{
auto& el = expectedTypes[index];
if (!el)
{
el = ty;
}
else
{
2022-06-16 21:05:14 -04:00
std::vector<TypeId> result = reduceUnion({*el, ty});
if (result.empty())
2022-07-07 21:22:39 -04:00
el = neverType;
else
el = result.size() == 1 ? result[0] : addType(UnionType{std::move(result)});
}
}
};
for (const TypeId overload : overloads)
{
if (const FunctionType* ftv = get<FunctionType>(overload))
{
auto [argsHead, argsTail] = flatten(ftv->argTypes);
size_t start = selfCall ? 1 : 0;
size_t index = 0;
for (size_t i = start; i < argsHead.size(); ++i)
assignOption(index++, argsHead[i]);
if (argsTail)
{
2022-06-16 21:05:14 -04:00
argsTail = follow(*argsTail);
if (const VariadicTypePack* vtp = get<VariadicTypePack>(*argsTail))
{
while (index < argumentCount)
assignOption(index++, vtp->ty);
}
}
}
}
2022-06-16 21:05:14 -04:00
Demoter demoter{&currentModule->internalTypes};
demoter.demote(expectedTypes);
return expectedTypes;
}
/*
* Note: We return a std::unique_ptr here rather than an optional to manage our stack consumption.
* If this was an optional, callers would have to pay the stack cost for the result. This is problematic
* for functions that need to support recursion up to 600 levels deep.
*/
std::unique_ptr<WithPredicate<TypePackId>> TypeChecker::checkCallOverload(const ScopePtr& scope, const AstExprCall& expr, TypeId fn,
TypePackId retPack, TypePackId argPack, TypePack* args, const std::vector<Location>* argLocations, const WithPredicate<TypePackId>& argListResult,
std::vector<TypeId>& overloadsThatMatchArgCount, std::vector<TypeId>& overloadsThatDont, std::vector<OverloadErrorEntry>& errors)
{
LUAU_ASSERT(argLocations);
fn = stripFromNilAndReport(fn, expr.func->location);
if (get<AnyType>(fn))
{
unify(anyTypePack, argPack, scope, expr.location);
return std::make_unique<WithPredicate<TypePackId>>(anyTypePack);
}
if (get<ErrorType>(fn))
{
return std::make_unique<WithPredicate<TypePackId>>(errorRecoveryTypePack(scope));
}
if (get<NeverType>(fn))
return std::make_unique<WithPredicate<TypePackId>>(uninhabitableTypePack);
2022-07-07 21:22:39 -04:00
if (auto ftv = get<FreeType>(fn))
{
// fn is one of the overloads of actualFunctionType, which
// has been instantiated, so is a monotype. We can therefore
// unify it with a monomorphic function.
TypeId r = addType(FunctionType(scope->level, argPack, retPack));
2022-04-14 19:57:43 -04:00
UnifierOptions options;
options.isFunctionCall = true;
unify(r, fn, scope, expr.location, options);
return std::make_unique<WithPredicate<TypePackId>>(retPack);
}
std::vector<Location> metaArgLocations;
// Might be a callable table or class
std::optional<TypeId> callTy = std::nullopt;
if (const MetatableType* mttv = get<MetatableType>(fn))
{
callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
else if (const ClassType* ctv = get<ClassType>(fn); ctv && ctv->metatable)
{
callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false);
}
if (callTy)
{
// Construct arguments with 'self' added in front
TypePackId metaCallArgPack = addTypePack(TypePackVar(TypePack{args->head, args->tail}));
TypePack* metaCallArgs = getMutable<TypePack>(metaCallArgPack);
metaCallArgs->head.insert(metaCallArgs->head.begin(), fn);
metaArgLocations = *argLocations;
metaArgLocations.insert(metaArgLocations.begin(), expr.func->location);
fn = instantiate(scope, *callTy, expr.func->location);
argPack = metaCallArgPack;
args = metaCallArgs;
argLocations = &metaArgLocations;
}
const FunctionType* ftv = get<FunctionType>(fn);
if (!ftv)
{
reportError(TypeError{expr.func->location, CannotCallNonFunction{fn}});
unify(errorRecoveryTypePack(scope), retPack, scope, expr.func->location);
return std::make_unique<WithPredicate<TypePackId>>(errorRecoveryTypePack(retPack));
}
// When this function type has magic functions and did return something, we select that overload instead.
// TODO: pass in a Unifier object to the magic functions? This will allow the magic functions to cooperate with overload resolution.
if (ftv->magicFunction)
{
// TODO: We're passing in the wrong TypePackId. Should be argPack, but a unit test fails otherwise. CLI-40458
2022-06-16 21:05:14 -04:00
if (std::optional<WithPredicate<TypePackId>> ret = ftv->magicFunction(*this, scope, expr, argListResult))
return std::make_unique<WithPredicate<TypePackId>>(std::move(*ret));
}
Unifier state = mkUnifier(scope, expr.location);
// Unify return types
checkArgumentList(scope, *expr.func, state, retPack, ftv->retTypes, /*argLocations*/ {});
if (!state.errors.empty())
{
return nullptr;
}
checkArgumentList(scope, *expr.func, state, argPack, ftv->argTypes, *argLocations);
if (!state.errors.empty())
{
bool argMismatch = false;
for (auto error : state.errors)
{
CountMismatch* cm = get<CountMismatch>(error);
if (!cm)
continue;
if (cm->context == CountMismatch::Arg)
{
argMismatch = true;
break;
}
}
if (!argMismatch)
overloadsThatMatchArgCount.push_back(fn);
2022-04-28 21:24:24 -04:00
else
overloadsThatDont.push_back(fn);
errors.emplace_back(std::move(state.errors), args->head, ftv);
}
else
{
2022-03-11 11:55:02 -05:00
state.log.commit();
currentModule->astOverloadResolvedTypes[&expr] = fn;
// We select this overload
return std::make_unique<WithPredicate<TypePackId>>(retPack);
}
return nullptr;
}
bool TypeChecker::handleSelfCallMismatch(const ScopePtr& scope, const AstExprCall& expr, TypePack* args, const std::vector<Location>& argLocations,
const std::vector<OverloadErrorEntry>& errors)
{
// No overloads succeeded: Scan for one that would have worked had the user
// used a.b() rather than a:b() or vice versa.
for (const auto& [_, argVec, ftv] : errors)
{
// Did you write foo:bar() when you should have written foo.bar()?
if (expr.self)
{
std::vector<Location> editedArgLocations(argLocations.begin() + 1, argLocations.end());
std::vector<TypeId> editedParamList(args->head.begin() + 1, args->head.end());
TypePackId editedArgPack = addTypePack(TypePack{editedParamList});
Unifier editedState = mkUnifier(scope, expr.location);
checkArgumentList(scope, *expr.func, editedState, editedArgPack, ftv->argTypes, editedArgLocations);
if (editedState.errors.empty())
{
2022-03-11 11:55:02 -05:00
editedState.log.commit();
reportError(TypeError{expr.location, FunctionDoesNotTakeSelf{}});
// This is a little bit suspect: If this overload would work with a . replaced by a :
// we eagerly assume that that's what you actually meant and we commit to it.
// This could be incorrect if the function has an additional overload that
// actually works.
2022-06-16 21:05:14 -04:00
// checkArgumentList(scope, editedState, retPack, ftv->retTypes, retLocations, CountMismatch::Return);
return true;
}
}
else if (ftv->hasSelf)
{
// Did you write foo.bar() when you should have written foo:bar()?
if (AstExprIndexName* indexName = expr.func->as<AstExprIndexName>())
{
std::vector<Location> editedArgLocations;
editedArgLocations.reserve(argLocations.size() + 1);
editedArgLocations.push_back(indexName->expr->location);
editedArgLocations.insert(editedArgLocations.end(), argLocations.begin(), argLocations.end());
std::vector<TypeId> editedArgList(args->head);
editedArgList.insert(editedArgList.begin(), checkExpr(scope, *indexName->expr).type);
TypePackId editedArgPack = addTypePack(TypePack{editedArgList});
Unifier editedState = mkUnifier(scope, expr.location);
checkArgumentList(scope, *expr.func, editedState, editedArgPack, ftv->argTypes, editedArgLocations);
if (editedState.errors.empty())
{
2022-03-11 11:55:02 -05:00
editedState.log.commit();
reportError(TypeError{expr.location, FunctionRequiresSelf{}});
// This is a little bit suspect: If this overload would work with a : replaced by a .
// we eagerly assume that that's what you actually meant and we commit to it.
// This could be incorrect if the function has an additional overload that
// actually works.
2022-06-16 21:05:14 -04:00
// checkArgumentList(scope, editedState, retPack, ftv->retTypes, retLocations, CountMismatch::Return);
return true;
}
}
}
}
return false;
}
void TypeChecker::reportOverloadResolutionError(const ScopePtr& scope, const AstExprCall& expr, TypePackId retPack, TypePackId argPack,
const std::vector<Location>& argLocations, const std::vector<TypeId>& overloads, const std::vector<TypeId>& overloadsThatMatchArgCount,
const std::vector<OverloadErrorEntry>& errors)
{
if (overloads.size() == 1)
{
reportErrors(std::get<0>(errors.front()));
return;
}
std::vector<TypeId> overloadTypes = overloadsThatMatchArgCount;
if (overloadsThatMatchArgCount.size() == 0)
{
reportError(TypeError{expr.location, GenericError{"No overload for function accepts " + std::to_string(size(argPack)) + " arguments."}});
// If no overloads match argument count, just list all overloads.
overloadTypes = overloads;
}
else
{
// Report errors of the first argument-count-matching, but failing overload
TypeId overload = overloadsThatMatchArgCount[0];
// Remove the overload we are reporting errors about, from the list of alternative
overloadTypes.erase(std::remove(overloadTypes.begin(), overloadTypes.end(), overload), overloadTypes.end());
const FunctionType* ftv = get<FunctionType>(overload);
auto error = std::find_if(errors.begin(), errors.end(), [ftv](const OverloadErrorEntry& e) {
return ftv == std::get<2>(e);
});
LUAU_ASSERT(error != errors.end());
reportErrors(std::get<0>(*error));
// If only one overload matched, we don't need this error because we provided the previous errors.
if (overloadsThatMatchArgCount.size() == 1)
return;
}
std::string s;
for (size_t i = 0; i < overloadTypes.size(); ++i)
{
TypeId overload = follow(overloadTypes[i]);
Unifier state = mkUnifier(scope, expr.location);
// Unify return types
if (const FunctionType* ftv = get<FunctionType>(overload))
{
checkArgumentList(scope, *expr.func, state, retPack, ftv->retTypes, {});
checkArgumentList(scope, *expr.func, state, argPack, ftv->argTypes, argLocations);
}
2022-03-11 11:55:02 -05:00
if (state.errors.empty())
state.log.commit();
if (i > 0)
s += "; ";
if (i > 0 && i == overloadTypes.size() - 1)
s += "and ";
s += toString(overload);
}
if (overloadsThatMatchArgCount.size() == 0)
reportError(expr.func->location, ExtraInformation{"Available overloads: " + s});
else
reportError(expr.func->location, ExtraInformation{"Other overloads are also not viable: " + s});
// No viable overload
return;
}
2022-06-16 21:05:14 -04:00
WithPredicate<TypePackId> TypeChecker::checkExprList(const ScopePtr& scope, const Location& location, const AstArray<AstExpr*>& exprs,
bool substituteFreeForNil, const std::vector<bool>& instantiateGenerics, const std::vector<std::optional<TypeId>>& expectedTypes)
{
2022-07-07 21:22:39 -04:00
bool uninhabitable = false;
TypePackId pack = addTypePack(TypePack{});
PredicateVec predicates; // At the moment we will be pushing all predicate sets into this. Do we need some way to split them up?
auto insert = [&predicates](PredicateVec& vec) {
for (Predicate& c : vec)
predicates.push_back(std::move(c));
};
if (exprs.size == 0)
return WithPredicate{pack};
TypePack* tp = getMutable<TypePack>(pack);
size_t lastIndex = exprs.size - 1;
tp->head.reserve(lastIndex);
Unifier state = mkUnifier(scope, location);
std::vector<TxnLog> inverseLogs;
for (size_t i = 0; i < exprs.size; ++i)
{
AstExpr* expr = exprs.data[i];
std::optional<TypeId> expectedType = i < expectedTypes.size() ? expectedTypes[i] : std::nullopt;
if (i == lastIndex && (expr->is<AstExprCall>() || expr->is<AstExprVarargs>()))
{
auto [typePack, exprPredicates] = checkExprPack(scope, *expr);
insert(exprPredicates);
if (containsNever(typePack))
2022-07-07 21:22:39 -04:00
{
2022-07-14 18:52:26 -04:00
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never,
// ...never)
2022-07-07 21:22:39 -04:00
uninhabitable = true;
continue;
}
else if (std::optional<TypeId> firstTy = first(typePack))
{
if (!currentModule->astTypes.find(expr))
currentModule->astTypes[expr] = follow(*firstTy);
}
if (expectedType)
currentModule->astExpectedTypes[expr] = *expectedType;
tp->tail = typePack;
}
else
{
auto [type, exprPredicates] = checkExpr(scope, *expr, expectedType);
insert(exprPredicates);
if (get<NeverType>(type))
2022-07-07 21:22:39 -04:00
{
2022-07-14 18:52:26 -04:00
// f(), g() where f() returns (never, string) or (string, never) means this whole TypePackId is uninhabitable, so return (never,
// ...never)
2022-07-07 21:22:39 -04:00
uninhabitable = true;
continue;
}
TypeId actualType = substituteFreeForNil && expr->is<AstExprConstantNil>() ? freshType(scope) : type;
if (!FFlag::LuauInstantiateInSubtyping)
{
if (instantiateGenerics.size() > i && instantiateGenerics[i])
actualType = instantiate(scope, actualType, expr->location);
}
if (expectedType)
{
state.tryUnify(actualType, *expectedType);
// Ugly: In future iterations of the loop, we might need the state of the unification we
// just performed. There's not a great way to pass that into checkExpr. Instead, we store
// the inverse of the current log, and commit it. When we're done, we'll commit all the
// inverses. This isn't optimal, and a better solution is welcome here.
2022-03-11 11:55:02 -05:00
inverseLogs.push_back(state.log.inverse());
state.log.commit();
}
tp->head.push_back(actualType);
}
}
2022-03-11 11:55:02 -05:00
for (TxnLog& log : inverseLogs)
log.commit();
if (uninhabitable)
return WithPredicate{uninhabitableTypePack};
return {pack, predicates};
}
std::optional<AstExpr*> TypeChecker::matchRequire(const AstExprCall& call)
{
const char* require = "require";
if (call.args.size != 1)
return std::nullopt;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != require)
return std::nullopt;
if (call.args.size != 1)
return std::nullopt;
return call.args.data[0];
}
TypeId TypeChecker::checkRequire(const ScopePtr& scope, const ModuleInfo& moduleInfo, const Location& location)
{
LUAU_TIMETRACE_SCOPE("TypeChecker::checkRequire", "TypeChecker");
LUAU_TIMETRACE_ARGUMENT("moduleInfo", moduleInfo.name.c_str());
if (moduleInfo.name.empty())
{
if (currentModule->mode == Mode::Strict)
{
reportError(TypeError{location, UnknownRequire{}});
return errorRecoveryType(anyType);
}
return anyType;
}
2022-04-07 17:29:01 -04:00
// Types of requires that transitively refer to current module have to be replaced with 'any'
2022-05-05 20:03:43 -04:00
std::string humanReadableName = resolver->getHumanReadableModuleName(moduleInfo.name);
2022-04-07 17:29:01 -04:00
2022-05-05 20:03:43 -04:00
for (const auto& [location, path] : requireCycles)
2022-04-07 17:29:01 -04:00
{
2022-05-05 20:03:43 -04:00
if (!path.empty() && path.front() == humanReadableName)
return anyType;
2022-04-07 17:29:01 -04:00
}
ModulePtr module = resolver->getModule(moduleInfo.name);
if (!module)
{
// There are two reasons why we might fail to find the module:
// either the file does not exist or there's a cycle. If there's a cycle
// we will already have reported the error.
if (!resolver->moduleExists(moduleInfo.name) && !moduleInfo.optional)
2022-05-05 20:03:43 -04:00
reportError(TypeError{location, UnknownRequire{humanReadableName}});
return errorRecoveryType(scope);
}
if (module->type != SourceCode::Module)
{
2022-05-05 20:03:43 -04:00
reportError(location, IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."});
return errorRecoveryType(scope);
}
TypePackId modulePack = module->returnType;
if (get<Unifiable::Error>(modulePack))
return errorRecoveryType(scope);
std::optional<TypeId> moduleType = first(modulePack);
if (!moduleType)
{
2022-05-05 20:03:43 -04:00
reportError(location, IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."});
return errorRecoveryType(scope);
}
2022-03-24 18:04:14 -04:00
return *moduleType;
}
void TypeChecker::tablify(TypeId type)
{
type = follow(type);
if (auto f = get<FreeType>(type))
*asMutable(type) = TableType{TableState::Free, f->level};
}
TypeId TypeChecker::anyIfNonstrict(TypeId ty) const
{
if (isNonstrictMode())
return anyType;
else
return ty;
}
bool TypeChecker::unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location)
2022-02-24 18:53:37 -05:00
{
UnifierOptions options;
return unify(subTy, superTy, scope, location, options);
2022-02-24 18:53:37 -05:00
}
bool TypeChecker::unify(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location, const UnifierOptions& options)
{
Unifier state = mkUnifier(scope, location);
2022-02-24 18:53:37 -05:00
state.tryUnify(subTy, superTy, options.isFunctionCall);
2022-03-11 11:55:02 -05:00
state.log.commit();
reportErrors(state.errors);
return state.errors.empty();
}
bool TypeChecker::unify(TypePackId subTy, TypePackId superTy, const ScopePtr& scope, const Location& location, CountMismatch::Context ctx)
{
Unifier state = mkUnifier(scope, location);
state.ctx = ctx;
state.tryUnify(subTy, superTy);
2022-03-11 11:55:02 -05:00
state.log.commit();
reportErrors(state.errors);
return state.errors.empty();
}
bool TypeChecker::unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, const ScopePtr& scope, const Location& location)
{
Unifier state = mkUnifier(scope, location);
unifyWithInstantiationIfNeeded(subTy, superTy, scope, state);
2022-03-11 11:55:02 -05:00
state.log.commit();
reportErrors(state.errors);
return state.errors.empty();
}
void TypeChecker::unifyWithInstantiationIfNeeded(TypeId subTy, TypeId superTy, const ScopePtr& scope, Unifier& state)
{
LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping);
if (!maybeGeneric(subTy))
// Quick check to see if we definitely can't instantiate
state.tryUnify(subTy, superTy, /*isFunctionCall*/ false);
else if (!maybeGeneric(superTy) && isGeneric(subTy))
{
// Quick check to see if we definitely have to instantiate
TypeId instantiated = instantiate(scope, subTy, state.location);
state.tryUnify(instantiated, superTy, /*isFunctionCall*/ false);
}
else
{
// First try unifying with the original uninstantiated type
// but if that fails, try the instantiated one.
Unifier child = state.makeChildUnifier();
child.tryUnify(subTy, superTy, /*isFunctionCall*/ false);
if (!child.errors.empty())
{
2022-02-04 11:45:57 -05:00
TypeId instantiated = instantiate(scope, subTy, state.location, &child.log);
if (subTy == instantiated)
{
// Instantiating the argument made no difference, so just report any child errors
2022-03-11 11:55:02 -05:00
state.log.concat(std::move(child.log));
state.errors.insert(state.errors.end(), child.errors.begin(), child.errors.end());
}
else
{
state.tryUnify(instantiated, superTy, /*isFunctionCall*/ false);
}
}
else
{
2022-03-11 11:55:02 -05:00
state.log.concat(std::move(child.log));
}
}
}
TypeId TypeChecker::quantify(const ScopePtr& scope, TypeId ty, Location location)
{
ty = follow(ty);
2022-06-30 19:52:43 -04:00
if (FFlag::DebugLuauSharedSelf)
2022-05-19 20:02:24 -04:00
{
if (auto ftv = get<FunctionType>(ty))
2022-06-30 19:52:43 -04:00
Luau::quantify(ty, scope->level);
else if (auto ttv = getTableType(ty); ttv && ttv->selfTy)
2022-05-19 20:02:24 -04:00
Luau::quantify(ty, scope->level);
}
else
{
const FunctionType* ftv = get<FunctionType>(ty);
2022-04-14 19:57:43 -04:00
2022-08-04 18:35:33 -04:00
if (ftv)
Luau::quantify(ty, scope->level);
2022-04-14 19:57:43 -04:00
}
2022-03-04 11:36:33 -05:00
return ty;
}
2022-02-04 11:45:57 -05:00
TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location location, const TxnLog* log)
{
2022-05-19 20:02:24 -04:00
ty = follow(ty);
2022-04-28 21:24:24 -04:00
const FunctionType* ftv = get<FunctionType>(ty);
2022-05-19 20:02:24 -04:00
if (ftv && ftv->hasNoGenerics)
return ty;
2022-04-14 19:57:43 -04:00
Instantiation instantiation{log, &currentModule->internalTypes, scope->level, /*scope*/ nullptr};
2022-04-14 19:57:43 -04:00
if (instantiationChildLimit)
2022-04-14 19:57:43 -04:00
instantiation.childLimit = *instantiationChildLimit;
std::optional<TypeId> instantiated = instantiation.substitute(ty);
if (instantiated.has_value())
return *instantiated;
else
{
reportError(location, UnificationTooComplex{});
return errorRecoveryType(scope);
}
}
TypeId TypeChecker::anyify(const ScopePtr& scope, TypeId ty, Location location)
{
Anyification anyification{&currentModule->internalTypes, scope, builtinTypes, iceHandler, anyType, anyTypePack};
std::optional<TypeId> any = anyification.substitute(ty);
2022-04-14 19:57:43 -04:00
if (anyification.normalizationTooComplex)
reportError(location, NormalizationTooComplex{});
if (any.has_value())
return *any;
else
{
reportError(location, UnificationTooComplex{});
return errorRecoveryType(anyType);
}
}
TypePackId TypeChecker::anyify(const ScopePtr& scope, TypePackId ty, Location location)
{
Anyification anyification{&currentModule->internalTypes, scope, builtinTypes, iceHandler, anyType, anyTypePack};
std::optional<TypePackId> any = anyification.substitute(ty);
if (any.has_value())
return *any;
else
{
reportError(location, UnificationTooComplex{});
return errorRecoveryTypePack(anyTypePack);
}
}
TypePackId TypeChecker::anyifyModuleReturnTypePackGenerics(TypePackId tp)
{
tp = follow(tp);
if (const VariadicTypePack* vtp = get<VariadicTypePack>(tp))
{
TypeId ty = follow(vtp->ty);
return get<GenericType>(ty) ? anyTypePack : tp;
}
if (!get<TypePack>(follow(tp)))
return tp;
std::vector<TypeId> resultTypes;
std::optional<TypePackId> resultTail;
TypePackIterator it = begin(tp);
for (TypePackIterator e = end(tp); it != e; ++it)
{
TypeId ty = follow(*it);
resultTypes.push_back(get<GenericType>(ty) ? anyType : ty);
}
if (std::optional<TypePackId> tail = it.tail())
resultTail = anyifyModuleReturnTypePackGenerics(*tail);
return addTypePack(resultTypes, resultTail);
}
void TypeChecker::reportError(const TypeError& error)
{
if (currentModule->mode == Mode::NoCheck)
return;
currentModule->errors.push_back(error);
currentModule->errors.back().moduleName = currentModuleName;
}
void TypeChecker::reportError(const Location& location, TypeErrorData errorData)
{
return reportError(TypeError{location, std::move(errorData)});
}
void TypeChecker::reportErrors(const ErrorVec& errors)
{
for (const auto& err : errors)
reportError(err);
}
void TypeChecker::ice(const std::string& message, const Location& location)
{
iceHandler->ice(message, location);
}
void TypeChecker::ice(const std::string& message)
{
iceHandler->ice(message);
}
void TypeChecker::prepareErrorsForDisplay(ErrorVec& errVec)
{
// Remove errors with names that were generated by recovery from a parse error
errVec.erase(std::remove_if(errVec.begin(), errVec.end(),
[](auto& err) {
return containsParseErrorName(err);
}),
errVec.end());
for (auto& err : errVec)
{
if (auto utk = get<UnknownProperty>(err))
diagnoseMissingTableKey(utk, err.data);
}
}
void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data)
{
std::string_view sv(utk->key);
std::set<Name> candidates;
auto accumulate = [&](const TableType::Props& props) {
for (const auto& [name, ty] : props)
{
if (sv != name && equalsLower(sv, name))
candidates.insert(name);
}
};
2022-02-24 18:53:37 -05:00
if (auto ttv = getTableType(utk->table))
accumulate(ttv->props);
else if (auto ctv = get<ClassType>(follow(utk->table)))
{
while (ctv)
{
accumulate(ctv->props);
if (!ctv->parent)
break;
ctv = get<ClassType>(*ctv->parent);
LUAU_ASSERT(ctv);
}
}
if (!candidates.empty())
data = TypeErrorData(UnknownPropButFoundLikeProp{utk->table, utk->key, candidates});
}
LUAU_NOINLINE void TypeChecker::reportErrorCodeTooComplex(const Location& location)
{
reportError(TypeError{location, CodeTooComplex{}});
}
// Creates a new Scope but without carrying forward the varargs from the parent.
ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& location, int subLevel)
{
ScopePtr scope = std::make_shared<Scope>(parent, subLevel);
currentModule->scopes.push_back(std::make_pair(location, scope));
return scope;
}
// Creates a new Scope and carries forward the varargs from the parent.
ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location)
{
ScopePtr scope = std::make_shared<Scope>(parent);
2022-02-24 18:53:37 -05:00
scope->level = parent->level;
scope->varargPack = parent->varargPack;
currentModule->scopes.push_back(std::make_pair(location, scope));
return scope;
}
void TypeChecker::merge(RefinementMap& l, const RefinementMap& r)
{
Luau::merge(l, r, [this](TypeId a, TypeId b) {
// TODO: normalize(UnionType{{a, b}})
std::unordered_set<TypeId> set;
if (auto utv = get<UnionType>(follow(a)))
set.insert(begin(utv), end(utv));
else
set.insert(a);
if (auto utv = get<UnionType>(follow(b)))
set.insert(begin(utv), end(utv));
else
set.insert(b);
std::vector<TypeId> options(set.begin(), set.end());
if (set.size() == 1)
return options[0];
return addType(UnionType{std::move(options)});
});
}
Unifier TypeChecker::mkUnifier(const ScopePtr& scope, const Location& location)
{
return Unifier{NotNull{&normalizer}, currentModule->mode, NotNull{scope.get()}, location, Variance::Covariant};
}
TypeId TypeChecker::freshType(const ScopePtr& scope)
{
return freshType(scope->level);
}
TypeId TypeChecker::freshType(TypeLevel level)
{
return currentModule->internalTypes.addType(Type(FreeType(level)));
}
TypeId TypeChecker::singletonType(bool value)
{
return value ? builtinTypes->trueType : builtinTypes->falseType;
}
TypeId TypeChecker::singletonType(std::string value)
{
// TODO: cache singleton types
return currentModule->internalTypes.addType(Type(SingletonType(StringSingleton{std::move(value)})));
}
TypeId TypeChecker::errorRecoveryType(const ScopePtr& scope)
{
return builtinTypes->errorRecoveryType();
}
TypeId TypeChecker::errorRecoveryType(TypeId guess)
{
return builtinTypes->errorRecoveryType(guess);
}
TypePackId TypeChecker::errorRecoveryTypePack(const ScopePtr& scope)
{
return builtinTypes->errorRecoveryTypePack();
}
TypePackId TypeChecker::errorRecoveryTypePack(TypePackId guess)
{
return builtinTypes->errorRecoveryTypePack(guess);
}
TypeIdPredicate TypeChecker::mkTruthyPredicate(bool sense, TypeId emptySetTy)
2022-02-17 20:18:01 -05:00
{
return [this, sense, emptySetTy](TypeId ty) -> std::optional<TypeId> {
2022-02-04 11:45:57 -05:00
// any/error/free gets a special pass unconditionally because they can't be decided.
if (get<AnyType>(ty) || get<ErrorType>(ty) || get<FreeType>(ty))
2022-02-04 11:45:57 -05:00
return ty;
// maps boolean primitive to the corresponding singleton equal to sense
if (isPrim(ty, PrimitiveType::Boolean))
2022-02-04 11:45:57 -05:00
return singletonType(sense);
// if we have boolean singleton, eliminate it if the sense doesn't match with that singleton
if (auto boolean = get<BooleanSingleton>(get<SingletonType>(ty)))
2022-02-04 11:45:57 -05:00
return boolean->value == sense ? std::optional<TypeId>(ty) : std::nullopt;
// if we have nil, eliminate it if sense is true, otherwise take it
if (isNil(ty))
return sense ? std::nullopt : std::optional<TypeId>(ty);
// at this point, anything else is kept if sense is true, or replaced by nil
return sense ? ty : emptySetTy;
2022-02-04 11:45:57 -05:00
};
}
2022-07-07 21:22:39 -04:00
std::optional<TypeId> TypeChecker::filterMapImpl(TypeId type, TypeIdPredicate predicate)
{
std::vector<TypeId> types = Luau::filterMap(type, predicate);
if (!types.empty())
return types.size() == 1 ? types[0] : addType(UnionType{std::move(types)});
return std::nullopt;
}
2022-07-07 21:22:39 -04:00
std::pair<std::optional<TypeId>, bool> TypeChecker::filterMap(TypeId type, TypeIdPredicate predicate)
{
TypeId ty = filterMapImpl(type, predicate).value_or(neverType);
return {ty, !bool(get<NeverType>(ty))};
2022-07-07 21:22:39 -04:00
}
std::pair<std::optional<TypeId>, bool> TypeChecker::pickTypesFromSense(TypeId type, bool sense, TypeId emptySetTy)
2022-02-04 11:45:57 -05:00
{
return filterMap(type, mkTruthyPredicate(sense, emptySetTy));
2022-02-04 11:45:57 -05:00
}
TypeId TypeChecker::addTV(Type&& tv)
{
return currentModule->internalTypes.addType(std::move(tv));
}
TypePackId TypeChecker::addTypePack(TypePackVar&& tv)
{
return currentModule->internalTypes.addTypePack(std::move(tv));
}
TypePackId TypeChecker::addTypePack(TypePack&& tp)
{
return addTypePack(TypePackVar(std::move(tp)));
}
TypePackId TypeChecker::addTypePack(const std::vector<TypeId>& ty)
{
return addTypePack(ty, std::nullopt);
}
TypePackId TypeChecker::addTypePack(const std::vector<TypeId>& ty, std::optional<TypePackId> tail)
{
return addTypePack(TypePackVar(TypePack{ty, tail}));
}
TypePackId TypeChecker::addTypePack(std::initializer_list<TypeId>&& ty)
{
return addTypePack(TypePackVar(TypePack{std::vector<TypeId>(begin(ty), end(ty)), std::nullopt}));
}
TypePackId TypeChecker::freshTypePack(const ScopePtr& scope)
{
return freshTypePack(scope->level);
}
TypePackId TypeChecker::freshTypePack(TypeLevel level)
{
return addTypePack(TypePackVar(FreeTypePack(level)));
}
TypeId TypeChecker::resolveType(const ScopePtr& scope, const AstType& annotation)
{
TypeId ty = resolveTypeWorker(scope, annotation);
currentModule->astResolvedTypes[&annotation] = ty;
return ty;
}
TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& annotation)
{
if (const auto& lit = annotation.as<AstTypeReference>())
{
std::optional<TypeFun> tf;
2022-02-17 20:18:01 -05:00
if (lit->prefix)
tf = scope->lookupImportedType(lit->prefix->value, lit->name.value);
else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_ice")
ice("_luau_ice encountered", lit->location);
else if (FFlag::DebugLuauMagicTypes && lit->name == "_luau_print")
{
if (lit->parameters.size != 1 || !lit->parameters.data[0].type)
{
reportError(TypeError{annotation.location, GenericError{"_luau_print requires one generic parameter"}});
return errorRecoveryType(anyType);
}
ToStringOptions opts;
opts.exhaustive = true;
opts.maxTableLength = 0;
2022-04-14 19:57:43 -04:00
opts.useLineBreaks = true;
TypeId param = resolveType(scope, *lit->parameters.data[0].type);
luauPrintLine(format("_luau_print\t%s\t|\t%s", toString(param, opts).c_str(), toString(lit->location).c_str()));
return param;
}
else
tf = scope->lookupType(lit->name.value);
if (!tf)
{
2022-02-17 20:18:01 -05:00
if (lit->name == kParseNameError)
return errorRecoveryType(scope);
std::string typeName;
2022-02-17 20:18:01 -05:00
if (lit->prefix)
typeName = std::string(lit->prefix->value) + ".";
typeName += lit->name.value;
if (scope->lookupPack(typeName))
reportError(TypeError{annotation.location, SwappedGenericTypeParameter{typeName, SwappedGenericTypeParameter::Type}});
else
reportError(TypeError{annotation.location, UnknownSymbol{typeName, UnknownSymbol::Type}});
return errorRecoveryType(scope);
}
if (lit->parameters.size == 0 && tf->typeParams.empty() && tf->typePackParams.empty())
return tf->type;
2022-01-14 11:20:09 -05:00
bool parameterCountErrorReported = false;
2022-03-04 11:36:33 -05:00
bool hasDefaultTypes = std::any_of(tf->typeParams.begin(), tf->typeParams.end(), [](auto&& el) {
return el.defaultValue.has_value();
});
bool hasDefaultPacks = std::any_of(tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& el) {
return el.defaultValue.has_value();
});
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (!lit->hasParameterList)
2022-01-14 11:20:09 -05:00
{
2022-03-04 11:36:33 -05:00
if ((!tf->typeParams.empty() && !hasDefaultTypes) || (!tf->typePackParams.empty() && !hasDefaultPacks))
2022-01-14 11:20:09 -05:00
{
reportError(TypeError{annotation.location, GenericError{"Type parameter list is required"}});
2022-03-04 11:36:33 -05:00
parameterCountErrorReported = true;
2022-01-14 11:20:09 -05:00
}
}
std::vector<TypeId> typeParams;
std::vector<TypeId> extraTypes;
std::vector<TypePackId> typePackParams;
for (size_t i = 0; i < lit->parameters.size; ++i)
{
if (AstType* type = lit->parameters.data[i].type)
{
TypeId ty = resolveType(scope, *type);
if (typeParams.size() < tf->typeParams.size() || tf->typePackParams.empty())
typeParams.push_back(ty);
else if (typePackParams.empty())
extraTypes.push_back(ty);
else
reportError(TypeError{annotation.location, GenericError{"Type parameters must come before type pack parameters"}});
}
else if (AstTypePack* typePack = lit->parameters.data[i].typePack)
{
TypePackId tp = resolveTypePack(scope, *typePack);
// If we have collected an implicit type pack, materialize it
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
// If we need more regular types, we can use single element type packs to fill those in
if (typeParams.size() < tf->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
typeParams.push_back(*first(tp));
else
typePackParams.push_back(tp);
}
}
// If we still haven't meterialized an implicit type pack, do it now
if (typePackParams.empty() && !extraTypes.empty())
typePackParams.push_back(addTypePack(extraTypes));
2022-03-04 11:36:33 -05:00
size_t typesProvided = typeParams.size();
size_t typesRequired = tf->typeParams.size();
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
size_t packsProvided = typePackParams.size();
size_t packsRequired = tf->typePackParams.size();
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
bool notEnoughParameters =
(typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired);
bool hasDefaultParameters = hasDefaultTypes || hasDefaultPacks;
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
// Add default type and type pack parameters if that's required and it's possible
if (notEnoughParameters && hasDefaultParameters)
{
// 'applyTypeFunction' is used to substitute default types that reference previous generic types
2022-08-04 18:35:33 -04:00
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes};
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
for (size_t i = 0; i < typesProvided; ++i)
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeParams[i];
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (typesProvided < typesRequired)
{
for (size_t i = typesProvided; i < typesRequired; ++i)
2022-01-14 11:20:09 -05:00
{
2022-03-04 11:36:33 -05:00
TypeId defaultTy = tf->typeParams[i].defaultValue.value_or(nullptr);
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (!defaultTy)
break;
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(defaultTy);
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (!maybeInstantiated.has_value())
{
reportError(annotation.location, UnificationTooComplex{});
maybeInstantiated = errorRecoveryType(scope);
2022-01-14 11:20:09 -05:00
}
2022-03-04 11:36:33 -05:00
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = *maybeInstantiated;
typeParams.push_back(*maybeInstantiated);
2022-01-14 11:20:09 -05:00
}
2022-03-04 11:36:33 -05:00
}
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
for (size_t i = 0; i < packsProvided; ++i)
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = typePackParams[i];
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (packsProvided < packsRequired)
{
for (size_t i = packsProvided; i < packsRequired; ++i)
2022-01-14 11:20:09 -05:00
{
2022-03-04 11:36:33 -05:00
TypePackId defaultTp = tf->typePackParams[i].defaultValue.value_or(nullptr);
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (!defaultTp)
break;
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
std::optional<TypePackId> maybeInstantiated = applyTypeFunction.substitute(defaultTp);
2022-01-14 11:20:09 -05:00
2022-03-04 11:36:33 -05:00
if (!maybeInstantiated.has_value())
{
reportError(annotation.location, UnificationTooComplex{});
maybeInstantiated = errorRecoveryTypePack(scope);
2022-01-14 11:20:09 -05:00
}
2022-03-04 11:36:33 -05:00
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = *maybeInstantiated;
typePackParams.push_back(*maybeInstantiated);
2022-01-14 11:20:09 -05:00
}
}
}
// If we didn't combine regular types into a type pack and we're still one type pack short, provide an empty type pack
if (extraTypes.empty() && typePackParams.size() + 1 == tf->typePackParams.size())
typePackParams.push_back(addTypePack({}));
if (typeParams.size() != tf->typeParams.size() || typePackParams.size() != tf->typePackParams.size())
{
2022-01-14 11:20:09 -05:00
if (!parameterCountErrorReported)
reportError(
TypeError{annotation.location, IncorrectGenericParameterCount{lit->name.value, *tf, typeParams.size(), typePackParams.size()}});
2022-04-28 21:24:24 -04:00
// Pad the types out with error recovery types
while (typeParams.size() < tf->typeParams.size())
typeParams.push_back(errorRecoveryType(scope));
while (typePackParams.size() < tf->typePackParams.size())
typePackParams.push_back(errorRecoveryTypePack(scope));
}
2022-04-28 21:24:24 -04:00
bool sameTys = std::equal(typeParams.begin(), typeParams.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& tp) {
return itp == tp.ty;
});
bool sameTps = std::equal(
typePackParams.begin(), typePackParams.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itpp, auto&& tpp) {
return itpp == tpp.tp;
2022-01-14 11:20:09 -05:00
});
2022-04-28 21:24:24 -04:00
// If the generic parameters and the type arguments are the same, we are about to
// perform an identity substitution, which we can just short-circuit.
if (sameTys && sameTps)
return tf->type;
return instantiateTypeFun(scope, *tf, typeParams, typePackParams, annotation.location);
}
else if (const auto& table = annotation.as<AstTypeTable>())
{
TableType::Props props;
std::optional<TableIndexer> tableIndexer;
for (const auto& prop : table->props)
props[prop.name.value] = {resolveType(scope, *prop.type)};
if (const auto& indexer = table->indexer)
tableIndexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
TableType ttv{props, tableIndexer, scope->level, TableState::Sealed};
2022-04-21 17:44:27 -04:00
ttv.definitionModuleName = currentModuleName;
ttv.definitionLocation = annotation.location;
2022-04-21 17:44:27 -04:00
return addType(std::move(ttv));
}
else if (const auto& func = annotation.as<AstTypeFunction>())
{
ScopePtr funcScope = childScope(scope, func->location);
funcScope->level = scope->level.incr();
auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks);
TypePackId argTypes = resolveTypePack(funcScope, func->argTypes);
TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes);
2022-01-14 11:20:09 -05:00
std::vector<TypeId> genericTys;
genericTys.reserve(generics.size());
std::transform(generics.begin(), generics.end(), std::back_inserter(genericTys), [](auto&& el) {
return el.ty;
});
std::vector<TypePackId> genericTps;
genericTps.reserve(genericPacks.size());
std::transform(genericPacks.begin(), genericPacks.end(), std::back_inserter(genericTps), [](auto&& el) {
return el.tp;
});
TypeId fnType = addType(FunctionType{funcScope->level, std::move(genericTys), std::move(genericTps), argTypes, retTypes});
FunctionType* ftv = getMutable<FunctionType>(fnType);
ftv->argNames.reserve(func->argNames.size);
for (const auto& el : func->argNames)
{
if (el)
ftv->argNames.push_back(FunctionArgument{el->first.value, el->second});
else
ftv->argNames.push_back(std::nullopt);
}
return fnType;
}
else if (auto typeOf = annotation.as<AstTypeTypeof>())
{
TypeId ty = checkExpr(scope, *typeOf->expr).type;
return ty;
}
else if (const auto& un = annotation.as<AstTypeUnion>())
{
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
return addType(UnionType{types});
}
else if (const auto& un = annotation.as<AstTypeIntersection>())
{
std::vector<TypeId> types;
for (AstType* ann : un->types)
types.push_back(resolveType(scope, *ann));
return addType(IntersectionType{types});
}
else if (const auto& tsb = annotation.as<AstTypeSingletonBool>())
{
return singletonType(tsb->value);
}
else if (const auto& tss = annotation.as<AstTypeSingletonString>())
{
return singletonType(std::string(tss->value.data, tss->value.size));
}
else if (annotation.is<AstTypeError>())
return errorRecoveryType(scope);
else
{
reportError(TypeError{annotation.location, GenericError{"Unknown type annotation?"}});
return errorRecoveryType(scope);
}
}
TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypeList& types)
{
if (types.types.size == 0 && types.tailType)
{
return resolveTypePack(scope, *types.tailType);
}
else if (types.types.size > 0)
{
std::vector<TypeId> head;
for (AstType* ann : types.types)
head.push_back(resolveType(scope, *ann));
std::optional<TypePackId> tail = types.tailType ? std::optional<TypePackId>(resolveTypePack(scope, *types.tailType)) : std::nullopt;
return addTypePack(TypePack{head, tail});
}
return addTypePack(TypePack{});
}
TypePackId TypeChecker::resolveTypePack(const ScopePtr& scope, const AstTypePack& annotation)
{
TypePackId result;
if (const AstTypePackVariadic* variadic = annotation.as<AstTypePackVariadic>())
{
result = addTypePack(TypePackVar{VariadicTypePack{resolveType(scope, *variadic->variadicType)}});
}
else if (const AstTypePackGeneric* generic = annotation.as<AstTypePackGeneric>())
{
Name genericName = Name(generic->genericName.value);
std::optional<TypePackId> genericTy = scope->lookupPack(genericName);
if (!genericTy)
{
if (scope->lookupType(genericName))
reportError(TypeError{generic->location, SwappedGenericTypeParameter{genericName, SwappedGenericTypeParameter::Pack}});
else
reportError(TypeError{generic->location, UnknownSymbol{genericName, UnknownSymbol::Type}});
result = errorRecoveryTypePack(scope);
}
else
{
result = *genericTy;
}
}
else if (const AstTypePackExplicit* explicitTp = annotation.as<AstTypePackExplicit>())
{
std::vector<TypeId> types;
for (auto type : explicitTp->typeList.types)
types.push_back(resolveType(scope, *type));
if (auto tailType = explicitTp->typeList.tailType)
result = addTypePack(types, resolveTypePack(scope, *tailType));
else
result = addTypePack(types);
}
else
{
ice("Unknown AstTypePack kind");
}
currentModule->astResolvedTypePacks[&annotation] = result;
return result;
}
TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& typePackParams, const Location& location)
{
if (tf.typeParams.empty() && tf.typePackParams.empty())
return tf.type;
2022-08-04 18:35:33 -04:00
ApplyTypeFunction applyTypeFunction{&currentModule->internalTypes};
2022-02-17 20:18:01 -05:00
for (size_t i = 0; i < tf.typeParams.size(); ++i)
2022-01-14 11:20:09 -05:00
applyTypeFunction.typeArguments[tf.typeParams[i].ty] = typeParams[i];
for (size_t i = 0; i < tf.typePackParams.size(); ++i)
2022-01-14 11:20:09 -05:00
applyTypeFunction.typePackArguments[tf.typePackParams[i].tp] = typePackParams[i];
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf.type);
if (!maybeInstantiated.has_value())
{
reportError(location, UnificationTooComplex{});
return errorRecoveryType(scope);
}
2022-04-28 21:24:24 -04:00
if (applyTypeFunction.encounteredForwardedType)
{
reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}});
return errorRecoveryType(scope);
}
TypeId instantiated = *maybeInstantiated;
TypeId target = follow(instantiated);
bool needsClone = follow(tf.type) == target;
2022-06-23 21:56:00 -04:00
bool shouldMutate = getTableType(tf.type);
TableType* ttv = getMutableTableType(target);
2022-04-14 19:57:43 -04:00
2022-03-04 11:36:33 -05:00
if (shouldMutate && ttv && needsClone)
{
// Substitution::clone is a shallow clone. If this is a metatable type, we
// want to mutate its table, so we need to explicitly clone that table as
// well. If we don't, we will mutate another module's type surface and cause
// a use-after-free.
if (get<MetatableType>(target))
{
instantiated = applyTypeFunction.clone(tf.type);
MetatableType* mtv = getMutable<MetatableType>(instantiated);
mtv->table = applyTypeFunction.clone(mtv->table);
ttv = getMutable<TableType>(mtv->table);
}
if (get<TableType>(target))
{
instantiated = applyTypeFunction.clone(tf.type);
ttv = getMutable<TableType>(instantiated);
}
}
2022-03-04 11:36:33 -05:00
if (shouldMutate && ttv)
{
ttv->instantiatedTypeParams = typeParams;
ttv->instantiatedTypePackParams = typePackParams;
2022-04-21 17:44:27 -04:00
ttv->definitionModuleName = currentModuleName;
ttv->definitionLocation = location;
}
return instantiated;
}
2022-01-14 11:20:09 -05:00
GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, std::optional<TypeLevel> levelOpt, const AstNode& node,
2022-02-04 15:46:08 -05:00
const AstArray<AstGenericType>& genericNames, const AstArray<AstGenericTypePack>& genericPackNames, bool useCache)
{
LUAU_ASSERT(scope->parent);
2022-03-04 11:36:33 -05:00
const TypeLevel level = levelOpt.value_or(scope->level);
2022-01-14 11:20:09 -05:00
std::vector<GenericTypeDefinition> generics;
for (const AstGenericType& generic : genericNames)
{
2022-01-14 11:20:09 -05:00
std::optional<TypeId> defaultValue;
2022-03-04 11:36:33 -05:00
if (generic.defaultValue)
2022-01-14 11:20:09 -05:00
defaultValue = resolveType(scope, *generic.defaultValue);
Name n = generic.name.value;
// These generics are the only thing that will ever be added to scope, so we can be certain that
// a collision can only occur when two generic types have the same name.
if (scope->privateTypeBindings.count(n) || scope->privateTypePackBindings.count(n))
{
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
reportError(TypeError{node.location, DuplicateGenericParameter{n}});
}
TypeId g;
2022-04-28 21:24:24 -04:00
if (useCache)
{
TypeId& cached = scope->parent->typeAliasTypeParameters[n];
if (!cached)
cached = addType(GenericType{level, n});
g = cached;
}
else
{
g = addType(Unifiable::Generic{level, n});
}
2022-01-14 11:20:09 -05:00
generics.push_back({g, defaultValue});
scope->privateTypeBindings[n] = TypeFun{{}, g};
}
2022-01-14 11:20:09 -05:00
std::vector<GenericTypePackDefinition> genericPacks;
for (const AstGenericTypePack& genericPack : genericPackNames)
{
2022-01-14 11:20:09 -05:00
std::optional<TypePackId> defaultValue;
2022-03-04 11:36:33 -05:00
if (genericPack.defaultValue)
2022-01-14 11:20:09 -05:00
defaultValue = resolveTypePack(scope, *genericPack.defaultValue);
Name n = genericPack.name.value;
// These generics are the only thing that will ever be added to scope, so we can be certain that
// a collision can only occur when two generic types have the same name.
if (scope->privateTypePackBindings.count(n) || scope->privateTypeBindings.count(n))
{
// TODO(jhuelsman): report the exact span of the generic type parameter whose name is a duplicate.
reportError(TypeError{node.location, DuplicateGenericParameter{n}});
}
2022-04-28 21:24:24 -04:00
TypePackId& cached = scope->parent->typeAliasTypePackParameters[n];
if (!cached)
cached = addTypePack(TypePackVar{Unifiable::Generic{level, n}});
2022-04-28 21:24:24 -04:00
genericPacks.push_back({cached, defaultValue});
scope->privateTypePackBindings[n] = cached;
}
return {generics, genericPacks};
}
void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate)
{
const LValue* target = &lvalue;
std::optional<LValue> key; // If set, we know we took the base of the lvalue path and should be walking down each option of the base's type.
auto ty = resolveLValue(scope, *target);
if (!ty)
return; // Do nothing. An error was already reported.
// If the provided lvalue is a local or global, then that's without a doubt the target.
// However, if there is a base lvalue, then we'll want that to be the target iff the base is a union type.
if (auto base = baseof(lvalue))
{
std::optional<TypeId> baseTy = resolveLValue(scope, *base);
if (baseTy && get<UnionType>(follow(*baseTy)))
{
ty = baseTy;
target = base;
key = lvalue;
}
}
// If we do not have a key, it means we're not trying to discriminate anything, so it's a simple matter of just filtering for a subset.
if (!key)
{
2022-07-07 21:22:39 -04:00
auto [result, ok] = filterMap(*ty, predicate);
addRefinement(refis, *target, *result);
return;
}
// Otherwise, we'll want to walk each option of ty, get its index type, and filter that.
auto utv = get<UnionType>(follow(*ty));
LUAU_ASSERT(utv);
std::unordered_set<TypeId> viableTargetOptions;
std::unordered_set<TypeId> viableChildOptions; // There may be additional refinements that apply. We add those here too.
for (TypeId option : utv)
{
std::optional<TypeId> discriminantTy;
if (auto field = Luau::get<Field>(*key)) // need to fully qualify Luau::get because of ADL.
2022-07-07 21:22:39 -04:00
discriminantTy = getIndexTypeFromType(scope, option, field->key, Location(), /* addErrors= */ false);
else
LUAU_ASSERT(!"Unhandled LValue alternative?");
if (!discriminantTy)
return; // Do nothing. An error was already reported, as per usual.
2022-07-07 21:22:39 -04:00
auto [result, ok] = filterMap(*discriminantTy, predicate);
if (!get<NeverType>(*result))
2022-07-07 21:22:39 -04:00
{
viableTargetOptions.insert(option);
viableChildOptions.insert(*result);
}
}
auto intoType = [this](const std::unordered_set<TypeId>& s) -> std::optional<TypeId> {
if (s.empty())
return std::nullopt;
// TODO: allocate UnionType and just normalize.
std::vector<TypeId> options(s.begin(), s.end());
if (options.size() == 1)
return options[0];
return addType(UnionType{std::move(options)});
};
if (std::optional<TypeId> viableTargetType = intoType(viableTargetOptions))
addRefinement(refis, *target, *viableTargetType);
if (std::optional<TypeId> viableChildType = intoType(viableChildOptions))
addRefinement(refis, lvalue, *viableChildType);
}
std::optional<TypeId> TypeChecker::resolveLValue(const ScopePtr& scope, const LValue& lvalue)
{
// We want to be walking the Scope parents.
// We'll also want to walk up the LValue path. As we do this, we need to save each LValue because we must walk back.
// For example:
// There exists an entry t.x.
// We are asked to look for t.x.y.
// We need to search in the provided Scope. Find t.x.y first.
// We fail to find t.x.y. Try t.x. We found it. Now we must return the type of the property y from the mapped-to type of t.x.
// If we completely fail to find the Symbol t but the Scope has that entry, then we should walk that all the way through and terminate.
2022-04-14 19:57:43 -04:00
const Symbol symbol = getBaseSymbol(lvalue);
ScopePtr currentScope = scope;
while (currentScope)
{
std::optional<TypeId> found;
2022-04-14 19:57:43 -04:00
const LValue* topLValue = nullptr;
for (topLValue = &lvalue; topLValue; topLValue = baseof(*topLValue))
{
2022-04-14 19:57:43 -04:00
if (auto it = currentScope->refinements.find(*topLValue); it != currentScope->refinements.end())
{
found = it->second;
break;
}
}
if (!found)
{
// Should not be using scope->lookup. This is already recursive.
if (auto it = currentScope->bindings.find(symbol); it != currentScope->bindings.end())
found = it->second.typeId;
else
{
// Nothing exists in this Scope. Just skip and try the parent one.
currentScope = currentScope->parent;
continue;
}
}
2022-04-14 19:57:43 -04:00
// We need to walk the l-value path in reverse, so we collect components into a vector
std::vector<const LValue*> childKeys;
for (const LValue* curr = &lvalue; curr != topLValue; curr = baseof(*curr))
childKeys.push_back(curr);
for (auto it = childKeys.rbegin(); it != childKeys.rend(); ++it)
{
2022-04-14 19:57:43 -04:00
const LValue& key = **it;
// Symbol can happen. Skip.
if (get<Symbol>(key))
continue;
else if (auto field = get<Field>(key))
{
2022-07-07 21:22:39 -04:00
found = getIndexTypeFromType(scope, *found, field->key, Location(), /* addErrors= */ false);
if (!found)
return std::nullopt; // Turns out this type doesn't have the property at all. We're done.
}
else
LUAU_ASSERT(!"New LValue alternative not handled here.");
}
return found;
}
// No entry for it at all. Can happen when LValue root is a global.
return std::nullopt;
}
std::optional<TypeId> TypeChecker::resolveLValue(const RefinementMap& refis, const ScopePtr& scope, const LValue& lvalue)
{
2022-02-04 11:45:57 -05:00
if (auto it = refis.find(lvalue); it != refis.end())
return it->second;
else
return resolveLValue(scope, lvalue);
}
// Only should be used for refinements!
// This can probably go away once we have something that can limit a free type's type domain.
static bool isUndecidable(TypeId ty)
{
ty = follow(ty);
return get<AnyType>(ty) || get<ErrorType>(ty) || get<FreeType>(ty);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const PredicateVec& predicates, const ScopePtr& scope, bool sense)
{
2022-05-19 20:02:24 -04:00
resolve(predicates, scope->refinements, scope, sense);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const PredicateVec& predicates, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
for (const Predicate& c : predicates)
2022-05-19 20:02:24 -04:00
resolve(c, refis, scope, sense, fromOr);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const Predicate& predicate, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
if (auto truthyP = get<TruthyPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*truthyP, refis, scope, sense, fromOr);
else if (auto andP = get<AndPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*andP, refis, scope, sense);
else if (auto orP = get<OrPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*orP, refis, scope, sense);
else if (auto notP = get<NotPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(notP->predicates, refis, scope, !sense, fromOr);
else if (auto isaP = get<IsAPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*isaP, refis, scope, sense);
else if (auto typeguardP = get<TypeGuardPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*typeguardP, refis, scope, sense);
else if (auto eqP = get<EqPredicate>(predicate))
2022-05-19 20:02:24 -04:00
resolve(*eqP, refis, scope, sense);
else
ice("Unhandled predicate kind");
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const TruthyPredicate& truthyP, RefinementMap& refis, const ScopePtr& scope, bool sense, bool fromOr)
{
2022-05-19 20:02:24 -04:00
std::optional<TypeId> ty = resolveLValue(refis, scope, truthyP.lvalue);
if (ty && fromOr)
return addRefinement(refis, truthyP.lvalue, *ty);
2022-02-04 11:45:57 -05:00
refineLValue(truthyP.lvalue, refis, scope, mkTruthyPredicate(sense, nilType));
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const AndPredicate& andP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
if (!sense)
{
OrPredicate orP{
{NotPredicate{std::move(andP.lhs)}},
{NotPredicate{std::move(andP.rhs)}},
};
2022-05-19 20:02:24 -04:00
return resolve(orP, refis, scope, !sense);
}
2022-05-19 20:02:24 -04:00
resolve(andP.lhs, refis, scope, sense);
resolve(andP.rhs, refis, scope, sense);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const OrPredicate& orP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
if (!sense)
{
AndPredicate andP{
{NotPredicate{std::move(orP.lhs)}},
{NotPredicate{std::move(orP.rhs)}},
};
2022-05-19 20:02:24 -04:00
return resolve(andP, refis, scope, !sense);
}
RefinementMap leftRefis;
2022-05-19 20:02:24 -04:00
resolve(orP.lhs, leftRefis, scope, sense);
RefinementMap rightRefis;
2022-05-19 20:02:24 -04:00
resolve(orP.lhs, rightRefis, scope, !sense);
resolve(orP.rhs, rightRefis, scope, sense, true); // :(
merge(refis, leftRefis);
merge(refis, rightRefis);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const IsAPredicate& isaP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
// This by itself is not truly enough to determine that A is stronger than B or vice versa.
bool optionIsSubtype = canUnify(option, isaP.ty, scope, isaP.location).empty();
bool targetIsSubtype = canUnify(isaP.ty, option, scope, isaP.location).empty();
// If A is a superset of B, then if sense is true, we promote A to B, otherwise we keep A.
if (!optionIsSubtype && targetIsSubtype)
return sense ? isaP.ty : option;
// If A is a subset of B, then if sense is true we pick A, otherwise we eliminate A.
if (optionIsSubtype && !targetIsSubtype)
return sense ? std::optional<TypeId>(option) : std::nullopt;
// If neither has any relationship, we only return A if sense is false.
if (!optionIsSubtype && !targetIsSubtype)
return sense ? std::nullopt : std::optional<TypeId>(option);
// If both are subtypes, then we're in one of the two situations:
// 1. Instance₁ <: Instance₂ ∧ Instance₂ <: Instance₁
// 2. any <: Instance ∧ Instance <: any
// Right now, we have to look at the types to see if they were undecidables.
// By this point, we also know free tables are also subtypes and supertypes.
if (optionIsSubtype && targetIsSubtype)
{
// We can only have (any, Instance) because the rhs is never undecidable right now.
// So we can just return the right hand side immediately.
// typeof(x) == "Instance" where x : any
auto ttv = get<TableType>(option);
if (isUndecidable(option) || (ttv && ttv->state == TableState::Free))
return sense ? isaP.ty : option;
// typeof(x) == "Instance" where x : Instance
if (sense)
return isaP.ty;
}
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> res = std::nullopt;
return res;
};
2022-05-19 20:02:24 -04:00
refineLValue(isaP.lvalue, refis, scope, predicate);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
// Rewrite the predicate 'type(foo) == "vector"' to be 'typeof(foo) == "Vector3"'. They're exactly identical.
// This allows us to avoid writing in edge cases.
if (!typeguardP.isTypeof && typeguardP.kind == "vector")
2022-05-19 20:02:24 -04:00
return resolve(TypeGuardPredicate{std::move(typeguardP.lvalue), typeguardP.location, "Vector3", true}, refis, scope, sense);
std::optional<TypeId> ty = resolveLValue(refis, scope, typeguardP.lvalue);
if (!ty)
return;
// In certain cases, the value may actually be nil, but Luau doesn't know about it. So we whitelist this.
if (sense && typeguardP.kind == "nil")
{
addRefinement(refis, typeguardP.lvalue, nilType);
return;
}
auto refine = [this, &lvalue = typeguardP.lvalue, &refis, &scope, sense](bool(f)(TypeId), std::optional<TypeId> mapsTo = std::nullopt) {
TypeIdPredicate predicate = [f, mapsTo, sense](TypeId ty) -> std::optional<TypeId> {
if (sense && get<UnknownType>(ty))
return mapsTo.value_or(ty);
2022-07-07 21:22:39 -04:00
if (f(ty) == sense)
return ty;
if (isUndecidable(ty))
return mapsTo.value_or(ty);
return std::nullopt;
};
refineLValue(lvalue, refis, scope, predicate);
};
// Note: "vector" never happens here at this point, so we don't have to write something for it.
if (typeguardP.kind == "nil")
return refine(isNil, nilType); // This can still happen when sense is false!
else if (typeguardP.kind == "string")
return refine(isString, stringType);
else if (typeguardP.kind == "number")
return refine(isNumber, numberType);
else if (typeguardP.kind == "boolean")
return refine(isBoolean, booleanType);
else if (typeguardP.kind == "thread")
return refine(isThread, threadType);
else if (typeguardP.kind == "table")
{
return refine([](TypeId ty) -> bool {
return isTableIntersection(ty) || get<TableType>(ty) || get<MetatableType>(ty);
});
}
else if (typeguardP.kind == "function")
{
return refine([](TypeId ty) -> bool {
return isOverloadedFunction(ty) || get<FunctionType>(ty);
});
}
else if (typeguardP.kind == "userdata")
{
// For now, we don't really care about being accurate with userdata if the typeguard was using typeof.
return refine([](TypeId ty) -> bool {
return get<ClassType>(ty);
});
}
if (!typeguardP.isTypeof)
2022-05-19 20:02:24 -04:00
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
auto typeFun = globalScope->lookupType(typeguardP.kind);
if (!typeFun || !typeFun->typeParams.empty() || !typeFun->typePackParams.empty())
2022-05-19 20:02:24 -04:00
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
TypeId type = follow(typeFun->type);
// You cannot refine to the top class type.
if (FFlag::LuauNegatedClassTypes)
{
if (type == builtinTypes->classType)
{
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
}
}
// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(type);
!ctv || (FFlag::LuauNegatedClassTypes ? (ctv->parent != builtinTypes->classType) : (ctv->parent != std::nullopt)))
2022-05-19 20:02:24 -04:00
return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope));
// This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA.
// Until then, we rewrite this to be the same as using IsA.
2022-05-19 20:02:24 -04:00
return resolve(IsAPredicate{std::move(typeguardP.lvalue), typeguardP.location, type}, refis, scope, sense);
}
2022-05-19 20:02:24 -04:00
void TypeChecker::resolve(const EqPredicate& eqP, RefinementMap& refis, const ScopePtr& scope, bool sense)
{
// This refinement will require success typing to do everything correctly. For now, we can get most of the way there.
auto options = [](TypeId ty) -> std::vector<TypeId> {
if (auto utv = get<UnionType>(follow(ty)))
return std::vector<TypeId>(begin(utv), end(utv));
return {ty};
};
2022-05-19 20:02:24 -04:00
std::vector<TypeId> rhs = options(eqP.type);
2022-05-19 20:02:24 -04:00
if (sense && std::any_of(rhs.begin(), rhs.end(), isUndecidable))
return; // Optimization: the other side has unknown types, so there's probably an overlap. Refining is no-op here.
2022-05-19 20:02:24 -04:00
auto predicate = [&](TypeId option) -> std::optional<TypeId> {
if (!sense && isNil(eqP.type))
return (isUndecidable(option) || !isNil(option)) ? std::optional<TypeId>(option) : std::nullopt;
2022-05-19 20:02:24 -04:00
if (maybeSingleton(eqP.type))
{
bool optionIsSubtype = canUnify(option, eqP.type, scope, eqP.location).empty();
bool targetIsSubtype = canUnify(eqP.type, option, scope, eqP.location).empty();
// terminology refresher:
// - option is the type of the expression `x`, and
// - eqP.type is the type of the expression `"hello"`
//
// "hello" == x where
// x : "hello" | "world" -> x : "hello"
// x : number | string -> x : "hello"
// x : number -> x : never
//
// "hello" ~= x where
// x : "hello" | "world" -> x : "world"
// x : number | string -> x : number | string
// x : number -> x : number
// local variable works around an odd gcc 9.3 warning: <anonymous> may be used uninitialized
std::optional<TypeId> nope = std::nullopt;
if (sense)
{
if (optionIsSubtype && !targetIsSubtype)
return option;
else if (!optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
else if (!optionIsSubtype && !targetIsSubtype)
return nope;
else if (optionIsSubtype && targetIsSubtype)
return follow(eqP.type);
}
else
{
bool isOptionSingleton = get<SingletonType>(option);
if (!isOptionSingleton)
return option;
else if (optionIsSubtype && targetIsSubtype)
return nope;
}
}
2022-05-19 20:02:24 -04:00
return option;
};
2022-05-19 20:02:24 -04:00
refineLValue(eqP.lvalue, refis, scope, predicate);
}
bool TypeChecker::isNonstrictMode() const
{
return (currentModule->mode == Mode::Nonstrict) || (currentModule->mode == Mode::NoCheck);
}
std::vector<TypeId> TypeChecker::unTypePack(const ScopePtr& scope, TypePackId tp, size_t expectedLength, const Location& location)
{
TypePackId expectedTypePack = addTypePack({});
TypePack* expectedPack = getMutable<TypePack>(expectedTypePack);
LUAU_ASSERT(expectedPack);
for (size_t i = 0; i < expectedLength; ++i)
expectedPack->head.push_back(freshType(scope));
2022-07-07 21:22:39 -04:00
size_t oldErrorsSize = currentModule->errors.size();
unify(tp, expectedTypePack, scope, location);
2022-07-07 21:22:39 -04:00
// HACK: tryUnify would undo the changes to the expectedTypePack if the length mismatches, but
// we want to tie up free types to be error types, so we do this instead.
currentModule->errors.resize(oldErrorsSize);
2022-07-07 21:22:39 -04:00
for (TypeId& tp : expectedPack->head)
tp = follow(tp);
return expectedPack->head;
}
std::vector<std::pair<Location, ScopePtr>> TypeChecker::getScopes() const
{
return currentModule->scopes;
}
} // namespace Luau