Skip to content

Commit

Permalink
Sync to upstream/release/605 (#1118)
Browse files Browse the repository at this point in the history
- Implemented [Require by String with Relative
Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md)
RFC
- Implemented [Require by String with
Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md)
RFC with support for `paths` and `alias` arrays in .luarc
- Added SUBRK and DIVRK bytecode instructions to speed up
constant-number and constant/number operations
- Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to
luau-compile to support code with vectors
 
New Solver
- Correctness fixes to subtyping
- Improvements to dataflow analysis

Native Code Generation
- Added bytecode analysis pass to predict type tags used in operations
- Fixed rare cases of numerical loops being generated without an
interrupt instruction
- Restored optimization data propagation into the linear block
- Duplicate buffer length checks are optimized away

Miscellaneous
- Small performance improvements to new non-strict mode
- Introduced more scripts for fuzzing Luau and processing the results,
including fuzzer build support for CMake

Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Co-authored-by: David Cope <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Vighnesh Vijay <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>

---------

Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Co-authored-by: David Cope <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
  • Loading branch information
8 people authored Dec 2, 2023
1 parent 89b437b commit c755875
Show file tree
Hide file tree
Showing 128 changed files with 3,896 additions and 979 deletions.
4 changes: 2 additions & 2 deletions Analysis/include/Luau/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ struct InstantiationConstraint
TypeId superType;
};

// iteratee is iterable
// iterators is the iteration types.
// variables ~ iterate iterator
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
struct IterableConstraint
{
TypePackId iterator;
Expand Down
1 change: 1 addition & 0 deletions Analysis/include/Luau/ConstraintGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ struct ConstraintGenerator
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index);
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize);
Expand Down
10 changes: 9 additions & 1 deletion Analysis/include/Luau/Error.h
Original file line number Diff line number Diff line change
Expand Up @@ -372,13 +372,21 @@ struct CheckedFunctionCallError
bool operator==(const CheckedFunctionCallError& rhs) const;
};

struct NonStrictFunctionDefinitionError
{
std::string functionName;
std::string argument;
TypeId argumentType;
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
};

using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError>;
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError>;

struct TypeErrorSummary
{
Expand Down
12 changes: 9 additions & 3 deletions Analysis/include/Luau/Subtyping.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ enum class SubtypingVariance
// Used for an empty key. Should never appear in actual code.
Invalid,
Covariant,
// This is used to identify cases where we have a covariant + a
// contravariant reason and we need to merge them.
Contravariant,
Invariant,
};

struct SubtypingReasoning
{
// The path, relative to the _root subtype_, where subtyping failed.
Path subPath;
// The path, relative to the _root supertype_, where subtyping failed.
Path superPath;
SubtypingVariance variance = SubtypingVariance::Covariant;

Expand All @@ -49,6 +54,9 @@ struct SubtypingReasoningHash
size_t operator()(const SubtypingReasoning& r) const;
};

using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>;
static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid};

struct SubtypingResult
{
bool isSubtype = false;
Expand All @@ -58,8 +66,7 @@ struct SubtypingResult

/// The reason for isSubtype to be false. May not be present even if
/// isSubtype is false, depending on the input types.
DenseHashSet<SubtypingReasoning, SubtypingReasoningHash> reasoning{
SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}};
SubtypingReasonings reasoning{kEmptyReasoning};

SubtypingResult& andAlso(const SubtypingResult& other);
SubtypingResult& orElse(const SubtypingResult& other);
Expand All @@ -69,7 +76,6 @@ struct SubtypingResult
SubtypingResult& withBothPath(TypePath::Path path);
SubtypingResult& withSubPath(TypePath::Path path);
SubtypingResult& withSuperPath(TypePath::Path path);
SubtypingResult& withVariance(SubtypingVariance variance);

