Skip to content

Commit

Permalink
Sync to upstream/release/609 (#1150)
Browse files Browse the repository at this point in the history
### What's changed?
* Syntax for [read-only and write-only
properties](luau-lang/rfcs#15) is now parsed,
but is not yet supported in typechecking

### New Type Solver
* `keyof` and `rawkeyof` type operators have been updated to match final
text of the [RFC](luau-lang/rfcs#16)
* Fixed issues with cyclic type families that were generated for mutable
loop variables

### Native Code Generation
* Fixed inference for number / vector operation that caused an
unnecessary VM assist

---
### Internal Contributors
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
  • Loading branch information
vegorov-rbx authored Jan 19, 2024
1 parent 73360e5 commit cdd1a38
Show file tree
Hide file tree
Showing 43 changed files with 575 additions and 227 deletions.
4 changes: 2 additions & 2 deletions Analysis/include/Luau/ConstraintGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ struct ConstraintGenerator
* Generate constraints to assign assignedTy to the expression expr
* @returns the type of the expression. This may or may not be assignedTy itself.
*/
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy);
Expand Down
3 changes: 2 additions & 1 deletion Analysis/include/Luau/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ struct Frontend
FrontendOptions options;
InternalErrorReporter iceHandler;
std::function<void(const ModuleName& name, const ScopePtr& scope, bool forAutocomplete)> prepareModuleScope;
std::function<void(const ModuleName& name, std::string log)> writeJsonLog = {};

std::unordered_map<ModuleName, std::shared_ptr<SourceNode>> sourceNodes;
std::unordered_map<ModuleName, std::shared_ptr<SourceModule>> sourceModules;
Expand All @@ -253,6 +254,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits, bool recordJsonLog);
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog);

} // namespace Luau
126 changes: 99 additions & 27 deletions Analysis/src/ConstraintGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1008,12 +1008,54 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
std::vector<TypeId> assignees;
assignees.reserve(assign->vars.size);

size_t i = 0;
for (AstExpr* lvalue : assign->vars)
{
TypeId assignee = arena->addType(BlockedType{});

checkLValue(scope, lvalue, assignee);
// This is a really weird thing to do, but it's critically important for some kinds of
// assignments with the current type state behavior. Consider this code:
// local function f(l, r)
// local i = l
// for _ = l, r do
// i = i + 1
// end
// end
//
// With type states now, we will not create a new state for `i` within the loop. This means
// that, in the absence of the analysis below, we would infer a too-broad bound for i: the
// cyclic type t1 where t1 = add<t1 | number, number>. In order to stop this, we say that
// assignments to a definition with a self-referential binary expression do not transform
// the type of the definition. This will only apply for loops, where the definition is
// shared in more places; for non-loops, there will be a separate DefId for the lvalue in
// the assignment, so we will deem the expression to be transformative.
//
// Deeming the addition in the code sample above as non-transformative means that i is known
// to be exactly number further on, ensuring the type family reduces down to number, as is
// expected for this code snippet.
//
// There is a potential for spurious errors here if the expression is more complex than a
// simple binary expression, e.g. i = (i + 1) * 2. At the time of writing, this case hasn't
// materialized.
bool transform = true;

if (assign->values.size > i)
{
AstExpr* value = assign->values.data[i];
if (auto bexp = value->as<AstExprBinary>())
{
DefId lvalueDef = dfg->getDef(lvalue);
DefId lDef = dfg->getDef(bexp->left);
DefId rDef = dfg->getDef(bexp->right);

if (lvalueDef == lDef || lvalueDef == rDef)
transform = false;
}
}

checkLValue(scope, lvalue, assignee, transform);
assignees.push_back(assignee);
++i;
}

TypePackId resultPack = checkPack(scope, assign->values).tp;
Expand All @@ -1027,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
TypeId resultTy = check(scope, &binop).ty;

checkLValue(scope, assign->var, resultTy);
checkLValue(scope, assign->var, resultTy, true);

DefId def = dfg->getDef(assign->var);
scope->lvalueTypes[def] = resultTy;
Expand Down Expand Up @@ -2210,10 +2252,10 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
}
}

std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy)
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform)
{
if (auto local = expr->as<AstExprLocal>())
return checkLValue(scope, local, assignedTy);
return checkLValue(scope, local, assignedTy, transform);
else if (auto global = expr->as<AstExprGlobal>())
return checkLValue(scope, global, assignedTy);
else if (auto indexName = expr->as<AstExprIndexName>())
Expand All @@ -2229,7 +2271,7 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
ice->ice("checkLValue is inexhaustive");
}

