From c7eca27909d787edc8136fc019dd1c13006c42cb Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 24 Feb 2022 15:53:37 -0800 Subject: [PATCH 1/8] Sync to upstream/release/516 (#397) --- Analysis/include/Luau/Module.h | 9 +- Analysis/include/Luau/TypeInfer.h | 2 + Analysis/include/Luau/TypeUtils.h | 4 +- Analysis/include/Luau/Unifier.h | 29 ++- Analysis/src/Autocomplete.cpp | 4 +- Analysis/src/Linter.cpp | 1 + Analysis/src/Module.cpp | 27 +-- Analysis/src/Transpiler.cpp | 8 + Analysis/src/TypeInfer.cpp | 67 +++--- Analysis/src/TypeUtils.cpp | 8 +- Analysis/src/TypeVar.cpp | 110 ++-------- Analysis/src/Unifier.cpp | 126 ++++++----- Ast/src/Parser.cpp | 8 +- VM/include/lua.h | 2 +- VM/src/ldo.cpp | 3 +- VM/src/lfunc.cpp | 62 ++---- VM/src/lgc.cpp | 218 +++---------------- VM/src/lgc.h | 10 +- VM/src/lgcdebug.cpp | 72 +------ VM/src/linit.cpp | 2 +- VM/src/lmem.cpp | 127 ++--------- VM/src/lmem.h | 4 - VM/src/lobject.h | 5 +- VM/src/lstate.cpp | 32 +-- VM/src/lstate.h | 5 - VM/src/lstring.cpp | 70 ++---- VM/src/ltable.cpp | 4 +- VM/src/ludata.cpp | 2 +- fuzz/linter.cpp | 8 +- fuzz/luau.proto | 55 +++-- fuzz/proto.cpp | 237 ++++++++++++++------- fuzz/protoprint.cpp | 129 +++++++++-- fuzz/prototest.cpp | 12 +- fuzz/typeck.cpp | 6 +- tests/Autocomplete.test.cpp | 1 - tests/Conformance.test.cpp | 51 ++++- tests/Linter.test.cpp | 13 ++ tests/Parser.test.cpp | 1 - tests/Transpiler.test.cpp | 15 ++ tests/TypeInfer.intersectionTypes.test.cpp | 6 +- tests/TypeInfer.refinements.test.cpp | 16 ++ tests/TypeInfer.singletons.test.cpp | 124 +++++++++++ tests/TypeInfer.tables.test.cpp | 18 ++ tests/TypeInfer.test.cpp | 6 - tests/TypeInfer.tryUnify.test.cpp | 4 +- tests/TypeVar.test.cpp | 12 -- tools/natvis/VM.natvis | 5 +- 47 files changed, 863 insertions(+), 877 deletions(-) diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index 6120077..6c689b7 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -13,8 +13,6 @@ #include #include -LUAU_FASTFLAG(LuauPrepopulateUnionOptionsBeforeAllocation) - namespace Luau { @@ -60,11 +58,8 @@ struct TypeArena template TypeId addType(T tv) { - if (FFlag::LuauPrepopulateUnionOptionsBeforeAllocation) - { - if constexpr (std::is_same_v) - LUAU_ASSERT(tv.options.size() >= 2); - } + if constexpr (std::is_same_v) + LUAU_ASSERT(tv.options.size() >= 2); return addTV(TypeVar(std::move(tv))); } diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 3c5ded3..2440c81 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -31,6 +31,7 @@ bool doesCallError(const AstExprCall* call); bool hasBreak(AstStat* node); const AstStat* getFallthrough(const AstStat* node); +struct UnifierOptions; struct Unifier; // A substitution which replaces generic types in a given set by free types. @@ -245,6 +246,7 @@ struct TypeChecker * Treat any failures as type errors in the final typecheck report. */ bool unify(TypeId subTy, TypeId superTy, const Location& location); + bool unify(TypeId subTy, TypeId superTy, const Location& location, const UnifierOptions& options); bool unify(TypePackId subTy, TypePackId superTy, const Location& location, CountMismatch::Context ctx = CountMismatch::Context::Arg); /** Attempt to unify the types. diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index ffddfe4..42c1bc0 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -13,7 +13,7 @@ namespace Luau using ScopePtr = std::shared_ptr; -std::optional findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location); -std::optional findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location); +std::optional findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location); +std::optional findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, Name name, Location location); } // namespace Luau diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 9db4e22..fe822b0 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -19,11 +19,31 @@ enum Variance Invariant }; +// A substitution which replaces singleton types by their wider types +struct Widen : Substitution +{ + Widen(TypeArena* arena) + : Substitution(TxnLog::empty(), arena) + { + } + + bool isDirty(TypeId ty) override; + bool isDirty(TypePackId ty) override; + TypeId clean(TypeId ty) override; + TypePackId clean(TypePackId ty) override; + bool ignoreChildren(TypeId ty) override; +}; + +// TODO: Use this more widely. +struct UnifierOptions +{ + bool isFunctionCall = false; +}; + struct Unifier { TypeArena* const types; Mode mode; - ScopePtr globalScope; // sigh. Needed solely to get at string's metatable. DEPRECATED_TxnLog DEPRECATED_log; TxnLog log; @@ -34,9 +54,9 @@ struct Unifier UnifierSharedState& sharedState; - Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState, + Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); - Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector>* sharedSeen, const Location& location, + Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog = nullptr); // Test whether the two type vars unify. Never commits the result. @@ -65,7 +85,10 @@ private: void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer); + + TypeId widen(TypeId ty); TypeId deeplyOptional(TypeId ty, std::unordered_map seen = {}); + void cacheResult(TypeId subTy, TypeId superTy); public: diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 5a1ae39..29a2c6b 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -236,10 +236,10 @@ static TypeCorrectKind checkTypeCorrectKind(const Module& module, TypeArena* typ { ty = follow(ty); - auto canUnify = [&typeArena, &module](TypeId subTy, TypeId superTy) { + auto canUnify = [&typeArena](TypeId subTy, TypeId superTy) { InternalErrorReporter iceReporter; UnifierSharedState unifierState(&iceReporter); - Unifier unifier(typeArena, Mode::Strict, module.getModuleScope(), Location(), Variance::Covariant, unifierState); + Unifier unifier(typeArena, Mode::Strict, Location(), Variance::Covariant, unifierState); if (FFlag::LuauAutocompleteAvoidMutation && !FFlag::LuauUseCommittingTxnLog) { diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 8d7d2d9..7635dc0 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -201,6 +201,7 @@ static bool similar(AstExpr* lhs, AstExpr* rhs) return true; } + CASE(AstExprIfElse) return similar(le->condition, re->condition) && similar(le->trueExpr, re->trueExpr) && similar(le->falseExpr, re->falseExpr); else { LUAU_ASSERT(!"Unknown expression type"); diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 817a33e..412b78b 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -16,7 +16,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauTrackOwningArena, false) // Remove with FFlagLuau LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300) LUAU_FASTFLAG(LuauTypeAliasDefaults) LUAU_FASTFLAG(LuauImmutableTypes) -LUAU_FASTFLAGVARIABLE(LuauPrepopulateUnionOptionsBeforeAllocation, false) namespace Luau { @@ -379,28 +378,14 @@ void TypeCloner::operator()(const AnyTypeVar& t) void TypeCloner::operator()(const UnionTypeVar& t) { - if (FFlag::LuauPrepopulateUnionOptionsBeforeAllocation) - { - std::vector options; - options.reserve(t.options.size()); + std::vector options; + options.reserve(t.options.size()); - for (TypeId ty : t.options) - options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); + for (TypeId ty : t.options) + options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); - TypeId result = dest.addType(UnionTypeVar{std::move(options)}); - seenTypes[typeId] = result; - } - else - { - TypeId result = dest.addType(UnionTypeVar{}); - seenTypes[typeId] = result; - - UnionTypeVar* option = getMutable(result); - LUAU_ASSERT(option != nullptr); - - for (TypeId ty : t.options) - option->options.push_back(clone(ty, dest, seenTypes, seenTypePacks, cloneState)); - } + TypeId result = dest.addType(UnionTypeVar{std::move(options)}); + seenTypes[typeId] = result; } void TypeCloner::operator()(const IntersectionTypeVar& t) diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 54bd0d5..a02d396 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -1153,6 +1153,14 @@ struct Printer writer.symbol(")"); } } + else if (const auto& a = typeAnnotation.as()) + { + writer.keyword(a->value ? "true" : "false"); + } + else if (const auto& a = typeAnnotation.as()) + { + writer.string(std::string_view(a->value.data, a->value.size)); + } else if (typeAnnotation.is()) { writer.symbol("%error-type%"); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index c29699b..faf60eb 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -29,7 +29,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(LuauRecursiveTypeParameterRestriction, false) LUAU_FASTFLAGVARIABLE(LuauGenericFunctionsDontCacheTypeParams, false) LUAU_FASTFLAGVARIABLE(LuauImmutableTypes, false) -LUAU_FASTFLAGVARIABLE(LuauNoSealedTypeMod, false) LUAU_FASTFLAGVARIABLE(LuauQuantifyInPlace2, false) LUAU_FASTFLAGVARIABLE(LuauSealExports, false) LUAU_FASTFLAGVARIABLE(LuauSingletonTypes, false) @@ -38,14 +37,14 @@ LUAU_FASTFLAGVARIABLE(LuauTypeAliasDefaults, false) LUAU_FASTFLAGVARIABLE(LuauExpectedTypesOfProperties, false) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryType, false) LUAU_FASTFLAGVARIABLE(LuauPropertiesGetExpectedType, false) -LUAU_FASTFLAGVARIABLE(LuauProperTypeLevels, false) LUAU_FASTFLAGVARIABLE(LuauAscribeCorrectLevelToInferredProperitesOfFreeTables, false) -LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAGVARIABLE(LuauUnsealedTableLiteral, false) LUAU_FASTFLAGVARIABLE(LuauTwoPassAliasDefinitionFix, false) LUAU_FASTFLAGVARIABLE(LuauAssertStripsFalsyTypes, false) LUAU_FASTFLAGVARIABLE(LuauReturnAnyInsteadOfICE, false) // Eventually removed as false. LUAU_FASTFLAGVARIABLE(LuauAnotherTypeLevelFix, false) +LUAU_FASTFLAG(LuauWidenIfSupertypeIsFree) +LUAU_FASTFLAGVARIABLE(LuauDoNotTryToReduce, false) namespace Luau { @@ -1125,7 +1124,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco ty = follow(ty); - if (tableSelf && (FFlag::LuauNoSealedTypeMod ? tableSelf->state != TableState::Sealed : !selfTy->persistent)) + if (tableSelf && tableSelf->state != TableState::Sealed) tableSelf->props[indexName->index.value] = {ty, /* deprecated */ false, {}, indexName->indexLocation}; const FunctionTypeVar* funTy = get(ty); @@ -1138,7 +1137,7 @@ void TypeChecker::check(const ScopePtr& scope, TypeId ty, const ScopePtr& funSco checkFunctionBody(funScope, ty, *function.func); - if (tableSelf && (FFlag::LuauNoSealedTypeMod ? tableSelf->state != TableState::Sealed : !selfTy->persistent)) + if (tableSelf && tableSelf->state != TableState::Sealed) tableSelf->props[indexName->index.value] = { follow(quantify(funScope, ty, indexName->indexLocation)), /* deprecated */ false, {}, indexName->indexLocation}; } @@ -1210,8 +1209,7 @@ void TypeChecker::check(const ScopePtr& scope, const AstStatTypeAlias& typealias { ScopePtr aliasScope = childScope(scope, typealias.location); aliasScope->level = scope->level.incr(); - if (FFlag::LuauProperTypeLevels) - aliasScope->level.subLevel = subLevel; + aliasScope->level.subLevel = subLevel; auto [generics, genericPacks] = createGenericTypes(aliasScope, scope->level, typealias, typealias.generics, typealias.genericPacks, /* useCache = */ true); @@ -1624,7 +1622,7 @@ ExprResult TypeChecker::checkExpr(const ScopePtr& scope, const AstExprIn std::optional TypeChecker::findTablePropertyRespectingMeta(TypeId lhsType, Name name, const Location& location) { ErrorVec errors; - auto result = Luau::findTablePropertyRespectingMeta(errors, globalScope, lhsType, name, location); + auto result = Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location); reportErrors(errors); return result; } @@ -1632,7 +1630,7 @@ std::optional TypeChecker::findTablePropertyRespectingMeta(TypeId lhsTyp std::optional TypeChecker::findMetatableEntry(TypeId type, std::string entry, const Location& location) { ErrorVec errors; - auto result = Luau::findMetatableEntry(errors, globalScope, type, entry, location); + auto result = Luau::findMetatableEntry(errors, type, entry, location); reportErrors(errors); return result; } @@ -1751,13 +1749,23 @@ std::optional TypeChecker::getIndexTypeFromType( return std::nullopt; } - // TODO(amccord): Write some logic to correctly handle intersections. CLI-34659 - std::vector result = reduceUnion(parts); + if (FFlag::LuauDoNotTryToReduce) + { + if (parts.size() == 1) + return parts[0]; - if (result.size() == 1) - return result[0]; + return addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct. + } + else + { + // TODO(amccord): Write some logic to correctly handle intersections. CLI-34659 + std::vector result = reduceUnion(parts); - return addType(IntersectionTypeVar{result}); + if (result.size() == 1) + return result[0]; + + return addType(IntersectionTypeVar{result}); + } } if (addErrors) @@ -2823,10 +2831,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex TypeId TypeChecker::checkFunctionName(const ScopePtr& scope, AstExpr& funName, TypeLevel level) { auto freshTy = [&]() { - if (FFlag::LuauProperTypeLevels) - return freshType(level); - else - return freshType(scope); + return freshType(level); }; if (auto globalName = funName.as()) @@ -3790,7 +3795,14 @@ std::optional> TypeChecker::checkCallOverload(const Scope // has been instantiated, so is a monotype. We can therefore // unify it with a monomorphic function. TypeId r = addType(FunctionTypeVar(scope->level, argPack, retPack)); - unify(fn, r, expr.location); + if (FFlag::LuauWidenIfSupertypeIsFree) + { + UnifierOptions options; + options.isFunctionCall = true; + unify(r, fn, expr.location, options); + } + else + unify(fn, r, expr.location); return {{retPack}}; } @@ -4243,9 +4255,15 @@ TypeId TypeChecker::anyIfNonstrict(TypeId ty) const } bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location) +{ + UnifierOptions options; + return unify(subTy, superTy, location, options); +} + +bool TypeChecker::unify(TypeId subTy, TypeId superTy, const Location& location, const UnifierOptions& options) { Unifier state = mkUnifier(location); - state.tryUnify(subTy, superTy); + state.tryUnify(subTy, superTy, options.isFunctionCall); if (FFlag::LuauUseCommittingTxnLog) state.log.commit(); @@ -4654,7 +4672,7 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d } }; - if (auto ttv = getTableType(FFlag::LuauUnionTagMatchFix ? utk->table : follow(utk->table))) + if (auto ttv = getTableType(utk->table)) accumulate(ttv->props); else if (auto ctv = get(follow(utk->table))) { @@ -4691,8 +4709,7 @@ ScopePtr TypeChecker::childFunctionScope(const ScopePtr& parent, const Location& ScopePtr TypeChecker::childScope(const ScopePtr& parent, const Location& location) { ScopePtr scope = std::make_shared(parent); - if (FFlag::LuauProperTypeLevels) - scope->level = parent->level; + scope->level = parent->level; scope->varargPack = parent->varargPack; currentModule->scopes.push_back(std::make_pair(location, scope)); @@ -4724,7 +4741,7 @@ void TypeChecker::merge(RefinementMap& l, const RefinementMap& r) Unifier TypeChecker::mkUnifier(const Location& location) { - return Unifier{¤tModule->internalTypes, currentModule->mode, globalScope, location, Variance::Covariant, unifierState}; + return Unifier{¤tModule->internalTypes, currentModule->mode, location, Variance::Covariant, unifierState}; } TypeId TypeChecker::freshType(const ScopePtr& scope) @@ -5444,7 +5461,7 @@ GenericTypeDefinitions TypeChecker::createGenericTypes(const ScopePtr& scope, st void TypeChecker::refineLValue(const LValue& lvalue, RefinementMap& refis, const ScopePtr& scope, TypeIdPredicate predicate) { - LUAU_ASSERT(FFlag::LuauDiscriminableUnions2); + LUAU_ASSERT(FFlag::LuauDiscriminableUnions2 || FFlag::LuauAssertStripsFalsyTypes); const LValue* target = &lvalue; std::optional 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. diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 593b54c..c243589 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -10,7 +10,7 @@ LUAU_FASTFLAGVARIABLE(LuauTerminateCyclicMetatableIndexLookup, false) namespace Luau { -std::optional findMetatableEntry(ErrorVec& errors, const ScopePtr& globalScope, TypeId type, std::string entry, Location location) +std::optional findMetatableEntry(ErrorVec& errors, TypeId type, std::string entry, Location location) { type = follow(type); @@ -37,7 +37,7 @@ std::optional findMetatableEntry(ErrorVec& errors, const ScopePtr& globa return std::nullopt; } -std::optional findTablePropertyRespectingMeta(ErrorVec& errors, const ScopePtr& globalScope, TypeId ty, Name name, Location location) +std::optional findTablePropertyRespectingMeta(ErrorVec& errors, TypeId ty, Name name, Location location) { if (get(ty)) return ty; @@ -49,7 +49,7 @@ std::optional findTablePropertyRespectingMeta(ErrorVec& errors, const Sc return it->second.type; } - std::optional mtIndex = findMetatableEntry(errors, globalScope, ty, "__index", location); + std::optional mtIndex = findMetatableEntry(errors, ty, "__index", location); int count = 0; while (mtIndex) { @@ -82,7 +82,7 @@ std::optional findTablePropertyRespectingMeta(ErrorVec& errors, const Sc else errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); - mtIndex = findMetatableEntry(errors, globalScope, *mtIndex, "__index", location); + mtIndex = findMetatableEntry(errors, *mtIndex, "__index", location); } return std::nullopt; diff --git a/Analysis/src/TypeVar.cpp b/Analysis/src/TypeVar.cpp index b2358c2..a1dcfdb 100644 --- a/Analysis/src/TypeVar.cpp +++ b/Analysis/src/TypeVar.cpp @@ -23,9 +23,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAGVARIABLE(LuauRefactorTypeVarQuestions, false) LUAU_FASTFLAG(LuauErrorRecoveryType) -LUAU_FASTFLAG(LuauUnionTagMatchFix) LUAU_FASTFLAG(LuauDiscriminableUnions2) namespace Luau @@ -145,20 +143,13 @@ bool isNil(TypeId ty) bool isBoolean(TypeId ty) { - if (FFlag::LuauRefactorTypeVarQuestions) - { - if (isPrim(ty, PrimitiveTypeVar::Boolean) || get(get(follow(ty)))) - return true; + if (isPrim(ty, PrimitiveTypeVar::Boolean) || get(get(follow(ty)))) + return true; - if (auto utv = get(follow(ty))) - return std::all_of(begin(utv), end(utv), isBoolean); + if (auto utv = get(follow(ty))) + return std::all_of(begin(utv), end(utv), isBoolean); - return false; - } - else - { - return isPrim(ty, PrimitiveTypeVar::Boolean); - } + return false; } bool isNumber(TypeId ty) @@ -168,20 +159,13 @@ bool isNumber(TypeId ty) bool isString(TypeId ty) { - if (FFlag::LuauRefactorTypeVarQuestions) - { - if (isPrim(ty, PrimitiveTypeVar::String) || get(get(follow(ty)))) - return true; + if (isPrim(ty, PrimitiveTypeVar::String) || get(get(follow(ty)))) + return true; - if (auto utv = get(follow(ty))) - return std::all_of(begin(utv), end(utv), isString); + if (auto utv = get(follow(ty))) + return std::all_of(begin(utv), end(utv), isString); - return false; - } - else - { - return isPrim(ty, PrimitiveTypeVar::String); - } + return false; } bool isThread(TypeId ty) @@ -194,45 +178,11 @@ bool isOptional(TypeId ty) if (isNil(ty)) return true; - if (FFlag::LuauRefactorTypeVarQuestions) - { - auto utv = get(follow(ty)); - if (!utv) - return false; - - return std::any_of(begin(utv), end(utv), isNil); - } - else - { - std::unordered_set seen; - std::deque queue{ty}; - while (!queue.empty()) - { - TypeId current = follow(queue.front()); - queue.pop_front(); - - if (seen.count(current)) - continue; - - seen.insert(current); - - if (isNil(current)) - return true; - - if (auto u = get(current)) - { - for (TypeId option : u->options) - { - if (isNil(option)) - return true; - - queue.push_back(option); - } - } - } - + auto utv = get(follow(ty)); + if (!utv) return false; - } + + return std::any_of(begin(utv), end(utv), isNil); } bool isTableIntersection(TypeId ty) @@ -263,38 +213,24 @@ std::optional getMetatable(TypeId type) return mtType->metatable; else if (const ClassTypeVar* classType = get(type)) return classType->metatable; - else if (FFlag::LuauRefactorTypeVarQuestions) + else if (isString(type)) { - if (isString(type)) - { - auto ptv = get(getSingletonTypes().stringType); - LUAU_ASSERT(ptv && ptv->metatable); - return ptv->metatable; - } - else - return std::nullopt; - } - else - { - if (const PrimitiveTypeVar* primitiveType = get(type); primitiveType && primitiveType->metatable) - { - LUAU_ASSERT(primitiveType->type == PrimitiveTypeVar::String); - return primitiveType->metatable; - } - else - return std::nullopt; + auto ptv = get(getSingletonTypes().stringType); + LUAU_ASSERT(ptv && ptv->metatable); + return ptv->metatable; } + + return std::nullopt; } const TableTypeVar* getTableType(TypeId type) { - if (FFlag::LuauUnionTagMatchFix) - type = follow(type); + type = follow(type); if (const TableTypeVar* ttv = get(type)) return ttv; else if (const MetatableTypeVar* mtv = get(type)) - return get(FFlag::LuauUnionTagMatchFix ? follow(mtv->table) : mtv->table); + return get(follow(mtv->table)); else return nullptr; } @@ -311,7 +247,7 @@ const std::string* getName(TypeId type) { if (mtv->syntheticName) return &*mtv->syntheticName; - type = FFlag::LuauUnionTagMatchFix ? follow(mtv->table) : mtv->table; + type = follow(mtv->table); } if (auto ttv = get(type)) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 322f6eb..d0eba01 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -21,9 +21,8 @@ LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false); LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false) LUAU_FASTFLAG(LuauSingletonTypes) LUAU_FASTFLAG(LuauErrorRecoveryType); -LUAU_FASTFLAG(LuauProperTypeLevels); -LUAU_FASTFLAGVARIABLE(LuauUnionTagMatchFix, false) LUAU_FASTFLAGVARIABLE(LuauFollowWithCommittingTxnLogInAnyUnification, false) +LUAU_FASTFLAGVARIABLE(LuauWidenIfSupertypeIsFree, false) namespace Luau { @@ -122,7 +121,7 @@ struct PromoteTypeLevels } }; -void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty) +static void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypeId ty) { // Type levels of types from other modules are already global, so we don't need to promote anything inside if (FFlag::LuauImmutableTypes && ty->owningArena != typeArena) @@ -133,6 +132,7 @@ void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const Typ visitTypeVarOnce(ty, ptl, seen); } +// TODO: use this and make it static. void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel, TypePackId tp) { // Type levels of types from other modules are already global, so we don't need to promote anything inside @@ -247,6 +247,48 @@ struct SkipCacheForType bool result = false; }; +bool Widen::isDirty(TypeId ty) +{ + return FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty)); +} + +bool Widen::isDirty(TypePackId) +{ + return false; +} + +TypeId Widen::clean(TypeId ty) +{ + LUAU_ASSERT(isDirty(ty)); + auto stv = FFlag::LuauUseCommittingTxnLog ? log->getMutable(ty) : getMutable(ty); + LUAU_ASSERT(stv); + + if (get(stv)) + return getSingletonTypes().stringType; + else + { + // If this assert trips, it's likely we now have number singletons. + LUAU_ASSERT(get(stv)); + return getSingletonTypes().booleanType; + } +} + +TypePackId Widen::clean(TypePackId) +{ + throw std::runtime_error("Widen attempted to clean a dirty type pack?"); +} + +bool Widen::ignoreChildren(TypeId ty) +{ + // Sometimes we unify ("hi") -> free1 with (free2) -> free3, so don't ignore functions. + // TODO: should we be doing this? we would need to rework how checkCallOverload does the unification. + if (FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty))) + return false; + + // We only care about unions. + return !(FFlag::LuauUseCommittingTxnLog ? log->is(ty) : bool(get(ty))); +} + static std::optional hasUnificationTooComplex(const ErrorVec& errors) { auto isUnificationTooComplex = [](const TypeError& te) { @@ -263,43 +305,22 @@ static std::optional hasUnificationTooComplex(const ErrorVec& errors) // Used for tagged union matching heuristic, returns first singleton type field static std::optional> getTableMatchTag(TypeId type) { - if (FFlag::LuauUnionTagMatchFix) + if (auto ttv = getTableType(type)) { - if (auto ttv = getTableType(type)) + for (auto&& [name, prop] : ttv->props) { - for (auto&& [name, prop] : ttv->props) - { - if (auto sing = get(follow(prop.type))) - return {{name, sing}}; - } - } - } - else - { - type = follow(type); - - if (auto ttv = get(type)) - { - for (auto&& [name, prop] : ttv->props) - { - if (auto sing = get(follow(prop.type))) - return {{name, sing}}; - } - } - else if (auto mttv = get(type)) - { - return getTableMatchTag(mttv->table); + if (auto sing = get(follow(prop.type))) + return {{name, sing}}; } } return std::nullopt; } -Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState, +Unifier::Unifier(TypeArena* types, Mode mode, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) : types(types) , mode(mode) - , globalScope(std::move(globalScope)) , log(parentLog) , location(location) , variance(variance) @@ -308,11 +329,10 @@ Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Locati LUAU_ASSERT(sharedState.iceHandler); } -Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector>* sharedSeen, const Location& location, +Unifier::Unifier(TypeArena* types, Mode mode, std::vector>* sharedSeen, const Location& location, Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog) : types(types) , mode(mode) - , globalScope(std::move(globalScope)) , DEPRECATED_log(sharedSeen) , log(parentLog, sharedSeen) , location(location) @@ -435,6 +455,8 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool } else if (superFree) { + TypeLevel superLevel = superFree->level; + occursCheck(superTy, subTy); bool occursFailed = false; if (FFlag::LuauUseCommittingTxnLog) @@ -442,8 +464,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool else occursFailed = bool(get(superTy)); - TypeLevel superLevel = superFree->level; - // Unification can't change the level of a generic. auto subGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable(subTy) : get(subTy); if (subGeneric && !subGeneric->level.subsumes(superLevel)) @@ -459,20 +479,14 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool if (FFlag::LuauUseCommittingTxnLog) { promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy); - log.replace(superTy, BoundTypeVar(subTy)); + log.replace(superTy, BoundTypeVar(widen(subTy))); } else { - if (FFlag::LuauProperTypeLevels) - promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy); - else if (auto subLevel = getMutableLevel(subTy)) - { - if (!subLevel->subsumes(superFree->level)) - *subLevel = superFree->level; - } + promoteTypeLevels(DEPRECATED_log, log, types, superLevel, subTy); DEPRECATED_log(superTy); - *asMutable(superTy) = BoundTypeVar(subTy); + *asMutable(superTy) = BoundTypeVar(widen(subTy)); } } @@ -507,16 +521,7 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool } else { - if (FFlag::LuauProperTypeLevels) - promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy); - else if (auto superLevel = getMutableLevel(superTy)) - { - if (!superLevel->subsumes(subFree->level)) - { - DEPRECATED_log(superTy); - *superLevel = subFree->level; - } - } + promoteTypeLevels(DEPRECATED_log, log, types, subLevel, superTy); DEPRECATED_log(subTy); *asMutable(subTy) = BoundTypeVar(superTy); @@ -2064,6 +2069,17 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection) } } +TypeId Unifier::widen(TypeId ty) +{ + if (!FFlag::LuauWidenIfSupertypeIsFree) + return ty; + + Widen widen{types}; + std::optional result = widen.substitute(ty); + // TODO: what does it mean for substitution to fail to widen? + return result.value_or(ty); +} + TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map seen) { ty = follow(ty); @@ -2915,7 +2931,7 @@ void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp) std::optional Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name) { - return Luau::findTablePropertyRespectingMeta(errors, globalScope, lhsType, name, location); + return Luau::findTablePropertyRespectingMeta(errors, lhsType, name, location); } void Unifier::occursCheck(TypeId needle, TypeId haystack) @@ -3096,9 +3112,9 @@ void Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, Typ Unifier Unifier::makeChildUnifier() { if (FFlag::LuauUseCommittingTxnLog) - return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, &log}; + return Unifier{types, mode, log.sharedSeen, location, variance, sharedState, &log}; else - return Unifier{types, mode, globalScope, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log}; + return Unifier{types, mode, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log}; } bool Unifier::isNonstrictMode() const diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 235d634..8767daa 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -12,7 +12,6 @@ LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000) LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauParseSingletonTypes, false) LUAU_FASTFLAGVARIABLE(LuauParseTypeAliasDefaults, false) -LUAU_FASTFLAGVARIABLE(LuauParseRecoverTypePackEllipsis, false) LUAU_FASTFLAGVARIABLE(LuauParseAllHotComments, false) LUAU_FASTFLAGVARIABLE(LuauTableFieldFunctionDebugname, false) @@ -2372,11 +2371,11 @@ std::pair, AstArray> Parser::parseG { Location nameLocation = lexer.current().location; AstName name = parseName().name; - if (lexer.current().type == Lexeme::Dot3 || (FFlag::LuauParseRecoverTypePackEllipsis && seenPack)) + if (lexer.current().type == Lexeme::Dot3 || seenPack) { seenPack = true; - if (FFlag::LuauParseRecoverTypePackEllipsis && lexer.current().type != Lexeme::Dot3) + if (lexer.current().type != Lexeme::Dot3) report(lexer.current().location, "Generic types come before generic type packs"); else nextLexeme(); @@ -2414,9 +2413,6 @@ std::pair, AstArray> Parser::parseG } else { - if (!FFlag::LuauParseRecoverTypePackEllipsis && seenPack) - report(lexer.current().location, "Generic types come before generic type packs"); - if (withDefaultValues && lexer.current().type == '=') { seenDefault = true; diff --git a/VM/include/lua.h b/VM/include/lua.h index 5f39cb3..af0e283 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -44,7 +44,7 @@ typedef int (*lua_Continuation)(lua_State* L, int status); ** prototype for memory-allocation functions */ -typedef void* (*lua_Alloc)(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize); +typedef void* (*lua_Alloc)(void* ud, void* ptr, size_t osize, size_t nsize); /* non-return type */ #define l_noret void LUA_NORETURN diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index d87f066..b5ae496 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -151,8 +151,7 @@ l_noret luaD_throw(lua_State* L, int errcode) static void correctstack(lua_State* L, TValue* oldstack) { L->top = (L->top - oldstack) + L->stack; - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - for (UpVal* up = L->openupval; up != NULL; up = (UpVal*)up->next) + for (UpVal* up = L->openupval; up != NULL; up = up->u.l.threadnext) up->v = (up->v - oldstack) + L->stack; for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++) { diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 582d462..66447a9 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -6,13 +6,10 @@ #include "lmem.h" #include "lgc.h" -LUAU_FASTFLAGVARIABLE(LuauNoDirectUpvalRemoval, false) -LUAU_FASTFLAG(LuauGcPagedSweep) - Proto* luaF_newproto(lua_State* L) { Proto* f = luaM_newgco(L, Proto, sizeof(Proto), L->activememcat); - luaC_link(L, f, LUA_TPROTO); + luaC_init(L, f, LUA_TPROTO); f->k = NULL; f->sizek = 0; f->p = NULL; @@ -40,7 +37,7 @@ Proto* luaF_newproto(lua_State* L) Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) { Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat); - luaC_link(L, c, LUA_TFUNCTION); + luaC_init(L, c, LUA_TFUNCTION); c->isC = 0; c->env = e; c->nupvalues = cast_byte(nelems); @@ -55,7 +52,7 @@ Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e) { Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat); - luaC_link(L, c, LUA_TFUNCTION); + luaC_init(L, c, LUA_TFUNCTION); c->isC = 1; c->env = e; c->nupvalues = cast_byte(nelems); @@ -82,8 +79,7 @@ UpVal* luaF_findupval(lua_State* L, StkId level) return p; } - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - pp = (UpVal**)&p->next; + pp = &p->u.l.threadnext; } UpVal* uv = luaM_newgco(L, UpVal, sizeof(UpVal), L->activememcat); /* not found: create a new one */ @@ -94,19 +90,10 @@ UpVal* luaF_findupval(lua_State* L, StkId level) // chain the upvalue in the threads open upvalue list at the proper position UpVal* next = *pp; - - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - uv->next = (GCObject*)next; - - if (FFlag::LuauGcPagedSweep) - { - uv->u.l.threadprev = pp; - if (next) - { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - next->u.l.threadprev = (UpVal**)&uv->next; - } - } + uv->u.l.threadnext = next; + uv->u.l.threadprev = pp; + if (next) + next->u.l.threadprev = &uv->u.l.threadnext; *pp = uv; @@ -125,15 +112,11 @@ void luaF_unlinkupval(UpVal* uv) uv->u.l.next->u.l.prev = uv->u.l.prev; uv->u.l.prev->u.l.next = uv->u.l.next; - if (FFlag::LuauGcPagedSweep) - { - // unlink upvalue from the thread open upvalue list - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and this and the following cast will not be required - *uv->u.l.threadprev = (UpVal*)uv->next; + // unlink upvalue from the thread open upvalue list + *uv->u.l.threadprev = uv->u.l.threadnext; - if (UpVal* next = (UpVal*)uv->next) - next->u.l.threadprev = uv->u.l.threadprev; - } + if (UpVal* next = uv->u.l.threadnext) + next->u.l.threadprev = uv->u.l.threadprev; } void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page) @@ -145,34 +128,27 @@ void luaF_freeupval(lua_State* L, UpVal* uv, lua_Page* page) void luaF_close(lua_State* L, StkId level) { - global_State* g = L->global; // TODO: remove with FFlagLuauNoDirectUpvalRemoval + global_State* g = L->global; UpVal* uv; while (L->openupval != NULL && (uv = L->openupval)->v >= level) { GCObject* o = obj2gco(uv); LUAU_ASSERT(!isblack(o) && uv->v != &uv->u.value); - if (!FFlag::LuauGcPagedSweep) - L->openupval = (UpVal*)uv->next; /* remove from `open' list */ + // by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue + luaF_unlinkupval(uv); - if (FFlag::LuauGcPagedSweep && isdead(g, o)) + if (isdead(g, o)) { - // by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue - luaF_unlinkupval(uv); // close the upvalue without copying the dead data so that luaF_freeupval will not unlink again uv->v = &uv->u.value; } - else if (!FFlag::LuauNoDirectUpvalRemoval && isdead(g, o)) - { - luaF_freeupval(L, uv, NULL); /* free upvalue */ - } else { - // by removing the upvalue from global/thread open upvalue lists, L->openupval will be pointing to the next upvalue - luaF_unlinkupval(uv); setobj(L, &uv->u.value, uv->v); - uv->v = &uv->u.value; /* now current value lives here */ - luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */ + uv->v = &uv->u.value; + // GC state of a new closed upvalue has to be initialized + luaC_initupval(L, uv); } } } diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index 724b24b..8c3a202 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauGcPagedSweep, false) - #define GC_SWEEPMAX 40 #define GC_SWEEPCOST 10 #define GC_SWEEPPAGESTEPCOST 4 @@ -64,7 +62,6 @@ static void recordGcStateTime(global_State* g, int startgcstate, double seconds, case GCSatomic: g->gcstats.currcycle.atomictime += seconds; break; - case GCSsweepstring: case GCSsweep: g->gcstats.currcycle.sweeptime += seconds; break; @@ -490,65 +487,6 @@ static void freeobj(lua_State* L, GCObject* o, lua_Page* page) } } -#define sweepwholelist(L, p) sweeplist(L, p, SIZE_MAX) - -static GCObject** sweeplist(lua_State* L, GCObject** p, size_t count) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - GCObject* curr; - global_State* g = L->global; - int deadmask = otherwhite(g); - LUAU_ASSERT(testbit(deadmask, FIXEDBIT)); /* make sure we never sweep fixed objects */ - while ((curr = *p) != NULL && count-- > 0) - { - int alive = (curr->gch.marked ^ WHITEBITS) & deadmask; - if (curr->gch.tt == LUA_TTHREAD) - { - sweepwholelist(L, (GCObject**)&gco2th(curr)->openupval); /* sweep open upvalues */ - - lua_State* th = gco2th(curr); - - if (alive) - { - resetbit(th->stackstate, THREAD_SLEEPINGBIT); - shrinkstack(th); - } - } - if (alive) - { /* not dead? */ - LUAU_ASSERT(!isdead(g, curr)); - makewhite(g, curr); /* make it white (for next cycle) */ - p = &curr->gch.next; - } - else - { /* must erase `curr' */ - LUAU_ASSERT(isdead(g, curr)); - *p = curr->gch.next; - if (curr == g->rootgc) /* is the first element of the list? */ - g->rootgc = curr->gch.next; /* adjust first */ - freeobj(L, curr, NULL); - } - } - - return p; -} - -static void deletelist(lua_State* L, GCObject** p, GCObject* limit) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - GCObject* curr; - while ((curr = *p) != limit) - { - if (curr->gch.tt == LUA_TTHREAD) /* delete open upvalues of each thread */ - deletelist(L, (GCObject**)&gco2th(curr)->openupval, NULL); - - *p = curr->gch.next; - freeobj(L, curr, NULL); - } -} - static void shrinkbuffers(lua_State* L) { global_State* g = L->global; @@ -570,8 +508,6 @@ static void shrinkbuffersfull(lua_State* L) static bool deletegco(void* context, lua_Page* page, GCObject* gco) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - // we are in the process of deleting everything // threads with open upvalues will attempt to close them all on removal // but those upvalues might point to stack values that were already deleted @@ -598,32 +534,13 @@ void luaC_freeall(lua_State* L) LUAU_ASSERT(L == g->mainthread); - if (FFlag::LuauGcPagedSweep) - { - luaM_visitgco(L, L, deletegco); + luaM_visitgco(L, L, deletegco); - for (int i = 0; i < g->strt.size; i++) /* free all string lists */ - LUAU_ASSERT(g->strt.hash[i] == NULL); + for (int i = 0; i < g->strt.size; i++) /* free all string lists */ + LUAU_ASSERT(g->strt.hash[i] == NULL); - LUAU_ASSERT(L->global->strt.nuse == 0); - LUAU_ASSERT(g->strbufgc == NULL); - } - else - { - LUAU_ASSERT(L->next == NULL); /* mainthread is at the end of rootgc list */ - - deletelist(L, &g->rootgc, obj2gco(L)); - - for (int i = 0; i < g->strt.size; i++) /* free all string lists */ - deletelist(L, (GCObject**)&g->strt.hash[i], NULL); - - LUAU_ASSERT(L->global->strt.nuse == 0); - deletelist(L, (GCObject**)&g->strbufgc, NULL); - - // unfortunately, when string objects are freed, the string table use count is decremented - // even when the string is a buffer that wasn't placed into the table - L->global->strt.nuse = 0; - } + LUAU_ASSERT(L->global->strt.nuse == 0); + LUAU_ASSERT(g->strbufgc == NULL); } static void markmt(global_State* g) @@ -687,26 +604,13 @@ static size_t atomic(lua_State* L) g->weak = NULL; /* flip current white */ g->currentwhite = cast_byte(otherwhite(g)); - g->sweepstrgc = 0; - - if (FFlag::LuauGcPagedSweep) - { - g->sweepgcopage = g->allgcopages; - g->gcstate = GCSsweep; - } - else - { - g->sweepgc = &g->rootgc; - g->gcstate = GCSsweepstring; - } - + g->sweepgcopage = g->allgcopages; + g->gcstate = GCSsweep; return work; } static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; int deadmask = otherwhite(g); @@ -740,8 +644,6 @@ static bool sweepgco(lua_State* L, lua_Page* page, GCObject* gco) // a version of generic luaM_visitpage specialized for the main sweep stage static int sweepgcopage(lua_State* L, lua_Page* page) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - char* start; char* end; int busyBlocks; @@ -819,75 +721,29 @@ static size_t gcstep(lua_State* L, size_t limit) cost = atomic(L); /* finish mark phase */ - if (FFlag::LuauGcPagedSweep) - LUAU_ASSERT(g->gcstate == GCSsweep); - else - LUAU_ASSERT(g->gcstate == GCSsweepstring); - break; - } - case GCSsweepstring: - { - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - while (g->sweepstrgc < g->strt.size && cost < limit) - { - sweepwholelist(L, (GCObject**)&g->strt.hash[g->sweepstrgc++]); - - cost += GC_SWEEPCOST; - } - - // nothing more to sweep? - if (g->sweepstrgc >= g->strt.size) - { - // sweep string buffer list and preserve used string count - uint32_t nuse = L->global->strt.nuse; - - sweepwholelist(L, (GCObject**)&g->strbufgc); - - L->global->strt.nuse = nuse; - - g->gcstate = GCSsweep; // end sweep-string phase - } + LUAU_ASSERT(g->gcstate == GCSsweep); break; } case GCSsweep: { - if (FFlag::LuauGcPagedSweep) + while (g->sweepgcopage && cost < limit) { - while (g->sweepgcopage && cost < limit) - { - lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page + lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page - int steps = sweepgcopage(L, g->sweepgcopage); + int steps = sweepgcopage(L, g->sweepgcopage); - g->sweepgcopage = next; - cost += steps * GC_SWEEPPAGESTEPCOST; - } - - // nothing more to sweep? - if (g->sweepgcopage == NULL) - { - // don't forget to visit main thread - sweepgco(L, NULL, obj2gco(g->mainthread)); - - shrinkbuffers(L); - g->gcstate = GCSpause; /* end collection */ - } + g->sweepgcopage = next; + cost += steps * GC_SWEEPPAGESTEPCOST; } - else + + // nothing more to sweep? + if (g->sweepgcopage == NULL) { - while (*g->sweepgc && cost < limit) - { - g->sweepgc = sweeplist(L, g->sweepgc, GC_SWEEPMAX); + // don't forget to visit main thread + sweepgco(L, NULL, obj2gco(g->mainthread)); - cost += GC_SWEEPMAX * GC_SWEEPCOST; - } - - if (*g->sweepgc == NULL) - { /* nothing more to sweep? */ - shrinkbuffers(L); - g->gcstate = GCSpause; /* end collection */ - } + shrinkbuffers(L); + g->gcstate = GCSpause; /* end collection */ } break; } @@ -1013,26 +869,18 @@ void luaC_fullgc(lua_State* L) if (g->gcstate <= GCSatomic) { /* reset sweep marks to sweep all elements (returning them to white) */ - g->sweepstrgc = 0; - if (FFlag::LuauGcPagedSweep) - g->sweepgcopage = g->allgcopages; - else - g->sweepgc = &g->rootgc; + g->sweepgcopage = g->allgcopages; /* reset other collector lists */ g->gray = NULL; g->grayagain = NULL; g->weak = NULL; - - if (FFlag::LuauGcPagedSweep) - g->gcstate = GCSsweep; - else - g->gcstate = GCSsweepstring; + g->gcstate = GCSsweep; } - LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); + LUAU_ASSERT(g->gcstate == GCSsweep); /* finish any pending sweep phase */ while (g->gcstate != GCSpause) { - LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); + LUAU_ASSERT(g->gcstate == GCSsweep); gcstep(L, SIZE_MAX); } @@ -1120,30 +968,19 @@ void luaC_barrierback(lua_State* L, Table* t) g->grayagain = o; } -void luaC_linkobj(lua_State* L, GCObject* o, uint8_t tt) +void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt) { global_State* g = L->global; - if (!FFlag::LuauGcPagedSweep) - { - o->gch.next = g->rootgc; - g->rootgc = o; - } o->gch.marked = luaC_white(g); o->gch.tt = tt; o->gch.memcat = L->activememcat; } -void luaC_linkupval(lua_State* L, UpVal* uv) +void luaC_initupval(lua_State* L, UpVal* uv) { global_State* g = L->global; GCObject* o = obj2gco(uv); - if (!FFlag::LuauGcPagedSweep) - { - o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */ - g->rootgc = o; - } - if (isgray(o)) { if (keepinvariant(g)) @@ -1221,9 +1058,6 @@ const char* luaC_statename(int state) case GCSatomic: return "atomic"; - case GCSsweepstring: - return "sweepstring"; - case GCSsweep: return "sweep"; diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 2acb5d8..253e269 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -13,9 +13,7 @@ #define GCSpropagate 1 #define GCSpropagateagain 2 #define GCSatomic 3 -// TODO: remove with FFlagLuauGcPagedSweep -#define GCSsweepstring 4 -#define GCSsweep 5 +#define GCSsweep 4 /* ** macro to tell when main invariant (white objects cannot point to black @@ -132,13 +130,13 @@ luaC_wakethread(L); \ } -#define luaC_link(L, o, tt) luaC_linkobj(L, cast_to(GCObject*, (o)), tt) +#define luaC_init(L, o, tt) luaC_initobj(L, cast_to(GCObject*, (o)), tt) LUAI_FUNC void luaC_freeall(lua_State* L); LUAI_FUNC void luaC_step(lua_State* L, bool assist); LUAI_FUNC void luaC_fullgc(lua_State* L); -LUAI_FUNC void luaC_linkobj(lua_State* L, GCObject* o, uint8_t tt); -LUAI_FUNC void luaC_linkupval(lua_State* L, UpVal* uv); +LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt); +LUAI_FUNC void luaC_initupval(lua_State* L, UpVal* uv); LUAI_FUNC void luaC_barrierupval(lua_State* L, GCObject* v); LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v); LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 30242e5..2b38619 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -13,8 +13,6 @@ #include #include -LUAU_FASTFLAG(LuauGcPagedSweep) - static void validateobjref(global_State* g, GCObject* f, GCObject* t) { LUAU_ASSERT(!isdead(g, t)); @@ -104,8 +102,7 @@ static void validatestack(global_State* g, lua_State* l) if (l->namecall) validateobjref(g, obj2gco(l), obj2gco(l->namecall)); - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - for (UpVal* uv = l->openupval; uv; uv = (UpVal*)uv->next) + for (UpVal* uv = l->openupval; uv; uv = uv->u.l.threadnext) { LUAU_ASSERT(uv->tt == LUA_TUPVAL); LUAU_ASSERT(uv->v != &uv->u.value); @@ -141,7 +138,7 @@ static void validateobj(global_State* g, GCObject* o) /* dead objects can only occur during sweep */ if (isdead(g, o)) { - LUAU_ASSERT(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); + LUAU_ASSERT(g->gcstate == GCSsweep); return; } @@ -180,18 +177,6 @@ static void validateobj(global_State* g, GCObject* o) } } -static void validatelist(global_State* g, GCObject* o) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - while (o) - { - validateobj(g, o); - - o = o->gch.next; - } -} - static void validategraylist(global_State* g, GCObject* o) { if (!keepinvariant(g)) @@ -224,8 +209,6 @@ static void validategraylist(global_State* g, GCObject* o) static bool validategco(void* context, lua_Page* page, GCObject* gco) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - lua_State* L = (lua_State*)context; global_State* g = L->global; @@ -248,20 +231,9 @@ void luaC_validate(lua_State* L) validategraylist(g, g->gray); validategraylist(g, g->grayagain); - if (FFlag::LuauGcPagedSweep) - { - validategco(L, NULL, obj2gco(g->mainthread)); + validategco(L, NULL, obj2gco(g->mainthread)); - luaM_visitgco(L, L, validategco); - } - else - { - for (int i = 0; i < g->strt.size; ++i) - validatelist(g, (GCObject*)(g->strt.hash[i])); - - validatelist(g, g->rootgc); - validatelist(g, (GCObject*)(g->strbufgc)); - } + luaM_visitgco(L, L, validategco); for (UpVal* uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { @@ -521,30 +493,8 @@ static void dumpobj(FILE* f, GCObject* o) } } -static void dumplist(FILE* f, GCObject* o) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - while (o) - { - dumpref(f, o); - fputc(':', f); - dumpobj(f, o); - fputc(',', f); - fputc('\n', f); - - // thread has additional list containing collectable objects that are not present in rootgc - if (o->gch.tt == LUA_TTHREAD) - dumplist(f, (GCObject*)gco2th(o)->openupval); - - o = o->gch.next; - } -} - static bool dumpgco(void* context, lua_Page* page, GCObject* gco) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - FILE* f = (FILE*)context; dumpref(f, gco); @@ -563,19 +513,9 @@ void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* fprintf(f, "{\"objects\":{\n"); - if (FFlag::LuauGcPagedSweep) - { - dumpgco(f, NULL, obj2gco(g->mainthread)); + dumpgco(f, NULL, obj2gco(g->mainthread)); - luaM_visitgco(L, f, dumpgco); - } - else - { - dumplist(f, g->rootgc); - dumplist(f, (GCObject*)(g->strbufgc)); - for (int i = 0; i < g->strt.size; ++i) - dumplist(f, (GCObject*)(g->strt.hash[i])); - } + luaM_visitgco(L, f, dumpgco); fprintf(f, "\"0\":{\"type\":\"userdata\",\"cat\":0,\"size\":0}\n"); // to avoid issues with trailing , fprintf(f, "},\"roots\":{\n"); diff --git a/VM/src/linit.cpp b/VM/src/linit.cpp index c93f431..fd95f59 100644 --- a/VM/src/linit.cpp +++ b/VM/src/linit.cpp @@ -68,7 +68,7 @@ void luaL_sandboxthread(lua_State* L) lua_setsafeenv(L, LUA_GLOBALSINDEX, true); } -static void* l_alloc(lua_State* L, void* ud, void* ptr, size_t osize, size_t nsize) +static void* l_alloc(void* ud, void* ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 19617b8..899cb0c 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -78,8 +78,6 @@ * allocated pages. */ -LUAU_FASTFLAG(LuauGcPagedSweep) - #ifndef __has_feature #define __has_feature(x) 0 #endif @@ -98,8 +96,10 @@ LUAU_FASTFLAG(LuauGcPagedSweep) * To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock * the sizes of all critical structures down. */ -#if defined(__APPLE__) && !defined(__MACH__) +#if defined(__APPLE__) #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : gcc32) +#elif defined(__i386__) +#define ABISWITCH(x64, ms32, gcc32) (gcc32) #else // Android somehow uses a similar ABI to MSVC, *not* to iOS... #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32) @@ -114,14 +114,8 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table #endif static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); -// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(16, 16, 16) -static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header"); -// TODO (FFlagLuauGcPagedSweep): this will become ABISWITCH(48, 32, 32) -static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header"); - -// TODO (FFlagLuauGcPagedSweep): new code with old 'next' pointer requires that GCObject start at the same point as TString/UpVal -static_assert(offsetof(GCObject, uv) == 0, "UpVal data must be located at the start of the GCObject"); -static_assert(offsetof(GCObject, ts) == 0, "TString data must be located at the start of the GCObject"); +static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header"); +static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header"); const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kMaxSmallSize = 512; @@ -208,53 +202,13 @@ l_noret luaM_toobig(lua_State* L) luaG_runerror(L, "memory allocation error: block too big"); } -static lua_Page* newpageold(lua_State* L, uint8_t sizeClass) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - global_State* g = L->global; - lua_Page* page = (lua_Page*)(*g->frealloc)(L, g->ud, NULL, 0, kPageSize); - if (!page) - luaD_throw(L, LUA_ERRMEM); - - int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + kBlockHeader; - int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize; - - ASAN_POISON_MEMORY_REGION(page->data, blockSize * blockCount); - - // setup page header - page->prev = NULL; - page->next = NULL; - - page->gcolistprev = NULL; - page->gcolistnext = NULL; - - page->pageSize = kPageSize; - page->blockSize = blockSize; - - // note: we start with the last block in the page and move downward - // either order would work, but that way we don't need to store the block count in the page - // additionally, GC stores objects in singly linked lists, and this way the GC lists end up in increasing pointer order - page->freeList = NULL; - page->freeNext = (blockCount - 1) * blockSize; - page->busyBlocks = 0; - - // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) - LUAU_ASSERT(!g->freepages[sizeClass]); - g->freepages[sizeClass] = page; - - return page; -} - static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; LUAU_ASSERT(pageSize - int(offsetof(lua_Page, data)) >= blockSize * blockCount); - lua_Page* page = (lua_Page*)(*g->frealloc)(L, g->ud, NULL, 0, pageSize); + lua_Page* page = (lua_Page*)(*g->frealloc)(g->ud, NULL, 0, pageSize); if (!page) luaD_throw(L, LUA_ERRMEM); @@ -290,8 +244,6 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0); int blockCount = (kPageSize - offsetof(lua_Page, data)) / blockSize; @@ -304,29 +256,8 @@ static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** g return page; } -static void freepageold(lua_State* L, lua_Page* page, uint8_t sizeClass) -{ - LUAU_ASSERT(!FFlag::LuauGcPagedSweep); - - global_State* g = L->global; - - // remove page from freelist - if (page->next) - page->next->prev = page->prev; - - if (page->prev) - page->prev->next = page->next; - else if (g->freepages[sizeClass] == page) - g->freepages[sizeClass] = page->next; - - // so long - (*g->frealloc)(L, g->ud, page, kPageSize, 0); -} - static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; if (gcopageset) @@ -342,13 +273,11 @@ static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page) } // so long - (*g->frealloc)(L, g->ud, page, page->pageSize, 0); + (*g->frealloc)(g->ud, page, page->pageSize, 0); } static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - // remove page from freelist if (page->next) page->next->prev = page->prev; @@ -368,12 +297,7 @@ static void* newblock(lua_State* L, int sizeClass) // slow path: no page in the freelist, allocate a new one if (!page) - { - if (FFlag::LuauGcPagedSweep) - page = newclasspage(L, g->freepages, NULL, sizeClass, true); - else - page = newpageold(L, sizeClass); - } + page = newclasspage(L, g->freepages, NULL, sizeClass, true); LUAU_ASSERT(!page->prev); LUAU_ASSERT(page->freeList || page->freeNext >= 0); @@ -416,8 +340,6 @@ static void* newblock(lua_State* L, int sizeClass) static void* newgcoblock(lua_State* L, int sizeClass) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; lua_Page* page = g->freegcopages[sizeClass]; @@ -496,17 +418,11 @@ static void freeblock(lua_State* L, int sizeClass, void* block) // if it's the last block in the page, we don't need the page if (page->busyBlocks == 0) - { - if (FFlag::LuauGcPagedSweep) - freeclasspage(L, g->freepages, NULL, page, sizeClass); - else - freepageold(L, page, sizeClass); - } + freeclasspage(L, g->freepages, NULL, page, sizeClass); } static void freegcoblock(lua_State* L, int sizeClass, void* block, lua_Page* page) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); LUAU_ASSERT(page && page->busyBlocks > 0); LUAU_ASSERT(page->blockSize == kSizeClassConfig.sizeOfClass[sizeClass]); LUAU_ASSERT(block >= page->data && block < (char*)page + page->pageSize); @@ -544,7 +460,7 @@ void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat) int nclass = sizeclass(nsize); - void* block = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(L, g->ud, NULL, 0, nsize); + void* block = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize); if (block == NULL && nsize > 0) luaD_throw(L, LUA_ERRMEM); @@ -556,9 +472,6 @@ void* luaM_new_(lua_State* L, size_t nsize, uint8_t memcat) GCObject* luaM_newgco_(lua_State* L, size_t nsize, uint8_t memcat) { - if (!FFlag::LuauGcPagedSweep) - return (GCObject*)luaM_new_(L, nsize, memcat); - // we need to accommodate space for link for free blocks (freegcolink) LUAU_ASSERT(nsize >= kGCOLinkOffset + sizeof(void*)); @@ -602,7 +515,7 @@ void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat) if (oclass >= 0) freeblock(L, oclass, block); else - (*g->frealloc)(L, g->ud, block, osize, 0); + (*g->frealloc)(g->ud, block, osize, 0); g->totalbytes -= osize; g->memcatbytes[memcat] -= osize; @@ -610,12 +523,6 @@ void luaM_free_(lua_State* L, void* block, size_t osize, uint8_t memcat) void luaM_freegco_(lua_State* L, GCObject* block, size_t osize, uint8_t memcat, lua_Page* page) { - if (!FFlag::LuauGcPagedSweep) - { - luaM_free_(L, block, osize, memcat); - return; - } - global_State* g = L->global; LUAU_ASSERT((osize == 0) == (block == NULL)); @@ -652,7 +559,7 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8 // if either block needs to be allocated using a block allocator, we can't use realloc directly if (nclass >= 0 || oclass >= 0) { - result = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(L, g->ud, NULL, 0, nsize); + result = nclass >= 0 ? newblock(L, nclass) : (*g->frealloc)(g->ud, NULL, 0, nsize); if (result == NULL && nsize > 0) luaD_throw(L, LUA_ERRMEM); @@ -662,11 +569,11 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8 if (oclass >= 0) freeblock(L, oclass, block); else - (*g->frealloc)(L, g->ud, block, osize, 0); + (*g->frealloc)(g->ud, block, osize, 0); } else { - result = (*g->frealloc)(L, g->ud, block, osize, nsize); + result = (*g->frealloc)(g->ud, block, osize, nsize); if (result == NULL && nsize > 0) luaD_throw(L, LUA_ERRMEM); } @@ -679,8 +586,6 @@ void* luaM_realloc_(lua_State* L, void* block, size_t osize, size_t nsize, uint8 void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - int blockCount = (page->pageSize - offsetof(lua_Page, data)) / page->blockSize; LUAU_ASSERT(page->freeNext >= -page->blockSize && page->freeNext <= (blockCount - 1) * page->blockSize); @@ -700,8 +605,6 @@ lua_Page* luaM_getnextgcopage(lua_Page* page) void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - char* start; char* end; int busyBlocks; @@ -730,8 +633,6 @@ void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; for (lua_Page* curr = g->allgcopages; curr;) diff --git a/VM/src/lmem.h b/VM/src/lmem.h index 1bfe48f..0078845 100644 --- a/VM/src/lmem.h +++ b/VM/src/lmem.h @@ -7,11 +7,7 @@ struct lua_Page; union GCObject; -// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_newgco to luaM_new -#define luaM_new(L, t, size, memcat) cast_to(t*, luaM_new_(L, size, memcat)) #define luaM_newgco(L, t, size, memcat) cast_to(t*, luaM_newgco_(L, size, memcat)) -// TODO: remove with FFlagLuauGcPagedSweep and rename luaM_freegco to luaM_free -#define luaM_free(L, p, size, memcat) luaM_free_(L, (p), size, memcat) #define luaM_freegco(L, p, size, memcat, page) luaM_freegco_(L, obj2gco(p), size, memcat, page) #define luaM_arraysize_(n, e) ((cast_to(size_t, (n)) <= SIZE_MAX / (e)) ? (n) * (e) : (luaM_toobig(L), SIZE_MAX)) diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 57ffd82..5e02c2e 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -15,7 +15,6 @@ typedef union GCObject GCObject; */ // clang-format off #define CommonHeader \ - GCObject* next; /* TODO: remove with FFlagLuauGcPagedSweep */ \ uint8_t tt; uint8_t marked; uint8_t memcat // clang-format on @@ -233,6 +232,8 @@ typedef struct TString int16_t atom; // 2 byte padding + TString* next; // next string in the hash table bucket or the string buffer linked list + unsigned int hash; unsigned int len; @@ -327,7 +328,7 @@ typedef struct UpVal struct UpVal* next; /* thread double linked list (when open) */ - // TODO: when FFlagLuauGcPagedSweep is removed, old outer 'next' value will be placed here + struct UpVal* threadnext; /* note: this is the location of a pointer to this upvalue in the previous element that can be either an UpVal or a lua_State */ struct UpVal** threadprev; } l; diff --git a/VM/src/lstate.cpp b/VM/src/lstate.cpp index d6d127c..d4f3f0a 100644 --- a/VM/src/lstate.cpp +++ b/VM/src/lstate.cpp @@ -10,7 +10,6 @@ #include "ldo.h" #include "ldebug.h" -LUAU_FASTFLAG(LuauGcPagedSweep) LUAU_FASTFLAGVARIABLE(LuauReduceStackReallocs, false) /* @@ -90,8 +89,6 @@ static void close_state(lua_State* L) global_State* g = L->global; luaF_close(L, L->stack); /* close all upvalues for this thread */ luaC_freeall(L); /* collect all objects */ - if (!FFlag::LuauGcPagedSweep) - LUAU_ASSERT(g->rootgc == obj2gco(L)); LUAU_ASSERT(g->strbufgc == NULL); LUAU_ASSERT(g->strt.nuse == 0); luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0); @@ -99,22 +96,20 @@ static void close_state(lua_State* L) for (int i = 0; i < LUA_SIZECLASSES; i++) { LUAU_ASSERT(g->freepages[i] == NULL); - if (FFlag::LuauGcPagedSweep) - LUAU_ASSERT(g->freegcopages[i] == NULL); + LUAU_ASSERT(g->freegcopages[i] == NULL); } - if (FFlag::LuauGcPagedSweep) - LUAU_ASSERT(g->allgcopages == NULL); + LUAU_ASSERT(g->allgcopages == NULL); LUAU_ASSERT(g->totalbytes == sizeof(LG)); LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG)); for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++) LUAU_ASSERT(g->memcatbytes[i] == 0); - (*g->frealloc)(L, g->ud, L, sizeof(LG), 0); + (*g->frealloc)(g->ud, L, sizeof(LG), 0); } lua_State* luaE_newthread(lua_State* L) { lua_State* L1 = luaM_newgco(L, lua_State, sizeof(lua_State), L->activememcat); - luaC_link(L, L1, LUA_TTHREAD); + luaC_init(L, L1, LUA_TTHREAD); preinit_state(L1, L->global); L1->activememcat = L->activememcat; // inherit the active memory category stack_init(L1, L); /* init stack */ @@ -184,13 +179,11 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) int i; lua_State* L; global_State* g; - void* l = (*f)(NULL, ud, NULL, 0, sizeof(LG)); + void* l = (*f)(ud, NULL, 0, sizeof(LG)); if (l == NULL) return NULL; L = (lua_State*)l; g = &((LG*)L)->g; - if (!FFlag::LuauGcPagedSweep) - L->next = NULL; L->tt = LUA_TTHREAD; L->marked = g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); L->memcat = 0; @@ -214,11 +207,6 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) setnilvalue(&g->pseudotemp); setnilvalue(registry(L)); g->gcstate = GCSpause; - if (!FFlag::LuauGcPagedSweep) - g->rootgc = obj2gco(L); - g->sweepstrgc = 0; - if (!FFlag::LuauGcPagedSweep) - g->sweepgc = &g->rootgc; g->gray = NULL; g->grayagain = NULL; g->weak = NULL; @@ -230,14 +218,10 @@ lua_State* lua_newstate(lua_Alloc f, void* ud) for (i = 0; i < LUA_SIZECLASSES; i++) { g->freepages[i] = NULL; - if (FFlag::LuauGcPagedSweep) - g->freegcopages[i] = NULL; - } - if (FFlag::LuauGcPagedSweep) - { - g->allgcopages = NULL; - g->sweepgcopage = NULL; + g->freegcopages[i] = NULL; } + g->allgcopages = NULL; + g->sweepgcopage = NULL; for (i = 0; i < LUA_T_COUNT; i++) g->mt[i] = NULL; for (i = 0; i < LUA_UTAG_LIMIT; i++) diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 6dd8913..3ee9671 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -142,11 +142,6 @@ typedef struct global_State uint8_t gcstate; /* state of garbage collector */ - int sweepstrgc; /* position of sweep in `strt' */ - // TODO: remove with FFlagLuauGcPagedSweep - GCObject* rootgc; /* list of all collectable objects */ - // TODO: remove with FFlagLuauGcPagedSweep - GCObject** sweepgc; /* position of sweep in `rootgc' */ GCObject* gray; /* list of gray objects */ GCObject* grayagain; /* list of objects to be traversed atomically */ GCObject* weak; /* list of weak tables (to be cleared) */ diff --git a/VM/src/lstring.cpp b/VM/src/lstring.cpp index 9bbc43d..8725014 100644 --- a/VM/src/lstring.cpp +++ b/VM/src/lstring.cpp @@ -7,8 +7,6 @@ #include -LUAU_FASTFLAG(LuauGcPagedSweep) - unsigned int luaS_hash(const char* str, size_t len) { // Note that this hashing algorithm is replicated in BytecodeBuilder.cpp, BytecodeBuilder::getStringHash @@ -46,8 +44,6 @@ unsigned int luaS_hash(const char* str, size_t len) void luaS_resize(lua_State* L, int newsize) { - if (L->global->gcstate == GCSsweepstring) - return; /* cannot resize during GC traverse */ TString** newhash = luaM_newarray(L, newsize, TString*, 0); stringtable* tb = &L->global->strt; for (int i = 0; i < newsize; i++) @@ -58,13 +54,11 @@ void luaS_resize(lua_State* L, int newsize) TString* p = tb->hash[i]; while (p) { /* for each node in the list */ - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - TString* next = (TString*)p->next; /* save next */ + TString* next = p->next; /* save next */ unsigned int h = p->hash; int h1 = lmod(h, newsize); /* new position */ LUAU_ASSERT(cast_int(h % newsize) == lmod(h, newsize)); - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - p->next = (GCObject*)newhash[h1]; /* chain it */ + p->next = newhash[h1]; /* chain it */ newhash[h1] = p; p = next; } @@ -91,8 +85,7 @@ static TString* newlstr(lua_State* L, const char* str, size_t l, unsigned int h) ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, l) : -1; tb = &L->global->strt; h = lmod(h, tb->size); - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the case will not be required - ts->next = (GCObject*)tb->hash[h]; /* chain new entry */ + ts->next = tb->hash[h]; /* chain new entry */ tb->hash[h] = ts; tb->nuse++; if (tb->nuse > cast_to(uint32_t, tb->size) && tb->size <= INT_MAX / 2) @@ -104,20 +97,9 @@ static void linkstrbuf(lua_State* L, TString* ts) { global_State* g = L->global; - if (FFlag::LuauGcPagedSweep) - { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - ts->next = (GCObject*)g->strbufgc; - g->strbufgc = ts; - ts->marked = luaC_white(g); - } - else - { - GCObject* o = obj2gco(ts); - o->gch.next = (GCObject*)g->strbufgc; - g->strbufgc = gco2ts(o); - o->gch.marked = luaC_white(g); - } + ts->next = g->strbufgc; + g->strbufgc = ts; + ts->marked = luaC_white(g); } static void unlinkstrbuf(lua_State* L, TString* ts) @@ -130,14 +112,12 @@ static void unlinkstrbuf(lua_State* L, TString* ts) { if (curr == ts) { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - *p = (TString*)curr->next; + *p = curr->next; return; } else { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - p = (TString**)&curr->next; + p = &curr->next; } } @@ -167,8 +147,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts) int bucket = lmod(h, tb->size); // search if we already have this string in the hash table - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - for (TString* el = tb->hash[bucket]; el != NULL; el = (TString*)el->next) + for (TString* el = tb->hash[bucket]; el != NULL; el = el->next) { if (el->len == ts->len && memcmp(el->data, ts->data, ts->len) == 0) { @@ -187,8 +166,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts) // Complete string object ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - ts->next = (GCObject*)tb->hash[bucket]; // chain new entry + ts->next = tb->hash[bucket]; // chain new entry tb->hash[bucket] = ts; tb->nuse++; @@ -201,8 +179,7 @@ TString* luaS_buffinish(lua_State* L, TString* ts) TString* luaS_newlstr(lua_State* L, const char* str, size_t l) { unsigned int h = luaS_hash(str, l); - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = (TString*)el->next) + for (TString* el = L->global->strt.hash[lmod(h, L->global->strt.size)]; el != NULL; el = el->next) { if (el->len == l && (memcmp(str, getstr(el), l) == 0)) { @@ -217,8 +194,6 @@ TString* luaS_newlstr(lua_State* L, const char* str, size_t l) static bool unlinkstr(lua_State* L, TString* ts) { - LUAU_ASSERT(FFlag::LuauGcPagedSweep); - global_State* g = L->global; TString** p = &g->strt.hash[lmod(ts->hash, g->strt.size)]; @@ -227,14 +202,12 @@ static bool unlinkstr(lua_State* L, TString* ts) { if (curr == ts) { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - *p = (TString*)curr->next; + *p = curr->next; return true; } else { - // TODO (FFlagLuauGcPagedSweep): 'next' type will change after removal of the flag and the cast will not be required - p = (TString**)&curr->next; + p = &curr->next; } } @@ -243,20 +216,11 @@ static bool unlinkstr(lua_State* L, TString* ts) void luaS_free(lua_State* L, TString* ts, lua_Page* page) { - if (FFlag::LuauGcPagedSweep) - { - // Unchain from the string table - if (!unlinkstr(L, ts)) - unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands - else - L->global->strt.nuse--; - - luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page); - } + // Unchain from the string table + if (!unlinkstr(L, ts)) + unlinkstrbuf(L, ts); // An unlikely scenario when we have a string buffer on our hands else - { L->global->strt.nuse--; - luaM_free(L, ts, sizestring(ts->len), ts->memcat); - } + luaM_freegco(L, ts, sizestring(ts->len), ts->memcat, page); } diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index c57374e..0412ea7 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -425,7 +425,7 @@ static void rehash(lua_State* L, Table* t, const TValue* ek) Table* luaH_new(lua_State* L, int narray, int nhash) { Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); - luaC_link(L, t, LUA_TTABLE); + luaC_init(L, t, LUA_TTABLE); t->metatable = NULL; t->flags = cast_byte(~0); t->array = NULL; @@ -742,7 +742,7 @@ int luaH_getn(Table* t) Table* luaH_clone(lua_State* L, Table* tt) { Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); - luaC_link(L, t, LUA_TTABLE); + luaC_init(L, t, LUA_TTABLE); t->metatable = tt->metatable; t->flags = tt->flags; t->array = NULL; diff --git a/VM/src/ludata.cpp b/VM/src/ludata.cpp index 758a9bd..0dfac50 100644 --- a/VM/src/ludata.cpp +++ b/VM/src/ludata.cpp @@ -12,7 +12,7 @@ Udata* luaU_newudata(lua_State* L, size_t s, int tag) if (s > INT_MAX - sizeof(Udata)) luaM_toobig(L); Udata* u = luaM_newgco(L, Udata, sizeudata(s), L->activememcat); - luaC_link(L, u, LUA_TUSERDATA); + luaC_init(L, u, LUA_TUSERDATA); u->len = int(s); u->metatable = NULL; LUAU_ASSERT(tag >= 0 && tag <= 255); diff --git a/fuzz/linter.cpp b/fuzz/linter.cpp index 04638d2..0bdd49f 100644 --- a/fuzz/linter.cpp +++ b/fuzz/linter.cpp @@ -1,10 +1,12 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include -#include "Luau/TypeInfer.h" -#include "Luau/Linter.h" + #include "Luau/BuiltinDefinitions.h" -#include "Luau/ModuleResolver.h" #include "Luau/Common.h" +#include "Luau/Linter.h" +#include "Luau/ModuleResolver.h" +#include "Luau/Parser.h" +#include "Luau/TypeInfer.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { diff --git a/fuzz/luau.proto b/fuzz/luau.proto index c78fcf3..190b8c5 100644 --- a/fuzz/luau.proto +++ b/fuzz/luau.proto @@ -96,11 +96,13 @@ message ExprIndexExpr { } message ExprFunction { - repeated Local args = 1; - required bool vararg = 2; - required StatBlock body = 3; - repeated Type types = 4; - repeated Type rettypes = 5; + repeated Typename generics = 1; + repeated Typename genericpacks = 2; + repeated Local args = 3; + required bool vararg = 4; + required StatBlock body = 5; + repeated Type types = 6; + repeated Type rettypes = 7; } message TableItem { @@ -153,7 +155,10 @@ message ExprBinary { message ExprIfElse { required Expr cond = 1; required Expr then = 2; - required Expr else = 3; + oneof else_oneof { + Expr else = 3; + ExprIfElse elseif = 4; + } } message LValue { @@ -183,6 +188,7 @@ message Stat { StatFunction function = 14; StatLocalFunction local_function = 15; StatTypeAlias type_alias = 16; + StatRequireIntoLocalHelper require_into_local = 17; } } @@ -276,9 +282,16 @@ message StatLocalFunction { } message StatTypeAlias { - required Typename name = 1; - required Type type = 2; - repeated Typename generics = 3; + required bool export = 1; + required Typename name = 2; + required Type type = 3; + repeated Typename generics = 4; + repeated Typename genericpacks = 5; +} + +message StatRequireIntoLocalHelper { + required Local var = 1; + required int32 modulenum = 2; } message Type { @@ -292,6 +305,8 @@ message Type { TypeIntersection intersection = 7; TypeClass class = 8; TypeRef ref = 9; + TypeBoolean boolean = 10; + TypeString string = 11; } } @@ -301,7 +316,8 @@ message TypePrimitive { message TypeLiteral { required Typename name = 1; - repeated Typename generics = 2; + repeated Type generics = 2; + repeated Typename genericpacks = 3; } message TypeTableItem { @@ -320,8 +336,10 @@ message TypeTable { } message TypeFunction { - repeated Type args = 1; - repeated Type rets = 2; + repeated Typename generics = 1; + repeated Typename genericpacks = 2; + repeated Type args = 3; + repeated Type rets = 4; // TODO: vararg? } @@ -347,3 +365,16 @@ message TypeRef { required Local prefix = 1; required Typename index = 2; } + +message TypeBoolean { + required bool val = 1; +} + +message TypeString { + required string val = 1; +} + +message ModuleSet { + optional StatBlock module = 1; + required StatBlock program = 2; +} diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index f407248..912fef2 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -2,16 +2,17 @@ #include "src/libfuzzer/libfuzzer_macro.h" #include "luau.pb.h" -#include "Luau/TypeInfer.h" #include "Luau/BuiltinDefinitions.h" -#include "Luau/ModuleResolver.h" -#include "Luau/ModuleResolver.h" -#include "Luau/Compiler.h" -#include "Luau/Linter.h" #include "Luau/BytecodeBuilder.h" #include "Luau/Common.h" +#include "Luau/Compiler.h" +#include "Luau/Frontend.h" +#include "Luau/Linter.h" +#include "Luau/ModuleResolver.h" +#include "Luau/Parser.h" #include "Luau/ToString.h" #include "Luau/Transpiler.h" +#include "Luau/TypeInfer.h" #include "lua.h" #include "lualib.h" @@ -30,7 +31,7 @@ const bool kFuzzTypes = true; static_assert(!(kFuzzVM && !kFuzzCompiler), "VM requires the compiler!"); -std::string protoprint(const luau::StatBlock& stat, bool types); +std::vector protoprint(const luau::ModuleSet& stat, bool types); LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) @@ -38,6 +39,7 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauTableTypeMaximumStringifierLength) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTarjanChildLimit) +LUAU_FASTFLAG(DebugLuauFreezeArena) std::chrono::milliseconds kInterruptTimeout(10); std::chrono::time_point interruptDeadline; @@ -135,10 +137,58 @@ int registerTypes(Luau::TypeChecker& env) return 0; } +struct FuzzFileResolver : Luau::FileResolver +{ + std::optional readSource(const Luau::ModuleName& name) override + { + auto it = source.find(name); + if (it == source.end()) + return std::nullopt; -static std::string debugsource; + return Luau::SourceCode{it->second, Luau::SourceCode::Module}; + } -DEFINE_PROTO_FUZZER(const luau::StatBlock& message) + std::optional resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* expr) override + { + if (Luau::AstExprGlobal* g = expr->as()) + return Luau::ModuleInfo{g->name.value}; + + return std::nullopt; + } + + std::string getHumanReadableModuleName(const Luau::ModuleName& name) const override + { + return name; + } + + std::optional getEnvironmentForModule(const Luau::ModuleName& name) const override + { + return std::nullopt; + } + + std::unordered_map source; +}; + +struct FuzzConfigResolver : Luau::ConfigResolver +{ + FuzzConfigResolver() + { + defaultConfig.mode = Luau::Mode::Nonstrict; // typecheckTwice option will cover Strict mode + defaultConfig.enabledLint.warningMask = ~0ull; + defaultConfig.parseOptions.captureComments = true; + } + + virtual const Luau::Config& getConfig(const Luau::ModuleName& name) const override + { + return defaultConfig; + } + + Luau::Config defaultConfig; +}; + +static std::vector debugsources; + +DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) { FInt::LuauTypeInferRecursionLimit.value = 100; FInt::LuauTypeInferTypePackLoopLimit.value = 100; @@ -151,91 +201,90 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message) if (strncmp(flag->name, "Luau", 4) == 0) flag->value = true; - Luau::Allocator allocator; - Luau::AstNameTable names(allocator); + FFlag::DebugLuauFreezeArena.value = true; - std::string source = protoprint(message, kFuzzTypes); + std::vector sources = protoprint(message, kFuzzTypes); // stash source in a global for easier crash dump debugging - debugsource = source; - - Luau::ParseResult parseResult = Luau::Parser::parse(source.c_str(), source.size(), names, allocator); - - // "static" here is to accelerate fuzzing process by only creating and populating the type environment once - static Luau::NullModuleResolver moduleResolver; - static Luau::InternalErrorReporter iceHandler; - static Luau::TypeChecker sharedEnv(&moduleResolver, &iceHandler); - static int once = registerTypes(sharedEnv); - (void)once; - static int once2 = (Luau::freeze(sharedEnv.globalTypes), 0); - (void)once2; - - iceHandler.onInternalError = [](const char* error) { - printf("ICE: %s\n", error); - LUAU_ASSERT(!"ICE"); - }; + debugsources = sources; static bool debug = getenv("LUAU_DEBUG") != 0; if (debug) { - fprintf(stdout, "--\n%s\n", source.c_str()); + for (std::string& source : sources) + fprintf(stdout, "--\n%s\n", source.c_str()); fflush(stdout); } - std::string bytecode; + // parse all sources + std::vector> parseAllocators; + std::vector> parseNameTables; - // compile - if (kFuzzCompiler && parseResult.errors.empty()) + Luau::ParseOptions parseOptions; + parseOptions.captureComments = true; + + std::vector parseResults; + + for (std::string& source : sources) { - Luau::CompileOptions compileOptions; + parseAllocators.push_back(std::make_unique()); + parseNameTables.push_back(std::make_unique(*parseAllocators.back())); - try - { - Luau::BytecodeBuilder bcb; - Luau::compileOrThrow(bcb, parseResult.root, names, compileOptions); - bytecode = bcb.getBytecode(); - } - catch (const Luau::CompileError&) - { - // not all valid ASTs can be compiled due to limits on number of registers - } + parseResults.push_back(Luau::Parser::parse(source.c_str(), source.size(), *parseNameTables.back(), *parseAllocators.back(), parseOptions)); } - // typecheck - if (kFuzzTypeck && parseResult.root) - { - Luau::SourceModule sourceModule; - sourceModule.root = parseResult.root; - sourceModule.mode = Luau::Mode::Nonstrict; - - Luau::TypeChecker typeck(&moduleResolver, &iceHandler); - typeck.globalScope = sharedEnv.globalScope; - - Luau::ModulePtr module = nullptr; - - try - { - module = typeck.check(sourceModule, Luau::Mode::Nonstrict); - } - catch (std::exception&) - { - // This catches internal errors that the type checker currently (unfortunately) throws in some cases - } - - // lint (note that we need access to types so we need to do this with typeck in scope) - if (kFuzzLinter) - { - Luau::LintOptions lintOptions = {~0u}; - Luau::lint(parseResult.root, names, sharedEnv.globalScope, module.get(), {}, lintOptions); - } - } - - // validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down - // note: it's important for typeck to be destroyed at this point! + // typecheck all sources if (kFuzzTypeck) { - for (auto& p : sharedEnv.globalScope->bindings) + static FuzzFileResolver fileResolver; + static Luau::NullConfigResolver configResolver; + static Luau::FrontendOptions options{true, true}; + static Luau::Frontend frontend(&fileResolver, &configResolver, options); + + static int once = registerTypes(frontend.typeChecker); + (void)once; + static int once2 = (Luau::freeze(frontend.typeChecker.globalTypes), 0); + (void)once2; + + frontend.iceHandler.onInternalError = [](const char* error) { + printf("ICE: %s\n", error); + LUAU_ASSERT(!"ICE"); + }; + + // restart + frontend.clear(); + fileResolver.source.clear(); + + // load sources + for (size_t i = 0; i < sources.size(); i++) + { + std::string name = "module" + std::to_string(i); + fileResolver.source[name] = sources[i]; + } + + // check sources + for (size_t i = 0; i < sources.size(); i++) + { + std::string name = "module" + std::to_string(i); + + try + { + Luau::CheckResult result = frontend.check(name, std::nullopt); + + // lint (note that we need access to types so we need to do this with typeck in scope) + if (kFuzzLinter && result.errors.empty()) + frontend.lint(name, std::nullopt); + } + catch (std::exception&) + { + // This catches internal errors that the type checker currently (unfortunately) throws in some cases + } + } + + // validate sharedEnv post-typecheck; valuable for debugging some typeck crashes but slows fuzzing down + // note: it's important for typeck to be destroyed at this point! + for (auto& p : frontend.typeChecker.globalScope->bindings) { Luau::ToStringOptions opts; opts.exhaustive = true; @@ -246,12 +295,44 @@ DEFINE_PROTO_FUZZER(const luau::StatBlock& message) } } - if (kFuzzTranspile && parseResult.root) + if (kFuzzTranspile) { - transpileWithTypes(*parseResult.root); + for (Luau::ParseResult& parseResult : parseResults) + { + if (parseResult.root) + transpileWithTypes(*parseResult.root); + } } - // run resulting bytecode + std::string bytecode; + + // compile + if (kFuzzCompiler) + { + for (size_t i = 0; i < parseResults.size(); i++) + { + Luau::ParseResult& parseResult = parseResults[i]; + Luau::AstNameTable& parseNameTable = *parseNameTables[i]; + + if (parseResult.errors.empty()) + { + Luau::CompileOptions compileOptions; + + try + { + Luau::BytecodeBuilder bcb; + Luau::compileOrThrow(bcb, parseResult.root, parseNameTable, compileOptions); + bytecode = bcb.getBytecode(); + } + catch (const Luau::CompileError&) + { + // not all valid ASTs can be compiled due to limits on number of registers + } + } + } + } + + // run resulting bytecode (from last successfully compiler module) if (kFuzzVM && bytecode.size()) { static lua_State* globalState = createGlobalState(); diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index e61b693..66a89f2 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -208,6 +208,35 @@ struct ProtoToLuau source += std::to_string(name.index() & 0xff); } + template + void genericidents(const T& node) + { + if (node.generics_size() || node.genericpacks_size()) + { + source += '<'; + bool first = true; + + for (size_t i = 0; i < node.generics_size(); ++i) + { + if (!first) + source += ','; + first = false; + ident(node.generics(i)); + } + + for (size_t i = 0; i < node.genericpacks_size(); ++i) + { + if (!first) + source += ','; + first = false; + ident(node.genericpacks(i)); + source += "..."; + } + + source += '>'; + } + } + void print(const luau::Expr& expr) { if (expr.has_group()) @@ -240,6 +269,8 @@ struct ProtoToLuau print(expr.unary()); else if (expr.has_binary()) print(expr.binary()); + else if (expr.has_ifelse()) + print(expr.ifelse()); else source += "_"; } @@ -350,6 +381,7 @@ struct ProtoToLuau void function(const luau::ExprFunction& expr) { + genericidents(expr); source += "("; for (int i = 0; i < expr.args_size(); ++i) { @@ -478,12 +510,21 @@ struct ProtoToLuau void print(const luau::ExprIfElse& expr) { - source += " if "; + source += "if "; print(expr.cond()); source += " then "; print(expr.then()); - source += " else "; - print(expr.else_()); + + if (expr.has_else_()) + { + source += " else "; + print(expr.else_()); + } + else if (expr.has_elseif()) + { + source += " else"; + print(expr.elseif()); + } } void print(const luau::LValue& expr) @@ -534,6 +575,8 @@ struct ProtoToLuau print(stat.local_function()); else if (stat.has_type_alias()) print(stat.type_alias()); + else if (stat.has_require_into_local()) + print(stat.require_into_local()); else source += "do end\n"; } @@ -804,26 +847,24 @@ struct ProtoToLuau void print(const luau::StatTypeAlias& stat) { + if (stat.export_()) + source += "export "; + source += "type "; ident(stat.name()); - - if (stat.generics_size()) - { - source += '<'; - for (size_t i = 0; i < stat.generics_size(); ++i) - { - if (i != 0) - source += ','; - ident(stat.generics(i)); - } - source += '>'; - } - + genericidents(stat); source += " = "; print(stat.type()); source += '\n'; } + void print(const luau::StatRequireIntoLocalHelper& stat) + { + source += "local "; + print(stat.var()); + source += " = require(module" + std::to_string(stat.modulenum() % 2) + ")\n"; + } + void print(const luau::Type& type) { if (type.has_primitive()) @@ -844,6 +885,10 @@ struct ProtoToLuau print(type.class_()); else if (type.has_ref()) print(type.ref()); + else if (type.has_boolean()) + print(type.boolean()); + else if (type.has_string()) + print(type.string()); else source += "any"; } @@ -858,15 +903,28 @@ struct ProtoToLuau { ident(type.name()); - if (type.generics_size()) + if (type.generics_size() || type.genericpacks_size()) { source += '<'; + bool first = true; + for (size_t i = 0; i < type.generics_size(); ++i) { - if (i != 0) + if (!first) source += ','; - ident(type.generics(i)); + first = false; + print(type.generics(i)); } + + for (size_t i = 0; i < type.genericpacks_size(); ++i) + { + if (!first) + source += ','; + first = false; + ident(type.genericpacks(i)); + source += "..."; + } + source += '>'; } } @@ -893,6 +951,7 @@ struct ProtoToLuau void print(const luau::TypeFunction& type) { + genericidents(type); source += '('; for (size_t i = 0; i < type.args_size(); ++i) { @@ -950,12 +1009,38 @@ struct ProtoToLuau source += '.'; ident(type.index()); } + + void print(const luau::TypeBoolean& type) + { + source += type.val() ? "true" : "false"; + } + + void print(const luau::TypeString& type) + { + source += '"'; + for (char ch : type.val()) + if (isgraph(ch)) + source += ch; + source += '"'; + } }; -std::string protoprint(const luau::StatBlock& stat, bool types) +std::vector protoprint(const luau::ModuleSet& stat, bool types) { + std::vector result; + + if (stat.has_module()) + { + ProtoToLuau printer; + printer.types = types; + printer.print(stat.module()); + result.push_back(printer.source); + } + ProtoToLuau printer; printer.types = types; - printer.print(stat); - return printer.source; + printer.print(stat.program()); + result.push_back(printer.source); + + return result; } diff --git a/fuzz/prototest.cpp b/fuzz/prototest.cpp index 804e708..ccaa197 100644 --- a/fuzz/prototest.cpp +++ b/fuzz/prototest.cpp @@ -2,11 +2,15 @@ #include "src/libfuzzer/libfuzzer_macro.h" #include "luau.pb.h" -std::string protoprint(const luau::StatBlock& stat, bool types); +std::vector protoprint(const luau::ModuleSet& stat, bool types); -DEFINE_PROTO_FUZZER(const luau::StatBlock& message) +DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) { - std::string source = protoprint(message, true); + std::vector sources = protoprint(message, true); - printf("%s\n", source.c_str()); + for (size_t i = 0; i < sources.size(); i++) + { + printf("Module 'l%d':\n", int(i)); + printf("%s\n", sources[i].c_str()); + } } diff --git a/fuzz/typeck.cpp b/fuzz/typeck.cpp index 5020c77..3905cc1 100644 --- a/fuzz/typeck.cpp +++ b/fuzz/typeck.cpp @@ -1,9 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include -#include "Luau/TypeInfer.h" + #include "Luau/BuiltinDefinitions.h" -#include "Luau/ModuleResolver.h" #include "Luau/Common.h" +#include "Luau/ModuleResolver.h" +#include "Luau/Parser.h" +#include "Luau/TypeInfer.h" LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 1978a0d..6aadef3 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -2752,7 +2752,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_on_string_singletons") ScopedFastFlag sffs[] = { {"LuauParseSingletonTypes", true}, {"LuauSingletonTypes", true}, - {"LuauRefactorTypeVarQuestions", true}, }; check(R"( diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 23e90f4..eb6ca74 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -837,7 +837,7 @@ TEST_CASE("ExceptionObject") return ExceptionResult{false, ""}; }; - auto reallocFunc = [](lua_State* L, void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* { + auto reallocFunc = [](void* /*ud*/, void* ptr, size_t /*osize*/, size_t nsize) -> void* { if (nsize == 0) { free(ptr); @@ -964,4 +964,53 @@ TEST_CASE("StringConversion") runConformance("strconv.lua"); } +TEST_CASE("GCDump") +{ + // internal function, declared in lgc.h - not exposed via lua.h + extern void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); + + StateRef globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + // push various objects on stack to cover different paths + lua_createtable(L, 1, 2); + lua_pushstring(L, "value"); + lua_setfield(L, -2, "key"); + + lua_pushinteger(L, 42); + lua_rawseti(L, -2, 1000); + + lua_pushinteger(L, 42); + lua_rawseti(L, -2, 1); + + lua_pushvalue(L, -1); + lua_setmetatable(L, -2); + + lua_newuserdata(L, 42); + lua_pushvalue(L, -2); + lua_setmetatable(L, -2); + + lua_pushinteger(L, 1); + lua_pushcclosure(L, lua_silence, "test", 1); + + lua_State* CL = lua_newthread(L); + + lua_pushstring(CL, "local x x = {} local function f() x[1] = math.abs(42) end function foo() coroutine.yield() end foo() return f"); + lua_loadstring(CL); + lua_resume(CL, nullptr, 0); + +#ifdef _WIN32 + const char* path = "NUL"; +#else + const char* path = "/dev/null"; +#endif + + FILE* f = fopen(path, "w"); + REQUIRE(f); + + luaC_dump(L, f, nullptr); + + fclose(f); +} + TEST_SUITE_END(); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index d4b9736..ab19cea 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1594,4 +1594,17 @@ TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf") REQUIRE_EQ(result.warnings.size(), 0); // --!nolint disables WrongComment lint :) } +TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsIfStatAndExpr") +{ + LintResult result = lint(R"( +if if 1 then 2 else 3 then +elseif if 1 then 2 else 3 then +elseif if 0 then 5 else 4 then +end +)"); + + REQUIRE_EQ(result.warnings.size(), 1); + CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2"); +} + TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 0d4c088..77e49ce 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2575,7 +2575,6 @@ do end TEST_CASE_FIXTURE(Fixture, "recover_expected_type_pack") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; - ScopedFastFlag luauParseRecoverTypePackEllipsis{"LuauParseRecoverTypePackEllipsis", true}; ParseResult result = tryParse(R"( type Y = (T...) -> U... diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index ac5be85..332aba9 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -651,4 +651,19 @@ local a: Packed CHECK_EQ(code, transpile(code, {}, true).code); } + +TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types") +{ + ScopedFastFlag luauParseSingletonTypes{"LuauParseSingletonTypes", true}; + + std::string code = R"( +type t1 = 'hello' +type t2 = true +type t3 = '' +type t4 = false + )"; + + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index d677e28..26881b5 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -175,6 +175,8 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") { + ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; + CheckResult result = check(R"( type A = {x: {y: {z: {thing: string}}}} type B = {x: {y: {z: {thing: string}}}} @@ -184,7 +186,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_dep )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(*typeChecker.stringType, *requireType("r")); + CHECK_EQ("string & string", toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_mixed_types") @@ -218,7 +220,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_part_missing_ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_one_property_of_type_any") { CheckResult result = check(R"( - type A = {x: number} + type A = {y: number} type B = {x: any} local t: A & B diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 48e6be6..bff8926 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -1115,6 +1115,22 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_on_properties_of_disjoint_tables_where_ LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersection_table") +{ + ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; + + CheckResult result = check(R"( + type T = {} & {f: ((string) -> string)?} + local function f(t: T, x) + if t.f then + t.f(x) + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x") { ScopedFastFlag sff[] = { diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 9021700..856549b 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -439,4 +439,128 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton") +{ + ScopedFastFlag sff[]{ + {"LuauSingletonTypes", true}, + {"LuauEqConstraint", true}, + {"LuauDiscriminableUnions2", true}, + {"LuauWidenIfSupertypeIsFree", true}, + {"LuauWeakEqConstraint", false}, + }; + + CheckResult result = check(R"( + local function foo(f, x) + if x == "hi" then + f(x) + f("foo") + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 18}))); + // should be ((string) -> a..., string) -> () but needs lower bounds calculation + CHECK_EQ("((string) -> (b...), a) -> ()", toString(requireType("foo"))); +} + +// TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") +// { +// ScopedFastFlag sff[]{ +// {"LuauParseSingletonTypes", true}, +// {"LuauSingletonTypes", true}, +// {"LuauDiscriminableUnions2", true}, +// {"LuauEqConstraint", true}, +// {"LuauWidenIfSupertypeIsFree", true}, +// {"LuauWeakEqConstraint", false}, +// }; + +// CheckResult result = check(R"( +// local function foo(f, x): "hello"? -- anyone there? +// return if x == "hi" +// then f(x) +// else nil +// end +// )"); + +// LUAU_REQUIRE_NO_ERRORS(result); + +// CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23}))); +// CHECK_EQ(R"(((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo"))); +// } + +TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere") +{ + ScopedFastFlag sff[]{ + {"LuauParseSingletonTypes", true}, + {"LuauSingletonTypes", true}, + {"LuauWidenIfSupertypeIsFree", true}, + }; + + CheckResult result = check(R"( + local foo: "foo" = "foo" + local copy = foo + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("string", toString(requireType("copy"))); +} + +TEST_CASE_FIXTURE(Fixture, "widening_happens_almost_everywhere_except_for_tables") +{ + ScopedFastFlag sff[]{ + {"LuauParseSingletonTypes", true}, + {"LuauSingletonTypes", true}, + {"LuauDiscriminableUnions2", true}, + {"LuauWidenIfSupertypeIsFree", true}, + }; + + CheckResult result = check(R"( + type Cat = {tag: "Cat", meows: boolean} + type Dog = {tag: "Dog", barks: boolean} + type Animal = Cat | Dog + + local function f(tag: "Cat" | "Dog"): Animal? + if tag == "Cat" then + local result = {tag = tag, meows = true} + return result + elseif tag == "Dog" then + local result = {tag = tag, barks = true} + return result + else + return nil + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "table_insert_with_a_singleton_argument") +{ + ScopedFastFlag sff[]{ + {"LuauParseSingletonTypes", true}, + {"LuauSingletonTypes", true}, + {"LuauWidenIfSupertypeIsFree", true}, + }; + + CheckResult result = check(R"( + local function foo(t, x) + if x == "hi" or x == "bye" then + table.insert(t, x) + end + + return t + end + + local t = foo({}, "hi") + table.insert(t, "totally_unrelated_type" :: "totally_unrelated_type") + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("{string}", toString(requireType("t"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 6bcd4b9..aa94978 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -2239,4 +2239,22 @@ TEST_CASE_FIXTURE(Fixture, "give_up_after_one_metatable_index_look_up") CHECK_EQ("Type 't2' does not have key 'x'", toString(result.errors[0])); } +TEST_CASE_FIXTURE(Fixture, "confusing_indexing") +{ + ScopedFastFlag sff{"LuauDoNotTryToReduce", true}; + + CheckResult result = check(R"( + type T = {} & {p: number | string} + local function f(t: T) + return t.p + end + + local foo = f({p = "string"}) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ("number | string", toString(requireType("foo"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 3235857..f44d9fd 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -5127,8 +5127,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") TEST_CASE_FIXTURE(Fixture, "do_not_modify_imported_types") { - ScopedFastFlag noSealedTypeMod{"LuauNoSealedTypeMod", true}; - fileResolver.source["game/A"] = R"( export type Type = { unrelated: boolean } return {} @@ -5190,7 +5188,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_string_singletons") { ScopedFastFlag sff[]{ {"LuauDiscriminableUnions2", true}, - {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, }; @@ -5210,7 +5207,6 @@ TEST_CASE_FIXTURE(Fixture, "indexing_on_union_of_string_singletons") { ScopedFastFlag sff[]{ {"LuauDiscriminableUnions2", true}, - {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, }; @@ -5230,7 +5226,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_string_singleton") { ScopedFastFlag sff[]{ {"LuauDiscriminableUnions2", true}, - {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, }; @@ -5250,7 +5245,6 @@ TEST_CASE_FIXTURE(Fixture, "taking_the_length_of_union_of_string_singleton") { ScopedFastFlag sff[]{ {"LuauDiscriminableUnions2", true}, - {"LuauRefactorTypeVarQuestions", true}, {"LuauSingletonTypes", true}, }; diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 4669ea8..c9bf510 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -19,7 +19,7 @@ struct TryUnifyFixture : Fixture ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}}; InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - Unifier state{&arena, Mode::Strict, globalScope, Location{}, Variance::Covariant, unifierState}; + Unifier state{&arena, Mode::Strict, Location{}, Variance::Covariant, unifierState}; }; TEST_SUITE_BEGIN("TryUnifyTests"); @@ -261,8 +261,6 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly") TEST_CASE_FIXTURE(TryUnifyFixture, "recursive_metatable_getmatchtag") { - ScopedFastFlag luauUnionTagMatchFix{"LuauUnionTagMatchFix", true}; - TypeVar redirect{FreeTypeVar{TypeLevel{}}}; TypeVar table{TableTypeVar{}}; TypeVar metatable{MetatableTypeVar{&redirect, &table}}; diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index e43161f..fd5f4db 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -361,16 +361,12 @@ local b: (T, T, T) -> T TEST_CASE("isString_on_string_singletons") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}}; CHECK(isString(&helloString)); } TEST_CASE("isString_on_unions_of_various_string_singletons") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}}; TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}}; TypeVar union_{UnionTypeVar{{&helloString, &byeString}}}; @@ -380,8 +376,6 @@ TEST_CASE("isString_on_unions_of_various_string_singletons") TEST_CASE("proof_that_isString_uses_all_of") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar helloString{SingletonTypeVar{StringSingleton{"hello"}}}; TypeVar byeString{SingletonTypeVar{StringSingleton{"bye"}}}; TypeVar booleanType{PrimitiveTypeVar{PrimitiveTypeVar::Boolean}}; @@ -392,16 +386,12 @@ TEST_CASE("proof_that_isString_uses_all_of") TEST_CASE("isBoolean_on_boolean_singletons") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}}; CHECK(isBoolean(&trueBool)); } TEST_CASE("isBoolean_on_unions_of_true_or_false_singletons") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}}; TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}}; TypeVar union_{UnionTypeVar{{&trueBool, &falseBool}}}; @@ -411,8 +401,6 @@ TEST_CASE("isBoolean_on_unions_of_true_or_false_singletons") TEST_CASE("proof_that_isBoolean_uses_all_of") { - ScopedFastFlag sff{"LuauRefactorTypeVarQuestions", true}; - TypeVar trueBool{SingletonTypeVar{BooleanSingleton{true}}}; TypeVar falseBool{SingletonTypeVar{BooleanSingleton{false}}}; TypeVar stringType{PrimitiveTypeVar{PrimitiveTypeVar::String}}; diff --git a/tools/natvis/VM.natvis b/tools/natvis/VM.natvis index 9924e19..ccc7e39 100644 --- a/tools/natvis/VM.natvis +++ b/tools/natvis/VM.natvis @@ -183,13 +183,12 @@ openupval - u.l.next + u.l.threadnext this - l_gt - env + gt userdata From db3a8a2f0ff795f4c0a5398cb44ae07143e65af0 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 24 Feb 2022 17:08:54 -0800 Subject: [PATCH 2/8] Update prototyping.yml (#398) This limits the scope of prototyping action to PRs to prototyping branch to minimize the GHA cost / latency, as cabal install sometimes takes forever --- .github/workflows/prototyping.yml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/prototyping.yml b/.github/workflows/prototyping.yml index fe45867..c92e580 100644 --- a/.github/workflows/prototyping.yml +++ b/.github/workflows/prototyping.yml @@ -1,25 +1,10 @@ name: prototyping on: - push: - branches: - - 'master' - - 'prototyping-*' - paths: - - '.github/workflows/**' - - 'prototyping/**' - - 'Analysis/**' - - 'Ast/**' - - 'CLI/Ast.cpp' - - 'CLI/FileUtils.*' pull_request: paths: - - '.github/workflows/**' + - '.github/workflows/prototyping.yml' - 'prototyping/**' - - 'Analysis/**' - - 'Ast/**' - - 'CLI/Ast.cpp' - - 'CLI/FileUtils.*' jobs: linux: From fb11ca00f4df0e3a18b9274d47965a95db10d81c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 28 Feb 2022 10:50:23 -0800 Subject: [PATCH 3/8] Update README.md linenoise -> isocline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d79a0bd..2ed7348 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Makefile builds combine both into a single target and can be ran via `make test` Luau uses C++ as its implementation language. The runtime requires C++11, whereas the compiler and analysis components require C++17. It should build without issues using Microsoft Visual Studio 2017 or later, or gcc-7 or clang-7 or later. -Other than the STL/CRT, Luau library components don't have external dependencies. The test suite depends on [doctest](https://github.com/onqtam/doctest) testing framework, and the REPL command-line depends on [cpp-linenoise](https://github.com/yhirose/cpp-linenoise). +Other than the STL/CRT, Luau library components don't have external dependencies. The test suite depends on [doctest](https://github.com/onqtam/doctest) testing framework, and the REPL command-line depends on [isocline](https://github.com/daanx/isocline). # License From 5e8242aeda28ca60765926abfc905b3fb9ed85ff Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 28 Feb 2022 14:15:33 -0800 Subject: [PATCH 4/8] RFC: table.clone (#362) --- rfcs/function-table-clone.md | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 rfcs/function-table-clone.md diff --git a/rfcs/function-table-clone.md b/rfcs/function-table-clone.md new file mode 100644 index 0000000..78d126e --- /dev/null +++ b/rfcs/function-table-clone.md @@ -0,0 +1,64 @@ +# table.clone + +## Summary + +Add `table.clone` function that, given a table, produces a copy of that table with the same keys/values/metatable. + +## Motivation + +There are multiple cases today when cloning tables is a useful operation. + +- When working with tables as data containers, some algorithms may require modifying the table that can't be done in place for some reason. +- When working with tables as objects, it can be useful to obtain an identical copy of the object for further modification, preserving the metatable. +- When working with immutable data structures, any modification needs to clone some parts of the data structure to produce a new version of the object. + +While it's possible to implement this function in user code today, it's impossible to implement it with maximum efficiency; furthermore, cloning is a reasonably fundamental +operation so from the ergonomics perspective it can be expected to be provided by the standard library. + +## Design + +`table.clone(t)` takes a table, `t`, and returns a new table that: + +- has the same metatable +- has the same keys and values +- is not frozen, even if `t` was + +The copy is shallow: implementing a deep recursive copy automatically is challenging (for similar reasons why we decided to avoid this in `table.freeze`), and often only certain keys need to be cloned recursively which can be done after the initial clone. + +The table can be modified after cloning; as such, functions that compute a slightly modified copy of the table can be easily built on top of `table.clone`. + +`table.clone(t)` is functionally equivalent to the following code, but it's more ergonomic (on the account of being built-in) and significantly faster: + +```lua +assert(type(t) == "table") +local nt = {} +for k,v in pairs(t) do + nt[k] = v +end +if type(getmetatable(t)) == "table" then + setmetatable(nt, getmetatable(t)) +end +``` + +The reason why `table.clone` can be dramatically more efficient is that it can directly copy the internal structure, preserving capacity and exact key order, and is thus +limited purely by memory bandwidth. In comparison, the code above can't predict the table size ahead of time, has to recreate the internal table structure one key at a time, +and bears the interpreter overhead (which can be avoided for numeric keys with `table.move` but that doesn't work for the general case of dictionaries). + +Out of the abundance of caution, `table.clone` will fail to clone the table if it has a protected metatable. This is motivated by the fact that you can't do this today, so +there are no new potential vectors to escape various sandboxes. Superficially it seems like it's probably reasonable to allow cloning tables with protected metatables, but +there may be cases where code manufactures tables with unique protected metatables expecting 1-1 relationship and cloning would break that, so for now this RFC proposes a more +conservative route. We are likely to relax this restriction in the future. + +## Drawbacks + +Adding a new function to `table` library theoretically increases complexity. In practice though, we already effectively implement `table.clone` internally for some VM optimizations, so exposing this to the users bears no cost. + +Assigning a type to this function is a little difficult if we want to enforce the "argument must be a table" constraint. It's likely that we'll need to type this as `table.clone(T): T` for the time being, which is less precise. + +## Alternatives + +We can implement something similar to `Object.assign` from JavaScript instead, that simultaneously assigns extra keys. However, this won't be fundamentally more efficient than +assigning the keys afterwards, and can be implemented in user space. Additionally, we can later extend `clone` with an extra argument if we so choose, so this proposal is the +minimal viable one. + +We can immediately remove the rule wrt protected metatables, as it's not clear that it's actually problematic to be able to clone tables with protected metatables. From 898c0501d17c502a6594d64a0f2851013074450e Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 28 Feb 2022 14:16:35 -0800 Subject: [PATCH 5/8] Update STATUS.md Add table.clone --- rfcs/STATUS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfcs/STATUS.md b/rfcs/STATUS.md index 72db200..5d2d835 100644 --- a/rfcs/STATUS.md +++ b/rfcs/STATUS.md @@ -56,3 +56,9 @@ This document tracks unimplemented RFCs. [RFC: Generalized iteration](https://github.com/Roblox/luau/blob/master/rfcs/generalized-iteration.md) **Status**: Needs implementation + +## table.clone + +[RFC: table.clone](https://github.com/Roblox/luau/blob/master/rfcs/function-table-clone.md) + +**Status**: Needs implementation From d277cc2c3bfa80fa280f3c2a69164eaff779ca94 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Wed, 2 Mar 2022 06:27:35 -0800 Subject: [PATCH 6/8] Luau Recap: February 2022 (#403) * Luau Recap: February 2022 * Rebuild pages * Fixed Markdown linter warnings, converted tabs to spaces and added a note on 'select' optimization * Apply suggestions from code review Co-authored-by: Alexander McCord <11488393+alexmccord@users.noreply.github.com> * Added information about new lint * Update docs/_posts/2022-02-28-luau-recap-february-2022.md Co-authored-by: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Co-authored-by: Alexander McCord <11488393+alexmccord@users.noreply.github.com> --- .../2022-02-28-luau-recap-february-2022.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/_posts/2022-02-28-luau-recap-february-2022.md diff --git a/docs/_posts/2022-02-28-luau-recap-february-2022.md b/docs/_posts/2022-02-28-luau-recap-february-2022.md new file mode 100644 index 0000000..412217d --- /dev/null +++ b/docs/_posts/2022-02-28-luau-recap-february-2022.md @@ -0,0 +1,164 @@ +--- +layout: single +title: "Luau Recap: February 2022" +--- + +Luau is our new language that you can read more about at [https://luau-lang.org](https://luau-lang.org). + +[Cross-posted to the [Roblox Developer Forum](https://devforum.roblox.com/t/luau-recap-february-2022/).] + +## Default type alias type parameters + +We have introduced a syntax to provide default type arguments inside the type alias type parameter list. + +It is now possible to have type functions where the instantiation can omit some type arguments. + +You can provide concrete types: + +```lua +--!strict +type FieldResolver = (T, Data) -> number + +local a: FieldResolver = ... +local b: FieldResolver = ... +``` + +Or reference parameters defined earlier in the list: + +```lua +--!strict +type EqComp = (l: T, r: U) -> boolean + +local a: EqComp = ... -- (l: number, r: number) -> boolean +local b: EqComp = ... -- (l: number, r: string) -> boolean +``` + +Type pack parameters can also have a default type pack: + +```lua +--!strict +type Process = (T) -> U... + +local a: Process = ... -- (number) -> ...string +local b: Process = ... -- (number) -> (boolean, string) +``` + +If all type parameters have a default type, it is now possible to reference that without providing any type arguments: + +```lua +--!strict +type All = (T) -> U + +local a: All -- ok +local b: All<> -- ok as well +``` + +For more details, you can read the original [RFC proposal](https://github.com/Roblox/luau/blob/master/rfcs/syntax-default-type-alias-type-parameters.md). + +## Typechecking improvements + +This month we had many fixes to improve our type inference and reduce false positive errors. + +if-then-else expression can now have different types in each branch: + +```lua +--!strict +local a = if x then 5 else nil -- 'a' will have type 'number?' +local b = if x then 1 else '2' -- 'b' will have type 'number | string' +``` + +And if the expected result type is known, you will not get an error in cases like these: + +```lua +--!strict +type T = {number | string} +-- different array element types don't give an error if that is expected +local c: T = if x then {1, "x", 2, "y"} else {0} +``` + +--- + +`assert` result is now known to not be 'falsy' (`false` or `nil`): + +```lua +--!strict +local function f(x: number?): number + return assert(x) -- no longer an error +end +``` + +--- + +We fixed cases where length operator `#` reported an error when used on a compatible type: + +```lua +--!strict +local union: {number} | {string} +local a = #union -- no longer an error +``` + +--- + +Functions with different variadic argument/return types are no longer compatible: + +```lua +--!strict +local function f(): (number, ...string) + return 2, "a", "b" +end + +local g: () -> (number, ...boolean) = f -- error +``` + +--- + +We have also fixed: + +* false positive errors caused by incorrect reuse of generic types across different function declarations +* issues with forward-declared intersection types +* wrong return type annotation for table.move +* various crashes reported by developers + +## Linter improvements + +A new static analysis warning was introduced to mark incorrect use of a '`a and b or c`' pattern. When 'b' is 'falsy' (`false` or `nil`), result will always be 'c', even if the expression 'a' was true: + +```lua +local function f(x: number) + -- The and-or expression always evaluates to the second alternative because the first alternative is false; consider using if-then-else expression instead + return x < 0.5 and false or 42 +end +``` + +Like we say in the warning, new if-then-else expression doesn't have this pitfall: + +```lua +local function g(x: number) + return if x < 0.5 then false else 42 +end +``` + +--- + +We have also introduced a check for misspelled comment directives: + +```lua +--!non-strict +-- ^ Unknown comment directive 'non-strict'; did you mean 'nonstrict'? +``` + +## Performance improvements + +For performance, we have changed how our Garbage Collector collects unreachable memory. +This rework makes it possible to free memory 2.5x faster and also comes with a small change to how we store Luau objects in memory. For example, each table now uses 16 fewer bytes on 64-bit platforms. + +Another optimization was made for `select(_, ...)` call. +It is now using a special fast path that has constant-time complexity in number of arguments (~3x faster with 10 arguments). + +## Thanks + +A special thanks to all the fine folks who contributed PRs this month! + +* [mikejsavage](https://github.com/mikejsavage) +* [TheGreatSageEqualToHeaven](https://github.com/TheGreatSageEqualToHeaven) +* [petrihakkinen](https://github.com/petrihakkinen) From c5477d522dcb31368a8ee864fb9c7b9c34d7730c Mon Sep 17 00:00:00 2001 From: Alan Jeffrey <403333+asajeffrey@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:02:51 -0600 Subject: [PATCH 7/8] Prototyping strict mode (#399) * First cut of strict mode Co-authored-by: Lily Brown --- prototyping/.gitignore | 2 - prototyping/Everything.agda | 8 + prototyping/Examples/OpSem.agda | 8 +- prototyping/Examples/Run.agda | 15 +- prototyping/Examples/Syntax.agda | 4 +- prototyping/Examples/Type.agda | 1 + prototyping/FFI/Data/Aeson.agda | 29 +- prototyping/FFI/Data/Maybe.agda | 6 + prototyping/FFI/Data/Vector.agda | 9 + prototyping/Interpreter.agda | 32 +- prototyping/Luau/Addr.agda | 5 +- prototyping/Luau/Heap.agda | 42 +- prototyping/Luau/OpSem.agda | 132 ++--- prototyping/Luau/Run.agda | 7 +- prototyping/Luau/RuntimeError.agda | 29 +- prototyping/Luau/RuntimeError/ToString.agda | 36 +- prototyping/Luau/RuntimeType.agda | 8 +- prototyping/Luau/StrictMode.agda | 205 ++++++++ prototyping/Luau/StrictMode/ToString.agda | 39 ++ prototyping/Luau/Substitution.agda | 11 +- prototyping/Luau/Syntax.agda | 64 ++- prototyping/Luau/Syntax/FromJSON.agda | 59 ++- prototyping/Luau/Syntax/ToString.agda | 29 +- prototyping/Luau/Type.agda | 135 ++++- prototyping/Luau/Type/FromJSON.agda | 2 + prototyping/Luau/Type/ToString.agda | 3 +- prototyping/Luau/TypeCheck.agda | 143 ++++++ prototyping/Luau/Value.agda | 20 - prototyping/Luau/Value/ToString.agda | 14 - prototyping/Luau/Var.agda | 6 +- prototyping/Luau/VarCtxt.agda | 43 ++ prototyping/PrettyPrinter.agda | 2 + prototyping/Properties.agda | 6 +- prototyping/Properties/Dec.agda | 5 +- prototyping/Properties/Product.agda | 14 + prototyping/Properties/Step.agda | 145 ++++-- prototyping/Properties/StrictMode.agda | 470 ++++++++++++++++++ prototyping/Properties/TypeCheck.agda | 103 ++++ .../Interpreter/binary_equality_bools/out.txt | 5 +- .../binary_equality_numbers/out.txt | 5 +- .../Tests/Interpreter/binary_exps/out.txt | 5 +- .../Tests/Interpreter/return_nil/out.txt | 8 +- 42 files changed, 1598 insertions(+), 316 deletions(-) create mode 100644 prototyping/Everything.agda create mode 100644 prototyping/Luau/StrictMode.agda create mode 100644 prototyping/Luau/StrictMode/ToString.agda create mode 100644 prototyping/Luau/TypeCheck.agda delete mode 100644 prototyping/Luau/Value.agda delete mode 100644 prototyping/Luau/Value/ToString.agda create mode 100644 prototyping/Luau/VarCtxt.agda create mode 100644 prototyping/Properties/Product.agda create mode 100644 prototyping/Properties/StrictMode.agda create mode 100644 prototyping/Properties/TypeCheck.agda diff --git a/prototyping/.gitignore b/prototyping/.gitignore index 10edac5..31e8c07 100644 --- a/prototyping/.gitignore +++ b/prototyping/.gitignore @@ -2,10 +2,8 @@ *.agdai Main MAlonzo -Examples PrettyPrinter Interpreter -Properties !Tests/Interpreter !Tests/PrettyPrinter .ghc.* diff --git a/prototyping/Everything.agda b/prototyping/Everything.agda new file mode 100644 index 0000000..d8a1fd5 --- /dev/null +++ b/prototyping/Everything.agda @@ -0,0 +1,8 @@ +{-# OPTIONS --rewriting #-} + +module Everything where + +import Examples +import Properties +import PrettyPrinter +import Interpreter diff --git a/prototyping/Examples/OpSem.agda b/prototyping/Examples/OpSem.agda index c4cfc5e..c3f74ec 100644 --- a/prototyping/Examples/OpSem.agda +++ b/prototyping/Examples/OpSem.agda @@ -1,8 +1,10 @@ +{-# OPTIONS --rewriting #-} + module Examples.OpSem where open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; subst) -open import Luau.Syntax using (Block; var; nil; local_←_; _∙_; done; return; block_is_end) +open import Luau.Syntax using (Block; var; val; nil; local_←_; _∙_; done; return; block_is_end) open import Luau.Heap using (∅) -ex1 : ∅ ⊢ (local (var "x") ← nil ∙ return (var "x") ∙ done) ⟶ᴮ (return nil ∙ done) ⊣ ∅ -ex1 = subst +ex1 : ∅ ⊢ (local (var "x") ← val nil ∙ return (var "x") ∙ done) ⟶ᴮ (return (val nil) ∙ done) ⊣ ∅ +ex1 = subst nil diff --git a/prototyping/Examples/Run.agda b/prototyping/Examples/Run.agda index ddec8e8..545ecf5 100644 --- a/prototyping/Examples/Run.agda +++ b/prototyping/Examples/Run.agda @@ -4,22 +4,17 @@ module Examples.Run where open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Bool using (true; false) -open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩; number; binexp; +; <; true; false) -open import Luau.Value using (nil; number; bool) +open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩; number; binexp; +; <; val; bool) open import Luau.Run using (run; return) -open import Luau.Heap using (lookup-next; next-emp; lookup-next-emp) -import Agda.Builtin.Equality.Rewrite -{-# REWRITE lookup-next next-emp lookup-next-emp #-} - -ex1 : (run (function "id" ⟨ var "x" ⟩ is return (var "x") ∙ done end ∙ return (var "id" $ nil) ∙ done) ≡ return nil _) +ex1 : (run (function "id" ⟨ var "x" ⟩ is return (var "x") ∙ done end ∙ return (var "id" $ val nil) ∙ done) ≡ return nil _) ex1 = refl -ex2 : (run (function "fn" ⟨ var "x" ⟩ is return (number 123.0) ∙ done end ∙ return (var "fn" $ nil) ∙ done) ≡ return (number 123.0) _) +ex2 : (run (function "fn" ⟨ var "x" ⟩ is return (val (number 123.0)) ∙ done end ∙ return (var "fn" $ val nil) ∙ done) ≡ return (number 123.0) _) ex2 = refl -ex3 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (number 1.0) + (number 2.0)) ∙ done end ∙ return (var "fn" $ nil) ∙ done) ≡ return (number 3.0) _) +ex3 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (val (number 1.0)) + (val (number 2.0))) ∙ done end ∙ return (var "fn" $ val nil) ∙ done) ≡ return (number 3.0) _) ex3 = refl -ex4 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (number 1.0) < (number 2.0)) ∙ done end ∙ return (var "fn" $ nil) ∙ done) ≡ return (bool true) _) +ex4 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (val (number 1.0)) < (val (number 2.0))) ∙ done end ∙ return (var "fn" $ val nil) ∙ done) ≡ return (bool true) _) ex4 = refl diff --git a/prototyping/Examples/Syntax.agda b/prototyping/Examples/Syntax.agda index 4ffcf4c..e279302 100644 --- a/prototyping/Examples/Syntax.agda +++ b/prototyping/Examples/Syntax.agda @@ -2,7 +2,7 @@ module Examples.Syntax where open import Agda.Builtin.Equality using (_≡_; refl) open import FFI.Data.String using (_++_) -open import Luau.Syntax using (var; _$_; return; nil; function_is_end; local_←_; done; _∙_; _⟨_⟩) +open import Luau.Syntax using (var; _$_; return; val; nil; function_is_end; local_←_; done; _∙_; _⟨_⟩) open import Luau.Syntax.ToString using (exprToString; blockToString) ex1 : exprToString(function "" ⟨ var "x" ⟩ is return (var "f" $ var "x") ∙ done end) ≡ @@ -11,7 +11,7 @@ ex1 : exprToString(function "" ⟨ var "x" ⟩ is return (var "f" $ var "x") ∙ "end" ex1 = refl -ex2 : blockToString(local var "x" ← nil ∙ return (var "x") ∙ done) ≡ +ex2 : blockToString(local var "x" ← (val nil) ∙ return (var "x") ∙ done) ≡ "local x = nil\n" ++ "return x" ex2 = refl diff --git a/prototyping/Examples/Type.agda b/prototyping/Examples/Type.agda index 859227e..3fdd37d 100644 --- a/prototyping/Examples/Type.agda +++ b/prototyping/Examples/Type.agda @@ -25,3 +25,4 @@ ex6 = refl ex7 : typeToString((nil ⇒ nil) ∪ ((nil ⇒ (nil ⇒ nil)) ∪ nil)) ≡ "((nil) -> nil | (nil) -> (nil) -> nil)?" ex7 = refl + diff --git a/prototyping/FFI/Data/Aeson.agda b/prototyping/FFI/Data/Aeson.agda index a9082d3..0cd2723 100644 --- a/prototyping/FFI/Data/Aeson.agda +++ b/prototyping/FFI/Data/Aeson.agda @@ -1,15 +1,21 @@ +{-# OPTIONS --rewriting #-} + module FFI.Data.Aeson where +open import Agda.Builtin.Equality using (_≡_) +open import Agda.Builtin.Equality.Rewrite using () open import Agda.Builtin.Bool using (Bool) open import Agda.Builtin.String using (String) open import FFI.Data.ByteString using (ByteString) open import FFI.Data.HaskellString using (HaskellString; pack) -open import FFI.Data.Maybe using (Maybe) +open import FFI.Data.Maybe using (Maybe; just; nothing) open import FFI.Data.Either using (Either; mapLeft) open import FFI.Data.Scientific using (Scientific) open import FFI.Data.Vector using (Vector) +open import Properties.Equality using (_≢_) + {-# FOREIGN GHC import qualified Data.Aeson #-} {-# FOREIGN GHC import qualified Data.Aeson.Key #-} {-# FOREIGN GHC import qualified Data.Aeson.KeyMap #-} @@ -19,14 +25,35 @@ postulate Key : Set fromString : String → Key toString : Key → String + empty : ∀ {A} → KeyMap A + singleton : ∀ {A} → Key → A → (KeyMap A) + insert : ∀ {A} → Key → A → (KeyMap A) → (KeyMap A) + delete : ∀ {A} → Key → (KeyMap A) → (KeyMap A) + unionWith : ∀ {A} → (A → A → A) → (KeyMap A) → (KeyMap A) → (KeyMap A) lookup : ∀ {A} → Key -> KeyMap A -> Maybe A {-# POLARITY KeyMap ++ #-} {-# COMPILE GHC KeyMap = type Data.Aeson.KeyMap.KeyMap #-} {-# COMPILE GHC Key = type Data.Aeson.Key.Key #-} {-# COMPILE GHC fromString = Data.Aeson.Key.fromText #-} {-# COMPILE GHC toString = Data.Aeson.Key.toText #-} +{-# COMPILE GHC empty = \_ -> Data.Aeson.KeyMap.empty #-} +{-# COMPILE GHC singleton = \_ -> Data.Aeson.KeyMap.singleton #-} +{-# COMPILE GHC insert = \_ -> Data.Aeson.KeyMap.insert #-} +{-# COMPILE GHC delete = \_ -> Data.Aeson.KeyMap.delete #-} +{-# COMPILE GHC unionWith = \_ -> Data.Aeson.KeyMap.unionWith #-} {-# COMPILE GHC lookup = \_ -> Data.Aeson.KeyMap.lookup #-} +postulate lookup-insert : ∀ {A} k v (m : KeyMap A) → (lookup k (insert k v m) ≡ just v) +postulate lookup-empty : ∀ {A} k → (lookup {A} k empty ≡ nothing) +postulate lookup-insert-not : ∀ {A} j k v (m : KeyMap A) → (j ≢ k) → (lookup k m ≡ lookup k (insert j v m)) +postulate singleton-insert-empty : ∀ {A} k (v : A) → (singleton k v ≡ insert k v empty) +postulate insert-swap : ∀ {A} j k (v w : A) m → (j ≢ k) → insert j v (insert k w m) ≡ insert k w (insert j v m) +postulate insert-over : ∀ {A} j k (v w : A) m → (j ≡ k) → insert j v (insert k w m) ≡ insert j v m +postulate to-from : ∀ k → toString(fromString k) ≡ k +postulate from-to : ∀ k → fromString(toString k) ≡ k + +{-# REWRITE lookup-insert lookup-empty singleton-insert-empty #-} + data Value : Set where object : KeyMap Value → Value array : Vector Value → Value diff --git a/prototyping/FFI/Data/Maybe.agda b/prototyping/FFI/Data/Maybe.agda index b16fa81..58fd148 100644 --- a/prototyping/FFI/Data/Maybe.agda +++ b/prototyping/FFI/Data/Maybe.agda @@ -1,8 +1,14 @@ module FFI.Data.Maybe where +open import Agda.Builtin.Equality using (_≡_; refl) + {-# FOREIGN GHC import qualified Data.Maybe #-} data Maybe (A : Set) : Set where nothing : Maybe A just : A → Maybe A {-# COMPILE GHC Maybe = data Data.Maybe.Maybe (Data.Maybe.Nothing|Data.Maybe.Just) #-} + +just-inv : ∀ {A} {x y : A} → (just x ≡ just y) → (x ≡ y) +just-inv refl = refl + diff --git a/prototyping/FFI/Data/Vector.agda b/prototyping/FFI/Data/Vector.agda index 7d2f501..08761ed 100644 --- a/prototyping/FFI/Data/Vector.agda +++ b/prototyping/FFI/Data/Vector.agda @@ -1,11 +1,15 @@ +{-# OPTIONS --rewriting #-} + module FFI.Data.Vector where open import Agda.Builtin.Equality using (_≡_) +open import Agda.Builtin.Equality.Rewrite using () open import Agda.Builtin.Int using (Int; pos; negsuc) open import Agda.Builtin.Nat using (Nat) open import Agda.Builtin.Bool using (Bool; false; true) open import FFI.Data.HaskellInt using (HaskellInt; haskellIntToInt; intToHaskellInt) open import FFI.Data.Maybe using (Maybe; just; nothing) +open import Properties.Equality using (_≢_) {-# FOREIGN GHC import qualified Data.Vector #-} @@ -30,8 +34,13 @@ postulate {-# COMPILE GHC snoc = \_ -> Data.Vector.snoc #-} postulate length-empty : ∀ {A} → (length (empty {A}) ≡ 0) +postulate lookup-empty : ∀ {A} n → (lookup (empty {A}) n ≡ nothing) postulate lookup-snoc : ∀ {A} (x : A) (v : Vector A) → (lookup (snoc v x) (length v) ≡ just x) +postulate lookup-length : ∀ {A} (v : Vector A) → (lookup v (length v) ≡ nothing) postulate lookup-snoc-empty : ∀ {A} (x : A) → (lookup (snoc empty x) 0 ≡ just x) +postulate lookup-snoc-not : ∀ {A n} (x : A) (v : Vector A) → (n ≢ length v) → (lookup v n ≡ lookup (snoc v x) n) + +{-# REWRITE length-empty lookup-snoc lookup-length lookup-snoc-empty lookup-empty #-} head : ∀ {A} → (Vector A) → (Maybe A) head vec with null vec diff --git a/prototyping/Interpreter.agda b/prototyping/Interpreter.agda index fe311e5..d803674 100644 --- a/prototyping/Interpreter.agda +++ b/prototyping/Interpreter.agda @@ -1,3 +1,5 @@ +{-# OPTIONS --rewriting #-} + module Interpreter where open import Agda.Builtin.IO using (IO) @@ -7,31 +9,41 @@ open import Agda.Builtin.Unit using (⊤) open import FFI.IO using (getContents; putStrLn; _>>=_; _>>_) open import FFI.Data.Aeson using (Value; eitherDecode) open import FFI.Data.Either using (Left; Right) +open import FFI.Data.Maybe using (just; nothing) open import FFI.Data.String using (String; _++_) open import FFI.Data.Text.Encoding using (encodeUtf8) open import FFI.System.Exit using (exitWith; ExitFailure) -open import Luau.Syntax using (Block) +open import Luau.StrictMode.ToString using (warningToStringᴮ) +open import Luau.Syntax using (Block; yes; maybe; isAnnotatedᴮ) open import Luau.Syntax.FromJSON using (blockFromJSON) -open import Luau.Syntax.ToString using (blockToString) +open import Luau.Syntax.ToString using (blockToString; valueToString) open import Luau.Run using (run; return; done; error) open import Luau.RuntimeError.ToString using (errToStringᴮ) -open import Luau.Value.ToString using (valueToString) -runBlock : ∀ {a} → Block a → IO ⊤ -runBlock block with run block -runBlock block | return V D = putStrLn (valueToString V) -runBlock block | done D = putStrLn "nil" -runBlock block | error E D = putStrLn (errToStringᴮ E) +open import Properties.StrictMode using (wellTypedProgramsDontGoWrong) + +runBlock′ : ∀ a → Block a → IO ⊤ +runBlock′ a block with run block +runBlock′ a block | return V D = putStrLn ("\nRAN WITH RESULT: " ++ valueToString V) +runBlock′ a block | done D = putStrLn ("\nRAN") +runBlock′ maybe block | error E D = putStrLn ("\nRUNTIME ERROR:\n" ++ errToStringᴮ _ E) +runBlock′ yes block | error E D with wellTypedProgramsDontGoWrong _ block _ D E +runBlock′ yes block | error E D | W = putStrLn ("\nRUNTIME ERROR:\n" ++ errToStringᴮ _ E ++ "\n\nTYPE ERROR:\n" ++ warningToStringᴮ _ W) + +runBlock : Block maybe → IO ⊤ +runBlock B with isAnnotatedᴮ B +runBlock B | nothing = putStrLn ("UNANNOTATED PROGRAM:\n" ++ blockToString B) >> runBlock′ maybe B +runBlock B | just B′ = putStrLn ("ANNOTATED PROGRAM:\n" ++ blockToString B) >> runBlock′ yes B′ runJSON : Value → IO ⊤ runJSON value with blockFromJSON(value) -runJSON value | (Left err) = putStrLn ("Luau error: " ++ err) >> exitWith (ExitFailure (pos 1)) +runJSON value | (Left err) = putStrLn ("LUAU ERROR: " ++ err) >> exitWith (ExitFailure (pos 1)) runJSON value | (Right block) = runBlock block runString : String → IO ⊤ runString txt with eitherDecode (encodeUtf8 txt) -runString txt | (Left err) = putStrLn ("JSON error: " ++ err) >> exitWith (ExitFailure (pos 1)) +runString txt | (Left err) = putStrLn ("JSON ERROR: " ++ err) >> exitWith (ExitFailure (pos 1)) runString txt | (Right value) = runJSON value main : IO ⊤ diff --git a/prototyping/Luau/Addr.agda b/prototyping/Luau/Addr.agda index c197804..b6f989f 100644 --- a/prototyping/Luau/Addr.agda +++ b/prototyping/Luau/Addr.agda @@ -5,13 +5,14 @@ open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Nat using (Nat; _==_) open import Agda.Builtin.String using (String) open import Agda.Builtin.TrustMe using (primTrustMe) -open import Properties.Dec using (Dec; yes; no; ⊥) +open import Properties.Dec using (Dec; yes; no) +open import Properties.Equality using (_≢_) Addr : Set Addr = Nat _≡ᴬ_ : (a b : Addr) → Dec (a ≡ b) a ≡ᴬ b with a == b -a ≡ᴬ b | false = no p where postulate p : (a ≡ b) → ⊥ +a ≡ᴬ b | false = no p where postulate p : (a ≢ b) a ≡ᴬ b | true = yes primTrustMe diff --git a/prototyping/Luau/Heap.agda b/prototyping/Luau/Heap.agda index 12f8dab..713b0a1 100644 --- a/prototyping/Luau/Heap.agda +++ b/prototyping/Luau/Heap.agda @@ -1,32 +1,39 @@ +{-# OPTIONS --rewriting #-} + module Luau.Heap where -open import Agda.Builtin.Equality using (_≡_) -open import FFI.Data.Maybe using (Maybe; just) -open import FFI.Data.Vector using (Vector; length; snoc; empty) -open import Luau.Addr using (Addr) +open import Agda.Builtin.Equality using (_≡_; refl) +open import FFI.Data.Maybe using (Maybe; just; nothing) +open import FFI.Data.Vector using (Vector; length; snoc; empty; lookup-snoc-not) +open import Luau.Addr using (Addr; _≡ᴬ_) open import Luau.Var using (Var) -open import Luau.Syntax using (Block; Expr; Annotated; FunDec; nil; addr; function_is_end) +open import Luau.Syntax using (Block; Expr; Annotated; FunDec; nil; function_is_end) +open import Properties.Equality using (_≢_; trans) +open import Properties.Remember using (remember; _,_) +open import Properties.Dec using (yes; no) -data HeapValue (a : Annotated) : Set where - function_is_end : FunDec a → Block a → HeapValue a +-- Heap-allocated objects +data Object (a : Annotated) : Set where + + function_is_end : FunDec a → Block a → Object a Heap : Annotated → Set -Heap a = Vector (HeapValue a) +Heap a = Vector (Object a) -data _≡_⊕_↦_ {a} : Heap a → Heap a → Addr → HeapValue a → Set where +data _≡_⊕_↦_ {a} : Heap a → Heap a → Addr → Object a → Set where defn : ∀ {H val} → ----------------------------------- (snoc H val) ≡ H ⊕ (length H) ↦ val -_[_] : ∀ {a} → Heap a → Addr → Maybe (HeapValue a) +_[_] : ∀ {a} → Heap a → Addr → Maybe (Object a) _[_] = FFI.Data.Vector.lookup ∅ : ∀ {a} → Heap a ∅ = empty -data AllocResult a (H : Heap a) (V : HeapValue a) : Set where +data AllocResult a (H : Heap a) (V : Object a) : Set where ok : ∀ b H′ → (H′ ≡ H ⊕ b ↦ V) → AllocResult a H V alloc : ∀ {a} H V → AllocResult a H V @@ -35,15 +42,8 @@ alloc H V = ok (length H) (snoc H V) defn next : ∀ {a} → Heap a → Addr next = length -allocated : ∀ {a} → Heap a → HeapValue a → Heap a +allocated : ∀ {a} → Heap a → Object a → Heap a allocated = snoc --- next-emp : (length ∅ ≡ 0) -next-emp = FFI.Data.Vector.length-empty - --- lookup-next : ∀ V H → (lookup (allocated H V) (next H) ≡ just V) -lookup-next = FFI.Data.Vector.lookup-snoc - --- lookup-next-emp : ∀ V → (lookup (allocated emp V) 0 ≡ just V) -lookup-next-emp = FFI.Data.Vector.lookup-snoc-empty - +lookup-not-allocated : ∀ {a} {H H′ : Heap a} {b c O} → (H′ ≡ H ⊕ b ↦ O) → (c ≢ b) → (H [ c ] ≡ H′ [ c ]) +lookup-not-allocated {H = H} {O = O} defn p = lookup-snoc-not O H p diff --git a/prototyping/Luau/OpSem.agda b/prototyping/Luau/OpSem.agda index bd75d9d..cbb5597 100644 --- a/prototyping/Luau/OpSem.agda +++ b/prototyping/Luau/OpSem.agda @@ -1,66 +1,53 @@ +{-# OPTIONS --rewriting #-} + module Luau.OpSem where open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Float using (Float; primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv; primFloatEquality; primFloatLess; primFloatInequality) open import Agda.Builtin.Bool using (Bool; true; false) open import Utility.Bool using (not; _or_; _and_) -open import Agda.Builtin.Nat using (_==_) -open import FFI.Data.Maybe using (just) +open import Agda.Builtin.Nat using () renaming (_==_ to _==ᴬ_) +open import FFI.Data.Maybe using (Maybe; just; nothing) open import Luau.Heap using (Heap; _≡_⊕_↦_; _[_]; function_is_end) open import Luau.Substitution using (_[_/_]ᴮ) -open import Luau.Syntax using (Expr; Stat; Block; nil; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg; binexp; BinaryOperator; +; -; *; /; <; >; ≡; ≅; ≤; ≥; number) -open import Luau.Value using (addr; val; number; Value; bool) +open import Luau.Syntax using (Value; Expr; Stat; Block; nil; addr; val; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg; binexp; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; number; bool) open import Luau.RuntimeType using (RuntimeType; valueType) +open import Properties.Product using (_×_; _,_) -evalNumOp : Float → BinaryOperator → Float → Value -evalNumOp x + y = number (primFloatPlus x y) -evalNumOp x - y = number (primFloatMinus x y) -evalNumOp x * y = number (primFloatTimes x y) -evalNumOp x / y = number (primFloatDiv x y) -evalNumOp x < y = bool (primFloatLess x y) -evalNumOp x > y = bool (primFloatLess y x) -evalNumOp x ≡ y = bool (primFloatEquality x y) -evalNumOp x ≅ y = bool (primFloatInequality x y) -evalNumOp x ≤ y = bool ((primFloatLess x y) or (primFloatEquality x y)) -evalNumOp x ≥ y = bool ((primFloatLess y x) or (primFloatEquality x y)) +evalEqOp : Value → Value → Bool +evalEqOp Value.nil Value.nil = true +evalEqOp (addr x) (addr y) = (x ==ᴬ y) +evalEqOp (number x) (number y) = primFloatEquality x y +evalEqOp (bool true) (bool y) = y +evalEqOp (bool false) (bool y) = not y +evalEqOp _ _ = false -evalEqOp : Value → Value → Value -evalEqOp Value.nil Value.nil = bool true -evalEqOp (addr x) (addr y) = bool (x == y) -evalEqOp (number x) (number y) = bool (primFloatEquality x y) -evalEqOp (bool true) (bool y) = bool y -evalEqOp (bool false) (bool y) = bool (not y) -evalEqOp _ _ = bool false - -evalNeqOp : Value → Value → Value -evalNeqOp Value.nil Value.nil = bool false -evalNeqOp (addr x) (addr y) = bool (not (x == y)) -evalNeqOp (number x) (number y) = bool (primFloatInequality x y) -evalNeqOp (bool true) (bool y) = bool (not y) -evalNeqOp (bool false) (bool y) = bool y -evalNeqOp _ _ = bool true - -coerceToBool : Value → Bool -coerceToBool Value.nil = false -coerceToBool (addr x) = true -coerceToBool (number x) = true -coerceToBool (bool x) = x +evalNeqOp : Value → Value → Bool +evalNeqOp (number x) (number y) = primFloatInequality x y +evalNeqOp x y = not (evalEqOp x y) +data _⟦_⟧_⟶_ : Value → BinaryOperator → Value → Value → Set where + + : ∀ m n → (number m) ⟦ + ⟧ (number n) ⟶ number (primFloatPlus m n) + - : ∀ m n → (number m) ⟦ - ⟧ (number n) ⟶ number (primFloatMinus m n) + / : ∀ m n → (number m) ⟦ / ⟧ (number n) ⟶ number (primFloatTimes m n) + * : ∀ m n → (number m) ⟦ * ⟧ (number n) ⟶ number (primFloatDiv m n) + < : ∀ m n → (number m) ⟦ < ⟧ (number n) ⟶ bool (primFloatLess m n) + > : ∀ m n → (number m) ⟦ > ⟧ (number n) ⟶ bool (primFloatLess n m) + <= : ∀ m n → (number m) ⟦ <= ⟧ (number n) ⟶ bool ((primFloatLess m n) or (primFloatEquality m n)) + >= : ∀ m n → (number m) ⟦ >= ⟧ (number n) ⟶ bool ((primFloatLess n m) or (primFloatEquality m n)) + == : ∀ v w → v ⟦ == ⟧ w ⟶ bool (evalEqOp v w) + ~= : ∀ v w → v ⟦ ~= ⟧ w ⟶ bool (evalNeqOp v w) + data _⊢_⟶ᴮ_⊣_ {a} : Heap a → Block a → Block a → Heap a → Set data _⊢_⟶ᴱ_⊣_ {a} : Heap a → Expr a → Expr a → Heap a → Set data _⊢_⟶ᴱ_⊣_ where - nil : ∀ {H} → - - ------------------- - H ⊢ nil ⟶ᴱ nil ⊣ H - - function : ∀ {H H′ a F B} → + function : ∀ a {H H′ F B} → H′ ≡ H ⊕ a ↦ (function F is B end) → ------------------------------------------- - H ⊢ (function F is B end) ⟶ᴱ (addr a) ⊣ H′ + H ⊢ (function F is B end) ⟶ᴱ val(addr a) ⊣ H′ app₁ : ∀ {H H′ M M′ N} → @@ -68,17 +55,18 @@ data _⊢_⟶ᴱ_⊣_ where ----------------------------- H ⊢ (M $ N) ⟶ᴱ (M′ $ N) ⊣ H′ - app₂ : ∀ {H H′ V N N′} → + app₂ : ∀ v {H H′ N N′} → H ⊢ N ⟶ᴱ N′ ⊣ H′ → ----------------------------- - H ⊢ (val V $ N) ⟶ᴱ (val V $ N′) ⊣ H′ + H ⊢ (val v $ N) ⟶ᴱ (val v $ N′) ⊣ H′ - beta : ∀ {H a F B V} → - - H [ a ] ≡ just(function F is B end) → + beta : ∀ O v {H a F B} → + + (O ≡ function F is B end) → + H [ a ] ≡ just(O) → ----------------------------------------------------------------------------- - H ⊢ (addr a $ val V) ⟶ᴱ (block (fun F) is (B [ V / name(arg F) ]ᴮ) end) ⊣ H + H ⊢ (val (addr a) $ val v) ⟶ᴱ (block (fun F) is (B [ v / name(arg F) ]ᴮ) end) ⊣ H block : ∀ {H H′ B B′ b} → @@ -86,44 +74,34 @@ data _⊢_⟶ᴱ_⊣_ where ---------------------------------------------------- H ⊢ (block b is B end) ⟶ᴱ (block b is B′ end) ⊣ H′ - return : ∀ {H V B b} → - + return : ∀ v {H B b} → + -------------------------------------------------------- - H ⊢ (block b is return (val V) ∙ B end) ⟶ᴱ (val V) ⊣ H + H ⊢ (block b is return (val v) ∙ B end) ⟶ᴱ val v ⊣ H done : ∀ {H b} → - --------------------------------- - H ⊢ (block b is done end) ⟶ᴱ nil ⊣ H + -------------------------------------------- + H ⊢ (block b is done end) ⟶ᴱ (val nil) ⊣ H - binOpEquality : - ∀ {H x y} → - --------------------------------------------------------------------------- - H ⊢ (binexp (val x) BinaryOperator.≡ (val y)) ⟶ᴱ (val (evalEqOp x y)) ⊣ H - - binOpInequality : - ∀ {H x y} → - ---------------------------------------------------------------------------- - H ⊢ (binexp (val x) BinaryOperator.≅ (val y)) ⟶ᴱ (val (evalNeqOp x y)) ⊣ H - - binOpNumbers : - ∀ {H x op y} → - ----------------------------------------------------------------------- - H ⊢ (binexp (number x) op (number y)) ⟶ᴱ (val (evalNumOp x op y)) ⊣ H - - binOp₁ : - ∀ {H H′ x x′ op y} → + binOp₀ : ∀ {H op v₁ v₂ w} → + + v₁ ⟦ op ⟧ v₂ ⟶ w → + -------------------------------------------------- + H ⊢ (binexp (val v₁) op (val v₂)) ⟶ᴱ (val w) ⊣ H + + binOp₁ : ∀ {H H′ x x′ op y} → + H ⊢ x ⟶ᴱ x′ ⊣ H′ → --------------------------------------------- H ⊢ (binexp x op y) ⟶ᴱ (binexp x′ op y) ⊣ H′ - binOp₂ : - ∀ {H H′ x op y y′} → + binOp₂ : ∀ {H H′ x op y y′} → + H ⊢ y ⟶ᴱ y′ ⊣ H′ → --------------------------------------------- H ⊢ (binexp x op y) ⟶ᴱ (binexp x op y′) ⊣ H′ - data _⊢_⟶ᴮ_⊣_ where local : ∀ {H H′ x M M′ B} → @@ -132,16 +110,16 @@ data _⊢_⟶ᴮ_⊣_ where ------------------------------------------------- H ⊢ (local x ← M ∙ B) ⟶ᴮ (local x ← M′ ∙ B) ⊣ H′ - subst : ∀ {H x v B} → + subst : ∀ v {H x B} → ------------------------------------------------------ H ⊢ (local x ← val v ∙ B) ⟶ᴮ (B [ v / name x ]ᴮ) ⊣ H - function : ∀ {H H′ a F B C} → + function : ∀ a {H H′ F B C} → H′ ≡ H ⊕ a ↦ (function F is C end) → -------------------------------------------------------------- - H ⊢ (function F is C end ∙ B) ⟶ᴮ (B [ addr a / fun F ]ᴮ) ⊣ H′ + H ⊢ (function F is C end ∙ B) ⟶ᴮ (B [ addr a / name(fun F) ]ᴮ) ⊣ H′ return : ∀ {H H′ M M′ B} → diff --git a/prototyping/Luau/Run.agda b/prototyping/Luau/Run.agda index d84f75d..bcd7557 100644 --- a/prototyping/Luau/Run.agda +++ b/prototyping/Luau/Run.agda @@ -1,15 +1,16 @@ +{-# OPTIONS --rewriting #-} + module Luau.Run where open import Agda.Builtin.Equality using (_≡_; refl) open import Luau.Heap using (Heap; ∅) -open import Luau.Syntax using (Block; return; _∙_; done) +open import Luau.Syntax using (Block; val; return; _∙_; done) open import Luau.OpSem using (_⊢_⟶*_⊣_; refl; step) -open import Luau.Value using (val) open import Properties.Step using (stepᴮ; step; return; done; error) open import Luau.RuntimeError using (RuntimeErrorᴮ) data RunResult {a} (H : Heap a) (B : Block a) : Set where - return : ∀ V {B′ H′} → (H ⊢ B ⟶* (return (val V) ∙ B′) ⊣ H′) → RunResult H B + return : ∀ v {B′ H′} → (H ⊢ B ⟶* (return (val v) ∙ B′) ⊣ H′) → RunResult H B done : ∀ {H′} → (H ⊢ B ⟶* done ⊣ H′) → RunResult H B error : ∀ {B′ H′} → (RuntimeErrorᴮ H′ B′) → (H ⊢ B ⟶* B′ ⊣ H′) → RunResult H B diff --git a/prototyping/Luau/RuntimeError.agda b/prototyping/Luau/RuntimeError.agda index b479a72..4440d2b 100644 --- a/prototyping/Luau/RuntimeError.agda +++ b/prototyping/Luau/RuntimeError.agda @@ -1,27 +1,40 @@ +{-# OPTIONS --rewriting #-} + module Luau.RuntimeError where open import Agda.Builtin.Equality using (_≡_) open import Luau.Heap using (Heap; _[_]) open import FFI.Data.Maybe using (just; nothing) open import FFI.Data.String using (String) -open import Luau.Syntax using (Block; Expr; nil; var; addr; block_is_end; _$_; local_←_; return; done; _∙_; number; binexp) -open import Luau.RuntimeType using (RuntimeType; valueType) -open import Luau.Value using (val) +open import Luau.Syntax using (BinaryOperator; Block; Expr; nil; var; val; addr; block_is_end; _$_; local_←_; return; done; _∙_; number; binexp; +; -; *; /; <; >; <=; >=) +open import Luau.RuntimeType using (RuntimeType; valueType; function; number) open import Properties.Equality using (_≢_) +data BinOpError : BinaryOperator → RuntimeType → Set where + + : ∀ {t} → (t ≢ number) → BinOpError + t + - : ∀ {t} → (t ≢ number) → BinOpError - t + * : ∀ {t} → (t ≢ number) → BinOpError * t + / : ∀ {t} → (t ≢ number) → BinOpError / t + < : ∀ {t} → (t ≢ number) → BinOpError < t + > : ∀ {t} → (t ≢ number) → BinOpError > t + <= : ∀ {t} → (t ≢ number) → BinOpError <= t + >= : ∀ {t} → (t ≢ number) → BinOpError >= t + data RuntimeErrorᴮ {a} (H : Heap a) : Block a → Set data RuntimeErrorᴱ {a} (H : Heap a) : Expr a → Set data RuntimeErrorᴱ H where - TypeMismatch : ∀ t v → (t ≢ valueType v) → RuntimeErrorᴱ H (val v) - UnboundVariable : ∀ x → RuntimeErrorᴱ H (var x) - SEGV : ∀ a → (H [ a ] ≡ nothing) → RuntimeErrorᴱ H (addr a) + FunctionMismatch : ∀ v w → (function ≢ valueType v) → RuntimeErrorᴱ H (val v $ val w) + BinOpMismatch₁ : ∀ v w {op} → (BinOpError op (valueType v)) → RuntimeErrorᴱ H (binexp (val v) op (val w)) + BinOpMismatch₂ : ∀ v w {op} → (BinOpError op (valueType w)) → RuntimeErrorᴱ H (binexp (val v) op (val w)) + UnboundVariable : ∀ {x} → RuntimeErrorᴱ H (var x) + SEGV : ∀ {a} → (H [ a ] ≡ nothing) → RuntimeErrorᴱ H (val (addr a)) app₁ : ∀ {M N} → RuntimeErrorᴱ H M → RuntimeErrorᴱ H (M $ N) app₂ : ∀ {M N} → RuntimeErrorᴱ H N → RuntimeErrorᴱ H (M $ N) - block : ∀ b {B} → RuntimeErrorᴮ H B → RuntimeErrorᴱ H (block b is B end) + block : ∀ {b B} → RuntimeErrorᴮ H B → RuntimeErrorᴱ H (block b is B end) bin₁ : ∀ {M N op} → RuntimeErrorᴱ H M → RuntimeErrorᴱ H (binexp M op N) bin₂ : ∀ {M N op} → RuntimeErrorᴱ H N → RuntimeErrorᴱ H (binexp M op N) data RuntimeErrorᴮ H where - local : ∀ x {M B} → RuntimeErrorᴱ H M → RuntimeErrorᴮ H (local x ← M ∙ B) + local : ∀ {x M B} → RuntimeErrorᴱ H M → RuntimeErrorᴮ H (local x ← M ∙ B) return : ∀ {M B} → RuntimeErrorᴱ H M → RuntimeErrorᴮ H (return M ∙ B) diff --git a/prototyping/Luau/RuntimeError/ToString.agda b/prototyping/Luau/RuntimeError/ToString.agda index d5528c8..6a6ed3e 100644 --- a/prototyping/Luau/RuntimeError/ToString.agda +++ b/prototyping/Luau/RuntimeError/ToString.agda @@ -1,27 +1,29 @@ +{-# OPTIONS --rewriting #-} + module Luau.RuntimeError.ToString where open import Agda.Builtin.Float using (primShowFloat) open import FFI.Data.String using (String; _++_) -open import Luau.RuntimeError using (RuntimeErrorᴮ; RuntimeErrorᴱ; local; return; TypeMismatch; UnboundVariable; SEGV; app₁; app₂; block; bin₁; bin₂) +open import Luau.RuntimeError using (RuntimeErrorᴮ; RuntimeErrorᴱ; local; return; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; block; bin₁; bin₂) open import Luau.RuntimeType.ToString using (runtimeTypeToString) open import Luau.Addr.ToString using (addrToString) -open import Luau.Syntax.ToString using (exprToString) +open import Luau.Syntax.ToString using (valueToString; exprToString) open import Luau.Var.ToString using (varToString) -open import Luau.Value.ToString using (valueToString) -open import Luau.Syntax using (name; _$_) +open import Luau.Syntax using (var; val; addr; binexp; block_is_end; local_←_; return; _∙_; name; _$_) -errToStringᴱ : ∀ {a H B} → RuntimeErrorᴱ {a} H B → String -errToStringᴮ : ∀ {a H B} → RuntimeErrorᴮ {a} H B → String +errToStringᴱ : ∀ {a H} M → RuntimeErrorᴱ {a} H M → String +errToStringᴮ : ∀ {a H} B → RuntimeErrorᴮ {a} H B → String -errToStringᴱ (UnboundVariable x) = "variable " ++ varToString x ++ " is unbound" -errToStringᴱ (SEGV a x) = "address " ++ addrToString a ++ " is unallocated" -errToStringᴱ (app₁ E) = errToStringᴱ E -errToStringᴱ (app₂ E) = errToStringᴱ E -errToStringᴱ (bin₁ E) = errToStringᴱ E -errToStringᴱ (bin₂ E) = errToStringᴱ E -errToStringᴱ (block b E) = errToStringᴮ E ++ "\n in call of function " ++ varToString b -errToStringᴱ (TypeMismatch t v _) = "value " ++ valueToString v ++ " is not a " ++ runtimeTypeToString t +errToStringᴱ (var x) (UnboundVariable) = "variable " ++ varToString x ++ " is unbound" +errToStringᴱ (val (addr a)) (SEGV p) = "address " ++ addrToString a ++ " is unallocated" +errToStringᴱ (M $ N) (FunctionMismatch v w p) = "value " ++ (valueToString v) ++ " is not a function" +errToStringᴱ (M $ N) (app₁ E) = errToStringᴱ M E +errToStringᴱ (M $ N) (app₂ E) = errToStringᴱ N E +errToStringᴱ (binexp M op N) (BinOpMismatch₁ v w p) = "value " ++ (valueToString v) ++ " is not a number" +errToStringᴱ (binexp M op N) (BinOpMismatch₂ v w p) = "value " ++ (valueToString w) ++ " is not a number" +errToStringᴱ (binexp M op N) (bin₁ E) = errToStringᴱ M E +errToStringᴱ (binexp M op N) (bin₂ E) = errToStringᴱ N E +errToStringᴱ (block b is B end) (block E) = errToStringᴮ B E ++ "\n in call of function " ++ varToString (name b) -errToStringᴮ (local x E) = errToStringᴱ E ++ "\n in definition of " ++ varToString (name x) -errToStringᴮ (return E) = errToStringᴱ E ++ "\n in return statement" - +errToStringᴮ (local x ← M ∙ B) (local E) = errToStringᴱ M E ++ "\n in definition of " ++ varToString (name x) +errToStringᴮ (return M ∙ B) (return E) = errToStringᴱ M E ++ "\n in return statement" diff --git a/prototyping/Luau/RuntimeType.agda b/prototyping/Luau/RuntimeType.agda index 375fc54..f6b8b4a 100644 --- a/prototyping/Luau/RuntimeType.agda +++ b/prototyping/Luau/RuntimeType.agda @@ -1,6 +1,6 @@ module Luau.RuntimeType where -open import Luau.Value using (Value; nil; addr; number; bool) +open import Luau.Syntax using (Value; nil; addr; number; bool) data RuntimeType : Set where function : RuntimeType @@ -10,6 +10,6 @@ data RuntimeType : Set where valueType : Value → RuntimeType valueType nil = nil -valueType (addr x) = function -valueType (number x) = number -valueType (bool _) = boolean +valueType (addr a) = function +valueType (number n) = number +valueType (bool b) = boolean diff --git a/prototyping/Luau/StrictMode.agda b/prototyping/Luau/StrictMode.agda new file mode 100644 index 0000000..d7b2129 --- /dev/null +++ b/prototyping/Luau/StrictMode.agda @@ -0,0 +1,205 @@ +{-# OPTIONS --rewriting #-} + +module Luau.StrictMode where + +open import Agda.Builtin.Equality using (_≡_) +open import FFI.Data.Maybe using (just; nothing) +open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; var; binexp; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; +; -; *; /; <; >; <=; >=) +open import Luau.Type using (Type; strict; nil; number; _⇒_; tgt) +open import Luau.Heap using (Heap; function_is_end) renaming (_[_] to _[_]ᴴ) +open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ) +open import Luau.TypeCheck(strict) using (_⊢ᴮ_∈_; _⊢ᴱ_∈_; ⊢ᴴ_; ⊢ᴼ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; var; addr; app; binexp; block; return; local; function) +open import Properties.Equality using (_≢_) +open import Properties.TypeCheck(strict) using (typeCheckᴮ) +open import Properties.Product using (_,_) + +src : Type → Type +src = Luau.Type.src strict + +data BinOpWarning : BinaryOperator → Type → Set where + + : ∀ {T} → (T ≢ number) → BinOpWarning + T + - : ∀ {T} → (T ≢ number) → BinOpWarning - T + * : ∀ {T} → (T ≢ number) → BinOpWarning * T + / : ∀ {T} → (T ≢ number) → BinOpWarning / T + < : ∀ {T} → (T ≢ number) → BinOpWarning < T + > : ∀ {T} → (T ≢ number) → BinOpWarning > T + <= : ∀ {T} → (T ≢ number) → BinOpWarning <= T + >= : ∀ {T} → (T ≢ number) → BinOpWarning >= T + +data Warningᴱ (H : Heap yes) {Γ} : ∀ {M T} → (Γ ⊢ᴱ M ∈ T) → Set +data Warningᴮ (H : Heap yes) {Γ} : ∀ {B T} → (Γ ⊢ᴮ B ∈ T) → Set + +data Warningᴱ H {Γ} where + + UnallocatedAddress : ∀ {a T} → + + (H [ a ]ᴴ ≡ nothing) → + --------------------- + Warningᴱ H (addr {a} T) + + UnboundVariable : ∀ {x T p} → + + (Γ [ x ]ⱽ ≡ nothing) → + ------------------------ + Warningᴱ H (var {x} {T} p) + + FunctionCallMismatch : ∀ {M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + (src T ≢ U) → + ----------------- + Warningᴱ H (app D₁ D₂) + + app₁ : ∀ {M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + Warningᴱ H D₁ → + ----------------- + Warningᴱ H (app D₁ D₂) + + app₂ : ∀ {M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + Warningᴱ H D₂ → + ----------------- + Warningᴱ H (app D₁ D₂) + + BinOpMismatch₁ : ∀ {op M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + BinOpWarning op T → + ------------------------------ + Warningᴱ H (binexp {op} D₁ D₂) + + BinOpMismatch₂ : ∀ {op M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + BinOpWarning op U → + ------------------------------ + Warningᴱ H (binexp {op} D₁ D₂) + + bin₁ : ∀ {op M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + Warningᴱ H D₁ → + ------------------------------ + Warningᴱ H (binexp {op} D₁ D₂) + + bin₂ : ∀ {op M N T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴱ N ∈ U} → + + Warningᴱ H D₂ → + ------------------------------ + Warningᴱ H (binexp {op} D₁ D₂) + + FunctionDefnMismatch : ∀ {f x B T U V} {D : (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V} → + + (U ≢ V) → + ------------------------- + Warningᴱ H (function {f} {U = U} D) + + function₁ : ∀ {f x B T U V} {D : (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V} → + + Warningᴮ H D → + ------------------------- + Warningᴱ H (function {f} {U = U} D) + + BlockMismatch : ∀ {b B T U} {D : Γ ⊢ᴮ B ∈ U} → + + (T ≢ U) → + ------------------------------ + Warningᴱ H (block {b} {T = T} D) + + block₁ : ∀ {b B T U} {D : Γ ⊢ᴮ B ∈ U} → + + Warningᴮ H D → + ------------------------------ + Warningᴱ H (block {b} {T = T} D) + +data Warningᴮ H {Γ} where + + return : ∀ {M B T U} {D₁ : Γ ⊢ᴱ M ∈ T} {D₂ : Γ ⊢ᴮ B ∈ U} → + + Warningᴱ H D₁ → + ------------------ + Warningᴮ H (return D₁ D₂) + + LocalVarMismatch : ∀ {x M B T U V} {D₁ : Γ ⊢ᴱ M ∈ U} {D₂ : (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V} → + + (T ≢ U) → + -------------------- + Warningᴮ H (local D₁ D₂) + + local₁ : ∀ {x M B T U V} {D₁ : Γ ⊢ᴱ M ∈ U} {D₂ : (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V} → + + Warningᴱ H D₁ → + -------------------- + Warningᴮ H (local D₁ D₂) + + local₂ : ∀ {x M B T U V} {D₁ : Γ ⊢ᴱ M ∈ U} {D₂ : (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V} → + + Warningᴮ H D₂ → + -------------------- + Warningᴮ H (local D₁ D₂) + + FunctionDefnMismatch : ∀ {f x B C T U V W} {D₁ : (Γ ⊕ x ↦ T) ⊢ᴮ C ∈ V} {D₂ : (Γ ⊕ f ↦ (T ⇒ U)) ⊢ᴮ B ∈ W} → + + (U ≢ V) → + ------------------------------------- + Warningᴮ H (function D₁ D₂) + + function₁ : ∀ {f x B C T U V W} {D₁ : (Γ ⊕ x ↦ T) ⊢ᴮ C ∈ V} {D₂ : (Γ ⊕ f ↦ (T ⇒ U)) ⊢ᴮ B ∈ W} → + + Warningᴮ H D₁ → + -------------------- + Warningᴮ H (function D₁ D₂) + + function₂ : ∀ {f x B C T U V W} {D₁ : (Γ ⊕ x ↦ T) ⊢ᴮ C ∈ V} {D₂ : (Γ ⊕ f ↦ (T ⇒ U)) ⊢ᴮ B ∈ W} → + + Warningᴮ H D₂ → + -------------------- + Warningᴮ H (function D₁ D₂) + +data Warningᴼ (H : Heap yes) : ∀ {V} → (⊢ᴼ V) → Set where + + FunctionDefnMismatch : ∀ {f x B T U V} {D : (x ↦ T) ⊢ᴮ B ∈ V} → + + (U ≢ V) → + --------------------------------- + Warningᴼ H (function {f} {U = U} D) + + function₁ : ∀ {f x B T U V} {D : (x ↦ T) ⊢ᴮ B ∈ V} → + + Warningᴮ H D → + --------------------------------- + Warningᴼ H (function {f} {U = U} D) + +data Warningᴴ H (D : ⊢ᴴ H) : Set where + + addr : ∀ a {O} → + + (p : H [ a ]ᴴ ≡ O) → + Warningᴼ H (D a p) → + --------------- + Warningᴴ H D + +data Warningᴴᴱ H {Γ M T} : (Γ ⊢ᴴᴱ H ▷ M ∈ T) → Set where + + heap : ∀ {D₁ : ⊢ᴴ H} {D₂ : Γ ⊢ᴱ M ∈ T} → + + Warningᴴ H D₁ → + ----------------- + Warningᴴᴱ H (D₁ , D₂) + + expr : ∀ {D₁ : ⊢ᴴ H} {D₂ : Γ ⊢ᴱ M ∈ T} → + + Warningᴱ H D₂ → + --------------------- + Warningᴴᴱ H (D₁ , D₂) + +data Warningᴴᴮ H {Γ B T} : (Γ ⊢ᴴᴮ H ▷ B ∈ T) → Set where + + heap : ∀ {D₁ : ⊢ᴴ H} {D₂ : Γ ⊢ᴮ B ∈ T} → + + Warningᴴ H D₁ → + ----------------- + Warningᴴᴮ H (D₁ , D₂) + + block : ∀ {D₁ : ⊢ᴴ H} {D₂ : Γ ⊢ᴮ B ∈ T} → + + Warningᴮ H D₂ → + --------------------- + Warningᴴᴮ H (D₁ , D₂) diff --git a/prototyping/Luau/StrictMode/ToString.agda b/prototyping/Luau/StrictMode/ToString.agda new file mode 100644 index 0000000..ca55888 --- /dev/null +++ b/prototyping/Luau/StrictMode/ToString.agda @@ -0,0 +1,39 @@ +{-# OPTIONS --rewriting #-} + +module Luau.StrictMode.ToString where + +open import FFI.Data.String using (String; _++_) +open import Luau.StrictMode using (Warningᴱ; Warningᴮ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; FunctionDefnMismatch; BlockMismatch; app₁; app₂; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; block₁; return; LocalVarMismatch; local₁; local₂; function₁; function₂; heap; expr; block; addr) +open import Luau.Syntax using (Expr; val; yes; var; var_∈_; _⟨_⟩∈_; _$_; addr; number; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name) +open import Luau.Type using (strict) +open import Luau.TypeCheck(strict) using (_⊢ᴮ_∈_; _⊢ᴱ_∈_) +open import Luau.Addr.ToString using (addrToString) +open import Luau.Var.ToString using (varToString) +open import Luau.Type.ToString using (typeToString) +open import Luau.Syntax.ToString using (binOpToString) + +warningToStringᴱ : ∀ {H Γ T} M → {D : Γ ⊢ᴱ M ∈ T} → Warningᴱ H D → String +warningToStringᴮ : ∀ {H Γ T} B → {D : Γ ⊢ᴮ B ∈ T} → Warningᴮ H D → String + +warningToStringᴱ (var x) (UnboundVariable p) = "Unbound variable " ++ varToString x +warningToStringᴱ (val (addr a)) (UnallocatedAddress p) = "Unallocated adress " ++ addrToString a +warningToStringᴱ (M $ N) (FunctionCallMismatch {T = T} {U = U} p) = "Function has type " ++ typeToString T ++ " but argument has type " ++ typeToString U +warningToStringᴱ (M $ N) (app₁ W) = warningToStringᴱ M W +warningToStringᴱ (M $ N) (app₂ W) = warningToStringᴱ N W +warningToStringᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) (FunctionDefnMismatch {V = V} p) = "Function expresion " ++ varToString f ++ " has return type " ++ typeToString U ++ " but body returns " ++ typeToString V +warningToStringᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) (function₁ W) = warningToStringᴮ B W ++ "\n in function expression " ++ varToString f +warningToStringᴱ block var b ∈ T is B end (BlockMismatch {U = U} p) = "Block " ++ varToString b ++ " has type " ++ typeToString T ++ " but body returns " ++ typeToString U +warningToStringᴱ block var b ∈ T is B end (block₁ W) = warningToStringᴮ B W ++ "\n in block " ++ varToString b +warningToStringᴱ (binexp M op N) (BinOpMismatch₁ {T = T} p) = "Binary operator " ++ binOpToString op ++ " lhs has type " ++ typeToString T +warningToStringᴱ (binexp M op N) (BinOpMismatch₂ {U = U} p) = "Binary operator " ++ binOpToString op ++ " rhs has type " ++ typeToString U +warningToStringᴱ (binexp M op N) (bin₁ W) = warningToStringᴱ M W +warningToStringᴱ (binexp M op N) (bin₂ W) = warningToStringᴱ N W + +warningToStringᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (FunctionDefnMismatch {V = V} p) = "Function declaration " ++ varToString f ++ " has return type " ++ typeToString U ++ " but body returns " ++ typeToString V +warningToStringᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (function₁ W) = warningToStringᴮ C W ++ "\n in function declaration " ++ varToString f +warningToStringᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (function₂ W) = warningToStringᴮ B W +warningToStringᴮ (local var x ∈ T ← M ∙ B) (LocalVarMismatch {U = U} p) = "Local variable " ++ varToString x ++ " has type " ++ typeToString T ++ " but expression has type " ++ typeToString U +warningToStringᴮ (local var x ∈ T ← M ∙ B) (local₁ W) = warningToStringᴱ M W ++ "\n in local variable declaration " ++ varToString x +warningToStringᴮ (local var x ∈ T ← M ∙ B) (local₂ W) = warningToStringᴮ B W +warningToStringᴮ (return M ∙ B) (return W) = warningToStringᴱ M W ++ "\n in return statement" + diff --git a/prototyping/Luau/Substitution.agda b/prototyping/Luau/Substitution.agda index d909c35..883cc63 100644 --- a/prototyping/Luau/Substitution.agda +++ b/prototyping/Luau/Substitution.agda @@ -1,7 +1,6 @@ module Luau.Substitution where -open import Luau.Syntax using (Expr; Stat; Block; nil; true; false; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; _⟨_⟩ ; name; fun; arg; number; binexp) -open import Luau.Value using (Value; val) +open import Luau.Syntax using (Value; Expr; Stat; Block; val; nil; bool; addr; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; _⟨_⟩ ; name; fun; arg; number; binexp) open import Luau.Var using (Var; _≡ⱽ_) open import Properties.Dec using (Dec; yes; no) @@ -10,18 +9,14 @@ _[_/_]ᴮ : ∀ {a} → Block a → Value → Var → Block a var_[_/_]ᴱwhenever_ : ∀ {a P} → Var → Value → Var → (Dec P) → Expr a _[_/_]ᴮunless_ : ∀ {a P} → Block a → Value → Var → (Dec P) → Block a -nil [ v / x ]ᴱ = nil -true [ v / x ]ᴱ = true -false [ v / x ]ᴱ = false +val w [ v / x ]ᴱ = val w var y [ v / x ]ᴱ = var y [ v / x ]ᴱwhenever (x ≡ⱽ y) -addr a [ v / x ]ᴱ = addr a -(number y) [ v / x ]ᴱ = number y (M $ N) [ v / x ]ᴱ = (M [ v / x ]ᴱ) $ (N [ v / x ]ᴱ) function F is C end [ v / x ]ᴱ = function F is C [ v / x ]ᴮunless (x ≡ⱽ name(arg F)) end block b is C end [ v / x ]ᴱ = block b is C [ v / x ]ᴮ end (binexp e₁ op e₂) [ v / x ]ᴱ = binexp (e₁ [ v / x ]ᴱ) op (e₂ [ v / x ]ᴱ) -(function F is C end ∙ B) [ v / x ]ᴮ = function F is (C [ v / x ]ᴮunless (x ≡ⱽ name(arg F))) end ∙ (B [ v / x ]ᴮunless (x ≡ⱽ fun F)) +(function F is C end ∙ B) [ v / x ]ᴮ = function F is (C [ v / x ]ᴮunless (x ≡ⱽ name(arg F))) end ∙ (B [ v / x ]ᴮunless (x ≡ⱽ name(fun F))) (local y ← M ∙ B) [ v / x ]ᴮ = local y ← (M [ v / x ]ᴱ) ∙ (B [ v / x ]ᴮunless (x ≡ⱽ name y)) (return M ∙ B) [ v / x ]ᴮ = return (M [ v / x ]ᴱ) ∙ (B [ v / x ]ᴮ) done [ v / x ]ᴮ = done diff --git a/prototyping/Luau/Syntax.agda b/prototyping/Luau/Syntax.agda index 57e59a7..8be0830 100644 --- a/prototyping/Luau/Syntax.agda +++ b/prototyping/Luau/Syntax.agda @@ -1,11 +1,12 @@ module Luau.Syntax where open import Agda.Builtin.Equality using (_≡_) +open import Agda.Builtin.Bool using (Bool; true; false) open import Agda.Builtin.Float using (Float) -open import Properties.Dec using (⊥) open import Luau.Var using (Var) open import Luau.Addr using (Addr) open import Luau.Type using (Type) +open import FFI.Data.Maybe using (Maybe; just; nothing) infixr 5 _∙_ @@ -25,9 +26,9 @@ data FunDec : Annotated → Set where _⟨_⟩∈_ : ∀ {a} → Var → VarDec a → Type → FunDec a _⟨_⟩ : Var → VarDec maybe → FunDec maybe -fun : ∀ {a} → FunDec a → Var -fun (f ⟨ x ⟩∈ T) = f -fun (f ⟨ x ⟩) = f +fun : ∀ {a} → FunDec a → VarDec a +fun (f ⟨ x ⟩∈ T) = (var f ∈ T) +fun (f ⟨ x ⟩) = (var f) arg : ∀ {a} → FunDec a → VarDec a arg (f ⟨ x ⟩∈ T) = x @@ -40,10 +41,16 @@ data BinaryOperator : Set where / : BinaryOperator < : BinaryOperator > : BinaryOperator - ≡ : BinaryOperator - ≅ : BinaryOperator - ≤ : BinaryOperator - ≥ : BinaryOperator + == : BinaryOperator + ~= : BinaryOperator + <= : BinaryOperator + >= : BinaryOperator + +data Value : Set where + nil : Value + addr : Addr → Value + number : Float → Value + bool : Bool → Value data Block (a : Annotated) : Set data Stat (a : Annotated) : Set @@ -59,13 +66,42 @@ data Stat a where return : Expr a → Stat a data Expr a where - nil : Expr a - true : Expr a - false : Expr a var : Var → Expr a - addr : Addr → Expr a + val : Value → Expr a _$_ : Expr a → Expr a → Expr a function_is_end : FunDec a → Block a → Expr a - block_is_end : Var → Block a → Expr a - number : Float → Expr a + block_is_end : VarDec a → Block a → Expr a binexp : Expr a → BinaryOperator → Expr a → Expr a + +isAnnotatedᴱ : ∀ {a} → Expr a → Maybe (Expr yes) +isAnnotatedᴮ : ∀ {a} → Block a → Maybe (Block yes) + +isAnnotatedᴱ (var x) = just (var x) +isAnnotatedᴱ (val v) = just (val v) +isAnnotatedᴱ (M $ N) with isAnnotatedᴱ M | isAnnotatedᴱ N +isAnnotatedᴱ (M $ N) | just M′ | just N′ = just (M′ $ N′) +isAnnotatedᴱ (M $ N) | _ | _ = nothing +isAnnotatedᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) with isAnnotatedᴮ B +isAnnotatedᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) | just B′ = just (function f ⟨ var x ∈ T ⟩∈ U is B′ end) +isAnnotatedᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) | _ = nothing +isAnnotatedᴱ (function _ is B end) = nothing +isAnnotatedᴱ (block var b ∈ T is B end) with isAnnotatedᴮ B +isAnnotatedᴱ (block var b ∈ T is B end) | just B′ = just (block var b ∈ T is B′ end) +isAnnotatedᴱ (block var b ∈ T is B end) | _ = nothing +isAnnotatedᴱ (block _ is B end) = nothing +isAnnotatedᴱ (binexp M op N) with isAnnotatedᴱ M | isAnnotatedᴱ N +isAnnotatedᴱ (binexp M op N) | just M′ | just N′ = just (binexp M′ op N′) +isAnnotatedᴱ (binexp M op N) | _ | _ = nothing + +isAnnotatedᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) with isAnnotatedᴮ B | isAnnotatedᴮ C +isAnnotatedᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) | just B′ | just C′ = just (function f ⟨ var x ∈ T ⟩∈ U is C′ end ∙ B′) +isAnnotatedᴮ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) | _ | _ = nothing +isAnnotatedᴮ (function _ is C end ∙ B) = nothing +isAnnotatedᴮ (local var x ∈ T ← M ∙ B) with isAnnotatedᴱ M | isAnnotatedᴮ B +isAnnotatedᴮ (local var x ∈ T ← M ∙ B) | just M′ | just B′ = just (local var x ∈ T ← M′ ∙ B′) +isAnnotatedᴮ (local var x ∈ T ← M ∙ B) | _ | _ = nothing +isAnnotatedᴮ (local _ ← M ∙ B) = nothing +isAnnotatedᴮ (return M ∙ B) with isAnnotatedᴱ M | isAnnotatedᴮ B +isAnnotatedᴮ (return M ∙ B) | just M′ | just B′ = just (return M′ ∙ B′) +isAnnotatedᴮ (return M ∙ B) | _ | _ = nothing +isAnnotatedᴮ done = just done diff --git a/prototyping/Luau/Syntax/FromJSON.agda b/prototyping/Luau/Syntax/FromJSON.agda index 330739b..a727a5b 100644 --- a/prototyping/Luau/Syntax/FromJSON.agda +++ b/prototyping/Luau/Syntax/FromJSON.agda @@ -1,6 +1,8 @@ +{-# OPTIONS --rewriting #-} + module Luau.Syntax.FromJSON where -open import Luau.Syntax using (Block; Stat ; Expr; nil; _$_; var; var_∈_; function_is_end; _⟨_⟩; local_←_; return; done; _∙_; maybe; VarDec; number; binexp; BinaryOperator; +; -; *; /; ≡; ≅; <; >; ≤; ≥) +open import Luau.Syntax using (Block; Stat ; Expr; _$_; val; nil; bool; number; var; var_∈_; function_is_end; _⟨_⟩; _⟨_⟩∈_; local_←_; return; done; _∙_; maybe; VarDec; binexp; BinaryOperator; +; -; *; /; ==; ~=; <; >; <=; >=) open import Luau.Type.FromJSON using (typeFromJSON) open import Agda.Builtin.List using (List; _∷_; []) @@ -26,6 +28,8 @@ vars = fromString "vars" op = fromString "op" left = fromString "left" right = fromString "right" +returnAnnotation = fromString "returnAnnotation" +types = fromString "types" data Lookup : Set where _,_ : String → Value → Lookup @@ -49,22 +53,22 @@ blockFromJSON : Value → Either String (Block maybe) blockFromArray : Array → Either String (Block maybe) binOpFromJSON (string s) = binOpFromString s -binOpFromJSON val = Left "Binary operator not a string" +binOpFromJSON _ = Left "Binary operator not a string" binOpFromString "Add" = Right + binOpFromString "Sub" = Right - binOpFromString "Mul" = Right * binOpFromString "Div" = Right / -binOpFromString "CompareEq" = Right ≡ -binOpFromString "CompareNe" = Right ≅ +binOpFromString "CompareEq" = Right == +binOpFromString "CompareNe" = Right ~= binOpFromString "CompareLt" = Right < -binOpFromString "CompareLe" = Right ≤ +binOpFromString "CompareLe" = Right <= binOpFromString "CompareGt" = Right > -binOpFromString "CompareGe" = Right ≥ +binOpFromString "CompareGe" = Right >= binOpFromString s = Left ("'" ++ s ++ "' is not a valid operator") varDecFromJSON (object arg) = varDecFromObject arg -varDecFromJSON val = Left "VarDec not an object" +varDecFromJSON _ = Left "VarDec not an object" varDecFromObject obj with lookup name obj | lookup type obj varDecFromObject obj | just (string name) | nothing = Right (var name) @@ -76,7 +80,7 @@ varDecFromObject obj | just _ | _ = Left "AstLocal name is not a string" varDecFromObject obj | nothing | _ = Left "AstLocal missing name" exprFromJSON (object obj) = exprFromObject obj -exprFromJSON val = Left "AstExpr not an object" +exprFromJSON _ = Left "AstExpr not an object" exprFromObject obj with lookup type obj exprFromObject obj | just (string "AstExprCall") with lookup func obj | lookup args obj @@ -89,29 +93,37 @@ exprFromObject obj | just (string "AstExprCall") | just value | just (array arr) exprFromObject obj | just (string "AstExprCall") | just value | just _ = Left ("AstExprCall args not an array") exprFromObject obj | just (string "AstExprCall") | nothing | _ = Left ("AstExprCall missing func") exprFromObject obj | just (string "AstExprCall") | _ | nothing = Left ("AstExprCall missing args") -exprFromObject obj | just (string "AstExprConstantNil") = Right nil -exprFromObject obj | just (string "AstExprFunction") with lookup args obj | lookup body obj -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue with head arr | blockFromJSON blockValue -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B with varDecFromJSON argValue -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B | Right arg = Right (function "" ⟨ arg ⟩ is B end) -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just argValue | Right B | Left err = Left err -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | nothing | Right B = Left "Unsupported AstExprFunction empty args" -exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | _ | Left err = Left err -exprFromObject obj | just (string "AstExprFunction") | just _ | just _ = Left "AstExprFunction args not an array" -exprFromObject obj | just (string "AstExprFunction") | nothing | _ = Left "AstExprFunction missing args" -exprFromObject obj | just (string "AstExprFunction") | _ | nothing = Left "AstExprFunction missing body" +exprFromObject obj | just (string "AstExprConstantNil") = Right (val nil) +exprFromObject obj | just (string "AstExprFunction") with lookup args obj | lookup body obj | lookup returnAnnotation obj +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | rtn with head arr | blockFromJSON blockValue +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | rtn | just argValue | Right B with varDecFromJSON argValue +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg with lookup types rtnObj +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just (array rtnTypes) with head rtnTypes +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just (array rtnTypes) | just rtnType with typeFromJSON rtnType +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just (array rtnTypes) | just rtnType | Left err = Left err +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just (array rtnTypes) | just rtnType | Right T = Right (function "" ⟨ arg ⟩∈ T is B end) +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just (array rtnTypes) | nothing = Right (function "" ⟨ arg ⟩ is B end) +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | just _ = Left "returnAnnotation types not an array" +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just (object rtnObj) | just argValue | Right B | Right arg | nothing = Left "returnAnnotation missing types" +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | just _ | just argValue | Right B | Right arg = Left "returnAnnotation not an object" +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | nothing | just argValue | Right B | Right arg = Right (function "" ⟨ arg ⟩ is B end) +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | rtn | just argValue | Right B | Left err = Left err +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | rtn | nothing | Right B = Left "Unsupported AstExprFunction empty args" +exprFromObject obj | just (string "AstExprFunction") | just (array arr) | just blockValue | rtn | _ | Left err = Left err +exprFromObject obj | just (string "AstExprFunction") | just _ | just _ | rtn = Left "AstExprFunction args not an array" +exprFromObject obj | just (string "AstExprFunction") | nothing | _ | rtn = Left "AstExprFunction missing args" +exprFromObject obj | just (string "AstExprFunction") | _ | nothing | rtn = Left "AstExprFunction missing body" exprFromObject obj | just (string "AstExprLocal") with lookup lokal obj exprFromObject obj | just (string "AstExprLocal") | just x with varDecFromJSON x exprFromObject obj | just (string "AstExprLocal") | just x | Right x′ = Right (var (Luau.Syntax.name x′)) exprFromObject obj | just (string "AstExprLocal") | just x | Left err = Left err exprFromObject obj | just (string "AstExprLocal") | nothing = Left "AstExprLocal missing local" exprFromObject obj | just (string "AstExprConstantNumber") with lookup value obj -exprFromObject obj | just (string "AstExprConstantNumber") | just (FFI.Data.Aeson.Value.number x) = Right (number (toFloat x)) +exprFromObject obj | just (string "AstExprConstantNumber") | just (FFI.Data.Aeson.Value.number x) = Right (val (number (toFloat x))) exprFromObject obj | just (string "AstExprConstantNumber") | just _ = Left "AstExprConstantNumber value is not a number" exprFromObject obj | just (string "AstExprConstantNumber") | nothing = Left "AstExprConstantNumber missing value" exprFromObject obj | just (string "AstExprConstantBool") with lookup value obj -exprFromObject obj | just (string "AstExprConstantBool") | just (FFI.Data.Aeson.Value.bool true) = Right Expr.true -exprFromObject obj | just (string "AstExprConstantBool") | just (FFI.Data.Aeson.Value.bool false) = Right Expr.false +exprFromObject obj | just (string "AstExprConstantBool") | just (FFI.Data.Aeson.Value.bool b) = Right (val (bool b)) exprFromObject obj | just (string "AstExprConstantBool") | just _ = Left "AstExprConstantBool value is not a bool" exprFromObject obj | just (string "AstExprConstantBool") | nothing = Left "AstExprConstantBool missing value" exprFromObject obj | just (string "AstExprBinary") with lookup op obj | lookup left obj | lookup right obj @@ -147,6 +159,7 @@ statFromObject obj | just(string "AstStatLocal") | nothing | _ = Left "AstStatLo statFromObject obj | just(string "AstStatLocalFunction") with lookup name obj | lookup func obj statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value with varDecFromJSON fnName | exprFromJSON value statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | Right fnVar | Right (function "" ⟨ x ⟩ is B end) = Right (function (Luau.Syntax.name fnVar) ⟨ x ⟩ is B end) +statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | Right fnVar | Right (function "" ⟨ x ⟩∈ T is B end) = Right (function (Luau.Syntax.name fnVar) ⟨ x ⟩∈ T is B end) statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | Left err | _ = Left err statFromObject obj | just(string "AstStatLocalFunction") | just fnName | just value | _ | Left err = Left err statFromObject obj | just(string "AstStatLocalFunction") | just _ | just _ | Right _ | Right _ = Left "AstStatLocalFunction func is not an AstExprFunction" @@ -180,4 +193,4 @@ blockFromArray arr | just value | Left err = Left err blockFromArray arr | just value | Right S with blockFromArray(tail arr) blockFromArray arr | just value | Right S | Left err = Left (err) blockFromArray arr | just value | Right S | Right B = Right (S ∙ B) - \ No newline at end of file + diff --git a/prototyping/Luau/Syntax/ToString.agda b/prototyping/Luau/Syntax/ToString.agda index 58a784b..60ec471 100644 --- a/prototyping/Luau/Syntax/ToString.agda +++ b/prototyping/Luau/Syntax/ToString.agda @@ -1,7 +1,8 @@ module Luau.Syntax.ToString where +open import Agda.Builtin.Bool using (true; false) open import Agda.Builtin.Float using (primShowFloat) -open import Luau.Syntax using (Block; Stat; Expr; VarDec; FunDec; nil; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_; number; BinaryOperator; +; -; *; /; <; >; ≡; ≅; ≤; ≥; binexp; true; false) +open import Luau.Syntax using (Value; Block; Stat; Expr; VarDec; FunDec; nil; bool; val; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_; number; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; binexp) open import FFI.Data.String using (String; _++_) open import Luau.Addr.ToString using (addrToString) open import Luau.Type.ToString using (typeToString) @@ -24,19 +25,24 @@ binOpToString * = "*" binOpToString / = "/" binOpToString < = "<" binOpToString > = ">" -binOpToString ≡ = "==" -binOpToString ≅ = "~=" -binOpToString ≤ = "<=" -binOpToString ≥ = ">=" +binOpToString == = "==" +binOpToString ~= = "~=" +binOpToString <= = "<=" +binOpToString >= = ">=" + +valueToString : Value → String +valueToString nil = "nil" +valueToString (addr a) = addrToString a +valueToString (number x) = primShowFloat x +valueToString (bool false) = "false" +valueToString (bool true) = "true" exprToString′ : ∀ {a} → String → Expr a → String statToString′ : ∀ {a} → String → Stat a → String blockToString′ : ∀ {a} → String → Block a → String -exprToString′ lb nil = - "nil" -exprToString′ lb (addr a) = - addrToString(a) +exprToString′ lb (val v) = + valueToString(v) exprToString′ lb (var x) = varToString(x) exprToString′ lb (M $ N) = @@ -46,13 +52,10 @@ exprToString′ lb (function F is B end) = " " ++ (blockToString′ (lb ++ " ") B) ++ lb ++ "end" exprToString′ lb (block b is B end) = - "(" ++ b ++ "()" ++ lb ++ + "(" ++ varDecToString b ++ "()" ++ lb ++ " " ++ (blockToString′ (lb ++ " ") B) ++ lb ++ "end)()" -exprToString′ lb (number x) = primShowFloat x exprToString′ lb (binexp x op y) = exprToString′ lb x ++ " " ++ binOpToString op ++ " " ++ exprToString′ lb y -exprToString′ lb true = "true" -exprToString′ lb false = "false" statToString′ lb (function F is B end) = "local " ++ funDecToString F ++ lb ++ diff --git a/prototyping/Luau/Type.agda b/prototyping/Luau/Type.agda index af5f857..10f39c4 100644 --- a/prototyping/Luau/Type.agda +++ b/prototyping/Luau/Type.agda @@ -1,5 +1,9 @@ module Luau.Type where +open import FFI.Data.Maybe using (Maybe; just; nothing; just-inv) +open import Agda.Builtin.Equality using (_≡_; refl) +open import Properties.Dec using (Dec; yes; no) +open import Properties.Equality using (cong) open import FFI.Data.Maybe using (Maybe; just; nothing) data Type : Set where @@ -7,18 +11,132 @@ data Type : Set where _⇒_ : Type → Type → Type none : Type any : Type + boolean : Type number : Type _∪_ : Type → Type → Type _∩_ : Type → Type → Type -src : Type → Type -src nil = none -src (S ⇒ T) = S -src none = none -src any = any -src number = none -src (S ∪ T) = (src S) ∪ (src T) -src (S ∩ T) = (src S) ∩ (src T) +lhs : Type → Type +lhs (T ⇒ _) = T +lhs (T ∪ _) = T +lhs (T ∩ _) = T +lhs nil = nil +lhs none = none +lhs any = any +lhs number = number +lhs boolean = boolean + +rhs : Type → Type +rhs (_ ⇒ T) = T +rhs (_ ∪ T) = T +rhs (_ ∩ T) = T +rhs nil = nil +rhs none = none +rhs any = any +rhs number = number +rhs boolean = boolean + +_≡ᵀ_ : ∀ (T U : Type) → Dec(T ≡ U) +nil ≡ᵀ nil = yes refl +nil ≡ᵀ (S ⇒ T) = no (λ ()) +nil ≡ᵀ none = no (λ ()) +nil ≡ᵀ any = no (λ ()) +nil ≡ᵀ number = no (λ ()) +nil ≡ᵀ boolean = no (λ ()) +nil ≡ᵀ (S ∪ T) = no (λ ()) +nil ≡ᵀ (S ∩ T) = no (λ ()) +(S ⇒ T) ≡ᵀ nil = no (λ ()) +(S ⇒ T) ≡ᵀ (U ⇒ V) with (S ≡ᵀ U) | (T ≡ᵀ V) +(S ⇒ T) ≡ᵀ (S ⇒ T) | yes refl | yes refl = yes refl +(S ⇒ T) ≡ᵀ (U ⇒ V) | _ | no p = no (λ q → p (cong rhs q)) +(S ⇒ T) ≡ᵀ (U ⇒ V) | no p | _ = no (λ q → p (cong lhs q)) +(S ⇒ T) ≡ᵀ none = no (λ ()) +(S ⇒ T) ≡ᵀ any = no (λ ()) +(S ⇒ T) ≡ᵀ number = no (λ ()) +(S ⇒ T) ≡ᵀ boolean = no (λ ()) +(S ⇒ T) ≡ᵀ (U ∪ V) = no (λ ()) +(S ⇒ T) ≡ᵀ (U ∩ V) = no (λ ()) +none ≡ᵀ nil = no (λ ()) +none ≡ᵀ (U ⇒ V) = no (λ ()) +none ≡ᵀ none = yes refl +none ≡ᵀ any = no (λ ()) +none ≡ᵀ number = no (λ ()) +none ≡ᵀ boolean = no (λ ()) +none ≡ᵀ (U ∪ V) = no (λ ()) +none ≡ᵀ (U ∩ V) = no (λ ()) +any ≡ᵀ nil = no (λ ()) +any ≡ᵀ (U ⇒ V) = no (λ ()) +any ≡ᵀ none = no (λ ()) +any ≡ᵀ any = yes refl +any ≡ᵀ number = no (λ ()) +any ≡ᵀ boolean = no (λ ()) +any ≡ᵀ (U ∪ V) = no (λ ()) +any ≡ᵀ (U ∩ V) = no (λ ()) +number ≡ᵀ nil = no (λ ()) +number ≡ᵀ (T ⇒ U) = no (λ ()) +number ≡ᵀ none = no (λ ()) +number ≡ᵀ any = no (λ ()) +number ≡ᵀ number = yes refl +number ≡ᵀ boolean = no (λ ()) +number ≡ᵀ (T ∪ U) = no (λ ()) +number ≡ᵀ (T ∩ U) = no (λ ()) +boolean ≡ᵀ nil = no (λ ()) +boolean ≡ᵀ (T ⇒ U) = no (λ ()) +boolean ≡ᵀ none = no (λ ()) +boolean ≡ᵀ any = no (λ ()) +boolean ≡ᵀ boolean = yes refl +boolean ≡ᵀ number = no (λ ()) +boolean ≡ᵀ (T ∪ U) = no (λ ()) +boolean ≡ᵀ (T ∩ U) = no (λ ()) +(S ∪ T) ≡ᵀ nil = no (λ ()) +(S ∪ T) ≡ᵀ (U ⇒ V) = no (λ ()) +(S ∪ T) ≡ᵀ none = no (λ ()) +(S ∪ T) ≡ᵀ any = no (λ ()) +(S ∪ T) ≡ᵀ number = no (λ ()) +(S ∪ T) ≡ᵀ boolean = no (λ ()) +(S ∪ T) ≡ᵀ (U ∪ V) with (S ≡ᵀ U) | (T ≡ᵀ V) +(S ∪ T) ≡ᵀ (S ∪ T) | yes refl | yes refl = yes refl +(S ∪ T) ≡ᵀ (U ∪ V) | _ | no p = no (λ q → p (cong rhs q)) +(S ∪ T) ≡ᵀ (U ∪ V) | no p | _ = no (λ q → p (cong lhs q)) +(S ∪ T) ≡ᵀ (U ∩ V) = no (λ ()) +(S ∩ T) ≡ᵀ nil = no (λ ()) +(S ∩ T) ≡ᵀ (U ⇒ V) = no (λ ()) +(S ∩ T) ≡ᵀ none = no (λ ()) +(S ∩ T) ≡ᵀ any = no (λ ()) +(S ∩ T) ≡ᵀ number = no (λ ()) +(S ∩ T) ≡ᵀ boolean = no (λ ()) +(S ∩ T) ≡ᵀ (U ∪ V) = no (λ ()) +(S ∩ T) ≡ᵀ (U ∩ V) with (S ≡ᵀ U) | (T ≡ᵀ V) +(S ∩ T) ≡ᵀ (U ∩ V) | yes refl | yes refl = yes refl +(S ∩ T) ≡ᵀ (U ∩ V) | _ | no p = no (λ q → p (cong rhs q)) +(S ∩ T) ≡ᵀ (U ∩ V) | no p | _ = no (λ q → p (cong lhs q)) + +_≡ᴹᵀ_ : ∀ (T U : Maybe Type) → Dec(T ≡ U) +nothing ≡ᴹᵀ nothing = yes refl +nothing ≡ᴹᵀ just U = no (λ ()) +just T ≡ᴹᵀ nothing = no (λ ()) +just T ≡ᴹᵀ just U with T ≡ᵀ U +(just T ≡ᴹᵀ just T) | yes refl = yes refl +(just T ≡ᴹᵀ just U) | no p = no (λ q → p (just-inv q)) + +data Mode : Set where + strict : Mode + nonstrict : Mode + +src : Mode → Type → Type +src m nil = none +src m number = none +src m boolean = none +src m (S ⇒ T) = S +-- In nonstrict mode, functions are covaraiant, in strict mode they're contravariant +src strict (S ∪ T) = (src strict S) ∩ (src strict T) +src nonstrict (S ∪ T) = (src nonstrict S) ∪ (src nonstrict T) +src strict (S ∩ T) = (src strict S) ∪ (src strict T) +src nonstrict (S ∩ T) = (src nonstrict S) ∩ (src nonstrict T) +src strict none = any +src nonstrict none = none +src strict any = none +src nonstrict any = any tgt : Type → Type tgt nil = none @@ -26,6 +144,7 @@ tgt (S ⇒ T) = T tgt none = none tgt any = any tgt number = none +tgt boolean = none tgt (S ∪ T) = (tgt S) ∪ (tgt T) tgt (S ∩ T) = (tgt S) ∩ (tgt T) diff --git a/prototyping/Luau/Type/FromJSON.agda b/prototyping/Luau/Type/FromJSON.agda index 49f7462..9057fef 100644 --- a/prototyping/Luau/Type/FromJSON.agda +++ b/prototyping/Luau/Type/FromJSON.agda @@ -1,3 +1,5 @@ +{-# OPTIONS --rewriting #-} + module Luau.Type.FromJSON where open import Luau.Type using (Type; nil; _⇒_; _∪_; _∩_; any; number) diff --git a/prototyping/Luau/Type/ToString.agda b/prototyping/Luau/Type/ToString.agda index e7a7c1c..dec7c14 100644 --- a/prototyping/Luau/Type/ToString.agda +++ b/prototyping/Luau/Type/ToString.agda @@ -1,7 +1,7 @@ module Luau.Type.ToString where open import FFI.Data.String using (String; _++_) -open import Luau.Type using (Type; nil; _⇒_; none; any; number; _∪_; _∩_; normalizeOptional) +open import Luau.Type using (Type; nil; _⇒_; none; any; number; boolean; _∪_; _∩_; normalizeOptional) {-# TERMINATING #-} typeToString : Type → String @@ -13,6 +13,7 @@ typeToString (S ⇒ T) = "(" ++ (typeToString S) ++ ") -> " ++ (typeToString T) typeToString none = "none" typeToString any = "any" typeToString number = "number" +typeToString boolean = "boolean" typeToString (S ∪ T) with normalizeOptional(S ∪ T) typeToString (S ∪ T) | ((S′ ⇒ T′) ∪ nil) = "(" ++ typeToString (S′ ⇒ T′) ++ ")?" typeToString (S ∪ T) | (S′ ∪ nil) = typeToString S′ ++ "?" diff --git a/prototyping/Luau/TypeCheck.agda b/prototyping/Luau/TypeCheck.agda new file mode 100644 index 0000000..f240e17 --- /dev/null +++ b/prototyping/Luau/TypeCheck.agda @@ -0,0 +1,143 @@ +{-# OPTIONS --rewriting #-} + +open import Luau.Type using (Mode) + +module Luau.TypeCheck (m : Mode) where + +open import Agda.Builtin.Equality using (_≡_) +open import FFI.Data.Maybe using (Maybe; just) +open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; number; bool; val; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; binexp; local_←_; _∙_; done; return; name; +; -; *; /; <; >; ==; ~=; <=; >=) +open import Luau.Var using (Var) +open import Luau.Addr using (Addr) +open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ) +open import Luau.Type using (Type; Mode; nil; none; number; boolean; _⇒_; tgt) +open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ) +open import FFI.Data.Vector using (Vector) +open import FFI.Data.Maybe using (Maybe; just; nothing) +open import Properties.Product using (_×_; _,_) + +src : Type → Type +src = Luau.Type.src m + +orNone : Maybe Type → Type +orNone nothing = none +orNone (just T) = T + +tgtBinOp : BinaryOperator → Type +tgtBinOp + = number +tgtBinOp - = number +tgtBinOp * = number +tgtBinOp / = number +tgtBinOp < = boolean +tgtBinOp > = boolean +tgtBinOp == = boolean +tgtBinOp ~= = boolean +tgtBinOp <= = boolean +tgtBinOp >= = boolean + +data _⊢ᴮ_∈_ : VarCtxt → Block yes → Type → Set +data _⊢ᴱ_∈_ : VarCtxt → Expr yes → Type → Set + +data _⊢ᴮ_∈_ where + + done : ∀ {Γ} → + + --------------- + Γ ⊢ᴮ done ∈ nil + + return : ∀ {M B T U Γ} → + + Γ ⊢ᴱ M ∈ T → + Γ ⊢ᴮ B ∈ U → + --------------------- + Γ ⊢ᴮ return M ∙ B ∈ T + + local : ∀ {x M B T U V Γ} → + + Γ ⊢ᴱ M ∈ U → + (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V → + -------------------------------- + Γ ⊢ᴮ local var x ∈ T ← M ∙ B ∈ V + + function : ∀ {f x B C T U V W Γ} → + + (Γ ⊕ x ↦ T) ⊢ᴮ C ∈ V → + (Γ ⊕ f ↦ (T ⇒ U)) ⊢ᴮ B ∈ W → + ------------------------------------------------- + Γ ⊢ᴮ function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B ∈ W + +data _⊢ᴱ_∈_ where + + nil : ∀ {Γ} → + + -------------------- + Γ ⊢ᴱ (val nil) ∈ nil + + var : ∀ {x T Γ} → + + T ≡ orNone(Γ [ x ]ⱽ) → + ---------------- + Γ ⊢ᴱ (var x) ∈ T + + addr : ∀ {a Γ} T → + + ----------------- + Γ ⊢ᴱ val(addr a) ∈ T + + number : ∀ {n Γ} → + + --------------------------- + Γ ⊢ᴱ val(number n) ∈ number + + bool : ∀ {b Γ} → + + -------------------------- + Γ ⊢ᴱ val(bool b) ∈ boolean + + app : ∀ {M N T U Γ} → + + Γ ⊢ᴱ M ∈ T → + Γ ⊢ᴱ N ∈ U → + ---------------------- + Γ ⊢ᴱ (M $ N) ∈ (tgt T) + + function : ∀ {f x B T U V Γ} → + + (Γ ⊕ x ↦ T) ⊢ᴮ B ∈ V → + ----------------------------------------------------- + Γ ⊢ᴱ (function f ⟨ var x ∈ T ⟩∈ U is B end) ∈ (T ⇒ U) + + block : ∀ {b B T U Γ} → + + Γ ⊢ᴮ B ∈ U → + ------------------------------------ + Γ ⊢ᴱ (block var b ∈ T is B end) ∈ T + + binexp : ∀ {op Γ M N T U} → + + Γ ⊢ᴱ M ∈ T → + Γ ⊢ᴱ N ∈ U → + ---------------------------------- + Γ ⊢ᴱ (binexp M op N) ∈ tgtBinOp op + +data ⊢ᴼ_ : Maybe(Object yes) → Set where + + nothing : + + --------- + ⊢ᴼ nothing + + function : ∀ {f x T U V B} → + + (x ↦ T) ⊢ᴮ B ∈ V → + ---------------------------------------------- + ⊢ᴼ (just function f ⟨ var x ∈ T ⟩∈ U is B end) + +⊢ᴴ_ : Heap yes → Set +⊢ᴴ H = ∀ a {O} → (H [ a ]ᴴ ≡ O) → (⊢ᴼ O) + +_⊢ᴴᴱ_▷_∈_ : VarCtxt → Heap yes → Expr yes → Type → Set +(Γ ⊢ᴴᴱ H ▷ M ∈ T) = (⊢ᴴ H) × (Γ ⊢ᴱ M ∈ T) + +_⊢ᴴᴮ_▷_∈_ : VarCtxt → Heap yes → Block yes → Type → Set +(Γ ⊢ᴴᴮ H ▷ B ∈ T) = (⊢ᴴ H) × (Γ ⊢ᴮ B ∈ T) diff --git a/prototyping/Luau/Value.agda b/prototyping/Luau/Value.agda deleted file mode 100644 index ed51abd..0000000 --- a/prototyping/Luau/Value.agda +++ /dev/null @@ -1,20 +0,0 @@ -module Luau.Value where - -open import Agda.Builtin.Bool using (Bool; true; false) -open import Agda.Builtin.Float using (Float) -open import Luau.Addr using (Addr) -open import Luau.Syntax using (Block; Expr; nil; addr; number; true; false) -open import Luau.Var using (Var) - -data Value : Set where - nil : Value - addr : Addr → Value - number : Float → Value - bool : Bool → Value - -val : ∀ {a} → Value → Expr a -val nil = nil -val (addr a) = addr a -val (number x) = number x -val (bool false) = false -val (bool true) = true diff --git a/prototyping/Luau/Value/ToString.agda b/prototyping/Luau/Value/ToString.agda deleted file mode 100644 index 3421d4c..0000000 --- a/prototyping/Luau/Value/ToString.agda +++ /dev/null @@ -1,14 +0,0 @@ -module Luau.Value.ToString where - -open import Agda.Builtin.String using (String) -open import Agda.Builtin.Float using (primShowFloat) -open import Agda.Builtin.Bool using (true; false) -open import Luau.Value using (Value; nil; addr; number; bool) -open import Luau.Addr.ToString using (addrToString) - -valueToString : Value → String -valueToString nil = "nil" -valueToString (addr a) = addrToString a -valueToString (number x) = primShowFloat x -valueToString (bool false) = "false" -valueToString (bool true) = "true" diff --git a/prototyping/Luau/Var.agda b/prototyping/Luau/Var.agda index 091ce12..23d8b56 100644 --- a/prototyping/Luau/Var.agda +++ b/prototyping/Luau/Var.agda @@ -4,13 +4,13 @@ open import Agda.Builtin.Bool using (true; false) open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.String using (String; primStringEquality) open import Agda.Builtin.TrustMe using (primTrustMe) -open import Properties.Dec using (Dec; yes; no; ⊥) +open import Properties.Dec using (Dec; yes; no) +open import Properties.Equality using (_≢_) Var : Set Var = String _≡ⱽ_ : (a b : Var) → Dec (a ≡ b) a ≡ⱽ b with primStringEquality a b -a ≡ⱽ b | false = no p where postulate p : (a ≡ b) → ⊥ +a ≡ⱽ b | false = no p where postulate p : (a ≢ b) a ≡ⱽ b | true = yes primTrustMe - diff --git a/prototyping/Luau/VarCtxt.agda b/prototyping/Luau/VarCtxt.agda new file mode 100644 index 0000000..76f64df --- /dev/null +++ b/prototyping/Luau/VarCtxt.agda @@ -0,0 +1,43 @@ +{-# OPTIONS --rewriting #-} + +module Luau.VarCtxt where + +open import Agda.Builtin.Equality using (_≡_) +open import Luau.Type using (Type; _∪_; _∩_) +open import Luau.Var using (Var) +open import FFI.Data.Aeson using (KeyMap; Key; empty; unionWith; singleton; insert; delete; lookup; toString; fromString; lookup-insert; lookup-insert-not; lookup-empty; to-from; insert-swap; insert-over) +open import FFI.Data.Maybe using (Maybe; just; nothing) +open import Properties.Equality using (_≢_; cong; sym; trans) + +VarCtxt : Set +VarCtxt = KeyMap Type + +∅ : VarCtxt +∅ = empty + +_⋒_ : VarCtxt → VarCtxt → VarCtxt +_⋒_ = unionWith _∩_ + +_⋓_ : VarCtxt → VarCtxt → VarCtxt +_⋓_ = unionWith _∪_ + +_[_] : VarCtxt → Var → Maybe Type +Γ [ x ] = lookup (fromString x) Γ + +_⊝_ : VarCtxt → Var → VarCtxt +Γ ⊝ x = delete (fromString x) Γ + +_↦_ : Var → Type → VarCtxt +x ↦ T = singleton (fromString x) T + +_⊕_↦_ : VarCtxt → Var → Type → VarCtxt +Γ ⊕ x ↦ T = insert (fromString x) T Γ + +⊕-over : ∀ {Γ x y T U} → (x ≡ y) → ((Γ ⊕ x ↦ T) ⊕ y ↦ U) ≡ (Γ ⊕ y ↦ U) +⊕-over p = insert-over _ _ _ _ _ (cong fromString (sym p)) + +⊕-swap : ∀ {Γ x y T U} → (x ≢ y) → ((Γ ⊕ x ↦ T) ⊕ y ↦ U) ≡ ((Γ ⊕ y ↦ U) ⊕ x ↦ T) +⊕-swap p = insert-swap _ _ _ _ _ (λ q → p (trans (sym (to-from _)) (trans (cong toString (sym q) ) (to-from _))) ) + +⊕-lookup-miss : ∀ x y T Γ → (x ≢ y) → (Γ [ y ] ≡ (Γ ⊕ x ↦ T) [ y ]) +⊕-lookup-miss x y T Γ p = lookup-insert-not (fromString x) (fromString y) T Γ λ q → p (trans (sym (to-from x)) (trans (cong toString q) (to-from y))) diff --git a/prototyping/PrettyPrinter.agda b/prototyping/PrettyPrinter.agda index fdad32d..3ce0905 100644 --- a/prototyping/PrettyPrinter.agda +++ b/prototyping/PrettyPrinter.agda @@ -1,3 +1,5 @@ +{-# OPTIONS --rewriting #-} + module PrettyPrinter where open import Agda.Builtin.IO using (IO) diff --git a/prototyping/Properties.agda b/prototyping/Properties.agda index 1a6f92f..8b30ce1 100644 --- a/prototyping/Properties.agda +++ b/prototyping/Properties.agda @@ -1,7 +1,11 @@ +{-# OPTIONS --rewriting #-} + module Properties where import Properties.Contradiction import Properties.Dec import Properties.Equality -import Properties.Step import Properties.Remember +import Properties.Step +import Properties.StrictMode +import Properties.TypeCheck diff --git a/prototyping/Properties/Dec.agda b/prototyping/Properties/Dec.agda index d89fd75..c61f236 100644 --- a/prototyping/Properties/Dec.agda +++ b/prototyping/Properties/Dec.agda @@ -1,8 +1,7 @@ module Properties.Dec where -data ⊥ : Set where +open import Properties.Contradiction using (¬) data Dec(A : Set) : Set where yes : A → Dec A - no : (A → ⊥) → Dec A - + no : ¬ A → Dec A diff --git a/prototyping/Properties/Product.agda b/prototyping/Properties/Product.agda new file mode 100644 index 0000000..0d41c81 --- /dev/null +++ b/prototyping/Properties/Product.agda @@ -0,0 +1,14 @@ +module Properties.Product where + +infixr 5 _×_ _,_ + +record Σ {A : Set} (B : A → Set) : Set where + + constructor _,_ + field fst : A + field snd : B fst + +open Σ public + +_×_ : Set → Set → Set +A × B = Σ (λ (a : A) → B) diff --git a/prototyping/Properties/Step.agda b/prototyping/Properties/Step.agda index 69dbac8..0b5fd06 100644 --- a/prototyping/Properties/Step.agda +++ b/prototyping/Properties/Step.agda @@ -1,24 +1,91 @@ +{-# OPTIONS --rewriting #-} + module Properties.Step where open import Agda.Builtin.Equality using (_≡_; refl) -open import Agda.Builtin.Float using (primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv) +open import Agda.Builtin.Float using (primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv; primFloatEquality; primFloatLess) open import Agda.Builtin.Bool using (true; false) open import FFI.Data.Maybe using (just; nothing) open import Luau.Heap using (Heap; _[_]; alloc; ok; function_is_end) -open import Luau.Syntax using (Block; Expr; nil; var; addr; true; false; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg; number; binexp; +; ≅) -open import Luau.OpSem using (_⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁ ; app₂ ; beta; function; block; return; done; local; subst; binOpNumbers; evalNumOp; binOp₁; binOp₂; evalEqOp; evalNeqOp; binOpEquality; binOpInequality) -open import Luau.RuntimeError using (RuntimeErrorᴱ; RuntimeErrorᴮ; TypeMismatch; UnboundVariable; SEGV; app₁; app₂; block; local; return; bin₁; bin₂) -open import Luau.RuntimeType using (function; number) +open import Luau.Syntax using (Block; Expr; nil; var; val; addr; bool; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg; number; binexp; +; -; *; /; <; >; <=; >=; ==; ~=) +open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁ ; app₂ ; beta; function; block; return; done; local; subst; binOp₀; binOp₁; binOp₂; +; -; *; /; <; >; <=; >=; ==; ~=; evalEqOp; evalNeqOp) +open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; block; local; return; bin₁; bin₂; +; -; *; /; <; >; <=; >=) +open import Luau.RuntimeType using (valueType; function; number) open import Luau.Substitution using (_[_/_]ᴮ) -open import Luau.Value using (nil; addr; val; number; bool) open import Properties.Remember using (remember; _,_) +open import Utility.Bool using (not; _or_) + +data BinOpStepResult v op w : Set where + step : ∀ x → (v ⟦ op ⟧ w ⟶ x) → BinOpStepResult v op w + error₁ : BinOpError op (valueType(v)) → BinOpStepResult v op w + error₂ : BinOpError op (valueType(w)) → BinOpStepResult v op w + +binOpStep : ∀ v op w → BinOpStepResult v op w +binOpStep nil + w = error₁ (+ (λ ())) +binOpStep (addr a) + w = error₁ (+ (λ ())) +binOpStep (number m) + nil = error₂ (+ (λ ())) +binOpStep (number m) + (addr a) = error₂ (+ (λ ())) +binOpStep (number m) + (number n) = step (number (primFloatPlus m n)) (+ m n) +binOpStep (number m) + (bool b) = error₂ (+ (λ ())) +binOpStep (bool b) + w = error₁ (+ (λ ())) +binOpStep nil - w = error₁ (- (λ ())) +binOpStep (addr a) - w = error₁ (- (λ ())) +binOpStep (number x) - nil = error₂ (- (λ ())) +binOpStep (number x) - (addr a) = error₂ (- (λ ())) +binOpStep (number x) - (number n) = step (number (primFloatMinus x n)) (- x n) +binOpStep (number x) - (bool b) = error₂ (- (λ ())) +binOpStep (bool b) - w = error₁ (- (λ ())) +binOpStep nil * w = error₁ (* (λ ())) +binOpStep (addr a) * w = error₁ (* (λ ())) +binOpStep (number m) * nil = error₂ (* (λ ())) +binOpStep (number m) * (addr a) = error₂ (* (λ ())) +binOpStep (number m) * (number n) = step (number (primFloatDiv m n)) (* m n) +binOpStep (number m) * (bool b) = error₂ (* (λ ())) +binOpStep (bool b) * w = error₁ (* (λ ())) +binOpStep nil / w = error₁ (/ (λ ())) +binOpStep (addr a) / w = error₁ (/ (λ ())) +binOpStep (number m) / nil = error₂ (/ (λ ())) +binOpStep (number m) / (addr a) = error₂ (/ (λ ())) +binOpStep (number m) / (number n) = step (number (primFloatTimes m n)) (/ m n) +binOpStep (number m) / (bool b) = error₂ (/ (λ ())) +binOpStep (bool b) / w = error₁ (/ (λ ())) +binOpStep nil < w = error₁ (< (λ ())) +binOpStep (addr a) < w = error₁ (< (λ ())) +binOpStep (number m) < nil = error₂ (< (λ ())) +binOpStep (number m) < (addr a) = error₂ (< (λ ())) +binOpStep (number m) < (number n) = step (bool (primFloatLess m n)) (< m n) +binOpStep (number m) < (bool b) = error₂ (< (λ ())) +binOpStep (bool b) < w = error₁ (< (λ ())) +binOpStep nil > w = error₁ (> (λ ())) +binOpStep (addr a) > w = error₁ (> (λ ())) +binOpStep (number m) > nil = error₂ (> (λ ())) +binOpStep (number m) > (addr a) = error₂ (> (λ ())) +binOpStep (number m) > (number n) = step (bool (primFloatLess n m)) (> m n) +binOpStep (number m) > (bool b) = error₂ (> (λ ())) +binOpStep (bool b) > w = error₁ (> (λ ())) +binOpStep v == w = step (bool (evalEqOp v w)) (== v w) +binOpStep v ~= w = step (bool (evalNeqOp v w)) (~= v w) +binOpStep nil <= w = error₁ (<= (λ ())) +binOpStep (addr a) <= w = error₁ (<= (λ ())) +binOpStep (number m) <= nil = error₂ (<= (λ ())) +binOpStep (number m) <= (addr a) = error₂ (<= (λ ())) +binOpStep (number m) <= (number n) = step (bool (primFloatLess m n or primFloatEquality m n)) (<= m n) +binOpStep (number m) <= (bool b) = error₂ (<= (λ ())) +binOpStep (bool b) <= w = error₁ (<= (λ ())) +binOpStep nil >= w = error₁ (>= (λ ())) +binOpStep (addr a) >= w = error₁ (>= (λ ())) +binOpStep (number m) >= nil = error₂ (>= (λ ())) +binOpStep (number m) >= (addr a) = error₂ (>= (λ ())) +binOpStep (number m) >= (number n) = step (bool (primFloatLess n m or primFloatEquality m n)) (>= m n) +binOpStep (number m) >= (bool b) = error₂ (>= (λ ())) +binOpStep (bool b) >= w = error₁ (>= (λ ())) data StepResultᴮ {a} (H : Heap a) (B : Block a) : Set data StepResultᴱ {a} (H : Heap a) (M : Expr a) : Set data StepResultᴮ H B where step : ∀ H′ B′ → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → StepResultᴮ H B - return : ∀ V {B′} → (B ≡ (return (val V) ∙ B′)) → StepResultᴮ H B + return : ∀ v {B′} → (B ≡ (return (val v) ∙ B′)) → StepResultᴮ H B done : (B ≡ done) → StepResultᴮ H B error : (RuntimeErrorᴮ H B) → StepResultᴮ H B @@ -30,54 +97,44 @@ data StepResultᴱ H M where stepᴱ : ∀ {a} H M → StepResultᴱ {a} H M stepᴮ : ∀ {a} H B → StepResultᴮ {a} H B -stepᴱ H nil = value nil refl -stepᴱ H (var x) = error (UnboundVariable x) -stepᴱ H (addr a) = value (addr a) refl -stepᴱ H (number x) = value (number x) refl -stepᴱ H (true) = value (bool true) refl -stepᴱ H (false) = value (bool false) refl +stepᴱ H (val v) = value v refl +stepᴱ H (var x) = error UnboundVariable stepᴱ H (M $ N) with stepᴱ H M stepᴱ H (M $ N) | step H′ M′ D = step H′ (M′ $ N) (app₁ D) -stepᴱ H (_ $ N) | value V refl with stepᴱ H N -stepᴱ H (_ $ N) | value V refl | step H′ N′ s = step H′ (val V $ N′) (app₂ s) -stepᴱ H (_ $ _) | value nil refl | value W refl = error (app₁ (TypeMismatch function nil λ())) -stepᴱ H (_ $ _) | value (number n) refl | value W refl = error (app₁ (TypeMismatch function (number n) λ())) -stepᴱ H (_ $ _) | value (bool x) refl | value W refl = error (app₁ (TypeMismatch function (bool x) λ())) -stepᴱ H (_ $ _) | value (addr a) refl | value W refl with remember (H [ a ]) -stepᴱ H (_ $ _) | value (addr a) refl | value W refl | (nothing , p) = error (app₁ (SEGV a p)) -stepᴱ H (_ $ _) | value (addr a) refl | value W refl | (just(function F is B end) , p) = step H (block fun F is B [ W / name (arg F) ]ᴮ end) (beta p) +stepᴱ H (_ $ N) | value v refl with stepᴱ H N +stepᴱ H (_ $ N) | value v refl | step H′ N′ s = step H′ (val v $ N′) (app₂ v s) +stepᴱ H (_ $ _) | value (addr a) refl | value w refl with remember (H [ a ]) +stepᴱ H (_ $ _) | value (addr a) refl | value w refl | (nothing , p) = error (app₁ (SEGV p)) +stepᴱ H (_ $ _) | value (addr a) refl | value w refl | (just(function F is B end) , p) = step H (block (fun F) is B [ w / name (arg F) ]ᴮ end) (beta function F is B end w refl p) +stepᴱ H (_ $ _) | value nil refl | value w refl = error (FunctionMismatch nil w (λ ())) +stepᴱ H (_ $ _) | value (number m) refl | value w refl = error (FunctionMismatch (number m) w (λ ())) +stepᴱ H (_ $ _) | value (bool b) refl | value w refl = error (FunctionMismatch (bool b) w (λ ())) stepᴱ H (M $ N) | value V p | error E = error (app₂ E) stepᴱ H (M $ N) | error E = error (app₁ E) stepᴱ H (block b is B end) with stepᴮ H B stepᴱ H (block b is B end) | step H′ B′ D = step H′ (block b is B′ end) (block D) -stepᴱ H (block b is (return _ ∙ B′) end) | return V refl = step H (val V) return -stepᴱ H (block b is done end) | done refl = step H nil done -stepᴱ H (block b is B end) | error E = error (block b E) +stepᴱ H (block b is (return _ ∙ B′) end) | return v refl = step H (val v) (return v) +stepᴱ H (block b is done end) | done refl = step H (val nil) done +stepᴱ H (block b is B end) | error E = error (block E) stepᴱ H (function F is C end) with alloc H (function F is C end) -stepᴱ H function F is C end | ok a H′ p = step H′ (addr a) (function p) -stepᴱ H (binexp x op y) with stepᴱ H x -stepᴱ H (binexp x op y) | value x′ refl with stepᴱ H y --- Have to use explicit form for ≡ here because it's a heavily overloaded symbol -stepᴱ H (binexp x Luau.Syntax.≡ y) | value x′ refl | value y′ refl = step H (val (evalEqOp x′ y′)) binOpEquality -stepᴱ H (binexp x ≅ y) | value x′ refl | value y′ refl = step H (val (evalNeqOp x′ y′)) binOpInequality -stepᴱ H (binexp x op y) | value (number x′) refl | value (number y′) refl = step H (val (evalNumOp x′ op y′)) binOpNumbers -stepᴱ H (binexp x op y) | value (number x′) refl | step H′ y′ s = step H′ (binexp (number x′) op y′) (binOp₂ s) -stepᴱ H (binexp x op y) | value (number x′) refl | error E = error (bin₂ E) -stepᴱ H (binexp x op y) | value nil refl | _ = error (bin₁ (TypeMismatch number nil λ())) -stepᴱ H (binexp x op y) | _ | value nil refl = error (bin₂ (TypeMismatch number nil λ())) -stepᴱ H (binexp x op y) | value (addr a) refl | _ = error (bin₁ (TypeMismatch number (addr a) λ())) -stepᴱ H (binexp x op y) | _ | value (addr a) refl = error (bin₂ (TypeMismatch number (addr a) λ())) -stepᴱ H (binexp x op y) | value (bool x′) refl | _ = error (bin₁ (TypeMismatch number (bool x′) λ())) -stepᴱ H (binexp x op y) | _ | value (bool y′) refl = error (bin₂ (TypeMismatch number (bool y′) λ())) -stepᴱ H (binexp x op y) | step H′ x′ s = step H′ (binexp x′ op y) (binOp₁ s) -stepᴱ H (binexp x op y) | error E = error (bin₁ E) +stepᴱ H function F is C end | ok a H′ p = step H′ (val (addr a)) (function a p) +stepᴱ H (binexp M op N) with stepᴱ H M +stepᴱ H (binexp M op N) | step H′ M′ s = step H′ (binexp M′ op N) (binOp₁ s) +stepᴱ H (binexp M op N) | error E = error (bin₁ E) +stepᴱ H (binexp M op N) | value v refl with stepᴱ H N +stepᴱ H (binexp M op N) | value v refl | step H′ N′ s = step H′ (binexp (val v) op N′) (binOp₂ s) +stepᴱ H (binexp M op N) | value v refl | error E = error (bin₂ E) +stepᴱ H (binexp M op N) | value v refl | value w refl with binOpStep v op w +stepᴱ H (binexp M op N) | value v refl | value w refl | step x p = step H (val x) (binOp₀ p) +stepᴱ H (binexp M op N) | value v refl | value w refl | error₁ E = error (BinOpMismatch₁ v w E) +stepᴱ H (binexp M op N) | value v refl | value w refl | error₂ E = error (BinOpMismatch₂ v w E) stepᴮ H (function F is C end ∙ B) with alloc H (function F is C end) -stepᴮ H (function F is C end ∙ B) | ok a H′ p = step H′ (B [ addr a / fun F ]ᴮ) (function p) +stepᴮ H (function F is C end ∙ B) | ok a H′ p = step H′ (B [ addr a / name (fun F) ]ᴮ) (function a p) stepᴮ H (local x ← M ∙ B) with stepᴱ H M stepᴮ H (local x ← M ∙ B) | step H′ M′ D = step H′ (local x ← M′ ∙ B) (local D) -stepᴮ H (local x ← _ ∙ B) | value V refl = step H (B [ V / name x ]ᴮ) subst -stepᴮ H (local x ← M ∙ B) | error E = error (local x E) +stepᴮ H (local x ← _ ∙ B) | value v refl = step H (B [ v / name x ]ᴮ) (subst v) +stepᴮ H (local x ← M ∙ B) | error E = error (local E) stepᴮ H (return M ∙ B) with stepᴱ H M stepᴮ H (return M ∙ B) | step H′ M′ D = step H′ (return M′ ∙ B) (return D) stepᴮ H (return _ ∙ B) | value V refl = return V refl diff --git a/prototyping/Properties/StrictMode.agda b/prototyping/Properties/StrictMode.agda new file mode 100644 index 0000000..0841118 --- /dev/null +++ b/prototyping/Properties/StrictMode.agda @@ -0,0 +1,470 @@ +{-# OPTIONS --rewriting #-} + +module Properties.StrictMode where + +import Agda.Builtin.Equality.Rewrite +open import Agda.Builtin.Equality using (_≡_; refl) +open import FFI.Data.Maybe using (Maybe; just; nothing) +open import Luau.Heap using (Heap; Object; function_is_end; defn; alloc; ok; next; lookup-not-allocated) renaming (_≡_⊕_↦_ to _≡ᴴ_⊕_↦_; _[_] to _[_]ᴴ; ∅ to ∅ᴴ) +open import Luau.StrictMode using (Warningᴱ; Warningᴮ; Warningᴼ; Warningᴴᴱ; Warningᴴᴮ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; app₁; app₂; BinOpWarning; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; BlockMismatch; block₁; return; LocalVarMismatch; local₁; local₂; FunctionDefnMismatch; function₁; function₂; heap; expr; block; addr; +; -; *; /; <; >; <=; >=) +open import Luau.Substitution using (_[_/_]ᴮ; _[_/_]ᴱ; _[_/_]ᴮunless_; var_[_/_]ᴱwhenever_) +open import Luau.Syntax using (Expr; yes; var; val; var_∈_; _⟨_⟩∈_; _$_; addr; number; bool; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name; ==; ~=) +open import Luau.Type using (Type; strict; nil; _⇒_; none; tgt; _≡ᵀ_; _≡ᴹᵀ_) +open import Luau.TypeCheck(strict) using (_⊢ᴮ_∈_; _⊢ᴱ_∈_; _⊢ᴴᴮ_▷_∈_; _⊢ᴴᴱ_▷_∈_; nil; var; addr; app; function; block; done; return; local; orNone; tgtBinOp) +open import Luau.Var using (_≡ⱽ_) +open import Luau.Addr using (_≡ᴬ_) +open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_; ⊕-lookup-miss; ⊕-swap; ⊕-over) renaming (_[_] to _[_]ⱽ) +open import Luau.VarCtxt using (VarCtxt; ∅) +open import Properties.Remember using (remember; _,_) +open import Properties.Equality using (_≢_; sym; cong; trans; subst₁) +open import Properties.Dec using (Dec; yes; no) +open import Properties.Contradiction using (CONTRADICTION) +open import Properties.TypeCheck(strict) using (typeOfᴼ; typeOfᴹᴼ; typeOfⱽ; typeOfᴱ; typeOfᴮ; typeCheckᴱ; typeCheckᴮ; typeCheckᴼ; typeCheckᴴᴱ; typeCheckᴴᴮ; mustBeFunction; mustBeNumber) +open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶*_⊣_; _⊢_⟶ᴮ_⊣_; _⊢_⟶ᴱ_⊣_; app₁; app₂; function; beta; return; block; done; local; subst; binOp₀; binOp₁; binOp₂; refl; step; +; -; *; /; <; >; ==; ~=; <=; >=) +open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; bin₁; bin₂; block; local; return; +; -; *; /; <; >; <=; >=) +open import Luau.RuntimeType using (valueType) + +src = Luau.Type.src strict + +data _⊑_ (H : Heap yes) : Heap yes → Set where + refl : (H ⊑ H) + snoc : ∀ {H′ a V} → (H′ ≡ᴴ H ⊕ a ↦ V) → (H ⊑ H′) + +rednᴱ⊑ : ∀ {H H′ M M′} → (H ⊢ M ⟶ᴱ M′ ⊣ H′) → (H ⊑ H′) +rednᴮ⊑ : ∀ {H H′ B B′} → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → (H ⊑ H′) + +rednᴱ⊑ (function a p) = snoc p +rednᴱ⊑ (app₁ s) = rednᴱ⊑ s +rednᴱ⊑ (app₂ p s) = rednᴱ⊑ s +rednᴱ⊑ (beta O v p q) = refl +rednᴱ⊑ (block s) = rednᴮ⊑ s +rednᴱ⊑ (return v) = refl +rednᴱ⊑ done = refl +rednᴱ⊑ (binOp₀ p) = refl +rednᴱ⊑ (binOp₁ s) = rednᴱ⊑ s +rednᴱ⊑ (binOp₂ s) = rednᴱ⊑ s + +rednᴮ⊑ (local s) = rednᴱ⊑ s +rednᴮ⊑ (subst v) = refl +rednᴮ⊑ (function a p) = snoc p +rednᴮ⊑ (return s) = rednᴱ⊑ s + +data LookupResult (H : Heap yes) a V : Set where + just : (H [ a ]ᴴ ≡ just V) → LookupResult H a V + nothing : (H [ a ]ᴴ ≡ nothing) → LookupResult H a V + +lookup-⊑-nothing : ∀ {H H′} a → (H ⊑ H′) → (H′ [ a ]ᴴ ≡ nothing) → (H [ a ]ᴴ ≡ nothing) +lookup-⊑-nothing {H} a refl p = p +lookup-⊑-nothing {H} a (snoc defn) p with a ≡ᴬ next H +lookup-⊑-nothing {H} a (snoc defn) p | yes refl = refl +lookup-⊑-nothing {H} a (snoc o) p | no q = trans (lookup-not-allocated o q) p + +data OrWarningᴱ {Γ M T} (H : Heap yes) (D : Γ ⊢ᴱ M ∈ T) A : Set where + ok : A → OrWarningᴱ H D A + warning : Warningᴱ H D → OrWarningᴱ H D A + +data OrWarningᴮ {Γ B T} (H : Heap yes) (D : Γ ⊢ᴮ B ∈ T) A : Set where + ok : A → OrWarningᴮ H D A + warning : Warningᴮ H D → OrWarningᴮ H D A + +data OrWarningᴴᴱ {Γ M T} H (D : Γ ⊢ᴴᴱ H ▷ M ∈ T) A : Set where + ok : A → OrWarningᴴᴱ H D A + warning : Warningᴴᴱ H D → OrWarningᴴᴱ H D A + +data OrWarningᴴᴮ {Γ B T} H (D : Γ ⊢ᴴᴮ H ▷ B ∈ T) A : Set where + ok : A → OrWarningᴴᴮ H D A + warning : Warningᴴᴮ H D → OrWarningᴴᴮ H D A + +heap-weakeningᴱ : ∀ H M {H′ Γ} → (H ⊑ H′) → OrWarningᴱ H (typeCheckᴱ H Γ M) (typeOfᴱ H Γ M ≡ typeOfᴱ H′ Γ M) +heap-weakeningᴮ : ∀ H B {H′ Γ} → (H ⊑ H′) → OrWarningᴮ H (typeCheckᴮ H Γ B) (typeOfᴮ H Γ B ≡ typeOfᴮ H′ Γ B) + +heap-weakeningᴱ H (var x) h = ok refl +heap-weakeningᴱ H (val nil) h = ok refl +heap-weakeningᴱ H (val (addr a)) refl = ok refl +heap-weakeningᴱ H (val (addr a)) (snoc {a = b} defn) with a ≡ᴬ b +heap-weakeningᴱ H (val (addr a)) (snoc {a = a} defn) | yes refl = warning (UnallocatedAddress refl) +heap-weakeningᴱ H (val (addr a)) (snoc {a = b} p) | no q = ok (cong orNone (cong typeOfᴹᴼ (lookup-not-allocated p q))) +heap-weakeningᴱ H (val (number n)) h = ok refl +heap-weakeningᴱ H (val (bool b)) h = ok refl +heap-weakeningᴱ H (binexp M op N) h = ok refl +heap-weakeningᴱ H (M $ N) h with heap-weakeningᴱ H M h +heap-weakeningᴱ H (M $ N) h | ok p = ok (cong tgt p) +heap-weakeningᴱ H (M $ N) h | warning W = warning (app₁ W) +heap-weakeningᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) h = ok refl +heap-weakeningᴱ H (block var b ∈ T is B end) h = ok refl +heap-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h with heap-weakeningᴮ H B h +heap-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h | ok p = ok p +heap-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h | warning W = warning (function₂ W) +heap-weakeningᴮ H (local var x ∈ T ← M ∙ B) h with heap-weakeningᴮ H B h +heap-weakeningᴮ H (local var x ∈ T ← M ∙ B) h | ok p = ok p +heap-weakeningᴮ H (local var x ∈ T ← M ∙ B) h | warning W = warning (local₂ W) +heap-weakeningᴮ H (return M ∙ B) h with heap-weakeningᴱ H M h +heap-weakeningᴮ H (return M ∙ B) h | ok p = ok p +heap-weakeningᴮ H (return M ∙ B) h | warning W = warning (return W) +heap-weakeningᴮ H (done) h = ok refl + +none-not-obj : ∀ O → none ≢ typeOfᴼ O +none-not-obj (function f ⟨ var x ∈ T ⟩∈ U is B end) () + +typeOf-val-not-none : ∀ {H Γ} v → OrWarningᴱ H (typeCheckᴱ H Γ (val v)) (none ≢ typeOfᴱ H Γ (val v)) +typeOf-val-not-none nil = ok (λ ()) +typeOf-val-not-none (number n) = ok (λ ()) +typeOf-val-not-none (bool b) = ok (λ ()) +typeOf-val-not-none {H = H} (addr a) with remember (H [ a ]ᴴ) +typeOf-val-not-none {H = H} (addr a) | (just O , p) = ok (λ q → none-not-obj O (trans q (cong orNone (cong typeOfᴹᴼ p)))) +typeOf-val-not-none {H = H} (addr a) | (nothing , p) = warning (UnallocatedAddress p) + +substitutivityᴱ : ∀ {Γ T} H M v x → (just T ≡ typeOfⱽ H v) → (typeOfᴱ H (Γ ⊕ x ↦ T) M ≡ typeOfᴱ H Γ (M [ v / x ]ᴱ)) +substitutivityᴱ-whenever-yes : ∀ {Γ T} H v x y (p : x ≡ y) → (just T ≡ typeOfⱽ H v) → (typeOfᴱ H (Γ ⊕ x ↦ T) (var y) ≡ typeOfᴱ H Γ (var y [ v / x ]ᴱwhenever (yes p))) +substitutivityᴱ-whenever-no : ∀ {Γ T} H v x y (p : x ≢ y) → (just T ≡ typeOfⱽ H v) → (typeOfᴱ H (Γ ⊕ x ↦ T) (var y) ≡ typeOfᴱ H Γ (var y [ v / x ]ᴱwhenever (no p))) +substitutivityᴮ : ∀ {Γ T} H B v x → (just T ≡ typeOfⱽ H v) → (typeOfᴮ H (Γ ⊕ x ↦ T) B ≡ typeOfᴮ H Γ (B [ v / x ]ᴮ)) +substitutivityᴮ-unless-yes : ∀ {Γ Γ′ T} H B v x y (p : x ≡ y) → (just T ≡ typeOfⱽ H v) → (Γ′ ≡ Γ) → (typeOfᴮ H Γ′ B ≡ typeOfᴮ H Γ (B [ v / x ]ᴮunless (yes p))) +substitutivityᴮ-unless-no : ∀ {Γ Γ′ T} H B v x y (p : x ≢ y) → (just T ≡ typeOfⱽ H v) → (Γ′ ≡ Γ ⊕ x ↦ T) → (typeOfᴮ H Γ′ B ≡ typeOfᴮ H Γ (B [ v / x ]ᴮunless (no p))) + +substitutivityᴱ H (var y) v x p with x ≡ⱽ y +substitutivityᴱ H (var y) v x p | yes q = substitutivityᴱ-whenever-yes H v x y q p +substitutivityᴱ H (var y) v x p | no q = substitutivityᴱ-whenever-no H v x y q p +substitutivityᴱ H (val w) v x p = refl +substitutivityᴱ H (binexp M op N) v x p = refl +substitutivityᴱ H (M $ N) v x p = cong tgt (substitutivityᴱ H M v x p) +substitutivityᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p = refl +substitutivityᴱ H (block var b ∈ T is B end) v x p = refl +substitutivityᴱ-whenever-yes H v x x refl q = cong orNone q +substitutivityᴱ-whenever-no H v x y p q = cong orNone ( sym (⊕-lookup-miss x y _ _ p)) +substitutivityᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p with x ≡ⱽ f +substitutivityᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p | yes q = substitutivityᴮ-unless-yes H B v x f q p (⊕-over q) +substitutivityᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p | no q = substitutivityᴮ-unless-no H B v x f q p (⊕-swap q) +substitutivityᴮ H (local var y ∈ T ← M ∙ B) v x p with x ≡ⱽ y +substitutivityᴮ H (local var y ∈ T ← M ∙ B) v x p | yes q = substitutivityᴮ-unless-yes H B v x y q p (⊕-over q) +substitutivityᴮ H (local var y ∈ T ← M ∙ B) v x p | no q = substitutivityᴮ-unless-no H B v x y q p (⊕-swap q) +substitutivityᴮ H (return M ∙ B) v x p = substitutivityᴱ H M v x p +substitutivityᴮ H done v x p = refl +substitutivityᴮ-unless-yes H B v x x refl q refl = refl +substitutivityᴮ-unless-no H B v x y p q refl = substitutivityᴮ H B v x q + +binOpPreservation : ∀ H {op v w x} → (v ⟦ op ⟧ w ⟶ x) → (tgtBinOp op ≡ typeOfᴱ H ∅ (val x)) +binOpPreservation H (+ m n) = refl +binOpPreservation H (- m n) = refl +binOpPreservation H (/ m n) = refl +binOpPreservation H (* m n) = refl +binOpPreservation H (< m n) = refl +binOpPreservation H (> m n) = refl +binOpPreservation H (<= m n) = refl +binOpPreservation H (>= m n) = refl +binOpPreservation H (== v w) = refl +binOpPreservation H (~= v w) = refl + +preservationᴱ : ∀ H M {H′ M′} → (H ⊢ M ⟶ᴱ M′ ⊣ H′) → OrWarningᴴᴱ H (typeCheckᴴᴱ H ∅ M) (typeOfᴱ H ∅ M ≡ typeOfᴱ H′ ∅ M′) +preservationᴮ : ∀ H B {H′ B′} → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → OrWarningᴴᴮ H (typeCheckᴴᴮ H ∅ B) (typeOfᴮ H ∅ B ≡ typeOfᴮ H′ ∅ B′) + +preservationᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) = ok refl +preservationᴱ H (M $ N) (app₁ s) with preservationᴱ H M s +preservationᴱ H (M $ N) (app₁ s) | ok p = ok (cong tgt p) +preservationᴱ H (M $ N) (app₁ s) | warning (expr W) = warning (expr (app₁ W)) +preservationᴱ H (M $ N) (app₁ s) | warning (heap W) = warning (heap W) +preservationᴱ H (M $ N) (app₂ p s) with heap-weakeningᴱ H M (rednᴱ⊑ s) +preservationᴱ H (M $ N) (app₂ p s) | ok q = ok (cong tgt q) +preservationᴱ H (M $ N) (app₂ p s) | warning W = warning (expr (app₁ W)) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) with remember (typeOfⱽ H v) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (just U , q) with S ≡ᵀ U | T ≡ᵀ typeOfᴮ H (x ↦ S) B +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (just U , q) | yes refl | yes refl = ok (cong tgt (cong orNone (cong typeOfᴹᴼ p))) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (just U , q) | yes refl | no r = warning (heap (addr a p (FunctionDefnMismatch r))) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (just U , q) | no r | _ = warning (expr (FunctionCallMismatch (λ s → r (trans (trans (sym (cong src (cong orNone (cong typeOfᴹᴼ p)))) s) (cong orNone q))))) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (nothing , q) with typeOf-val-not-none v +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (nothing , q) | ok r = CONTRADICTION (r (sym (cong orNone q))) +preservationᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ S ⟩∈ T is B end) v refl p) | (nothing , q) | warning W = warning (expr (app₂ W)) +preservationᴱ H (block var b ∈ T is B end) (block s) = ok refl +preservationᴱ H (block var b ∈ T is return M ∙ B end) (return v) with T ≡ᵀ typeOfᴱ H ∅ (val v) +preservationᴱ H (block var b ∈ T is return M ∙ B end) (return v) | yes p = ok p +preservationᴱ H (block var b ∈ T is return M ∙ B end) (return v) | no p = warning (expr (BlockMismatch p)) +preservationᴱ H (block var b ∈ T is done end) (done) with T ≡ᵀ nil +preservationᴱ H (block var b ∈ T is done end) (done) | yes p = ok p +preservationᴱ H (block var b ∈ T is done end) (done) | no p = warning (expr (BlockMismatch p)) +preservationᴱ H (binexp M op N) (binOp₀ s) = ok (binOpPreservation H s) +preservationᴱ H (binexp M op N) (binOp₁ s) = ok refl +preservationᴱ H (binexp M op N) (binOp₂ s) = ok refl + +preservationᴮ H (local var x ∈ T ← M ∙ B) (local s) with heap-weakeningᴮ H B (rednᴱ⊑ s) +preservationᴮ H (local var x ∈ T ← M ∙ B) (local s) | ok p = ok p +preservationᴮ H (local var x ∈ T ← M ∙ B) (local s) | warning W = warning (block (local₂ W)) +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) with remember (typeOfⱽ H v) +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (just U , p) with T ≡ᵀ U +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (just T , p) | yes refl = ok (substitutivityᴮ H B v x (sym p)) +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (just U , p) | no q = warning (block (LocalVarMismatch (λ r → q (trans r (cong orNone p))))) +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (nothing , p) with typeOf-val-not-none v +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (nothing , p) | ok q = CONTRADICTION (q (sym (cong orNone p))) +preservationᴮ H (local var x ∈ T ← M ∙ B) (subst v) | (nothing , p) | warning W = warning (block (local₁ W)) +preservationᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (function a defn) with heap-weakeningᴮ H B (snoc defn) +preservationᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (function a defn) | ok r = ok (trans r (substitutivityᴮ _ B (addr a) f refl)) +preservationᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) (function a defn) | warning W = warning (block (function₂ W)) +preservationᴮ H (return M ∙ B) (return s) with preservationᴱ H M s +preservationᴮ H (return M ∙ B) (return s) | ok p = ok p +preservationᴮ H (return M ∙ B) (return s) | warning (expr W) = warning (block (return W)) +preservationᴮ H (return M ∙ B) (return s) | warning (heap W) = warning (heap W) + +reflect-substitutionᴱ : ∀ {Γ T} H M v x → (just T ≡ typeOfⱽ H v) → Warningᴱ H (typeCheckᴱ H Γ (M [ v / x ]ᴱ)) → Warningᴱ H (typeCheckᴱ H (Γ ⊕ x ↦ T) M) +reflect-substitutionᴱ-whenever-yes : ∀ {Γ T} H v x y (p : x ≡ y) → (just T ≡ typeOfⱽ H v) → Warningᴱ H (typeCheckᴱ H Γ (var y [ v / x ]ᴱwhenever yes p)) → Warningᴱ H (typeCheckᴱ H (Γ ⊕ x ↦ T) (var y)) +reflect-substitutionᴱ-whenever-no : ∀ {Γ T} H v x y (p : x ≢ y) → (just T ≡ typeOfⱽ H v) → Warningᴱ H (typeCheckᴱ H Γ (var y [ v / x ]ᴱwhenever no p)) → Warningᴱ H (typeCheckᴱ H (Γ ⊕ x ↦ T) (var y)) +reflect-substitutionᴮ : ∀ {Γ T} H B v x → (just T ≡ typeOfⱽ H v) → Warningᴮ H (typeCheckᴮ H Γ (B [ v / x ]ᴮ)) → Warningᴮ H (typeCheckᴮ H (Γ ⊕ x ↦ T) B) +reflect-substitutionᴮ-unless-yes : ∀ {Γ Γ′ T} H B v x y (r : x ≡ y) → (just T ≡ typeOfⱽ H v) → (Γ′ ≡ Γ) → Warningᴮ H (typeCheckᴮ H Γ (B [ v / x ]ᴮunless yes r)) → Warningᴮ H (typeCheckᴮ H Γ′ B) +reflect-substitutionᴮ-unless-no : ∀ {Γ Γ′ T} H B v x y (r : x ≢ y) → (just T ≡ typeOfⱽ H v) → (Γ′ ≡ Γ ⊕ x ↦ T) → Warningᴮ H (typeCheckᴮ H Γ (B [ v / x ]ᴮunless no r)) → Warningᴮ H (typeCheckᴮ H Γ′ B) + +reflect-substitutionᴱ H (var y) v x p W with x ≡ⱽ y +reflect-substitutionᴱ H (var y) v x p W | yes r = reflect-substitutionᴱ-whenever-yes H v x y r p W +reflect-substitutionᴱ H (var y) v x p W | no r = reflect-substitutionᴱ-whenever-no H v x y r p W +reflect-substitutionᴱ H (val (addr a)) v x p (UnallocatedAddress r) = UnallocatedAddress r +reflect-substitutionᴱ H (M $ N) v x p (FunctionCallMismatch q) = FunctionCallMismatch (λ s → q (trans (cong src (sym (substitutivityᴱ H M v x p))) (trans s (substitutivityᴱ H N v x p)))) +reflect-substitutionᴱ H (M $ N) v x p (app₁ W) = app₁ (reflect-substitutionᴱ H M v x p W) +reflect-substitutionᴱ H (M $ N) v x p (app₂ W) = app₂ (reflect-substitutionᴱ H N v x p W) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (FunctionDefnMismatch q) with (x ≡ⱽ y) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (FunctionDefnMismatch q) | yes r = FunctionDefnMismatch (λ s → q (trans s (substitutivityᴮ-unless-yes H B v x y r p (⊕-over r)))) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (FunctionDefnMismatch q) | no r = FunctionDefnMismatch (λ s → q (trans s (substitutivityᴮ-unless-no H B v x y r p (⊕-swap r)))) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (function₁ W) with (x ≡ⱽ y) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (function₁ W) | yes r = function₁ (reflect-substitutionᴮ-unless-yes H B v x y r p (⊕-over r) W) +reflect-substitutionᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) v x p (function₁ W) | no r = function₁ (reflect-substitutionᴮ-unless-no H B v x y r p (⊕-swap r) W) +reflect-substitutionᴱ H (block var b ∈ T is B end) v x p (BlockMismatch q) = BlockMismatch (λ r → q (trans r (substitutivityᴮ H B v x p))) +reflect-substitutionᴱ H (block var b ∈ T is B end) v x p (block₁ W) = block₁ (reflect-substitutionᴮ H B v x p W) +reflect-substitutionᴱ H (binexp M op N) x v p (BinOpMismatch₁ q) = BinOpMismatch₁ (subst₁ (BinOpWarning op) (sym (substitutivityᴱ H M x v p)) q) +reflect-substitutionᴱ H (binexp M op N) x v p (BinOpMismatch₂ q) = BinOpMismatch₂ (subst₁ (BinOpWarning op) (sym (substitutivityᴱ H N x v p)) q) +reflect-substitutionᴱ H (binexp M op N) x v p (bin₁ W) = bin₁ (reflect-substitutionᴱ H M x v p W) +reflect-substitutionᴱ H (binexp M op N) x v p (bin₂ W) = bin₂ (reflect-substitutionᴱ H N x v p W) + +reflect-substitutionᴱ-whenever-no H v x y p q (UnboundVariable r) = UnboundVariable (trans (sym (⊕-lookup-miss x y _ _ p)) r) +reflect-substitutionᴱ-whenever-yes H (addr a) x x refl p (UnallocatedAddress q) with trans p (cong typeOfᴹᴼ q) +reflect-substitutionᴱ-whenever-yes H (addr a) x x refl p (UnallocatedAddress q) | () + +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (FunctionDefnMismatch q) with (x ≡ⱽ y) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (FunctionDefnMismatch q) | yes r = FunctionDefnMismatch (λ s → q (trans s (substitutivityᴮ-unless-yes H C v x y r p (⊕-over r)))) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (FunctionDefnMismatch q) | no r = FunctionDefnMismatch (λ s → q (trans s (substitutivityᴮ-unless-no H C v x y r p (⊕-swap r)))) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₁ W) with (x ≡ⱽ y) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₁ W) | yes r = function₁ (reflect-substitutionᴮ-unless-yes H C v x y r p (⊕-over r) W) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₁ W) | no r = function₁ (reflect-substitutionᴮ-unless-no H C v x y r p (⊕-swap r) W) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₂ W) with (x ≡ⱽ f) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₂ W)| yes r = function₂ (reflect-substitutionᴮ-unless-yes H B v x f r p (⊕-over r) W) +reflect-substitutionᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) v x p (function₂ W)| no r = function₂ (reflect-substitutionᴮ-unless-no H B v x f r p (⊕-swap r) W) +reflect-substitutionᴮ H (local var y ∈ T ← M ∙ B) v x p (LocalVarMismatch q) = LocalVarMismatch (λ r → q (trans r (substitutivityᴱ H M v x p))) +reflect-substitutionᴮ H (local var y ∈ T ← M ∙ B) v x p (local₁ W) = local₁ (reflect-substitutionᴱ H M v x p W) +reflect-substitutionᴮ H (local var y ∈ T ← M ∙ B) v x p (local₂ W) with (x ≡ⱽ y) +reflect-substitutionᴮ H (local var y ∈ T ← M ∙ B) v x p (local₂ W) | yes r = local₂ (reflect-substitutionᴮ-unless-yes H B v x y r p (⊕-over r) W) +reflect-substitutionᴮ H (local var y ∈ T ← M ∙ B) v x p (local₂ W) | no r = local₂ (reflect-substitutionᴮ-unless-no H B v x y r p (⊕-swap r) W) +reflect-substitutionᴮ H (return M ∙ B) v x p (return W) = return (reflect-substitutionᴱ H M v x p W) + +reflect-substitutionᴮ-unless-yes H B v x y r p refl W = W +reflect-substitutionᴮ-unless-no H B v x y r p refl W = reflect-substitutionᴮ H B v x p W + +reflect-weakeningᴱ : ∀ H M {H′ Γ} → (H ⊑ H′) → Warningᴱ H′ (typeCheckᴱ H′ Γ M) → Warningᴱ H (typeCheckᴱ H Γ M) +reflect-weakeningᴮ : ∀ H B {H′ Γ} → (H ⊑ H′) → Warningᴮ H′ (typeCheckᴮ H′ Γ B) → Warningᴮ H (typeCheckᴮ H Γ B) + +reflect-weakeningᴱ H (var x) h (UnboundVariable p) = (UnboundVariable p) +reflect-weakeningᴱ H (val (addr a)) h (UnallocatedAddress p) = UnallocatedAddress (lookup-⊑-nothing a h p) +reflect-weakeningᴱ H (M $ N) h (FunctionCallMismatch p) with heap-weakeningᴱ H M h | heap-weakeningᴱ H N h +reflect-weakeningᴱ H (M $ N) h (FunctionCallMismatch p) | ok q₁ | ok q₂ = FunctionCallMismatch (λ r → p (trans (cong src (sym q₁)) (trans r q₂))) +reflect-weakeningᴱ H (M $ N) h (FunctionCallMismatch p) | warning W | _ = app₁ W +reflect-weakeningᴱ H (M $ N) h (FunctionCallMismatch p) | _ | warning W = app₂ W +reflect-weakeningᴱ H (M $ N) h (app₁ W) = app₁ (reflect-weakeningᴱ H M h W) +reflect-weakeningᴱ H (M $ N) h (app₂ W) = app₂ (reflect-weakeningᴱ H N h W) +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₁ p) with heap-weakeningᴱ H M h +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₁ p) | ok q = BinOpMismatch₁ (subst₁ (BinOpWarning op) (sym q) p) +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₁ p) | warning W = bin₁ W +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₂ p) with heap-weakeningᴱ H N h +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₂ p) | ok q = BinOpMismatch₂ (subst₁ (BinOpWarning op) (sym q) p) +reflect-weakeningᴱ H (binexp M op N) h (BinOpMismatch₂ p) | warning W = bin₂ W +reflect-weakeningᴱ H (binexp M op N) h (bin₁ W′) = bin₁ (reflect-weakeningᴱ H M h W′) +reflect-weakeningᴱ H (binexp M op N) h (bin₂ W′) = bin₂ (reflect-weakeningᴱ H N h W′) +reflect-weakeningᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) h (FunctionDefnMismatch p) with heap-weakeningᴮ H B h +reflect-weakeningᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) h (FunctionDefnMismatch p) | ok q = FunctionDefnMismatch (λ r → p (trans r q)) +reflect-weakeningᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) h (FunctionDefnMismatch p) | warning W = function₁ W +reflect-weakeningᴱ H (function f ⟨ var y ∈ T ⟩∈ U is B end) h (function₁ W) = function₁ (reflect-weakeningᴮ H B h W) +reflect-weakeningᴱ H (block var b ∈ T is B end) h (BlockMismatch p) with heap-weakeningᴮ H B h +reflect-weakeningᴱ H (block var b ∈ T is B end) h (BlockMismatch p) | ok q = BlockMismatch (λ r → p (trans r q)) +reflect-weakeningᴱ H (block var b ∈ T is B end) h (BlockMismatch p) | warning W = block₁ W +reflect-weakeningᴱ H (block var b ∈ T is B end) h (block₁ W) = block₁ (reflect-weakeningᴮ H B h W) + +reflect-weakeningᴮ H (return M ∙ B) h (return W) = return (reflect-weakeningᴱ H M h W) +reflect-weakeningᴮ H (local var y ∈ T ← M ∙ B) h (LocalVarMismatch p) with heap-weakeningᴱ H M h +reflect-weakeningᴮ H (local var y ∈ T ← M ∙ B) h (LocalVarMismatch p) | ok q = LocalVarMismatch (λ r → p (trans r q)) +reflect-weakeningᴮ H (local var y ∈ T ← M ∙ B) h (LocalVarMismatch p) | warning W = local₁ W +reflect-weakeningᴮ H (local var y ∈ T ← M ∙ B) h (local₁ W) = local₁ (reflect-weakeningᴱ H M h W) +reflect-weakeningᴮ H (local var y ∈ T ← M ∙ B) h (local₂ W) = local₂ (reflect-weakeningᴮ H B h W) +reflect-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h (FunctionDefnMismatch p) with heap-weakeningᴮ H C h +reflect-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h (FunctionDefnMismatch p) | ok q = FunctionDefnMismatch (λ r → p (trans r q)) +reflect-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h (FunctionDefnMismatch p) | warning W = function₁ W +reflect-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h (function₁ W) = function₁ (reflect-weakeningᴮ H C h W) +reflect-weakeningᴮ H (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) h (function₂ W) = function₂ (reflect-weakeningᴮ H B h W) + +reflect-weakeningᴼ : ∀ H O {H′} → (H ⊑ H′) → Warningᴼ H′ (typeCheckᴼ H′ O) → Warningᴼ H (typeCheckᴼ H O) +reflect-weakeningᴼ H (just (function f ⟨ var x ∈ T ⟩∈ U is B end)) h (FunctionDefnMismatch p) with heap-weakeningᴮ H B h +reflect-weakeningᴼ H (just (function f ⟨ var x ∈ T ⟩∈ U is B end)) h (FunctionDefnMismatch p) | ok q = FunctionDefnMismatch (λ r → p (trans r q)) +reflect-weakeningᴼ H (just (function f ⟨ var x ∈ T ⟩∈ U is B end)) h (FunctionDefnMismatch p) | warning W = function₁ W +reflect-weakeningᴼ H (just (function f ⟨ var x ∈ T ⟩∈ U is B end)) h (function₁ W′) = function₁ (reflect-weakeningᴮ H B h W′) + +reflectᴱ : ∀ H M {H′ M′} → (H ⊢ M ⟶ᴱ M′ ⊣ H′) → Warningᴱ H′ (typeCheckᴱ H′ ∅ M′) → Warningᴴᴱ H (typeCheckᴴᴱ H ∅ M) +reflectᴮ : ∀ H B {H′ B′} → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → Warningᴮ H′ (typeCheckᴮ H′ ∅ B′) → Warningᴴᴮ H (typeCheckᴴᴮ H ∅ B) + +reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) with preservationᴱ H M s | heap-weakeningᴱ H N (rednᴱ⊑ s) +reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) | ok q | ok q′ = expr (FunctionCallMismatch (λ r → p (trans (trans (cong src (sym q)) r) q′))) +reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) | warning (expr W) | _ = expr (app₁ W) +reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) | warning (heap W) | _ = heap W +reflectᴱ H (M $ N) (app₁ s) (FunctionCallMismatch p) | _ | warning W = expr (app₂ W) +reflectᴱ H (M $ N) (app₁ s) (app₁ W′) with reflectᴱ H M s W′ +reflectᴱ H (M $ N) (app₁ s) (app₁ W′) | heap W = heap W +reflectᴱ H (M $ N) (app₁ s) (app₁ W′) | expr W = expr (app₁ W) +reflectᴱ H (M $ N) (app₁ s) (app₂ W′) = expr (app₂ (reflect-weakeningᴱ H N (rednᴱ⊑ s) W′)) +reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch p′) with heap-weakeningᴱ H (val p) (rednᴱ⊑ s) | preservationᴱ H N s +reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch p′) | ok q | ok q′ = expr (FunctionCallMismatch (λ r → p′ (trans (trans (cong src (sym q)) r) q′))) +reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch p′) | warning W | _ = expr (app₁ W) +reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch p′) | _ | warning (expr W) = expr (app₂ W) +reflectᴱ H (M $ N) (app₂ p s) (FunctionCallMismatch p′) | _ | warning (heap W) = heap W +reflectᴱ H (M $ N) (app₂ p s) (app₁ W′) = expr (app₁ (reflect-weakeningᴱ H M (rednᴱ⊑ s) W′)) +reflectᴱ H (M $ N) (app₂ p s) (app₂ W′) with reflectᴱ H N s W′ +reflectᴱ H (M $ N) (app₂ p s) (app₂ W′) | heap W = heap W +reflectᴱ H (M $ N) (app₂ p s) (app₂ W′) | expr W = expr (app₂ W) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) with remember (typeOfⱽ H v) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (just S , r) with S ≡ᵀ T +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (just T , r) | yes refl = heap (addr a p (FunctionDefnMismatch (λ s → q (trans s (substitutivityᴮ H B v x (sym r)))))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (just S , r) | no s = expr (FunctionCallMismatch (λ t → s (trans (cong orNone (sym r)) (trans (sym t) (cong src (cong orNone (cong typeOfᴹᴼ p))))))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (nothing , r) with typeOf-val-not-none v +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (nothing , r) | ok s = CONTRADICTION (s (cong orNone (sym r))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (BlockMismatch q) | (nothing , r) | warning W = expr (app₂ W) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) with remember (typeOfⱽ H v) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (just S , q) with S ≡ᵀ T +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (just T , q) | yes refl = heap (addr a p (function₁ (reflect-substitutionᴮ H B v x (sym q) W′))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (just S , q) | no r = expr (FunctionCallMismatch (λ s → r (trans (cong orNone (sym q)) (trans (sym s) (cong src (cong orNone (cong typeOfᴹᴼ p))))))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (nothing , q) with typeOf-val-not-none v +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (nothing , q) | ok r = CONTRADICTION (r (cong orNone (sym q))) +reflectᴱ H (val (addr a) $ N) (beta (function f ⟨ var x ∈ T ⟩∈ U is B end) v refl p) (block₁ W′) | (nothing , q) | warning W = expr (app₂ W) +reflectᴱ H (block var b ∈ T is B end) (block s) (BlockMismatch p) with preservationᴮ H B s +reflectᴱ H (block var b ∈ T is B end) (block s) (BlockMismatch p) | ok q = expr (BlockMismatch (λ r → p (trans r q))) +reflectᴱ H (block var b ∈ T is B end) (block s) (BlockMismatch p) | warning (heap W) = heap W +reflectᴱ H (block var b ∈ T is B end) (block s) (BlockMismatch p) | warning (block W) = expr (block₁ W) +reflectᴱ H (block var b ∈ T is B end) (block s) (block₁ W′) with reflectᴮ H B s W′ +reflectᴱ H (block var b ∈ T is B end) (block s) (block₁ W′) | heap W = heap W +reflectᴱ H (block var b ∈ T is B end) (block s) (block₁ W′) | block W = expr (block₁ W) +reflectᴱ H (block var b ∈ T is B end) (return v) W′ = expr (block₁ (return W′)) +reflectᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) (UnallocatedAddress ()) +reflectᴱ H (binexp M op N) (binOp₀ ()) (UnallocatedAddress p) +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) with preservationᴱ H M s +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) | ok q = expr (BinOpMismatch₁ (subst₁ (BinOpWarning op) (sym q) p)) +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) | warning (heap W) = heap W +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₁ p) | warning (expr W) = expr (bin₁ W) +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₂ p) with heap-weakeningᴱ H N (rednᴱ⊑ s) +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₂ p) | ok q = expr (BinOpMismatch₂ ((subst₁ (BinOpWarning op) (sym q) p))) +reflectᴱ H (binexp M op N) (binOp₁ s) (BinOpMismatch₂ p) | warning W = expr (bin₂ W) +reflectᴱ H (binexp M op N) (binOp₁ s) (bin₁ W′) with reflectᴱ H M s W′ +reflectᴱ H (binexp M op N) (binOp₁ s) (bin₁ W′) | heap W = heap W +reflectᴱ H (binexp M op N) (binOp₁ s) (bin₁ W′) | expr W = expr (bin₁ W) +reflectᴱ H (binexp M op N) (binOp₁ s) (bin₂ W′) = expr (bin₂ (reflect-weakeningᴱ H N (rednᴱ⊑ s) W′)) +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₁ p) with heap-weakeningᴱ H M (rednᴱ⊑ s) +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₁ p) | ok q = expr (BinOpMismatch₁ (subst₁ (BinOpWarning op) (sym q) p)) +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₁ p) | warning W = expr (bin₁ W) +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) with preservationᴱ H N s +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) | ok q = expr (BinOpMismatch₂ (subst₁ (BinOpWarning op) (sym q) p)) +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) | warning (heap W) = heap W +reflectᴱ H (binexp M op N) (binOp₂ s) (BinOpMismatch₂ p) | warning (expr W) = expr (bin₂ W) +reflectᴱ H (binexp M op N) (binOp₂ s) (bin₁ W′) = expr (bin₁ (reflect-weakeningᴱ H M (rednᴱ⊑ s) W′)) +reflectᴱ H (binexp M op N) (binOp₂ s) (bin₂ W′) with reflectᴱ H N s W′ +reflectᴱ H (binexp M op N) (binOp₂ s) (bin₂ W′) | heap W = heap W +reflectᴱ H (binexp M op N) (binOp₂ s) (bin₂ W′) | expr W = expr (bin₂ W) + +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (LocalVarMismatch p) with preservationᴱ H M s +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (LocalVarMismatch p) | ok q = block (LocalVarMismatch (λ r → p (trans r q))) +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (LocalVarMismatch p) | warning (expr W) = block (local₁ W) +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (LocalVarMismatch p) | warning (heap W) = heap W +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (local₁ W′) with reflectᴱ H M s W′ +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (local₁ W′) | heap W = heap W +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (local₁ W′) | expr W = block (local₁ W) +reflectᴮ H (local var x ∈ T ← M ∙ B) (local s) (local₂ W′) = block (local₂ (reflect-weakeningᴮ H B (rednᴱ⊑ s) W′)) +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ with remember (typeOfⱽ H v) +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (just S , p) with S ≡ᵀ T +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (just T , p) | yes refl = block (local₂ (reflect-substitutionᴮ H B v x (sym p) W′)) +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (just S , p) | no q = block (LocalVarMismatch (λ r → q (trans (cong orNone (sym p)) (sym r)))) +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (nothing , p) with typeOf-val-not-none v +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (nothing , p) | ok r = CONTRADICTION (r (cong orNone (sym p))) +reflectᴮ H (local var x ∈ T ← M ∙ B) (subst v) W′ | (nothing , p) | warning W = block (local₁ W) +reflectᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a defn) W′ = block (function₂ (reflect-weakeningᴮ H B (snoc defn) (reflect-substitutionᴮ _ B (addr a) f refl W′))) +reflectᴮ H (return M ∙ B) (return s) (return W′) with reflectᴱ H M s W′ +reflectᴮ H (return M ∙ B) (return s) (return W′) | heap W = heap W +reflectᴮ H (return M ∙ B) (return s) (return W′) | expr W = block (return W) + +reflectᴴᴱ : ∀ H M {H′ M′} → (H ⊢ M ⟶ᴱ M′ ⊣ H′) → Warningᴴᴱ H′ (typeCheckᴴᴱ H′ ∅ M′) → Warningᴴᴱ H (typeCheckᴴᴱ H ∅ M) +reflectᴴᴮ : ∀ H B {H′ B′} → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → Warningᴴᴮ H′ (typeCheckᴴᴮ H′ ∅ B′) → Warningᴴᴮ H (typeCheckᴴᴮ H ∅ B) + +reflectᴴᴱ H M s (expr W′) = reflectᴱ H M s W′ +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a p) (heap (addr b refl W′)) with b ≡ᴬ a +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl with heap-weakeningᴮ H B (snoc defn) +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl | ok r = expr (FunctionDefnMismatch λ q → p (trans q r)) +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl | warning W = expr (function₁ W) +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a defn) (heap (addr a refl (function₁ W′))) | yes refl = expr (function₁ (reflect-weakeningᴮ H B (snoc defn) W′)) +reflectᴴᴱ H (function f ⟨ var x ∈ T ⟩∈ U is B end) (function a p) (heap (addr b refl W′)) | no r = heap (addr b (lookup-not-allocated p r) (reflect-weakeningᴼ H _ (snoc p) W′)) +reflectᴴᴱ H (M $ N) (app₁ s) (heap W′) with reflectᴴᴱ H M s (heap W′) +reflectᴴᴱ H (M $ N) (app₁ s) (heap W′) | heap W = heap W +reflectᴴᴱ H (M $ N) (app₁ s) (heap W′) | expr W = expr (app₁ W) +reflectᴴᴱ H (M $ N) (app₂ p s) (heap W′) with reflectᴴᴱ H N s (heap W′) +reflectᴴᴱ H (M $ N) (app₂ p s) (heap W′) | heap W = heap W +reflectᴴᴱ H (M $ N) (app₂ p s) (heap W′) | expr W = expr (app₂ W) +reflectᴴᴱ H (M $ N) (beta O v p q) (heap W′) = heap W′ +reflectᴴᴱ H (block var b ∈ T is B end) (block s) (heap W′) with reflectᴴᴮ H B s (heap W′) +reflectᴴᴱ H (block var b ∈ T is B end) (block s) (heap W′) | heap W = heap W +reflectᴴᴱ H (block var b ∈ T is B end) (block s) (heap W′) | block W = expr (block₁ W) +reflectᴴᴱ H (block var b ∈ T is return N ∙ B end) (return v) (heap W′) = heap W′ +reflectᴴᴱ H (block var b ∈ T is done end) done (heap W′) = heap W′ +reflectᴴᴱ H (binexp M op N) (binOp₀ s) (heap W′) = heap W′ +reflectᴴᴱ H (binexp M op N) (binOp₁ s) (heap W′) with reflectᴴᴱ H M s (heap W′) +reflectᴴᴱ H (binexp M op N) (binOp₁ s) (heap W′) | heap W = heap W +reflectᴴᴱ H (binexp M op N) (binOp₁ s) (heap W′) | expr W = expr (bin₁ W) +reflectᴴᴱ H (binexp M op N) (binOp₂ s) (heap W′) with reflectᴴᴱ H N s (heap W′) +reflectᴴᴱ H (binexp M op N) (binOp₂ s) (heap W′) | heap W = heap W +reflectᴴᴱ H (binexp M op N) (binOp₂ s) (heap W′) | expr W = expr (bin₂ W) + +reflectᴴᴮ H B s (block W′) = reflectᴮ H B s W′ +reflectᴴᴮ H (local var x ∈ T ← M ∙ B) (local s) (heap W′) with reflectᴴᴱ H M s (heap W′) +reflectᴴᴮ H (local var x ∈ T ← M ∙ B) (local s) (heap W′) | heap W = heap W +reflectᴴᴮ H (local var x ∈ T ← M ∙ B) (local s) (heap W′) | expr W = block (local₁ W) +reflectᴴᴮ H (local var x ∈ T ← M ∙ B) (subst v) (heap W′) = heap W′ +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a p) (heap (addr b refl W′)) with b ≡ᴬ a +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl with heap-weakeningᴮ H C (snoc defn) +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl | ok r = block (FunctionDefnMismatch (λ q → p (trans q r))) +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a defn) (heap (addr a refl (FunctionDefnMismatch p))) | yes refl | warning W = block (function₁ W) +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a defn) (heap (addr a refl (function₁ W′))) | yes refl = block (function₁ (reflect-weakeningᴮ H C (snoc defn) W′)) +reflectᴴᴮ H (function f ⟨ var y ∈ T ⟩∈ U is C end ∙ B) (function a p) (heap (addr b refl W′)) | no r = heap (addr b (lookup-not-allocated p r) (reflect-weakeningᴼ H _ (snoc p) W′)) +reflectᴴᴮ H (return M ∙ B) (return s) (heap W′) with reflectᴴᴱ H M s (heap W′) +reflectᴴᴮ H (return M ∙ B) (return s) (heap W′) | heap W = heap W +reflectᴴᴮ H (return M ∙ B) (return s) (heap W′) | expr W = block (return W) + +reflect* : ∀ H B {H′ B′} → (H ⊢ B ⟶* B′ ⊣ H′) → Warningᴴᴮ H′ (typeCheckᴴᴮ H′ ∅ B′) → Warningᴴᴮ H (typeCheckᴴᴮ H ∅ B) +reflect* H B refl W = W +reflect* H B (step s t) W = reflectᴴᴮ H B s (reflect* _ _ t W) + +runtimeBinOpWarning : ∀ H {op} v → BinOpError op (valueType v) → BinOpWarning op (orNone (typeOfⱽ H v)) +runtimeBinOpWarning H v (+ p) = + (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (- p) = - (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (* p) = * (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (/ p) = / (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (< p) = < (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (> p) = > (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (<= p) = <= (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (>= p) = >= (λ q → p (mustBeNumber H ∅ v q)) + +runtimeWarningᴱ : ∀ H M → RuntimeErrorᴱ H M → Warningᴱ H (typeCheckᴱ H ∅ M) +runtimeWarningᴮ : ∀ H B → RuntimeErrorᴮ H B → Warningᴮ H (typeCheckᴮ H ∅ B) + +runtimeWarningᴱ H (var x) UnboundVariable = UnboundVariable refl +runtimeWarningᴱ H (val (addr a)) (SEGV p) = UnallocatedAddress p +runtimeWarningᴱ H (M $ N) (FunctionMismatch v w p) with typeOf-val-not-none w +runtimeWarningᴱ H (M $ N) (FunctionMismatch v w p) | ok q = FunctionCallMismatch (λ r → p (mustBeFunction H ∅ v (λ r′ → q (trans r′ r)))) +runtimeWarningᴱ H (M $ N) (FunctionMismatch v w p) | warning W = app₂ W +runtimeWarningᴱ H (M $ N) (app₁ err) = app₁ (runtimeWarningᴱ H M err) +runtimeWarningᴱ H (M $ N) (app₂ err) = app₂ (runtimeWarningᴱ H N err) +runtimeWarningᴱ H (block var b ∈ T is B end) (block err) = block₁ (runtimeWarningᴮ H B err) +runtimeWarningᴱ H (binexp M op N) (BinOpMismatch₁ v w p) = BinOpMismatch₁ (runtimeBinOpWarning H v p) +runtimeWarningᴱ H (binexp M op N) (BinOpMismatch₂ v w p) = BinOpMismatch₂ (runtimeBinOpWarning H w p) +runtimeWarningᴱ H (binexp M op N) (bin₁ err) = bin₁ (runtimeWarningᴱ H M err) +runtimeWarningᴱ H (binexp M op N) (bin₂ err) = bin₂ (runtimeWarningᴱ H N err) + +runtimeWarningᴮ H (local var x ∈ T ← M ∙ B) (local err) = local₁ (runtimeWarningᴱ H M err) +runtimeWarningᴮ H (return M ∙ B) (return err) = return (runtimeWarningᴱ H M err) + +wellTypedProgramsDontGoWrong : ∀ H′ B B′ → (∅ᴴ ⊢ B ⟶* B′ ⊣ H′) → (RuntimeErrorᴮ H′ B′) → Warningᴮ ∅ᴴ (typeCheckᴮ ∅ᴴ ∅ B) +wellTypedProgramsDontGoWrong H′ B B′ t err with reflect* ∅ᴴ B t (block (runtimeWarningᴮ H′ B′ err)) +wellTypedProgramsDontGoWrong H′ B B′ t err | heap (addr a refl ()) +wellTypedProgramsDontGoWrong H′ B B′ t err | block W = W diff --git a/prototyping/Properties/TypeCheck.agda b/prototyping/Properties/TypeCheck.agda new file mode 100644 index 0000000..c144bc1 --- /dev/null +++ b/prototyping/Properties/TypeCheck.agda @@ -0,0 +1,103 @@ +{-# OPTIONS --rewriting #-} + +open import Luau.Type using (Mode) + +module Properties.TypeCheck (m : Mode) where + +open import Agda.Builtin.Equality using (_≡_; refl) +open import Agda.Builtin.Bool using (Bool; true; false) +open import FFI.Data.Maybe using (Maybe; just; nothing) +open import FFI.Data.Either using (Either) +open import Luau.TypeCheck(m) using (_⊢ᴱ_∈_; _⊢ᴮ_∈_; ⊢ᴼ_; ⊢ᴴ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; nil; var; addr; number; bool; app; function; block; binexp; done; return; local; nothing; orNone; tgtBinOp) +open import Luau.Syntax using (Block; Expr; Value; BinaryOperator; yes; nil; addr; number; bool; val; var; binexp; _$_; function_is_end; block_is_end; _∙_; return; done; local_←_; _⟨_⟩; _⟨_⟩∈_; var_∈_; name; fun; arg; +; -; *; /; <; >; ==; ~=; <=; >=) +open import Luau.Type using (Type; nil; any; none; number; boolean; _⇒_; tgt) +open import Luau.RuntimeType using (RuntimeType; nil; number; function; valueType) +open import Luau.VarCtxt using (VarCtxt; ∅; _↦_; _⊕_↦_; _⋒_; _⊝_) renaming (_[_] to _[_]ⱽ) +open import Luau.Addr using (Addr) +open import Luau.Var using (Var; _≡ⱽ_) +open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ) +open import Properties.Contradiction using (CONTRADICTION) +open import Properties.Dec using (yes; no) +open import Properties.Equality using (_≢_; sym; trans; cong) +open import Properties.Product using (_×_; _,_) +open import Properties.Remember using (Remember; remember; _,_) + +src : Type → Type +src = Luau.Type.src m + +typeOfᴼ : Object yes → Type +typeOfᴼ (function f ⟨ var x ∈ S ⟩∈ T is B end) = (S ⇒ T) + +typeOfᴹᴼ : Maybe(Object yes) → Maybe Type +typeOfᴹᴼ nothing = nothing +typeOfᴹᴼ (just O) = just (typeOfᴼ O) + +typeOfⱽ : Heap yes → Value → Maybe Type +typeOfⱽ H nil = just nil +typeOfⱽ H (bool b) = just boolean +typeOfⱽ H (addr a) = typeOfᴹᴼ (H [ a ]ᴴ) +typeOfⱽ H (number n) = just number + +typeOfᴱ : Heap yes → VarCtxt → (Expr yes) → Type +typeOfᴮ : Heap yes → VarCtxt → (Block yes) → Type + +typeOfᴱ H Γ (var x) = orNone(Γ [ x ]ⱽ) +typeOfᴱ H Γ (val v) = orNone(typeOfⱽ H v) +typeOfᴱ H Γ (M $ N) = tgt(typeOfᴱ H Γ M) +typeOfᴱ H Γ (function f ⟨ var x ∈ S ⟩∈ T is B end) = S ⇒ T +typeOfᴱ H Γ (block var b ∈ T is B end) = T +typeOfᴱ H Γ (binexp M op N) = tgtBinOp op + +typeOfᴮ H Γ (function f ⟨ var x ∈ S ⟩∈ T is C end ∙ B) = typeOfᴮ H (Γ ⊕ f ↦ (S ⇒ T)) B +typeOfᴮ H Γ (local var x ∈ T ← M ∙ B) = typeOfᴮ H (Γ ⊕ x ↦ T) B +typeOfᴮ H Γ (return M ∙ B) = typeOfᴱ H Γ M +typeOfᴮ H Γ done = nil + +mustBeFunction : ∀ H Γ v → (none ≢ src (typeOfᴱ H Γ (val v))) → (function ≡ valueType(v)) +mustBeFunction H Γ nil p = CONTRADICTION (p refl) +mustBeFunction H Γ (addr a) p = refl +mustBeFunction H Γ (number n) p = CONTRADICTION (p refl) +mustBeFunction H Γ (bool true) p = CONTRADICTION (p refl) +mustBeFunction H Γ (bool false) p = CONTRADICTION (p refl) + +mustBeNumber : ∀ H Γ v → (typeOfᴱ H Γ (val v) ≡ number) → (valueType(v) ≡ number) +mustBeNumber H Γ nil () +mustBeNumber H Γ (addr a) p with remember (H [ a ]ᴴ) +mustBeNumber H Γ (addr a) p | (just O , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p +mustBeNumber H Γ (addr a) p | (just function f ⟨ var x ∈ T ⟩∈ U is B end , q) | () +mustBeNumber H Γ (addr a) p | (nothing , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p +mustBeNumber H Γ (addr a) p | nothing , q | () +mustBeNumber H Γ (number n) p = refl +mustBeNumber H Γ (bool true) () +mustBeNumber H Γ (bool false) () + +typeCheckᴱ : ∀ H Γ M → (Γ ⊢ᴱ M ∈ (typeOfᴱ H Γ M)) +typeCheckᴮ : ∀ H Γ B → (Γ ⊢ᴮ B ∈ (typeOfᴮ H Γ B)) + +typeCheckᴱ H Γ (var x) = var refl +typeCheckᴱ H Γ (val nil) = nil +typeCheckᴱ H Γ (val (addr a)) = addr (orNone (typeOfᴹᴼ (H [ a ]ᴴ))) +typeCheckᴱ H Γ (val (number n)) = number +typeCheckᴱ H Γ (val (bool b)) = bool +typeCheckᴱ H Γ (M $ N) = app (typeCheckᴱ H Γ M) (typeCheckᴱ H Γ N) +typeCheckᴱ H Γ (function f ⟨ var x ∈ T ⟩∈ U is B end) = function (typeCheckᴮ H (Γ ⊕ x ↦ T) B) +typeCheckᴱ H Γ (block var b ∈ T is B end) = block (typeCheckᴮ H Γ B) +typeCheckᴱ H Γ (binexp M op N) = binexp (typeCheckᴱ H Γ M) (typeCheckᴱ H Γ N) + +typeCheckᴮ H Γ (function f ⟨ var x ∈ T ⟩∈ U is C end ∙ B) = function (typeCheckᴮ H (Γ ⊕ x ↦ T) C) (typeCheckᴮ H (Γ ⊕ f ↦ (T ⇒ U)) B) +typeCheckᴮ H Γ (local var x ∈ T ← M ∙ B) = local (typeCheckᴱ H Γ M) (typeCheckᴮ H (Γ ⊕ x ↦ T) B) +typeCheckᴮ H Γ (return M ∙ B) = return (typeCheckᴱ H Γ M) (typeCheckᴮ H Γ B) +typeCheckᴮ H Γ done = done + +typeCheckᴼ : ∀ H O → (⊢ᴼ O) +typeCheckᴼ H nothing = nothing +typeCheckᴼ H (just function f ⟨ var x ∈ T ⟩∈ U is B end) = function (typeCheckᴮ H (x ↦ T) B) + +typeCheckᴴ : ∀ H → (⊢ᴴ H) +typeCheckᴴ H a {O} p = typeCheckᴼ H (O) + +typeCheckᴴᴱ : ∀ H Γ M → (Γ ⊢ᴴᴱ H ▷ M ∈ typeOfᴱ H Γ M) +typeCheckᴴᴱ H Γ M = (typeCheckᴴ H , typeCheckᴱ H Γ M) + +typeCheckᴴᴮ : ∀ H Γ M → (Γ ⊢ᴴᴮ H ▷ M ∈ typeOfᴮ H Γ M) +typeCheckᴴᴮ H Γ M = (typeCheckᴴ H , typeCheckᴮ H Γ M) diff --git a/prototyping/Tests/Interpreter/binary_equality_bools/out.txt b/prototyping/Tests/Interpreter/binary_equality_bools/out.txt index c508d53..ee6f2a2 100644 --- a/prototyping/Tests/Interpreter/binary_equality_bools/out.txt +++ b/prototyping/Tests/Interpreter/binary_equality_bools/out.txt @@ -1 +1,4 @@ -false +ANNOTATED PROGRAM: +return true == false + +RAN WITH RESULT: false diff --git a/prototyping/Tests/Interpreter/binary_equality_numbers/out.txt b/prototyping/Tests/Interpreter/binary_equality_numbers/out.txt index 27ba77d..86a499a 100644 --- a/prototyping/Tests/Interpreter/binary_equality_numbers/out.txt +++ b/prototyping/Tests/Interpreter/binary_equality_numbers/out.txt @@ -1 +1,4 @@ -true +ANNOTATED PROGRAM: +return 1.0 == 1.0 + +RAN WITH RESULT: true diff --git a/prototyping/Tests/Interpreter/binary_exps/out.txt b/prototyping/Tests/Interpreter/binary_exps/out.txt index d3827e7..5b4d264 100644 --- a/prototyping/Tests/Interpreter/binary_exps/out.txt +++ b/prototyping/Tests/Interpreter/binary_exps/out.txt @@ -1 +1,4 @@ -1.0 +ANNOTATED PROGRAM: +return 1.0 + 2.0 - 2.0 * 2.0 / 2.0 + +RAN WITH RESULT: 1.0 diff --git a/prototyping/Tests/Interpreter/return_nil/out.txt b/prototyping/Tests/Interpreter/return_nil/out.txt index 607602c..1d5f45b 100644 --- a/prototyping/Tests/Interpreter/return_nil/out.txt +++ b/prototyping/Tests/Interpreter/return_nil/out.txt @@ -1 +1,7 @@ -nil +UNANNOTATED PROGRAM: +local function foo(x) + return nil +end +return foo(nil) + +RAN WITH RESULT: nil From 6c923b88021df9dc902ed788933996cee556dc30 Mon Sep 17 00:00:00 2001 From: Lily Brown Date: Wed, 2 Mar 2022 15:26:58 -0800 Subject: [PATCH 8/8] Prototyping: strings (#390) --- .github/workflows/prototyping.yml | 3 +- prototyping/Examples/Run.agda | 5 ++- prototyping/Luau/OpSem.agda | 6 ++-- prototyping/Luau/RuntimeError.agda | 5 +-- prototyping/Luau/RuntimeError/ToString.agda | 4 ++- prototyping/Luau/RuntimeType.agda | 4 ++- prototyping/Luau/RuntimeType/ToString.agda | 3 +- prototyping/Luau/StrictMode.agda | 5 +-- prototyping/Luau/Syntax.agda | 3 ++ prototyping/Luau/Syntax/FromJSON.agda | 7 +++- prototyping/Luau/Syntax/ToString.agda | 5 ++- prototyping/Luau/Type.agda | 22 ++++++++++++ prototyping/Luau/Type/FromJSON.agda | 3 +- prototyping/Luau/Type/ToString.agda | 3 +- prototyping/Luau/TypeCheck.agda | 10 ++++-- prototyping/Properties/Step.agda | 36 +++++++++++++++++-- prototyping/Properties/StrictMode.agda | 14 +++++--- prototyping/Properties/TypeCheck.agda | 23 ++++++++---- .../concat_number_and_string/in.lua | 3 ++ .../concat_number_and_string/out.txt | 11 ++++++ .../Interpreter/concat_two_strings/in.lua | 3 ++ .../Interpreter/concat_two_strings/out.txt | 6 ++++ .../Tests/Interpreter/return_string/in.lua | 1 + .../Tests/Interpreter/return_string/out.txt | 4 +++ 24 files changed, 156 insertions(+), 33 deletions(-) create mode 100644 prototyping/Tests/Interpreter/concat_number_and_string/in.lua create mode 100644 prototyping/Tests/Interpreter/concat_number_and_string/out.txt create mode 100644 prototyping/Tests/Interpreter/concat_two_strings/in.lua create mode 100644 prototyping/Tests/Interpreter/concat_two_strings/out.txt create mode 100644 prototyping/Tests/Interpreter/return_string/in.lua create mode 100644 prototyping/Tests/Interpreter/return_string/out.txt diff --git a/.github/workflows/prototyping.yml b/.github/workflows/prototyping.yml index c92e580..6bc8a81 100644 --- a/.github/workflows/prototyping.yml +++ b/.github/workflows/prototyping.yml @@ -37,8 +37,7 @@ jobs: - name: check targets working-directory: prototyping run: | - ~/.cabal/bin/agda Examples.agda - ~/.cabal/bin/agda Properties.agda + ~/.cabal/bin/agda Everything.agda - name: build executables working-directory: prototyping run: | diff --git a/prototyping/Examples/Run.agda b/prototyping/Examples/Run.agda index 545ecf5..84ebf84 100644 --- a/prototyping/Examples/Run.agda +++ b/prototyping/Examples/Run.agda @@ -4,7 +4,7 @@ module Examples.Run where open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Bool using (true; false) -open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩; number; binexp; +; <; val; bool) +open import Luau.Syntax using (nil; var; _$_; function_is_end; return; _∙_; done; _⟨_⟩; number; binexp; +; <; val; bool; ~=; string) open import Luau.Run using (run; return) ex1 : (run (function "id" ⟨ var "x" ⟩ is return (var "x") ∙ done end ∙ return (var "id" $ val nil) ∙ done) ≡ return nil _) @@ -18,3 +18,6 @@ ex3 = refl ex4 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (val (number 1.0)) < (val (number 2.0))) ∙ done end ∙ return (var "fn" $ val nil) ∙ done) ≡ return (bool true) _) ex4 = refl + +ex5 : (run (function "fn" ⟨ var "x" ⟩ is return (binexp (val (string "foo")) ~= (val (string "bar"))) ∙ done end ∙ return (var "fn" $ val nil) ∙ done) ≡ return (bool true) _) +ex5 = refl diff --git a/prototyping/Luau/OpSem.agda b/prototyping/Luau/OpSem.agda index cbb5597..1f616c7 100644 --- a/prototyping/Luau/OpSem.agda +++ b/prototyping/Luau/OpSem.agda @@ -5,12 +5,13 @@ module Luau.OpSem where open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Float using (Float; primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv; primFloatEquality; primFloatLess; primFloatInequality) open import Agda.Builtin.Bool using (Bool; true; false) +open import Agda.Builtin.String using (primStringEquality; primStringAppend) open import Utility.Bool using (not; _or_; _and_) open import Agda.Builtin.Nat using () renaming (_==_ to _==ᴬ_) open import FFI.Data.Maybe using (Maybe; just; nothing) open import Luau.Heap using (Heap; _≡_⊕_↦_; _[_]; function_is_end) open import Luau.Substitution using (_[_/_]ᴮ) -open import Luau.Syntax using (Value; Expr; Stat; Block; nil; addr; val; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg; binexp; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; number; bool) +open import Luau.Syntax using (Value; Expr; Stat; Block; nil; addr; val; var; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; fun; arg; binexp; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; ··; number; bool; string) open import Luau.RuntimeType using (RuntimeType; valueType) open import Properties.Product using (_×_; _,_) @@ -37,7 +38,8 @@ data _⟦_⟧_⟶_ : Value → BinaryOperator → Value → Value → Set where >= : ∀ m n → (number m) ⟦ >= ⟧ (number n) ⟶ bool ((primFloatLess n m) or (primFloatEquality m n)) == : ∀ v w → v ⟦ == ⟧ w ⟶ bool (evalEqOp v w) ~= : ∀ v w → v ⟦ ~= ⟧ w ⟶ bool (evalNeqOp v w) - + ·· : ∀ x y → (string x) ⟦ ·· ⟧ (string y) ⟶ string (primStringAppend x y) + data _⊢_⟶ᴮ_⊣_ {a} : Heap a → Block a → Block a → Heap a → Set data _⊢_⟶ᴱ_⊣_ {a} : Heap a → Expr a → Expr a → Heap a → Set diff --git a/prototyping/Luau/RuntimeError.agda b/prototyping/Luau/RuntimeError.agda index 4440d2b..0c8a594 100644 --- a/prototyping/Luau/RuntimeError.agda +++ b/prototyping/Luau/RuntimeError.agda @@ -6,8 +6,8 @@ open import Agda.Builtin.Equality using (_≡_) open import Luau.Heap using (Heap; _[_]) open import FFI.Data.Maybe using (just; nothing) open import FFI.Data.String using (String) -open import Luau.Syntax using (BinaryOperator; Block; Expr; nil; var; val; addr; block_is_end; _$_; local_←_; return; done; _∙_; number; binexp; +; -; *; /; <; >; <=; >=) -open import Luau.RuntimeType using (RuntimeType; valueType; function; number) +open import Luau.Syntax using (BinaryOperator; Block; Expr; nil; var; val; addr; block_is_end; _$_; local_←_; return; done; _∙_; number; string; binexp; +; -; *; /; <; >; <=; >=; ··) +open import Luau.RuntimeType using (RuntimeType; valueType; function; number; string) open import Properties.Equality using (_≢_) data BinOpError : BinaryOperator → RuntimeType → Set where @@ -19,6 +19,7 @@ data BinOpError : BinaryOperator → RuntimeType → Set where > : ∀ {t} → (t ≢ number) → BinOpError > t <= : ∀ {t} → (t ≢ number) → BinOpError <= t >= : ∀ {t} → (t ≢ number) → BinOpError >= t + ·· : ∀ {t} → (t ≢ string) → BinOpError ·· t data RuntimeErrorᴮ {a} (H : Heap a) : Block a → Set data RuntimeErrorᴱ {a} (H : Heap a) : Expr a → Set diff --git a/prototyping/Luau/RuntimeError/ToString.agda b/prototyping/Luau/RuntimeError/ToString.agda index 6a6ed3e..cd5f18f 100644 --- a/prototyping/Luau/RuntimeError/ToString.agda +++ b/prototyping/Luau/RuntimeError/ToString.agda @@ -9,7 +9,7 @@ open import Luau.RuntimeType.ToString using (runtimeTypeToString) open import Luau.Addr.ToString using (addrToString) open import Luau.Syntax.ToString using (valueToString; exprToString) open import Luau.Var.ToString using (varToString) -open import Luau.Syntax using (var; val; addr; binexp; block_is_end; local_←_; return; _∙_; name; _$_) +open import Luau.Syntax using (var; val; addr; binexp; block_is_end; local_←_; return; _∙_; name; _$_; ··) errToStringᴱ : ∀ {a H} M → RuntimeErrorᴱ {a} H M → String errToStringᴮ : ∀ {a H} B → RuntimeErrorᴮ {a} H B → String @@ -19,6 +19,8 @@ errToStringᴱ (val (addr a)) (SEGV p) = "address " ++ addrToString a ++ " is un errToStringᴱ (M $ N) (FunctionMismatch v w p) = "value " ++ (valueToString v) ++ " is not a function" errToStringᴱ (M $ N) (app₁ E) = errToStringᴱ M E errToStringᴱ (M $ N) (app₂ E) = errToStringᴱ N E +errToStringᴱ (binexp M ·· N) (BinOpMismatch₁ v w p) = "value " ++ (valueToString v) ++ " is not a string" +errToStringᴱ (binexp M ·· N) (BinOpMismatch₂ v w p) = "value " ++ (valueToString w) ++ " is not a string" errToStringᴱ (binexp M op N) (BinOpMismatch₁ v w p) = "value " ++ (valueToString v) ++ " is not a number" errToStringᴱ (binexp M op N) (BinOpMismatch₂ v w p) = "value " ++ (valueToString w) ++ " is not a number" errToStringᴱ (binexp M op N) (bin₁ E) = errToStringᴱ M E diff --git a/prototyping/Luau/RuntimeType.agda b/prototyping/Luau/RuntimeType.agda index f6b8b4a..d585b51 100644 --- a/prototyping/Luau/RuntimeType.agda +++ b/prototyping/Luau/RuntimeType.agda @@ -1,15 +1,17 @@ module Luau.RuntimeType where -open import Luau.Syntax using (Value; nil; addr; number; bool) +open import Luau.Syntax using (Value; nil; addr; number; bool; string) data RuntimeType : Set where function : RuntimeType number : RuntimeType nil : RuntimeType boolean : RuntimeType + string : RuntimeType valueType : Value → RuntimeType valueType nil = nil valueType (addr a) = function valueType (number n) = number valueType (bool b) = boolean +valueType (string x) = string diff --git a/prototyping/Luau/RuntimeType/ToString.agda b/prototyping/Luau/RuntimeType/ToString.agda index dfb7c95..f3dd125 100644 --- a/prototyping/Luau/RuntimeType/ToString.agda +++ b/prototyping/Luau/RuntimeType/ToString.agda @@ -1,10 +1,11 @@ module Luau.RuntimeType.ToString where open import FFI.Data.String using (String) -open import Luau.RuntimeType using (RuntimeType; function; number; nil; boolean) +open import Luau.RuntimeType using (RuntimeType; function; number; nil; boolean; string) runtimeTypeToString : RuntimeType → String runtimeTypeToString function = "function" runtimeTypeToString number = "number" runtimeTypeToString nil = "nil" runtimeTypeToString boolean = "boolean" +runtimeTypeToString string = "string" diff --git a/prototyping/Luau/StrictMode.agda b/prototyping/Luau/StrictMode.agda index d7b2129..08e0f50 100644 --- a/prototyping/Luau/StrictMode.agda +++ b/prototyping/Luau/StrictMode.agda @@ -4,8 +4,8 @@ module Luau.StrictMode where open import Agda.Builtin.Equality using (_≡_) open import FFI.Data.Maybe using (just; nothing) -open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; var; binexp; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; +; -; *; /; <; >; <=; >=) -open import Luau.Type using (Type; strict; nil; number; _⇒_; tgt) +open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; var; binexp; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; local_←_; _∙_; done; return; name; +; -; *; /; <; >; <=; >=; ··) +open import Luau.Type using (Type; strict; nil; number; string; _⇒_; tgt) open import Luau.Heap using (Heap; function_is_end) renaming (_[_] to _[_]ᴴ) open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ) open import Luau.TypeCheck(strict) using (_⊢ᴮ_∈_; _⊢ᴱ_∈_; ⊢ᴴ_; ⊢ᴼ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; var; addr; app; binexp; block; return; local; function) @@ -25,6 +25,7 @@ data BinOpWarning : BinaryOperator → Type → Set where > : ∀ {T} → (T ≢ number) → BinOpWarning > T <= : ∀ {T} → (T ≢ number) → BinOpWarning <= T >= : ∀ {T} → (T ≢ number) → BinOpWarning >= T + ·· : ∀ {T} → (T ≢ string) → BinOpWarning ·· T data Warningᴱ (H : Heap yes) {Γ} : ∀ {M T} → (Γ ⊢ᴱ M ∈ T) → Set data Warningᴮ (H : Heap yes) {Γ} : ∀ {B T} → (Γ ⊢ᴮ B ∈ T) → Set diff --git a/prototyping/Luau/Syntax.agda b/prototyping/Luau/Syntax.agda index 8be0830..d917506 100644 --- a/prototyping/Luau/Syntax.agda +++ b/prototyping/Luau/Syntax.agda @@ -3,6 +3,7 @@ module Luau.Syntax where open import Agda.Builtin.Equality using (_≡_) open import Agda.Builtin.Bool using (Bool; true; false) open import Agda.Builtin.Float using (Float) +open import Agda.Builtin.String using (String) open import Luau.Var using (Var) open import Luau.Addr using (Addr) open import Luau.Type using (Type) @@ -45,12 +46,14 @@ data BinaryOperator : Set where ~= : BinaryOperator <= : BinaryOperator >= : BinaryOperator + ·· : BinaryOperator data Value : Set where nil : Value addr : Addr → Value number : Float → Value bool : Bool → Value + string : String → Value data Block (a : Annotated) : Set data Stat (a : Annotated) : Set diff --git a/prototyping/Luau/Syntax/FromJSON.agda b/prototyping/Luau/Syntax/FromJSON.agda index a727a5b..2263615 100644 --- a/prototyping/Luau/Syntax/FromJSON.agda +++ b/prototyping/Luau/Syntax/FromJSON.agda @@ -2,7 +2,7 @@ module Luau.Syntax.FromJSON where -open import Luau.Syntax using (Block; Stat ; Expr; _$_; val; nil; bool; number; var; var_∈_; function_is_end; _⟨_⟩; _⟨_⟩∈_; local_←_; return; done; _∙_; maybe; VarDec; binexp; BinaryOperator; +; -; *; /; ==; ~=; <; >; <=; >=) +open import Luau.Syntax using (Block; Stat ; Expr; _$_; val; nil; bool; number; var; var_∈_; function_is_end; _⟨_⟩; _⟨_⟩∈_; local_←_; return; done; _∙_; maybe; VarDec; binexp; BinaryOperator; +; -; *; /; ==; ~=; <; >; <=; >=; ··; string) open import Luau.Type.FromJSON using (typeFromJSON) open import Agda.Builtin.List using (List; _∷_; []) @@ -65,6 +65,7 @@ binOpFromString "CompareLt" = Right < binOpFromString "CompareLe" = Right <= binOpFromString "CompareGt" = Right > binOpFromString "CompareGe" = Right >= +binOpFromString "Concat" = Right ·· binOpFromString s = Left ("'" ++ s ++ "' is not a valid operator") varDecFromJSON (object arg) = varDecFromObject arg @@ -122,6 +123,10 @@ exprFromObject obj | just (string "AstExprConstantNumber") with lookup value obj exprFromObject obj | just (string "AstExprConstantNumber") | just (FFI.Data.Aeson.Value.number x) = Right (val (number (toFloat x))) exprFromObject obj | just (string "AstExprConstantNumber") | just _ = Left "AstExprConstantNumber value is not a number" exprFromObject obj | just (string "AstExprConstantNumber") | nothing = Left "AstExprConstantNumber missing value" +exprFromObject obj | just (string "AstExprConstantString") with lookup value obj +exprFromObject obj | just (string "AstExprConstantString") | just (string x) = Right (val (string x)) +exprFromObject obj | just (string "AstExprConstantString") | just _ = Left "AstExprConstantString value is not a string" +exprFromObject obj | just (string "AstExprConstantString") | nothing = Left "AstExprConstantString missing value" exprFromObject obj | just (string "AstExprConstantBool") with lookup value obj exprFromObject obj | just (string "AstExprConstantBool") | just (FFI.Data.Aeson.Value.bool b) = Right (val (bool b)) exprFromObject obj | just (string "AstExprConstantBool") | just _ = Left "AstExprConstantBool value is not a bool" diff --git a/prototyping/Luau/Syntax/ToString.agda b/prototyping/Luau/Syntax/ToString.agda index 60ec471..1743071 100644 --- a/prototyping/Luau/Syntax/ToString.agda +++ b/prototyping/Luau/Syntax/ToString.agda @@ -2,7 +2,8 @@ module Luau.Syntax.ToString where open import Agda.Builtin.Bool using (true; false) open import Agda.Builtin.Float using (primShowFloat) -open import Luau.Syntax using (Value; Block; Stat; Expr; VarDec; FunDec; nil; bool; val; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_; number; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; binexp) +open import Agda.Builtin.String using (primShowString) +open import Luau.Syntax using (Value; Block; Stat; Expr; VarDec; FunDec; nil; bool; val; var; var_∈_; addr; _$_; function_is_end; return; local_←_; _∙_; done; block_is_end; _⟨_⟩; _⟨_⟩∈_; number; BinaryOperator; +; -; *; /; <; >; ==; ~=; <=; >=; ··; binexp; string) open import FFI.Data.String using (String; _++_) open import Luau.Addr.ToString using (addrToString) open import Luau.Type.ToString using (typeToString) @@ -29,6 +30,7 @@ binOpToString == = "==" binOpToString ~= = "~=" binOpToString <= = "<=" binOpToString >= = ">=" +binOpToString ·· = ".." valueToString : Value → String valueToString nil = "nil" @@ -36,6 +38,7 @@ valueToString (addr a) = addrToString a valueToString (number x) = primShowFloat x valueToString (bool false) = "false" valueToString (bool true) = "true" +valueToString (string x) = primShowString x exprToString′ : ∀ {a} → String → Expr a → String statToString′ : ∀ {a} → String → Stat a → String diff --git a/prototyping/Luau/Type.agda b/prototyping/Luau/Type.agda index 10f39c4..c5b7614 100644 --- a/prototyping/Luau/Type.agda +++ b/prototyping/Luau/Type.agda @@ -13,6 +13,7 @@ data Type : Set where any : Type boolean : Type number : Type + string : Type _∪_ : Type → Type → Type _∩_ : Type → Type → Type @@ -25,6 +26,7 @@ lhs none = none lhs any = any lhs number = number lhs boolean = boolean +lhs string = string rhs : Type → Type rhs (_ ⇒ T) = T @@ -35,6 +37,7 @@ rhs none = none rhs any = any rhs number = number rhs boolean = boolean +rhs string = string _≡ᵀ_ : ∀ (T U : Type) → Dec(T ≡ U) nil ≡ᵀ nil = yes refl @@ -45,6 +48,14 @@ nil ≡ᵀ number = no (λ ()) nil ≡ᵀ boolean = no (λ ()) nil ≡ᵀ (S ∪ T) = no (λ ()) nil ≡ᵀ (S ∩ T) = no (λ ()) +nil ≡ᵀ string = no (λ ()) +(S ⇒ T) ≡ᵀ string = no (λ ()) +none ≡ᵀ string = no (λ ()) +any ≡ᵀ string = no (λ ()) +boolean ≡ᵀ string = no (λ ()) +number ≡ᵀ string = no (λ ()) +(S ∪ T) ≡ᵀ string = no (λ ()) +(S ∩ T) ≡ᵀ string = no (λ ()) (S ⇒ T) ≡ᵀ nil = no (λ ()) (S ⇒ T) ≡ᵀ (U ⇒ V) with (S ≡ᵀ U) | (T ≡ᵀ V) (S ⇒ T) ≡ᵀ (S ⇒ T) | yes refl | yes refl = yes refl @@ -88,6 +99,15 @@ boolean ≡ᵀ boolean = yes refl boolean ≡ᵀ number = no (λ ()) boolean ≡ᵀ (T ∪ U) = no (λ ()) boolean ≡ᵀ (T ∩ U) = no (λ ()) +string ≡ᵀ nil = no (λ ()) +string ≡ᵀ (x ⇒ x₁) = no (λ ()) +string ≡ᵀ none = no (λ ()) +string ≡ᵀ any = no (λ ()) +string ≡ᵀ boolean = no (λ ()) +string ≡ᵀ number = no (λ ()) +string ≡ᵀ string = yes refl +string ≡ᵀ (U ∪ V) = no (λ ()) +string ≡ᵀ (U ∩ V) = no (λ ()) (S ∪ T) ≡ᵀ nil = no (λ ()) (S ∪ T) ≡ᵀ (U ⇒ V) = no (λ ()) (S ∪ T) ≡ᵀ none = no (λ ()) @@ -127,6 +147,7 @@ src : Mode → Type → Type src m nil = none src m number = none src m boolean = none +src m string = none src m (S ⇒ T) = S -- In nonstrict mode, functions are covaraiant, in strict mode they're contravariant src strict (S ∪ T) = (src strict S) ∩ (src strict T) @@ -145,6 +166,7 @@ tgt none = none tgt any = any tgt number = none tgt boolean = none +tgt string = none tgt (S ∪ T) = (tgt S) ∪ (tgt T) tgt (S ∩ T) = (tgt S) ∩ (tgt T) diff --git a/prototyping/Luau/Type/FromJSON.agda b/prototyping/Luau/Type/FromJSON.agda index 9057fef..2d6ba68 100644 --- a/prototyping/Luau/Type/FromJSON.agda +++ b/prototyping/Luau/Type/FromJSON.agda @@ -2,7 +2,7 @@ module Luau.Type.FromJSON where -open import Luau.Type using (Type; nil; _⇒_; _∪_; _∩_; any; number) +open import Luau.Type using (Type; nil; _⇒_; _∪_; _∩_; any; number; string) open import Agda.Builtin.List using (List; _∷_; []) open import Agda.Builtin.Bool using (true; false) @@ -44,6 +44,7 @@ typeFromJSON (object o) | just (string "AstTypeReference") with lookup name o typeFromJSON (object o) | just (string "AstTypeReference") | just (string "nil") = Right nil typeFromJSON (object o) | just (string "AstTypeReference") | just (string "any") = Right any typeFromJSON (object o) | just (string "AstTypeReference") | just (string "number") = Right number +typeFromJSON (object o) | just (string "AstTypeReference") | just (string "string") = Right string typeFromJSON (object o) | just (string "AstTypeReference") | _ = Left "Unknown referenced type" typeFromJSON (object o) | just (string "AstTypeUnion") with lookup types o diff --git a/prototyping/Luau/Type/ToString.agda b/prototyping/Luau/Type/ToString.agda index dec7c14..2efe663 100644 --- a/prototyping/Luau/Type/ToString.agda +++ b/prototyping/Luau/Type/ToString.agda @@ -1,7 +1,7 @@ module Luau.Type.ToString where open import FFI.Data.String using (String; _++_) -open import Luau.Type using (Type; nil; _⇒_; none; any; number; boolean; _∪_; _∩_; normalizeOptional) +open import Luau.Type using (Type; nil; _⇒_; none; any; number; boolean; string; _∪_; _∩_; normalizeOptional) {-# TERMINATING #-} typeToString : Type → String @@ -14,6 +14,7 @@ typeToString none = "none" typeToString any = "any" typeToString number = "number" typeToString boolean = "boolean" +typeToString string = "string" typeToString (S ∪ T) with normalizeOptional(S ∪ T) typeToString (S ∪ T) | ((S′ ⇒ T′) ∪ nil) = "(" ++ typeToString (S′ ⇒ T′) ++ ")?" typeToString (S ∪ T) | (S′ ∪ nil) = typeToString S′ ++ "?" diff --git a/prototyping/Luau/TypeCheck.agda b/prototyping/Luau/TypeCheck.agda index f240e17..65ed183 100644 --- a/prototyping/Luau/TypeCheck.agda +++ b/prototyping/Luau/TypeCheck.agda @@ -6,11 +6,11 @@ module Luau.TypeCheck (m : Mode) where open import Agda.Builtin.Equality using (_≡_) open import FFI.Data.Maybe using (Maybe; just) -open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; number; bool; val; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; binexp; local_←_; _∙_; done; return; name; +; -; *; /; <; >; ==; ~=; <=; >=) +open import Luau.Syntax using (Expr; Stat; Block; BinaryOperator; yes; nil; addr; number; bool; string; val; var; var_∈_; _⟨_⟩∈_; function_is_end; _$_; block_is_end; binexp; local_←_; _∙_; done; return; name; +; -; *; /; <; >; ==; ~=; <=; >=; ··) open import Luau.Var using (Var) open import Luau.Addr using (Addr) open import Luau.Heap using (Heap; Object; function_is_end) renaming (_[_] to _[_]ᴴ) -open import Luau.Type using (Type; Mode; nil; none; number; boolean; _⇒_; tgt) +open import Luau.Type using (Type; Mode; nil; none; number; boolean; string; _⇒_; tgt) open import Luau.VarCtxt using (VarCtxt; ∅; _⋒_; _↦_; _⊕_↦_; _⊝_) renaming (_[_] to _[_]ⱽ) open import FFI.Data.Vector using (Vector) open import FFI.Data.Maybe using (Maybe; just; nothing) @@ -34,6 +34,7 @@ tgtBinOp == = boolean tgtBinOp ~= = boolean tgtBinOp <= = boolean tgtBinOp >= = boolean +tgtBinOp ·· = string data _⊢ᴮ_∈_ : VarCtxt → Block yes → Type → Set data _⊢ᴱ_∈_ : VarCtxt → Expr yes → Type → Set @@ -93,6 +94,11 @@ data _⊢ᴱ_∈_ where -------------------------- Γ ⊢ᴱ val(bool b) ∈ boolean + + string : ∀ {x Γ} → + + --------------------------- + Γ ⊢ᴱ val(string x) ∈ string app : ∀ {M N T U Γ} → diff --git a/prototyping/Properties/Step.agda b/prototyping/Properties/Step.agda index 0b5fd06..cadd153 100644 --- a/prototyping/Properties/Step.agda +++ b/prototyping/Properties/Step.agda @@ -5,11 +5,12 @@ module Properties.Step where open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Float using (primFloatPlus; primFloatMinus; primFloatTimes; primFloatDiv; primFloatEquality; primFloatLess) open import Agda.Builtin.Bool using (true; false) +open import Agda.Builtin.String using (primStringAppend) open import FFI.Data.Maybe using (just; nothing) open import Luau.Heap using (Heap; _[_]; alloc; ok; function_is_end) -open import Luau.Syntax using (Block; Expr; nil; var; val; addr; bool; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg; number; binexp; +; -; *; /; <; >; <=; >=; ==; ~=) -open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁ ; app₂ ; beta; function; block; return; done; local; subst; binOp₀; binOp₁; binOp₂; +; -; *; /; <; >; <=; >=; ==; ~=; evalEqOp; evalNeqOp) -open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; block; local; return; bin₁; bin₂; +; -; *; /; <; >; <=; >=) +open import Luau.Syntax using (Block; Expr; nil; var; val; addr; bool; function_is_end; block_is_end; _$_; local_←_; return; done; _∙_; name; fun; arg; number; binexp; +; -; *; /; <; >; <=; >=; ==; ~=; ··; string) +open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶ᴱ_⊣_; _⊢_⟶ᴮ_⊣_; app₁ ; app₂ ; beta; function; block; return; done; local; subst; binOp₀; binOp₁; binOp₂; +; -; *; /; <; >; <=; >=; ==; ~=; ··; evalEqOp; evalNeqOp) +open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; block; local; return; bin₁; bin₂; +; -; *; /; <; >; <=; >=; ··) open import Luau.RuntimeType using (valueType; function; number) open import Luau.Substitution using (_[_/_]ᴮ) open import Properties.Remember using (remember; _,_) @@ -27,6 +28,16 @@ binOpStep (number m) + nil = error₂ (+ (λ ())) binOpStep (number m) + (addr a) = error₂ (+ (λ ())) binOpStep (number m) + (number n) = step (number (primFloatPlus m n)) (+ m n) binOpStep (number m) + (bool b) = error₂ (+ (λ ())) +binOpStep (number m) + (string x) = error₂ (+ (λ ())) +binOpStep (number m) - (string x) = error₂ (- (λ ())) +binOpStep (number m) * (string x) = error₂ (* (λ ())) +binOpStep (number m) / (string x) = error₂ (/ (λ ())) +binOpStep (number m) < (string x) = error₂ (< (λ ())) +binOpStep (number m) > (string x) = error₂ (> (λ ())) +binOpStep (number m) == (string x) = step (bool false) (== (number m) (string x)) +binOpStep (number m) ~= (string x) = step (bool true) (~= (number m) (string x)) +binOpStep (number m) <= (string x) = error₂ (<= (λ ())) +binOpStep (number m) >= (string x) = error₂ (>= (λ ())) binOpStep (bool b) + w = error₁ (+ (λ ())) binOpStep nil - w = error₁ (- (λ ())) binOpStep (addr a) - w = error₁ (- (λ ())) @@ -79,6 +90,23 @@ binOpStep (number m) >= (addr a) = error₂ (>= (λ ())) binOpStep (number m) >= (number n) = step (bool (primFloatLess n m or primFloatEquality m n)) (>= m n) binOpStep (number m) >= (bool b) = error₂ (>= (λ ())) binOpStep (bool b) >= w = error₁ (>= (λ ())) +binOpStep (string x) + w = error₁ (+ (λ ())) +binOpStep (string x) - w = error₁ (- (λ ())) +binOpStep (string x) * w = error₁ (* (λ ())) +binOpStep (string x) / w = error₁ (/ (λ ())) +binOpStep (string x) < w = error₁ (< (λ ())) +binOpStep (string x) > w = error₁ (> (λ ())) +binOpStep (string x) <= w = error₁ (<= (λ ())) +binOpStep (string x) >= w = error₁ (>= (λ ())) +binOpStep nil ·· y = error₁ (·· (λ ())) +binOpStep (addr x) ·· y = error₁ (BinOpError.·· (λ ())) +binOpStep (number x) ·· y = error₁ (BinOpError.·· (λ ())) +binOpStep (bool x) ·· y = error₁ (BinOpError.·· (λ ())) +binOpStep (string x) ·· nil = error₂ (·· (λ ())) +binOpStep (string x) ·· (addr y) = error₂ (·· (λ ())) +binOpStep (string x) ·· (number y) = error₂ (·· (λ ())) +binOpStep (string x) ·· (bool y) = error₂ (·· (λ ())) +binOpStep (string x) ·· (string y) = step (string (primStringAppend x y)) (·· x y) data StepResultᴮ {a} (H : Heap a) (B : Block a) : Set data StepResultᴱ {a} (H : Heap a) (M : Expr a) : Set @@ -109,6 +137,7 @@ stepᴱ H (_ $ _) | value (addr a) refl | value w refl | (just(function F is B stepᴱ H (_ $ _) | value nil refl | value w refl = error (FunctionMismatch nil w (λ ())) stepᴱ H (_ $ _) | value (number m) refl | value w refl = error (FunctionMismatch (number m) w (λ ())) stepᴱ H (_ $ _) | value (bool b) refl | value w refl = error (FunctionMismatch (bool b) w (λ ())) +stepᴱ H (_ $ _) | value (string x) refl | value w refl = error (FunctionMismatch (string x) w (λ ())) stepᴱ H (M $ N) | value V p | error E = error (app₂ E) stepᴱ H (M $ N) | error E = error (app₁ E) stepᴱ H (block b is B end) with stepᴮ H B @@ -140,3 +169,4 @@ stepᴮ H (return M ∙ B) | step H′ M′ D = step H′ (return M′ ∙ B) (r stepᴮ H (return _ ∙ B) | value V refl = return V refl stepᴮ H (return M ∙ B) | error E = error (return E) stepᴮ H done = done refl + \ No newline at end of file diff --git a/prototyping/Properties/StrictMode.agda b/prototyping/Properties/StrictMode.agda index 0841118..77852f1 100644 --- a/prototyping/Properties/StrictMode.agda +++ b/prototyping/Properties/StrictMode.agda @@ -6,9 +6,9 @@ import Agda.Builtin.Equality.Rewrite open import Agda.Builtin.Equality using (_≡_; refl) open import FFI.Data.Maybe using (Maybe; just; nothing) open import Luau.Heap using (Heap; Object; function_is_end; defn; alloc; ok; next; lookup-not-allocated) renaming (_≡_⊕_↦_ to _≡ᴴ_⊕_↦_; _[_] to _[_]ᴴ; ∅ to ∅ᴴ) -open import Luau.StrictMode using (Warningᴱ; Warningᴮ; Warningᴼ; Warningᴴᴱ; Warningᴴᴮ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; app₁; app₂; BinOpWarning; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; BlockMismatch; block₁; return; LocalVarMismatch; local₁; local₂; FunctionDefnMismatch; function₁; function₂; heap; expr; block; addr; +; -; *; /; <; >; <=; >=) +open import Luau.StrictMode using (Warningᴱ; Warningᴮ; Warningᴼ; Warningᴴᴱ; Warningᴴᴮ; UnallocatedAddress; UnboundVariable; FunctionCallMismatch; app₁; app₂; BinOpWarning; BinOpMismatch₁; BinOpMismatch₂; bin₁; bin₂; BlockMismatch; block₁; return; LocalVarMismatch; local₁; local₂; FunctionDefnMismatch; function₁; function₂; heap; expr; block; addr; +; -; *; /; <; >; <=; >=; ··) open import Luau.Substitution using (_[_/_]ᴮ; _[_/_]ᴱ; _[_/_]ᴮunless_; var_[_/_]ᴱwhenever_) -open import Luau.Syntax using (Expr; yes; var; val; var_∈_; _⟨_⟩∈_; _$_; addr; number; bool; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name; ==; ~=) +open import Luau.Syntax using (Expr; yes; var; val; var_∈_; _⟨_⟩∈_; _$_; addr; number; bool; string; binexp; nil; function_is_end; block_is_end; done; return; local_←_; _∙_; fun; arg; name; ==; ~=) open import Luau.Type using (Type; strict; nil; _⇒_; none; tgt; _≡ᵀ_; _≡ᴹᵀ_) open import Luau.TypeCheck(strict) using (_⊢ᴮ_∈_; _⊢ᴱ_∈_; _⊢ᴴᴮ_▷_∈_; _⊢ᴴᴱ_▷_∈_; nil; var; addr; app; function; block; done; return; local; orNone; tgtBinOp) open import Luau.Var using (_≡ⱽ_) @@ -19,9 +19,9 @@ open import Properties.Remember using (remember; _,_) open import Properties.Equality using (_≢_; sym; cong; trans; subst₁) open import Properties.Dec using (Dec; yes; no) open import Properties.Contradiction using (CONTRADICTION) -open import Properties.TypeCheck(strict) using (typeOfᴼ; typeOfᴹᴼ; typeOfⱽ; typeOfᴱ; typeOfᴮ; typeCheckᴱ; typeCheckᴮ; typeCheckᴼ; typeCheckᴴᴱ; typeCheckᴴᴮ; mustBeFunction; mustBeNumber) -open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶*_⊣_; _⊢_⟶ᴮ_⊣_; _⊢_⟶ᴱ_⊣_; app₁; app₂; function; beta; return; block; done; local; subst; binOp₀; binOp₁; binOp₂; refl; step; +; -; *; /; <; >; ==; ~=; <=; >=) -open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; bin₁; bin₂; block; local; return; +; -; *; /; <; >; <=; >=) +open import Properties.TypeCheck(strict) using (typeOfᴼ; typeOfᴹᴼ; typeOfⱽ; typeOfᴱ; typeOfᴮ; typeCheckᴱ; typeCheckᴮ; typeCheckᴼ; typeCheckᴴᴱ; typeCheckᴴᴮ; mustBeFunction; mustBeNumber; mustBeString) +open import Luau.OpSem using (_⟦_⟧_⟶_; _⊢_⟶*_⊣_; _⊢_⟶ᴮ_⊣_; _⊢_⟶ᴱ_⊣_; app₁; app₂; function; beta; return; block; done; local; subst; binOp₀; binOp₁; binOp₂; refl; step; +; -; *; /; <; >; ==; ~=; <=; >=; ··) +open import Luau.RuntimeError using (BinOpError; RuntimeErrorᴱ; RuntimeErrorᴮ; FunctionMismatch; BinOpMismatch₁; BinOpMismatch₂; UnboundVariable; SEGV; app₁; app₂; bin₁; bin₂; block; local; return; +; -; *; /; <; >; <=; >=; ··) open import Luau.RuntimeType using (valueType) src = Luau.Type.src strict @@ -86,6 +86,7 @@ heap-weakeningᴱ H (val (addr a)) (snoc {a = a} defn) | yes refl = warning (Una heap-weakeningᴱ H (val (addr a)) (snoc {a = b} p) | no q = ok (cong orNone (cong typeOfᴹᴼ (lookup-not-allocated p q))) heap-weakeningᴱ H (val (number n)) h = ok refl heap-weakeningᴱ H (val (bool b)) h = ok refl +heap-weakeningᴱ H (val (string x)) h = ok refl heap-weakeningᴱ H (binexp M op N) h = ok refl heap-weakeningᴱ H (M $ N) h with heap-weakeningᴱ H M h heap-weakeningᴱ H (M $ N) h | ok p = ok (cong tgt p) @@ -110,6 +111,7 @@ typeOf-val-not-none : ∀ {H Γ} v → OrWarningᴱ H (typeCheckᴱ H Γ (val v) typeOf-val-not-none nil = ok (λ ()) typeOf-val-not-none (number n) = ok (λ ()) typeOf-val-not-none (bool b) = ok (λ ()) +typeOf-val-not-none (string x) = ok (λ ()) typeOf-val-not-none {H = H} (addr a) with remember (H [ a ]ᴴ) typeOf-val-not-none {H = H} (addr a) | (just O , p) = ok (λ q → none-not-obj O (trans q (cong orNone (cong typeOfᴹᴼ p)))) typeOf-val-not-none {H = H} (addr a) | (nothing , p) = warning (UnallocatedAddress p) @@ -153,6 +155,7 @@ binOpPreservation H (<= m n) = refl binOpPreservation H (>= m n) = refl binOpPreservation H (== v w) = refl binOpPreservation H (~= v w) = refl +binOpPreservation H (·· v w) = refl preservationᴱ : ∀ H M {H′ M′} → (H ⊢ M ⟶ᴱ M′ ⊣ H′) → OrWarningᴴᴱ H (typeCheckᴴᴱ H ∅ M) (typeOfᴱ H ∅ M ≡ typeOfᴱ H′ ∅ M′) preservationᴮ : ∀ H B {H′ B′} → (H ⊢ B ⟶ᴮ B′ ⊣ H′) → OrWarningᴴᴮ H (typeCheckᴴᴮ H ∅ B) (typeOfᴮ H ∅ B ≡ typeOfᴮ H′ ∅ B′) @@ -444,6 +447,7 @@ runtimeBinOpWarning H v (< p) = < (λ q → p (mustBeNumber H ∅ v q)) runtimeBinOpWarning H v (> p) = > (λ q → p (mustBeNumber H ∅ v q)) runtimeBinOpWarning H v (<= p) = <= (λ q → p (mustBeNumber H ∅ v q)) runtimeBinOpWarning H v (>= p) = >= (λ q → p (mustBeNumber H ∅ v q)) +runtimeBinOpWarning H v (·· p) = ·· (λ q → p (mustBeString H ∅ v q)) runtimeWarningᴱ : ∀ H M → RuntimeErrorᴱ H M → Warningᴱ H (typeCheckᴱ H ∅ M) runtimeWarningᴮ : ∀ H B → RuntimeErrorᴮ H B → Warningᴮ H (typeCheckᴮ H ∅ B) diff --git a/prototyping/Properties/TypeCheck.agda b/prototyping/Properties/TypeCheck.agda index c144bc1..bec6884 100644 --- a/prototyping/Properties/TypeCheck.agda +++ b/prototyping/Properties/TypeCheck.agda @@ -8,10 +8,10 @@ open import Agda.Builtin.Equality using (_≡_; refl) open import Agda.Builtin.Bool using (Bool; true; false) open import FFI.Data.Maybe using (Maybe; just; nothing) open import FFI.Data.Either using (Either) -open import Luau.TypeCheck(m) using (_⊢ᴱ_∈_; _⊢ᴮ_∈_; ⊢ᴼ_; ⊢ᴴ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; nil; var; addr; number; bool; app; function; block; binexp; done; return; local; nothing; orNone; tgtBinOp) -open import Luau.Syntax using (Block; Expr; Value; BinaryOperator; yes; nil; addr; number; bool; val; var; binexp; _$_; function_is_end; block_is_end; _∙_; return; done; local_←_; _⟨_⟩; _⟨_⟩∈_; var_∈_; name; fun; arg; +; -; *; /; <; >; ==; ~=; <=; >=) -open import Luau.Type using (Type; nil; any; none; number; boolean; _⇒_; tgt) -open import Luau.RuntimeType using (RuntimeType; nil; number; function; valueType) +open import Luau.TypeCheck(m) using (_⊢ᴱ_∈_; _⊢ᴮ_∈_; ⊢ᴼ_; ⊢ᴴ_; _⊢ᴴᴱ_▷_∈_; _⊢ᴴᴮ_▷_∈_; nil; var; addr; number; bool; string; app; function; block; binexp; done; return; local; nothing; orNone; tgtBinOp) +open import Luau.Syntax using (Block; Expr; Value; BinaryOperator; yes; nil; addr; number; bool; string; val; var; binexp; _$_; function_is_end; block_is_end; _∙_; return; done; local_←_; _⟨_⟩; _⟨_⟩∈_; var_∈_; name; fun; arg; +; -; *; /; <; >; ==; ~=; <=; >=) +open import Luau.Type using (Type; nil; any; none; number; boolean; string; _⇒_; tgt) +open import Luau.RuntimeType using (RuntimeType; nil; number; function; string; valueType) open import Luau.VarCtxt using (VarCtxt; ∅; _↦_; _⊕_↦_; _⋒_; _⊝_) renaming (_[_] to _[_]ⱽ) open import Luau.Addr using (Addr) open import Luau.Var using (Var; _≡ⱽ_) @@ -37,6 +37,7 @@ typeOfⱽ H nil = just nil typeOfⱽ H (bool b) = just boolean typeOfⱽ H (addr a) = typeOfᴹᴼ (H [ a ]ᴴ) typeOfⱽ H (number n) = just number +typeOfⱽ H (string x) = just string typeOfᴱ : Heap yes → VarCtxt → (Expr yes) → Type typeOfᴮ : Heap yes → VarCtxt → (Block yes) → Type @@ -59,17 +60,23 @@ mustBeFunction H Γ (addr a) p = refl mustBeFunction H Γ (number n) p = CONTRADICTION (p refl) mustBeFunction H Γ (bool true) p = CONTRADICTION (p refl) mustBeFunction H Γ (bool false) p = CONTRADICTION (p refl) +mustBeFunction H Γ (string x) p = CONTRADICTION (p refl) mustBeNumber : ∀ H Γ v → (typeOfᴱ H Γ (val v) ≡ number) → (valueType(v) ≡ number) -mustBeNumber H Γ nil () mustBeNumber H Γ (addr a) p with remember (H [ a ]ᴴ) mustBeNumber H Γ (addr a) p | (just O , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p mustBeNumber H Γ (addr a) p | (just function f ⟨ var x ∈ T ⟩∈ U is B end , q) | () mustBeNumber H Γ (addr a) p | (nothing , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p mustBeNumber H Γ (addr a) p | nothing , q | () mustBeNumber H Γ (number n) p = refl -mustBeNumber H Γ (bool true) () -mustBeNumber H Γ (bool false) () + +mustBeString : ∀ H Γ v → (typeOfᴱ H Γ (val v) ≡ string) → (valueType(v) ≡ string) +mustBeString H Γ (addr a) p with remember (H [ a ]ᴴ) +mustBeString H Γ (addr a) p | (just O , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p +mustBeString H Γ (addr a) p | (just function f ⟨ var x ∈ T ⟩∈ U is B end , q) | () +mustBeString H Γ (addr a) p | (nothing , q) with trans (cong orNone (cong typeOfᴹᴼ (sym q))) p +mustBeString H Γ (addr a) p | (nothing , q) | () +mustBeString H Γ (string x) p = refl typeCheckᴱ : ∀ H Γ M → (Γ ⊢ᴱ M ∈ (typeOfᴱ H Γ M)) typeCheckᴮ : ∀ H Γ B → (Γ ⊢ᴮ B ∈ (typeOfᴮ H Γ B)) @@ -79,6 +86,7 @@ typeCheckᴱ H Γ (val nil) = nil typeCheckᴱ H Γ (val (addr a)) = addr (orNone (typeOfᴹᴼ (H [ a ]ᴴ))) typeCheckᴱ H Γ (val (number n)) = number typeCheckᴱ H Γ (val (bool b)) = bool +typeCheckᴱ H Γ (val (string x)) = string typeCheckᴱ H Γ (M $ N) = app (typeCheckᴱ H Γ M) (typeCheckᴱ H Γ N) typeCheckᴱ H Γ (function f ⟨ var x ∈ T ⟩∈ U is B end) = function (typeCheckᴮ H (Γ ⊕ x ↦ T) B) typeCheckᴱ H Γ (block var b ∈ T is B end) = block (typeCheckᴮ H Γ B) @@ -101,3 +109,4 @@ typeCheckᴴᴱ H Γ M = (typeCheckᴴ H , typeCheckᴱ H Γ M) typeCheckᴴᴮ : ∀ H Γ M → (Γ ⊢ᴴᴮ H ▷ M ∈ typeOfᴮ H Γ M) typeCheckᴴᴮ H Γ M = (typeCheckᴴ H , typeCheckᴮ H Γ M) + \ No newline at end of file diff --git a/prototyping/Tests/Interpreter/concat_number_and_string/in.lua b/prototyping/Tests/Interpreter/concat_number_and_string/in.lua new file mode 100644 index 0000000..ba5acb3 --- /dev/null +++ b/prototyping/Tests/Interpreter/concat_number_and_string/in.lua @@ -0,0 +1,3 @@ +local x: string = "hello" +local y: string = 37 +return x .. y diff --git a/prototyping/Tests/Interpreter/concat_number_and_string/out.txt b/prototyping/Tests/Interpreter/concat_number_and_string/out.txt new file mode 100644 index 0000000..a437438 --- /dev/null +++ b/prototyping/Tests/Interpreter/concat_number_and_string/out.txt @@ -0,0 +1,11 @@ +ANNOTATED PROGRAM: +local x : string = "hello" +local y : string = 37.0 +return x .. y + +RUNTIME ERROR: +value 37.0 is not a string + in return statement + +TYPE ERROR: +Local variable y has type string but expression has type number diff --git a/prototyping/Tests/Interpreter/concat_two_strings/in.lua b/prototyping/Tests/Interpreter/concat_two_strings/in.lua new file mode 100644 index 0000000..c6ccdce --- /dev/null +++ b/prototyping/Tests/Interpreter/concat_two_strings/in.lua @@ -0,0 +1,3 @@ +local x: string = "hello" +local y: string = "world" +return x .. y diff --git a/prototyping/Tests/Interpreter/concat_two_strings/out.txt b/prototyping/Tests/Interpreter/concat_two_strings/out.txt new file mode 100644 index 0000000..d45f589 --- /dev/null +++ b/prototyping/Tests/Interpreter/concat_two_strings/out.txt @@ -0,0 +1,6 @@ +ANNOTATED PROGRAM: +local x : string = "hello" +local y : string = "world" +return x .. y + +RAN WITH RESULT: "helloworld" diff --git a/prototyping/Tests/Interpreter/return_string/in.lua b/prototyping/Tests/Interpreter/return_string/in.lua new file mode 100644 index 0000000..67febb7 --- /dev/null +++ b/prototyping/Tests/Interpreter/return_string/in.lua @@ -0,0 +1 @@ +return "foo bar" diff --git a/prototyping/Tests/Interpreter/return_string/out.txt b/prototyping/Tests/Interpreter/return_string/out.txt new file mode 100644 index 0000000..81915c7 --- /dev/null +++ b/prototyping/Tests/Interpreter/return_string/out.txt @@ -0,0 +1,4 @@ +ANNOTATED PROGRAM: +return "foo bar" + +RAN WITH RESULT: "foo bar"