// Only negates the `isSubtype`.
static SubtypingResult negate(const SubtypingResult& result);
Expand Down
2 changes: 1 addition & 1 deletion Analysis/include/Luau/Unifier2.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct Unifier2
bool unify(TypeId subTy, const UnionType* superUnion);
bool unify(const IntersectionType* subIntersection, TypeId superTy);
bool unify(TypeId subTy, const IntersectionType* superIntersection);
bool unify(const TableType* subTable, const TableType* superTable);
bool unify(TableType* subTable, const TableType* superTable);
bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable);

// TODO think about this one carefully. We don't do unions or intersections of type packs
Expand Down
54 changes: 36 additions & 18 deletions Analysis/src/ConstraintGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,17 +750,18 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI

for (AstLocal* var : forIn->vars)
{
TypeId ty = nullptr;
if (var->annotation)
ty = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
else
ty = freshType(loopScope);

loopScope->bindings[var] = Binding{ty, var->location};

TypeId assignee = arena->addType(BlockedType{});
variableTypes.push_back(assignee);

if (var->annotation)
{
TypeId annotationTy = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
loopScope->bindings[var] = Binding{annotationTy, var->location};
addConstraint(scope, var->location, SubtypeConstraint{assignee, annotationTy});
}
else
loopScope->bindings[var] = Binding{assignee, var->location};

DefId def = dfg->getDef(var);
loopScope->lvalueTypes[def] = assignee;
}
Expand Down Expand Up @@ -1439,9 +1440,6 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
module->astOriginalCallTypes[call->func] = fnType;
module->astOriginalCallTypes[call] = fnType;

TypeId instantiatedFnType = arena->addType(BlockedType{});
addConstraint(scope, call->location, InstantiationConstraint{instantiatedFnType, fnType});

Checkpoint argBeginCheckpoint = checkpoint(this);

std::vector<TypeId> args;
Expand Down Expand Up @@ -1740,12 +1738,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
}
}

Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index)
{
TypeId obj = check(scope, indexName->expr).ty;
TypeId obj = check(scope, indexee).ty;
TypeId result = arena->addType(BlockedType{});

const RefinementKey* key = dfg->getRefinementKey(indexName);
if (key)
{
if (auto ty = lookup(scope.get(), key->def))
Expand All @@ -1754,18 +1751,31 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* in
scope->rvalueRefinements[key->def] = result;
}

addConstraint(scope, indexName->expr->location, HasPropConstraint{result, obj, indexName->index.value});
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index)});

if (key)
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
else
return Inference{result};
}

Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
{
const RefinementKey* key = dfg->getRefinementKey(indexName);
return checkIndexName(scope, key, indexName->expr, indexName->index.value);
}

Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
{
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data);
}

TypeId obj = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty;

TypeId result = freshType(scope);

const RefinementKey* key = dfg->getRefinementKey(indexExpr);
Expand Down Expand Up @@ -3079,15 +3089,23 @@ struct GlobalPrepopulator : AstVisitor
{
}

bool visit(AstExprGlobal* global) override
{
if (auto ty = globalScope->lookup(global->name))
{
DefId def = dfg->getDef(global);
globalScope->lvalueTypes[def] = *ty;
}

return true;
}