std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy)
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform)
{
std::optional<TypeId> annotatedTy = scope->lookup(local->local);
LUAU_ASSERT(annotatedTy);
Expand All @@ -2241,8 +2283,11 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As

if (ty)
{
if (auto lt = getMutable<LocalType>(*ty))
++lt->blockCount;
if (transform)
{
if (auto lt = getMutable<LocalType>(*ty))
++lt->blockCount;
}
}
else
{
Expand All @@ -2251,13 +2296,17 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
scope->lvalueTypes[defId] = *ty;
}

addConstraint(scope, local->location, UnpackConstraint{
arena->addTypePack({*ty}),
arena->addTypePack({assignedTy}),
/*resultIsLValue*/ true
});
if (transform)
{
addConstraint(scope, local->location, UnpackConstraint{
arena->addTypePack({*ty}),
arena->addTypePack({assignedTy}),
/*resultIsLValue*/ true
});

recordInferredBinding(local->local, *ty);
}

recordInferredBinding(local->local, *ty);

return ty;
}
Expand Down Expand Up @@ -2821,20 +2870,38 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool

for (const AstTableProp& prop : tab->props)
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
// TODO: Fill in location.
props[name] = {propTy};
if (prop.access == AstTableAccess::Read)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (prop.access == AstTableAccess::Write)
reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (prop.access == AstTableAccess::ReadWrite)
{
std::string name = prop.name.value;
// TODO: Recursion limit.
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
props[name] = {propTy};
props[name].typeLocation = prop.location;
}
else
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
}

if (tab->indexer)
if (AstTableIndexer* astIndexer = tab->indexer)
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, tab->indexer->indexType, inTypeArguments),
resolveType(scope, tab->indexer->resultType, inTypeArguments),
};
if (astIndexer->access == AstTableAccess::Read)
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
else if (astIndexer->access == AstTableAccess::Write)
reportError(astIndexer->accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
else if (astIndexer->access == AstTableAccess::ReadWrite)
{
// TODO: Recursion limit.
indexer = TableIndexer{
resolveType(scope, astIndexer->indexType, inTypeArguments),
resolveType(scope, astIndexer->resultType, inTypeArguments),
};
}
else
ice->ice("Unexpected property access " + std::to_string(int(astIndexer->access)));
}

result = arena->addType(TableType{props, indexer, scope->level, scope.get(), TableState::Sealed});
Expand Down Expand Up @@ -3174,11 +3241,16 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
const auto& [scope, location, types] = p;

std::vector<TypeId> tys(types.begin(), types.end());
if (tys.size() == 1)
scope->bindings[symbol] = Binding{tys.front(), location};
else
{
TypeId ty = arena->addType(BlockedType{});
addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});

TypeId ty = arena->addType(BlockedType{});
addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});
scope->bindings[symbol] = Binding{ty, location};
}

scope->bindings[symbol] = Binding{ty, location};
}
}

Expand Down
49 changes: 4 additions & 45 deletions Analysis/src/EmbeddedBuiltinDefinitions.cpp
Original file line number Diff line number Diff line change
@@ -1,47 +1,13 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"

LUAU_FASTFLAGVARIABLE(LuauBufferTypeck, false)
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions, false);
LUAU_FASTFLAGVARIABLE(LuauCheckedEmbeddedDefinitions2, false);
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);

