Skip to content

Commit

Permalink
Sync to upstream/release/646 (#1458)
Browse files Browse the repository at this point in the history
# General Updates

* Fix some cases where documentation symbols would not be available when
mouseovering at certain positions in the code
* Scaffolding to help embedders have more control over how `typeof(x)`
refines types
* Refinements to require-by-string semantics. See
luau-lang/rfcs#56 for details.
* Fix for #1405

# New Solver

* Fix many crashes (thanks you for your bug reports!)
* Type functions can now call each other
* Type functions all evaluate in a single VM. This should improve
typechecking performance and reduce memory use.
* `export type function` is now forbidden and fails with a clear error
message
* Type functions that access locals in the surrounding environment are
now properly a parse error
* You can now use `:setindexer(types.never, types.never)` to delete an
indexer from a table type.

# Internal Contributors

Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Hunter Goldstein <[email protected]>
Co-authored-by: Varun Saini <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
  • Loading branch information
5 people authored Oct 4, 2024
1 parent 02241b6 commit 543de6e
Show file tree
Hide file tree
Showing 70 changed files with 1,391 additions and 539 deletions.
7 changes: 7 additions & 0 deletions Analysis/include/Luau/ConstraintGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct Scope;
using ScopePtr = std::shared_ptr<Scope>;

struct DcrLogger;
struct TypeFunctionRuntime;

struct Inference
{
Expand Down Expand Up @@ -108,6 +109,8 @@ struct ConstraintGenerator

// Needed to be able to enable error-suppression preservation for immediate refinements.
NotNull<Normalizer> normalizer;
// Needed to register all available type functions for execution at later stages.
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
// Needed to resolve modules to make 'require' import types properly.
NotNull<ModuleResolver> moduleResolver;
// Occasionally constraint generation needs to produce an ICE.
Expand All @@ -125,6 +128,7 @@ struct ConstraintGenerator
ConstraintGenerator(
ModulePtr module,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<ModuleResolver> moduleResolver,
NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> ice,
Expand Down Expand Up @@ -223,7 +227,10 @@ struct ConstraintGenerator
);
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);

LUAU_NOINLINE void checkAliases(const ScopePtr& scope, AstStatBlock* block);

ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
ControlFlow visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block);

ControlFlow visit(const ScopePtr& scope, AstStat* stat);
ControlFlow visit(const ScopePtr& scope, AstStatBlock* block);
Expand Down
15 changes: 0 additions & 15 deletions Analysis/include/Luau/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,6 @@ struct LoadDefinitionFileResult

std::optional<Mode> parseMode(const std::vector<HotComment>& hotcomments);

std::vector<std::string_view> parsePathExpr(const AstExpr& pathExpr);

// Exported only for convenient testing.
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& expr);

/** Try to convert an AST fragment into a ModuleName.
* Returns std::nullopt if the expression cannot be resolved. This will most likely happen in cases where
* the import path involves some dynamic computation that we cannot see into at typechecking time.
*
* Unintuitively, weirdly-formulated modules (like game.Parent.Parent.Parent.Foo) will successfully produce a ModuleName
* as long as it falls within the permitted syntax. This is ok because we will fail to find the module and produce an
* error when we try during typechecking.
*/
std::optional<ModuleName> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& expr);