bool visit(AstStatFunction* function) override
{
if (AstExprGlobal* g = function->name->as<AstExprGlobal>())
{
TypeId bt = arena->addType(BlockedType{});
globalScope->bindings[g->name] = Binding{bt};

DefId def = dfg->getDef(function->name);
globalScope->lvalueTypes[def] = bt;
}

return true;
Expand Down
56 changes: 14 additions & 42 deletions Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1263,9 +1263,6 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
if (isBlocked(subjectType))
return block(subjectType, constraint);

if (!force && get<FreeType>(subjectType))
return block(subjectType, constraint);

std::optional<TypeId> existingPropType = subjectType;
for (const std::string& segment : c.path)
{
Expand Down Expand Up @@ -1301,25 +1298,13 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con

if (get<FreeType>(subjectType))
{
TypeId ty = freshType(arena, builtinTypes, constraint->scope);

// Mint a chain of free tables per c.path
for (auto it = rbegin(c.path); it != rend(c.path); ++it)
{
TableType t{TableState::Free, TypeLevel{}, constraint->scope};
t.props[*it] = {ty};

ty = arena->addType(std::move(t));
}

LUAU_ASSERT(ty);

bind(subjectType, ty);
if (follow(c.resultType) != follow(ty))
bind(c.resultType, ty);
unblock(subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
/*
* This should never occur because lookupTableProp() will add bounds to
* any free types it encounters. There will always be an
* existingPropType if the subject is free.
*/
LUAU_ASSERT(false);
return false;
}
else if (auto ttv = getMutable<TableType>(subjectType))
{
Expand All @@ -1328,7 +1313,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
LUAU_ASSERT(!subjectType->persistent);

ttv->props[c.path[0]] = Property{c.propType};
bind(c.resultType, c.subjectType);
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
Expand All @@ -1337,26 +1322,12 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
LUAU_ASSERT(!subjectType->persistent);

updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
bind(c.resultType, c.subjectType);
unblock(subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
else
{
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}
}
else
{
// Other kinds of types don't change shape when properties are assigned
// to them. (if they allow properties at all!)
bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}

bind(c.resultType, subjectType);
unblock(c.resultType, constraint->location);
return true;
}

bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
Expand Down Expand Up @@ -1908,6 +1879,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
TypeId retIndex;
if (isNil(firstIndexTy) || isOptional(firstIndexTy))
{
// FIXME freshType is suspect here
firstIndex = arena->addType(UnionType{{freshType(arena, builtinTypes, constraint->scope), builtinTypes->nilType}});
retIndex = firstIndex;
}
Expand Down Expand Up @@ -1949,7 +1921,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
modifiedNextRetHead.push_back(*it);

TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
auto psc = pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, modifiedNextRetPack});
auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack});
inheritBlocks(constraint, psc);

return true;
Expand Down
15 changes: 15 additions & 0 deletions Analysis/src/Error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,12 @@ struct ErrorConverter
return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + std::to_string(e.argumentIndex) +
", but got '" + Luau::toString(e.passed) + "'";
}

std::string operator()(const NonStrictFunctionDefinitionError& e) const
{
return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName +
"' is used in a way that will run time error";
}
};

struct InvalidNameChecker
Expand Down Expand Up @@ -861,6 +867,11 @@ bool CheckedFunctionCallError::operator==(const CheckedFunctionCallError& rhs) c
argumentIndex == rhs.argumentIndex;
}

bool NonStrictFunctionDefinitionError::operator==(const NonStrictFunctionDefinitionError& rhs) const
{
return functionName == rhs.functionName && argument == rhs.argument && argumentType == rhs.argumentType;
}

std::string toString(const TypeError& error)
{
return toString(error, TypeErrorToStringOptions{});
Expand Down Expand Up @@ -1032,6 +1043,10 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.expected = clone(e.expected);
e.passed = clone(e.passed);
}
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
{
e.argumentType = clone(e.argumentType);
}
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}
Expand Down
3 changes: 3 additions & 0 deletions Analysis/src/IostreamHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ static void errorToString(std::ostream& stream, const T& err)
else if constexpr (std::is_same_v<T, CheckedFunctionCallError>)
stream << "CheckedFunctionCallError { expected = '" << toString(err.expected) << "', passed = '" << toString(err.passed)
<< "', checkedFunctionName = " << err.checkedFunctionName << ", argumentIndex = " << std::to_string(err.argumentIndex) << " }";
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
"', argumentType = '" + toString(err.argumentType) + "' }";
else
static_assert(always_false_v<T>, "Non-exhaustive type switch");
}
Expand Down
Loading

0 comments on commit c755875

Please sign in to comment.