namespace Luau
{

static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type
declare class buffer end
declare buffer: {
create: (size: number) -> buffer,
fromstring: (str: string) -> buffer,
tostring: () -> string,
len: (b: buffer) -> number,
copy: (target: buffer, targetOffset: number, source: buffer, sourceOffset: number?, count: number?) -> (),
fill: (b: buffer, offset: number, value: number, count: number?) -> (),
readi8: (b: buffer, offset: number) -> number,
readu8: (b: buffer, offset: number) -> number,
readi16: (b: buffer, offset: number) -> number,
readu16: (b: buffer, offset: number) -> number,
readi32: (b: buffer, offset: number) -> number,
readu32: (b: buffer, offset: number) -> number,
readf32: (b: buffer, offset: number) -> number,
readf64: (b: buffer, offset: number) -> number,
writei8: (b: buffer, offset: number, value: number) -> (),
writeu8: (b: buffer, offset: number, value: number) -> (),
writei16: (b: buffer, offset: number, value: number) -> (),
writeu16: (b: buffer, offset: number, value: number) -> (),
writei32: (b: buffer, offset: number, value: number) -> (),
writeu32: (b: buffer, offset: number, value: number) -> (),
writef32: (b: buffer, offset: number, value: number) -> (),
writef64: (b: buffer, offset: number, value: number) -> (),
readstring: (b: buffer, offset: number, count: number) -> string,
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";

static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare buffer: {
create: (size: number) -> buffer,
Expand Down Expand Up @@ -70,9 +36,6 @@ declare buffer: {
writestring: (b: buffer, offset: number, value: string, count: number?) -> (),
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionLuaSrc = R"BUILTIN_SRC(
declare bit32: {
band: (...number) -> number,
bor: (...number) -> number,
Expand Down Expand Up @@ -488,12 +451,8 @@ std::string getBuiltinDefinitionSource()
{
std::string result = kBuiltinDefinitionLuaSrc;

if (FFlag::LuauBufferTypeck)
result = kBuiltinDefinitionBufferSrc + result;
else
result = kBuiltinDefinitionBufferSrc_DEPRECATED + result;
// Annotates each non generic function as checked
if (FFlag::LuauCheckedEmbeddedDefinitions)
if (FFlag::LuauCheckedEmbeddedDefinitions2 && FFlag::LuauCheckedFunctionSyntax)
result = kBuiltinDefinitionLuaSrcChecked;

return result;
Expand Down
15 changes: 10 additions & 5 deletions Analysis/src/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(LuauRethrowSingleModuleIce, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)

namespace Luau
{
Expand Down Expand Up @@ -872,6 +873,7 @@ void Frontend::addBuildQueueItems(std::vector<BuildQueueItem>& items, std::vecto

data.config = configResolver->getConfig(moduleName);
data.environmentScope = getModuleEnvironment(*sourceModule, data.config, frontendOptions.forAutocomplete);
data.recordJsonLog = FFlag::DebugLuauLogSolverToJson;

Mode mode = sourceModule->mode.value_or(data.config.mode);

Expand Down Expand Up @@ -1169,17 +1171,17 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits)
TypeCheckLimits limits, std::function<void(const ModuleName&, std::string)> writeJsonLog)
{
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
return check(sourceModule, mode, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope,
std::move(prepareModuleScope), options, limits, recordJsonLog);
std::move(prepareModuleScope), options, limits, recordJsonLog, writeJsonLog);
}

ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits, bool recordJsonLog)
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog)
{
ModulePtr result = std::make_shared<Module>();
result->name = sourceModule.name;
Expand Down Expand Up @@ -1281,7 +1283,10 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
if (recordJsonLog)
{
std::string output = logger->compileOutput();
printf("%s\n", output.c_str());
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
writeJsonLog(sourceModule.name, std::move(output));
else
printf("%s\n", output.c_str());
}

return result;
Expand All @@ -1301,7 +1306,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
{
return Luau::check(sourceModule, mode, requireCycles, builtinTypes, NotNull{&iceHandler},
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog);
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog, writeJsonLog);
}
catch (const InternalCompilerError& err)
{
Expand Down
5 changes: 1 addition & 4 deletions Analysis/src/GlobalTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include "Luau/GlobalTypes.h"

LUAU_FASTFLAG(LuauBufferTypeck)

namespace Luau
{

Expand All @@ -18,8 +16,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
globalScope->addBuiltinTypeBinding("string", TypeFun{{}, builtinTypes->stringType});
globalScope->addBuiltinTypeBinding("boolean", TypeFun{{}, builtinTypes->booleanType});
globalScope->addBuiltinTypeBinding("thread", TypeFun{{}, builtinTypes->threadType});
if (FFlag::LuauBufferTypeck)
globalScope->addBuiltinTypeBinding("buffer", TypeFun{{}, builtinTypes->bufferType});
globalScope->addBuiltinTypeBinding("buffer", TypeFun{{}, builtinTypes->bufferType});
globalScope->addBuiltinTypeBinding("unknown", TypeFun{{}, builtinTypes->unknownType});
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});

Expand Down
4 changes: 1 addition & 3 deletions Analysis/src/Linter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)

LUAU_FASTFLAG(LuauBufferTypeck)

namespace Luau
{

Expand Down Expand Up @@ -1107,7 +1105,7 @@ class LintUnknownType : AstVisitor
TypeKind getTypeKind(const std::string& name)
{
if (name == "nil" || name == "boolean" || name == "userdata" || name == "number" || name == "string" || name == "table" ||
name == "function" || name == "thread" || (FFlag::LuauBufferTypeck && name == "buffer"))
name == "function" || name == "thread" || name == "buffer")
return Kind_Primitive;

if (name == "vector")
Expand Down
Loading

0 comments on commit cdd1a38

Please sign in to comment.