struct SourceNode
{
bool hasDirtySourceModule() const
Expand Down
2 changes: 0 additions & 2 deletions Analysis/include/Luau/ModuleResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ struct ModuleResolver
virtual ~ModuleResolver() {}

/** Compute a ModuleName from an AST fragment. This AST fragment is generally the argument to the require() function.
*
* You probably want to implement this with some variation of pathExprToModuleName.
*
* @returns The ModuleInfo if the expression is a syntactically legal path.
* @returns std::nullopt if we are unable to determine whether or not the expression is a valid path. Type inference will
Expand Down
2 changes: 2 additions & 0 deletions Analysis/include/Luau/NonStrictTypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ namespace Luau
{

struct BuiltinTypes;
struct TypeFunctionRuntime;
struct UnifierSharedState;
struct TypeCheckLimits;

void checkNonStrict(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> ice,
NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg,
Expand Down
9 changes: 5 additions & 4 deletions Analysis/include/Luau/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -613,20 +613,17 @@ struct TypeFunctionInstanceType
std::vector<TypePackId> packArguments;

std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs

TypeFunctionInstanceType(
NotNull<const TypeFunction> function,
std::vector<TypeId> typeArguments,
std::vector<TypePackId> packArguments,
std::optional<AstName> userFuncName = std::nullopt,
std::optional<AstExprFunction*> userFuncBody = std::nullopt
std::optional<AstName> userFuncName = std::nullopt
)
: function(function)
, typeArguments(typeArguments)
, packArguments(packArguments)
, userFuncName(userFuncName)
, userFuncBody(userFuncBody)
{
}

Expand Down Expand Up @@ -1159,6 +1156,10 @@ TypeId freshType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, S
using TypeIdPredicate = std::function<std::optional<TypeId>(TypeId)>;
std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);

// A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`.
// Any classes which derive from this type will have typeof return this type.
static constexpr char kTypeofRootTag[] = "typeofRoot";

void attachTag(TypeId ty, const std::string& tagName);
void attachTag(Property& prop, const std::string& tagName);

Expand Down
4 changes: 3 additions & 1 deletion Analysis/include/Luau/TypeChecker2.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct Reasonings

void check(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> sharedState,
NotNull<TypeCheckLimits> limits,
DcrLogger* logger,
Expand All @@ -70,6 +71,7 @@ void check(
struct TypeChecker2
{
NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeFunctionRuntime> typeFunctionRuntime;
DcrLogger* logger;
const NotNull<TypeCheckLimits> limits;
const NotNull<InternalErrorReporter> ice;
Expand All @@ -83,12 +85,12 @@ struct TypeChecker2
DenseHashSet<TypeId> seenTypeFunctionInstances{nullptr};

Normalizer normalizer;
TypeFunctionRuntime typeFunctionRuntime;
Subtyping _subtyping;
NotNull<Subtyping> subtyping;

TypeChecker2(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits,
DcrLogger* logger,
Expand Down
22 changes: 21 additions & 1 deletion Analysis/include/Luau/TypeFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <string>
#include <optional>

struct lua_State;

namespace Luau
{

Expand All @@ -20,11 +22,30 @@ struct TxnLog;
struct ConstraintSolver;
class Normalizer;

using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;

struct TypeFunctionRuntime
{
TypeFunctionRuntime(NotNull<InternalErrorReporter> ice, NotNull<TypeCheckLimits> limits);
~TypeFunctionRuntime();

// Return value is an error message if registration failed
std::optional<std::string> registerFunction(AstStatTypeFunction* function);

// For user-defined type functions, we store all generated types and packs for the duration of the typecheck
TypedAllocator<TypeFunctionType> typeArena;
TypedAllocator<TypeFunctionTypePackVar> typePackArena;

NotNull<InternalErrorReporter> ice;
NotNull<TypeCheckLimits> limits;

StateRef state;

// Evaluation of type functions should only be performed in the absence of parse errors in the source module
bool allowEvaluation = true;

private:
void prepareState();
};

struct TypeFunctionContext
Expand All @@ -43,7 +64,6 @@ struct TypeFunctionContext
const Constraint* constraint;

std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
std::optional<AstExprFunction*> userFuncBody; // Body of the user-defined type function; only available for UDTFs

TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint);

Expand Down
2 changes: 2 additions & 0 deletions Analysis/include/Luau/TypePath.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ struct Index
/// Represents fields of a type or pack that contain a type.
enum class TypeField
{
/// The table of a metatable type.
Table,
/// The metatable of a type. This could be a metatable type, a primitive
/// type, a class type, or perhaps even a string singleton type.
Metatable,
Expand Down
81 changes: 75 additions & 6 deletions Analysis/src/AstQuery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

LUAU_FASTFLAG(LuauSolverV2)

LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition, false)

namespace Luau
{

Expand Down Expand Up @@ -509,6 +511,38 @@ static std::optional<DocumentationSymbol> checkOverloadedDocumentationSymbol(
return documentationSymbol;
}

static std::optional<DocumentationSymbol> getMetatableDocumentation(
const Module& module,
AstExpr* parentExpr,
const TableType* mtable,
const AstName& index
)
{
LUAU_ASSERT(FFlag::LuauDocumentationAtPosition);
auto indexIt = mtable->props.find("__index");
if (indexIt == mtable->props.end())
return std::nullopt;

TypeId followed = follow(indexIt->second.type());
const TableType* ttv = get<TableType>(followed);
if (!ttv)
return std::nullopt;

auto propIt = ttv->props.find(index.value);
if (propIt == ttv->props.end())
return std::nullopt;

if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);

return std::nullopt;
}

std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const SourceModule& source, const Module& module, Position position)
{
std::vector<AstNode*> ancestry = findAstAncestryOfPosition(source, position);
Expand Down Expand Up @@ -541,15 +575,50 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
}
else if (const ClassType* ctv = get<ClassType>(parentTy))
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
if (FFlag::LuauDocumentationAtPosition)
{
if (FFlag::LuauSolverV2)
while (ctv)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
ctv = ctv->parent ? Luau::get<Luau::ClassType>(*ctv->parent) : nullptr;
}
}
else
{
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
{
if (FFlag::LuauSolverV2)
{
if (auto ty = propIt->second.readTy)
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
}
else
return checkOverloadedDocumentationSymbol(
module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol
);
}
}
}
else if (FFlag::LuauDocumentationAtPosition)
{
if (const PrimitiveType* ptv = get<PrimitiveType>(parentTy); ptv && ptv->metatable)
{
if (auto mtable = get<TableType>(*ptv->metatable))
{
if (std::optional<std::string> docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index))
return docSymbol;
}
else
return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion Analysis/src/Autocomplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ static bool checkTypeMatch(TypeId subTy, TypeId superTy, NotNull<Scope> scope, T

if (FFlag::LuauSolverV2)
{
TypeFunctionRuntime typeFunctionRuntime; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&iceReporter}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime

if (FFlag::LuauAutocompleteNewSolverLimit)
{
Expand Down
Loading

0 comments on commit 543de6e

Please sign in to comment.