diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 3db3f29d5..806efa311 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -665,7 +665,7 @@ void Scanner::scanToken() case '.': // . Number advance(); - if (isDecimalDigit(m_char)) + if (m_kind != ScannerKind::ExperimentalSolidity && isDecimalDigit(m_char)) token = scanNumber('.'); else token = Token::Period; diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 1673e407f..abcdf6d5d 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -269,7 +269,19 @@ namespace solidity::langutil T(Leave, "leave", 0) \ \ T(NonExperimentalEnd, nullptr, 0) /* used as non-experimental enum end marker */ \ + /* Experimental Solidity specific keywords. */ \ + K(Class, "class", 0) \ + K(Instantiation, "instantiation", 0) \ + K(Word, "word", 0) \ + K(Integer, "integer", 0) \ + K(Itself, "itself", 0) \ + K(Void, "void", 0) \ + K(Pair, "pair", 0) \ + K(Fun, "fun", 0) \ + K(Unit, "unit", 0) \ + K(StaticAssert, "static_assert", 0) \ T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \ + \ /* Illegal token - not able to scan. */ \ T(Illegal, "ILLEGAL", 0) \ \ @@ -292,7 +304,12 @@ namespace TokenTraits constexpr size_t count() { return static_cast(Token::NUM_TOKENS); } // Predicates - constexpr bool isElementaryTypeName(Token tok) { return Token::Int <= tok && tok < Token::TypesEnd; } + constexpr bool isElementaryTypeName(Token tok) + { + return (Token::Int <= tok && tok < Token::TypesEnd) || + tok == Token::Word || tok == Token::Void || tok == Token::Integer || + tok == Token::Pair || tok == Token::Unit || tok == Token::Fun; + } constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; } constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; } constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd || @@ -324,46 +341,22 @@ namespace TokenTraits tok == Token::Default || tok == Token::For || tok == Token::Break || tok == Token::Continue || tok == Token::Leave || tok == Token::TrueLiteral || tok == Token::FalseLiteral || tok == Token::HexStringLiteral || tok == Token::Hex; } + constexpr bool isBuiltinTypeClassName(Token tok) + { + return tok == Token::Integer || (isBinaryOp(tok) && tok != Token::Comma) || + isCompareOp(tok) || isUnaryOp(tok) || (isAssignmentOp(tok) && tok != Token::Assign); + } constexpr bool isExperimentalSolidityKeyword(Token tok) { - return tok == Token::Assembly || tok == Token::Contract || tok == Token::External || tok == Token::Fallback; + return tok == Token::Assembly || tok == Token::Contract || tok == Token::External || tok == Token::Fallback || + tok == Token::Pragma || tok == Token::Import || tok == Token::As || tok == Token::Function || tok == Token::Let || + tok == Token::Return || tok == Token::Type || tok == Token::Bool || tok == Token::If || tok == Token::Else || + tok == Token::Do || tok == Token::While || tok == Token::For || tok == Token::Continue || tok == Token::Break || + (tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd); } - constexpr bool isExperimentalSolidityOnlyKeyword(Token) + constexpr bool isExperimentalSolidityOnlyKeyword(Token tok) { - return false; - } - - constexpr bool isExperimentalSolidityKeyword(Token token) - { - return - token == Token::Assembly || - token == Token::Contract || - token == Token::External || - token == Token::Fallback || - token == Token::Pragma || - token == Token::Import || - token == Token::As || - token == Token::Function || - token == Token::Let || - token == Token::Return || - token == Token::Type || - token == Token::If || - token == Token::Else || - token == Token::Do || - token == Token::While || - token == Token::For || - token == Token::Continue || - token == Token::Break; - // TODO: see isExperimentalSolidityKeyword below - // || (token > Token::NonExperimentalEnd && token < Token::ExperimentalEnd); - } - - constexpr bool isExperimentalSolidityOnlyKeyword(Token) - { - // TODO: use token > Token::NonExperimentalEnd && token < Token::ExperimentalEnd - // as soon as other experimental tokens are added. For now the comparison generates - // a warning from clang because it is always false. - return false; + return tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd; } bool isYulKeyword(std::string const& _literal); diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index b2c847363..46d43291e 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -46,8 +46,6 @@ set(sources analysis/TypeChecker.h analysis/ViewPureChecker.cpp analysis/ViewPureChecker.h - analysis/experimental/Analysis.cpp - analysis/experimental/Analysis.h ast/AST.cpp ast/AST.h ast/AST_accept.h @@ -92,8 +90,6 @@ set(sources codegen/ReturnInfo.cpp codegen/YulUtilFunctions.h codegen/YulUtilFunctions.cpp - codegen/experimental/IRGenerator.cpp - codegen/experimental/IRGenerator.h codegen/ir/Common.cpp codegen/ir/Common.h codegen/ir/IRGenerator.cpp @@ -188,6 +184,29 @@ set(sources parsing/Parser.cpp parsing/Parser.h parsing/Token.h + experimental/analysis/Analysis.cpp + experimental/analysis/Analysis.h + experimental/analysis/DebugWarner.cpp + experimental/analysis/DebugWarner.h + experimental/analysis/TypeInference.cpp + experimental/analysis/TypeInference.h + experimental/analysis/TypeRegistration.cpp + experimental/analysis/TypeRegistration.h + experimental/analysis/SyntaxRestrictor.cpp + experimental/analysis/SyntaxRestrictor.h + experimental/ast/Type.cpp + experimental/ast/Type.h + experimental/ast/TypeSystem.cpp + experimental/ast/TypeSystem.h + experimental/ast/TypeSystemHelper.cpp + experimental/ast/TypeSystemHelper.h + experimental/codegen/Common.h + experimental/codegen/Common.cpp + experimental/codegen/IRGenerationContext.h + experimental/codegen/IRGenerator.cpp + experimental/codegen/IRGenerator.h + experimental/codegen/IRGeneratorForStatements.cpp + experimental/codegen/IRGeneratorForStatements.h ) add_library(solidity ${sources}) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index d6ff63dbf..94d056811 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -38,11 +38,13 @@ namespace solidity::frontend NameAndTypeResolver::NameAndTypeResolver( GlobalContext& _globalContext, langutil::EVMVersion _evmVersion, - ErrorReporter& _errorReporter + ErrorReporter& _errorReporter, + bool _experimentalSolidity ): m_evmVersion(_evmVersion), m_errorReporter(_errorReporter), - m_globalContext(_globalContext) + m_globalContext(_globalContext), + m_experimentalSolidity(_experimentalSolidity) { m_scopes[nullptr] = std::make_shared(); for (Declaration const* declaration: _globalContext.declarations()) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 6ba591e8a..a6cf5ed76 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -59,7 +59,8 @@ public: NameAndTypeResolver( GlobalContext& _globalContext, langutil::EVMVersion _evmVersion, - langutil::ErrorReporter& _errorReporter + langutil::ErrorReporter& _errorReporter, + bool _experimentalSolidity ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. @@ -107,6 +108,7 @@ public: /// Sets the current scope. void setScope(ASTNode const* _node); + bool experimentalSolidity() const { return m_experimentalSolidity; } private: /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); @@ -132,6 +134,7 @@ private: DeclarationContainer* m_currentScope = nullptr; langutil::ErrorReporter& m_errorReporter; GlobalContext& m_globalContext; + bool m_experimentalSolidity = false; }; /** diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index e68002133..3db08ed14 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -112,6 +112,21 @@ bool ReferencesResolver::visit(VariableDeclaration const& _varDecl) if (_varDecl.documentation()) resolveInheritDoc(*_varDecl.documentation(), _varDecl.annotation()); + if (m_resolver.experimentalSolidity()) + { + solAssert(!_varDecl.hasTypeName()); + if (_varDecl.typeExpression()) + { + ScopedSaveAndRestore typeContext{m_typeContext, true}; + _varDecl.typeExpression()->accept(*this); + } + if (_varDecl.overrides()) + _varDecl.overrides()->accept(*this); + if (_varDecl.value()) + _varDecl.value()->accept(*this); + return false; + } + return true; } @@ -120,6 +135,8 @@ bool ReferencesResolver::visit(Identifier const& _identifier) auto declarations = m_resolver.nameFromCurrentScope(_identifier.name()); if (declarations.empty()) { + if (m_resolver.experimentalSolidity() && m_typeContext) + return false; std::string suggestions = m_resolver.similarNameSuggestions(_identifier.name()); std::string errorMessage = "Undeclared identifier."; if (!suggestions.empty()) @@ -140,7 +157,7 @@ bool ReferencesResolver::visit(Identifier const& _identifier) bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition) { - m_returnParameters.push_back(_functionDefinition.returnParameterList().get()); + m_functionDefinitions.push_back(&_functionDefinition); if (_functionDefinition.documentation()) resolveInheritDoc(*_functionDefinition.documentation(), _functionDefinition.annotation()); @@ -150,13 +167,13 @@ bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition) void ReferencesResolver::endVisit(FunctionDefinition const&) { - solAssert(!m_returnParameters.empty(), ""); - m_returnParameters.pop_back(); + solAssert(!m_functionDefinitions.empty(), ""); + m_functionDefinitions.pop_back(); } bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition) { - m_returnParameters.push_back(nullptr); + m_functionDefinitions.push_back(nullptr); if (_modifierDefinition.documentation()) resolveInheritDoc(*_modifierDefinition.documentation(), _modifierDefinition.annotation()); @@ -166,8 +183,8 @@ bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition) void ReferencesResolver::endVisit(ModifierDefinition const&) { - solAssert(!m_returnParameters.empty(), ""); - m_returnParameters.pop_back(); + solAssert(!m_functionDefinitions.empty(), ""); + m_functionDefinitions.pop_back(); } void ReferencesResolver::endVisit(IdentifierPath const& _path) @@ -227,11 +244,30 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) bool ReferencesResolver::visit(Return const& _return) { - solAssert(!m_returnParameters.empty(), ""); - _return.annotation().functionReturnParameters = m_returnParameters.back(); + solAssert(!m_functionDefinitions.empty(), ""); + _return.annotation().function = m_functionDefinitions.back(); + _return.annotation().functionReturnParameters = m_functionDefinitions.back() ? m_functionDefinitions.back()->returnParameterList().get() : nullptr; return true; } +bool ReferencesResolver::visit(BinaryOperation const& _binaryOperation) +{ + if (m_resolver.experimentalSolidity()) + { + _binaryOperation.leftExpression().accept(*this); + if (_binaryOperation.getOperator() == Token::Colon) + { + ScopedSaveAndRestore typeContext(m_typeContext, !m_typeContext); + _binaryOperation.rightExpression().accept(*this); + } + else + _binaryOperation.rightExpression().accept(*this); + return false; + } + else + return ASTConstVisitor::visit(_binaryOperation); +} + void ReferencesResolver::operator()(yul::FunctionDefinition const& _function) { solAssert(nativeLocationOf(_function) == originLocationOf(_function), ""); @@ -252,6 +288,47 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier) { solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), ""); + if (m_resolver.experimentalSolidity()) + { + std::vector splitName; + boost::split(splitName, _identifier.name.str(), boost::is_any_of(".")); + solAssert(!splitName.empty()); + if (splitName.size() > 2) + { + m_errorReporter.declarationError( + 0000_error, + nativeLocationOf(_identifier), + "Unsupported identifier in inline assembly." + ); + return; + } + std::string name = splitName.front(); + auto declarations = m_resolver.nameFromCurrentScope(name); + switch(declarations.size()) + { + case 0: + if (splitName.size() > 1) + m_errorReporter.declarationError( + 0000_error, + nativeLocationOf(_identifier), + "Unsupported identifier in inline assembly." + ); + break; + case 1: + m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front(); + m_yulAnnotation->externalReferences[&_identifier].suffix = splitName.size() > 1 ? splitName.back() : ""; + break; + default: + m_errorReporter.declarationError( + 0000_error, + nativeLocationOf(_identifier), + "Multiple matching identifiers. Resolving overloaded identifiers is not supported." + ); + break; + } + return; + } + static std::set suffixes{"slot", "offset", "length", "address", "selector"}; std::string suffix; for (std::string const& s: suffixes) diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 512a681b4..13752e9c9 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -85,6 +85,7 @@ private: bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(Return const& _return) override; bool visit(UsingForDirective const& _usingFor) override; + bool visit(BinaryOperation const& _binaryOperation) override; void operator()(yul::FunctionDefinition const& _function) override; void operator()(yul::Identifier const& _identifier) override; @@ -98,12 +99,13 @@ private: langutil::ErrorReporter& m_errorReporter; NameAndTypeResolver& m_resolver; langutil::EVMVersion m_evmVersion; - /// Stack of return parameters. - std::vector m_returnParameters; + /// Stack of function definitions. + std::vector m_functionDefinitions; bool const m_resolveInsideCode; InlineAssemblyAnnotation* m_yulAnnotation = nullptr; bool m_yulInsideFunction = false; + bool m_typeContext = false; }; } diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index b2dd73cc2..ac23ca36a 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -443,7 +443,9 @@ bool SyntaxChecker::visit(UsingForDirective const& _usingFor) bool SyntaxChecker::visit(FunctionDefinition const& _function) { - solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), ""); + if (m_sourceUnit && m_sourceUnit->experimentalSolidity()) + // Handled in experimental::SyntaxRestrictor instead. + return true; if (!_function.isFree() && !_function.isConstructor() && _function.noVisibilitySpecified()) { @@ -498,3 +500,13 @@ bool SyntaxChecker::visit(StructDefinition const& _struct) return true; } + +bool SyntaxChecker::visitNode(ASTNode const& _node) +{ + if (_node.experimentalSolidityOnly()) + { + solAssert(m_sourceUnit); + solAssert(m_sourceUnit->experimentalSolidity()); + } + return ASTConstVisitor::visitNode(_node); +} diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index f221df09f..69bda229f 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -97,6 +97,8 @@ private: bool visit(StructDefinition const& _struct) override; bool visit(Literal const& _literal) override; + bool visitNode(ASTNode const&) override; + langutil::ErrorReporter& m_errorReporter; bool m_useYulOptimizer = false; diff --git a/libsolidity/analysis/experimental/Analysis.cpp b/libsolidity/analysis/experimental/Analysis.cpp deleted file mode 100644 index 5138ff280..000000000 --- a/libsolidity/analysis/experimental/Analysis.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -// SPDX-License-Identifier: GPL-3.0 -#include - -using namespace solidity::langutil; -using namespace solidity::frontend::experimental; - -bool Analysis::check(ASTNode const&) -{ - return true; -} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index c0bf90091..bae169675 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -1057,3 +1057,15 @@ TryCatchClause const* TryStatement::errorClause() const { TryCatchClause const* TryStatement::fallbackClause() const { return findClause(m_clauses); } + +/// Experimental Solidity nodes +/// @{ +TypeClassDefinitionAnnotation& TypeClassDefinition::annotation() const +{ + return initAnnotation(); +} +TypeDeclarationAnnotation& TypeDefinition::annotation() const +{ + return initAnnotation(); +} +/// @} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index ca23b0d61..faaae7c87 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -125,6 +125,8 @@ public: bool operator!=(ASTNode const& _other) const { return !operator==(_other); } ///@} + virtual bool experimentalSolidityOnly() const { return false; } + protected: size_t const m_id = 0; @@ -960,7 +962,8 @@ public: ASTPointer const& _parameters, std::vector> _modifiers, ASTPointer const& _returnParameters, - ASTPointer const& _body + ASTPointer const& _body, + ASTPointer const& _experimentalReturnExpression = {} ): CallableDeclaration(_id, _location, _name, _nameLocation, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), StructurallyDocumented(_documentation), @@ -969,10 +972,12 @@ public: m_free(_free), m_kind(_kind), m_functionModifiers(std::move(_modifiers)), - m_body(_body) + m_body(_body), + m_experimentalReturnExpression(_experimentalReturnExpression) { solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, ""); solAssert(isOrdinary() == !name().empty(), ""); + // TODO: assert _returnParameters implies non-experimental _experimentalReturnExpression implies experimental } void accept(ASTVisitor& _visitor) override; @@ -1030,12 +1035,15 @@ public: ContractDefinition const* _searchStart = nullptr ) const override; + Expression const* experimentalReturnExpression() const { return m_experimentalReturnExpression.get(); } + private: StateMutability m_stateMutability; bool m_free; Token const m_kind; std::vector> m_functionModifiers; ASTPointer m_body; + ASTPointer m_experimentalReturnExpression; }; /** @@ -1070,7 +1078,8 @@ public: bool _isIndexed = false, Mutability _mutability = Mutability::Mutable, ASTPointer _overrides = nullptr, - Location _referenceLocation = Location::Unspecified + Location _referenceLocation = Location::Unspecified, + ASTPointer _typeExpression = {} ): Declaration(_id, _location, _name, std::move(_nameLocation), _visibility), StructurallyDocumented(std::move(_documentation)), @@ -1079,15 +1088,18 @@ public: m_isIndexed(_isIndexed), m_mutability(_mutability), m_overrides(std::move(_overrides)), - m_location(_referenceLocation) + m_location(_referenceLocation), + m_typeExpression(std::move(_typeExpression)) { - solAssert(m_typeName, ""); + // TODO: consider still asserting unless we are in experimental solidity. + // solAssert(m_typeName, ""); solAssert(!m_typeExpression, ""); } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; + bool hasTypeName() const { return m_typeName != nullptr; } TypeName const& typeName() const { return *m_typeName; } ASTPointer const& value() const { return m_value; } @@ -1142,6 +1154,7 @@ public: /// @returns null when it is not accessible as a function. FunctionTypePointer functionType(bool /*_internal*/) const override; + ASTPointer const& typeExpression() const { return m_typeExpression; } VariableDeclarationAnnotation& annotation() const override; protected: @@ -1157,6 +1170,7 @@ private: Mutability m_mutability = Mutability::Mutable; ASTPointer m_overrides; ///< Contains the override specifier node Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type. + ASTPointer m_typeExpression; }; /** @@ -2138,7 +2152,8 @@ public: ): Expression(_id, _location), m_left(std::move(_left)), m_operator(_operator), m_right(std::move(_right)) { - solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); + // TODO: assert against colon for non-experimental solidity + solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator) || _operator == Token::Colon || _operator == Token::RightArrow, ""); } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -2449,4 +2464,136 @@ private: /// @} +/// Experimental Solidity nodes +/// @{ +class TypeClassDefinition: public Declaration, public StructurallyDocumented, public ScopeOpener +{ +public: + TypeClassDefinition( + int64_t _id, + SourceLocation const& _location, + ASTPointer _typeVariable, + ASTPointer const& _name, + SourceLocation _nameLocation, + ASTPointer const& _documentation, + std::vector> _subNodes + ): + Declaration(_id, _location, _name, std::move(_nameLocation)), + StructurallyDocumented(_documentation), + m_typeVariable(std::move(_typeVariable)), + m_subNodes(std::move(_subNodes)) + {} + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + VariableDeclaration const& typeVariable() const { return *m_typeVariable; } + std::vector> const& subNodes() const { return m_subNodes; } + + TypeClassDefinitionAnnotation& annotation() const override; + + Type const* type() const override { solAssert(false, "Requested type of experimental solidity node."); } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_typeVariable; + std::vector> m_subNodes; +}; +class TypeClassInstantiation: public ASTNode, public ScopeOpener +{ +public: + TypeClassInstantiation( + int64_t _id, + SourceLocation const& _location, + ASTPointer _typeConstructor, + ASTPointer _argumentSorts, + ASTPointer _class, + std::vector> _subNodes + ): + ASTNode(_id, _location), + m_typeConstructor(std::move(_typeConstructor)), + m_argumentSorts(std::move(_argumentSorts)), + m_class(std::move(_class)), + m_subNodes(std::move(_subNodes)) + {} + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + TypeName const& typeConstructor() const { return *m_typeConstructor; } + ParameterList const* argumentSorts() const { return m_argumentSorts.get(); } + TypeClassName const& typeClass() const { return *m_class; } + std::vector> const& subNodes() const { return m_subNodes; } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_typeConstructor; + ASTPointer m_argumentSorts; + ASTPointer m_class; + std::vector> m_subNodes; +}; + +class TypeDefinition: public Declaration, public ScopeOpener +{ +public: + TypeDefinition( + int64_t _id, + SourceLocation const& _location, + ASTPointer _name, + SourceLocation _nameLocation, + ASTPointer _arguments, + ASTPointer _typeExpression + ): + Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default), + m_arguments(std::move(_arguments)), + m_typeExpression(std::move(_typeExpression)) + { + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Type const* type() const override { return nullptr; } + + TypeDeclarationAnnotation& annotation() const override; + + ParameterList const* arguments() const { return m_arguments.get(); } + Expression const* typeExpression() const { return m_typeExpression.get(); } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_arguments; + ASTPointer m_typeExpression; +}; + +class TypeClassName: public ASTNode +{ +public: + TypeClassName( + int64_t _id, + SourceLocation const& _location, + std::variant> _name + ): + ASTNode(_id, _location), + m_name(std::move(_name)) + { + if (Token const* token = std::get_if(&_name)) + solAssert(TokenTraits::isBuiltinTypeClassName(*token)); + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + bool experimentalSolidityOnly() const override { return true; } + + std::variant> name() const { return m_name; } +private: + std::variant> m_name; +}; + +/// @} + } diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 2573015c2..e2b4ab7a1 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -244,6 +244,8 @@ struct ReturnAnnotation: StatementAnnotation { /// Reference to the return parameters of the function. ParameterList const* functionReturnParameters = nullptr; + /// Reference to the function containing the return statement. + FunctionDefinition const* function = nullptr; }; struct TypeNameAnnotation: ASTAnnotation @@ -341,4 +343,12 @@ struct FunctionCallAnnotation: ExpressionAnnotation bool tryCall = false; }; +/// Experimental Solidity annotations. +/// Used to intergrate with name and type resolution. +/// @{ +struct TypeClassDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation +{ +}; +/// @} + } diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 79e6a4ecf..f13b6e55d 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -99,6 +99,14 @@ class ElementaryTypeNameExpression; class Literal; class StructuredDocumentation; +/// Experimental Solidity nodes +/// @{ +class TypeClassDefinition; +class TypeClassInstantiation; +class TypeClassName; +class TypeDefinition; +/// @} + class VariableScope; template diff --git a/libsolidity/ast/ASTJsonExporter.cpp b/libsolidity/ast/ASTJsonExporter.cpp index b15917809..65dd018d4 100644 --- a/libsolidity/ast/ASTJsonExporter.cpp +++ b/libsolidity/ast/ASTJsonExporter.cpp @@ -1032,6 +1032,15 @@ void ASTJsonExporter::endVisit(EventDefinition const&) m_inEvent = false; } +bool ASTJsonExporter::visitNode(ASTNode const& _node) +{ + solAssert(false, _node.experimentalSolidityOnly() ? + "Attempt to export an AST of experimental solidity." : + "Attempt to export an AST that contains unexpected nodes." + ); + return false; +} + std::string ASTJsonExporter::location(VariableDeclaration::Location _location) { switch (_location) diff --git a/libsolidity/ast/ASTJsonExporter.h b/libsolidity/ast/ASTJsonExporter.h index 71d15ce0a..ff3b94fa2 100644 --- a/libsolidity/ast/ASTJsonExporter.h +++ b/libsolidity/ast/ASTJsonExporter.h @@ -130,6 +130,7 @@ public: void endVisit(EventDefinition const&) override; + bool visitNode(ASTNode const& _node) override; private: void setJsonNode( ASTNode const& _node, diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index ef03e6339..959cd5dce 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -109,6 +109,13 @@ public: virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); } virtual bool visit(StructuredDocumentation& _node) { return visitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual bool visit(TypeClassDefinition& _node) { return visitNode(_node); } + virtual bool visit(TypeClassInstantiation& _node) { return visitNode(_node); } + virtual bool visit(TypeDefinition& _node) { return visitNode(_node); } + virtual bool visit(TypeClassName& _node) { return visitNode(_node); } + /// @} virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); } @@ -165,6 +172,13 @@ public: virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); } virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual void endVisit(TypeClassDefinition& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassInstantiation& _node) { endVisitNode(_node); } + virtual void endVisit(TypeDefinition& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassName& _node) { endVisitNode(_node); } + /// @} protected: /// Generic function called by default for each node, to be overridden by derived classes @@ -243,6 +257,13 @@ public: virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); } virtual bool visit(StructuredDocumentation const& _node) { return visitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual bool visit(TypeClassDefinition const& _node) { return visitNode(_node); } + virtual bool visit(TypeClassInstantiation const& _node) { return visitNode(_node); } + virtual bool visit(TypeDefinition const& _node) { return visitNode(_node); } + virtual bool visit(TypeClassName const& _node) { return visitNode(_node); } + /// @} virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); } @@ -299,6 +320,13 @@ public: virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); } virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual void endVisit(TypeClassDefinition const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassInstantiation const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeDefinition const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassName const& _node) { endVisitNode(_node); } + /// @} protected: /// Generic function called by default for each node, to be overridden by derived classes diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 78dc0e5dd..eaf179e73 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -265,6 +265,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + if (m_experimentalReturnExpression) + m_experimentalReturnExpression->accept(_visitor); listAccept(m_functionModifiers, _visitor); if (m_body) m_body->accept(_visitor); @@ -283,6 +285,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + if (m_experimentalReturnExpression) + m_experimentalReturnExpression->accept(_visitor); listAccept(m_functionModifiers, _visitor); if (m_body) m_body->accept(_visitor); @@ -296,6 +300,8 @@ void VariableDeclaration::accept(ASTVisitor& _visitor) { if (m_typeName) m_typeName->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); if (m_value) @@ -310,6 +316,8 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const { if (m_typeName) m_typeName->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); if (m_value) @@ -1024,4 +1032,102 @@ void Literal::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +/// Experimental Solidity nodes +/// @{ +void TypeClassDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_typeVariable->accept(_visitor); + if (m_documentation) + m_documentation->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_typeVariable->accept(_visitor); + if (m_documentation) + m_documentation->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassInstantiation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_typeConstructor->accept(_visitor); + if(m_argumentSorts) + m_argumentSorts->accept(_visitor); + m_class->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassInstantiation::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_typeConstructor->accept(_visitor); + if(m_argumentSorts) + m_argumentSorts->accept(_visitor); + m_class->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (m_arguments) + m_arguments->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TypeDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (m_arguments) + m_arguments->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); + } + _visitor.endVisit(*this); +} + + +void TypeClassName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (auto* path = std::get_if>(&m_name)) + (*path)->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (auto* path = std::get_if>(&m_name)) + (*path)->accept(_visitor); + } + _visitor.endVisit(*this); +} +/// @} + } diff --git a/libsolidity/codegen/experimental/IRGenerator.cpp b/libsolidity/codegen/experimental/IRGenerator.cpp deleted file mode 100644 index 0db02a163..000000000 --- a/libsolidity/codegen/experimental/IRGenerator.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -// SPDX-License-Identifier: GPL-3.0 - -#include - -#include - -#include -#include -#include -#include - -#include - -#include - -#include - -using namespace std; -using namespace solidity; -using namespace solidity::frontend::experimental; -using namespace solidity::langutil; -using namespace solidity::util; - -string IRGenerator::run( - ContractDefinition const& _contract, - bytes const& /*_cborMetadata*/, - map const& /*_otherYulSources*/ -) const -{ - - Whiskers t(R"( - object "" { - code { - codecopy(0, dataoffset(""), datasize("")) - return(0, datasize("")) - } - object "" { - code { - - } - } - } - )"); - t("CreationObject", IRNames::creationObject(_contract)); - t("DeployedObject", IRNames::deployedObject(_contract)); - t("code", generate(_contract)); - - return t.render(); -} - -string IRGenerator::generate(ContractDefinition const& _contract) const -{ - std::stringstream code; - code << "{\n"; - if (_contract.fallbackFunction()) - { - code << IRNames::function(*_contract.fallbackFunction()) << "()\n"; - } - code << "revert(0,0)\n"; - code << "}\n"; - - for (FunctionDefinition const* f: _contract.definedFunctions()) - code << generate(*f); - - return code.str(); -} - -string IRGenerator::generate(FunctionDefinition const& _function) const -{ - std::stringstream code; - code << "function " << IRNames::function(_function) << "() {\n"; - for (auto _statement: _function.body().statements()) - { - if (auto assembly = dynamic_cast(_statement.get())) - code << generate(*assembly) << "\n"; - else - solUnimplemented("Unsupported statement type."); - } - code << "}\n"; - return code.str(); -} - -namespace { - -struct CopyTranslate: public yul::ASTCopier -{ - CopyTranslate( - yul::Dialect const& _dialect, - map _references - ): m_dialect(_dialect), m_references(std::move(_references)) {} - - using ASTCopier::operator(); - - yul::Expression operator()(yul::Identifier const& _identifier) override - { - // The operator() function is only called in lvalue context. In rvalue context, - // only translate(yul::Identifier) is called. - if (m_references.count(&_identifier)) - return translateReference(_identifier); - else - return ASTCopier::operator()(_identifier); - } - - yul::YulString translateIdentifier(yul::YulString _name) override - { - if (m_dialect.builtin(_name)) - return _name; - else - return yul::YulString{"usr$" + _name.str()}; - } - - yul::Identifier translate(yul::Identifier const& _identifier) override - { - if (!m_references.count(&_identifier)) - return ASTCopier::translate(_identifier); - - yul::Expression translated = translateReference(_identifier); - solAssert(holds_alternative(translated)); - return get(std::move(translated)); - } - -private: - - /// Translates a reference to a local variable, potentially including - /// a suffix. Might return a literal, which causes this to be invalid in - /// lvalue-context. - yul::Expression translateReference(yul::Identifier const&) - { - solUnimplemented("External references in inline assembly not implemented."); - } - - yul::Dialect const& m_dialect; - map m_references; -}; - -} - -string IRGenerator::generate(InlineAssembly const& _assembly) const -{ - CopyTranslate bodyCopier{_assembly.dialect(), {}}; - yul::Statement modified = bodyCopier(_assembly.operations()); - solAssert(holds_alternative(modified)); - return yul::AsmPrinter()(std::get(modified)); -} \ No newline at end of file diff --git a/libsolidity/experimental/analysis/Analysis.cpp b/libsolidity/experimental/analysis/Analysis.cpp index 672aa4f87..7695f15e8 100644 --- a/libsolidity/experimental/analysis/Analysis.cpp +++ b/libsolidity/experimental/analysis/Analysis.cpp @@ -16,19 +16,143 @@ */ // SPDX-License-Identifier: GPL-3.0 #include +#include +#include +#include +#include -#include - +using namespace std; using namespace solidity::langutil; using namespace solidity::frontend::experimental; -bool Analysis::check(std::vector> const&) +// TODO: creating all of them for all nodes up front may be wasteful, we should improve the mechanism. +struct Analysis::AnnotationContainer { - m_errorReporter.error( - 6547_error, - Error::Type::UnimplementedFeatureError, - SourceLocation{}, - "Experimental Analysis is not implemented yet." - ); - return false; + TypeRegistration::Annotation typeRegistrationAnnotation; + TypeInference::Annotation typeInferenceAnnotation; +}; + +struct Analysis::GlobalAnnotationContainer +{ + TypeRegistration::GlobalAnnotation typeRegistrationAnnotation; + TypeInference::GlobalAnnotation typeInferenceAnnotation; +}; + +template<> +TypeRegistration::Annotation& solidity::frontend::experimental::detail::AnnotationFetcher::get(ASTNode const& _node) +{ + return analysis.annotationContainer(_node).typeRegistrationAnnotation; +} + +template<> +TypeRegistration::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().typeRegistrationAnnotation; +} + + +template<> +TypeRegistration::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().typeRegistrationAnnotation; +} + +template<> +TypeRegistration::Annotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get(ASTNode const& _node) const +{ + return analysis.annotationContainer(_node).typeRegistrationAnnotation; +} + +template<> +TypeInference::Annotation& solidity::frontend::experimental::detail::AnnotationFetcher::get(ASTNode const& _node) +{ + return analysis.annotationContainer(_node).typeInferenceAnnotation; +} + +template<> +TypeInference::Annotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get(ASTNode const& _node) const +{ + return analysis.annotationContainer(_node).typeInferenceAnnotation; +} + +template<> +TypeInference::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().typeInferenceAnnotation; +} + + +template<> +TypeInference::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().typeInferenceAnnotation; +} + +Analysis::AnnotationContainer& Analysis::annotationContainer(ASTNode const& _node) +{ + solAssert(_node.id() > 0); + size_t id = static_cast(_node.id()); + solAssert(id <= m_maxAstId); + return m_annotations[id]; +} + +Analysis::AnnotationContainer const& Analysis::annotationContainer(ASTNode const& _node) const +{ + solAssert(_node.id() > 0); + size_t id = static_cast(_node.id()); + solAssert(id <= m_maxAstId); + return m_annotations[id]; +} + +Analysis::Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId): + m_errorReporter(_errorReporter), + m_maxAstId(_maxAstId), + m_annotations(std::make_unique(static_cast(_maxAstId + 1))), + m_globalAnnotation(std::make_unique()) +{ +} + +Analysis::~Analysis() +{} + +template +std::tuple...> makeIndexTuple(std::index_sequence) { + return std::make_tuple( std::integral_constant{}...); +} + +bool Analysis::check(vector> const& _sourceUnits) +{ + using AnalysisSteps = std::tuple; + + return std::apply([&](auto... _indexTuple) { + return ([&](auto&& _step) { + for (auto source: _sourceUnits) + if (!_step.analyze(*source)) + return false; + return true; + }(std::tuple_element_t{*this}) && ...); + }, makeIndexTuple(std::make_index_sequence>{})); + +/* + { + SyntaxRestrictor syntaxRestrictor{*this}; + for (auto source: _sourceUnits) + if (!syntaxRestrictor.analyze(*source)) + return false; + } + + { + TypeRegistration typeRegistration{*this}; + for (auto source: _sourceUnits) + if (!typeRegistration.analyze(*source)) + return false; + } + { + TypeInference typeInference{*this}; + for (auto source: _sourceUnits) + if (!typeInference.analyze(*source)) + return false; + } + return true; + */ } diff --git a/libsolidity/experimental/analysis/Analysis.h b/libsolidity/experimental/analysis/Analysis.h index 729ff2b93..780e25015 100644 --- a/libsolidity/experimental/analysis/Analysis.h +++ b/libsolidity/experimental/analysis/Analysis.h @@ -17,12 +17,16 @@ // SPDX-License-Identifier: GPL-3.0 #pragma once -#include +#include + +#include #include +#include namespace solidity::frontend { class SourceUnit; +class ASTNode; } namespace solidity::langutil @@ -33,17 +37,72 @@ class ErrorReporter; namespace solidity::frontend::experimental { +class TypeSystem; + +class Analysis; + +namespace detail +{ +template +struct AnnotationFetcher +{ + Analysis& analysis; + typename Step::Annotation& get(ASTNode const& _node); + typename Step::GlobalAnnotation& get(); +}; +template +struct ConstAnnotationFetcher +{ + Analysis const& analysis; + typename Step::Annotation const& get(ASTNode const& _node) const; + typename Step::GlobalAnnotation const& get() const; +}; +} + class Analysis { + struct AnnotationContainer; + struct GlobalAnnotationContainer; public: - Analysis(langutil::ErrorReporter& _errorReporter): - m_errorReporter(_errorReporter) - {} - + Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId); + Analysis(Analysis const&) = delete; + ~Analysis(); + Analysis const& operator=(Analysis const&) = delete; bool check(std::vector> const& _sourceUnits); - + langutil::ErrorReporter& errorReporter() { return m_errorReporter; } + uint64_t maxAstId() const { return m_maxAstId; } + TypeSystem& typeSystem() { return m_typeSystem; } + TypeSystem const& typeSystem() const { return m_typeSystem; } + template + typename Step::Annotation& annotation(ASTNode const& _node) + { + return detail::AnnotationFetcher{*this}.get(_node); + } + template + typename Step::Annotation const& annotation(ASTNode const& _node) const + { + return detail::ConstAnnotationFetcher{*this}.get(_node); + } + template + typename Step::GlobalAnnotation& annotation() + { + return detail::AnnotationFetcher{*this}.get(); + } + template + typename Step::GlobalAnnotation const& annotation() const + { + return detail::ConstAnnotationFetcher{*this}.get(); + } + AnnotationContainer& annotationContainer(ASTNode const& _node); + AnnotationContainer const& annotationContainer(ASTNode const& _node) const; + GlobalAnnotationContainer& annotationContainer() { return *m_globalAnnotation; } + GlobalAnnotationContainer const& annotationContainer() const { return *m_globalAnnotation; } private: langutil::ErrorReporter& m_errorReporter; + TypeSystem m_typeSystem; + uint64_t m_maxAstId = 0; + std::unique_ptr m_annotations; + std::unique_ptr m_globalAnnotation; }; } diff --git a/libsolidity/experimental/analysis/DebugWarner.cpp b/libsolidity/experimental/analysis/DebugWarner.cpp new file mode 100644 index 000000000..cf65d8da8 --- /dev/null +++ b/libsolidity/experimental/analysis/DebugWarner.cpp @@ -0,0 +1,57 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include + +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +DebugWarner::DebugWarner(Analysis& _analysis): m_analysis(_analysis), m_errorReporter(_analysis.errorReporter()) +{} + +bool DebugWarner::analyze(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + return !Error::containsErrors(m_errorReporter.errors()); +} + +bool DebugWarner::visitNode(ASTNode const& _node) +{ + auto const& typeInferenceAnnotation = m_analysis.annotation(_node); + if (typeInferenceAnnotation.type) + { + Type type = *typeInferenceAnnotation.type; + Sort sort = m_analysis.typeSystem().env().sort(type); + std::string sortString; + if (sort.classes.size() != 1 || *sort.classes.begin() != m_analysis.typeSystem().primitiveClass(PrimitiveClass::Type)) + sortString = ":" + TypeSystemHelpers{m_analysis.typeSystem()}.sortToString(m_analysis.typeSystem().env().sort(type)); + m_errorReporter.info( + 0000_error, + _node.location(), + "Inferred type: " + TypeEnvironmentHelpers{m_analysis.typeSystem().env()}.typeToString(type) + sortString + ); + } + return true; +} diff --git a/libsolidity/analysis/experimental/Analysis.h b/libsolidity/experimental/analysis/DebugWarner.h similarity index 74% rename from libsolidity/analysis/experimental/Analysis.h rename to libsolidity/experimental/analysis/DebugWarner.h index 9433508e4..a7b5ac6d0 100644 --- a/libsolidity/analysis/experimental/Analysis.h +++ b/libsolidity/experimental/analysis/DebugWarner.h @@ -17,26 +17,25 @@ // SPDX-License-Identifier: GPL-3.0 #pragma once -namespace solidity::frontend -{ -class ASTNode; -} +#include -namespace solidity::langutil -{ -class ErrorReporter; -} +#include namespace solidity::frontend::experimental { +class Analysis; -class Analysis +class DebugWarner: public ASTConstVisitor { public: - Analysis(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) - {} - bool check(ASTNode const& _ast); + DebugWarner(Analysis& _analysis); + + bool analyze(ASTNode const& _astRoot); + private: + bool visitNode(ASTNode const& _node) override; + + Analysis& m_analysis; langutil::ErrorReporter& m_errorReporter; }; diff --git a/libsolidity/experimental/analysis/SyntaxRestrictor.cpp b/libsolidity/experimental/analysis/SyntaxRestrictor.cpp new file mode 100644 index 000000000..75ed77b2d --- /dev/null +++ b/libsolidity/experimental/analysis/SyntaxRestrictor.cpp @@ -0,0 +1,113 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +SyntaxRestrictor::SyntaxRestrictor(Analysis& _analysis): m_errorReporter(_analysis.errorReporter()) +{} + +bool SyntaxRestrictor::analyze(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + return !Error::containsErrors(m_errorReporter.errors()); +} + +bool SyntaxRestrictor::visitNode(ASTNode const& _node) +{ + if (!_node.experimentalSolidityOnly()) + m_errorReporter.syntaxError(0000_error, _node.location(), "Unsupported AST node."); + return false; +} + +bool SyntaxRestrictor::visit(ContractDefinition const& _contractDefinition) +{ + if (_contractDefinition.contractKind() != ContractKind::Contract) + m_errorReporter.syntaxError(0000_error, _contractDefinition.location(), "Only contracts are supported."); + if (!_contractDefinition.baseContracts().empty()) + m_errorReporter.syntaxError(0000_error, _contractDefinition.location(), "Inheritance unsupported."); + return true; +} + +bool SyntaxRestrictor::visit(FunctionDefinition const& _functionDefinition) +{ + if (!_functionDefinition.isImplemented()) + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Functions must be implemented."); + if (!_functionDefinition.modifiers().empty()) + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Function may not have modifiers."); + if (_functionDefinition.overrides()) + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Function may not have override specifiers."); + solAssert(!_functionDefinition.returnParameterList()); + if (_functionDefinition.isFree()) + { + if (_functionDefinition.stateMutability() != StateMutability::NonPayable) + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Free functions may not have a mutability."); + } + else + { + if (_functionDefinition.isFallback()) + { + if (_functionDefinition.visibility() != Visibility::External) + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Fallback function must be external."); + } + else + m_errorReporter.syntaxError(0000_error, _functionDefinition.location(), "Only fallback functions are supported in contracts."); + } + + return true; +} + +bool SyntaxRestrictor::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + if (_variableDeclarationStatement.declarations().size() == 1) + { + if (!_variableDeclarationStatement.declarations().front()) + m_errorReporter.syntaxError(0000_error, _variableDeclarationStatement.initialValue()->location(), "Variable declaration has to declare a single variable."); + } + else + m_errorReporter.syntaxError(0000_error, _variableDeclarationStatement.initialValue()->location(), "Variable declarations can only declare a single variable."); + return true; +} + +bool SyntaxRestrictor::visit(VariableDeclaration const& _variableDeclaration) +{ + if (_variableDeclaration.value()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.value()->location(), "Variable declarations with initial value not supported."); + if (_variableDeclaration.isStateVariable()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "State variables are not supported."); + if (!_variableDeclaration.isLocalVariable()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Only local variables are supported."); + if (_variableDeclaration.mutability() != VariableDeclaration::Mutability::Mutable) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Only mutable variables are supported."); + if (_variableDeclaration.isIndexed()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Indexed variables are not supported."); + if (!_variableDeclaration.noVisibilitySpecified()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Variables with visibility not supported."); + if (_variableDeclaration.overrides()) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Variables with override specifier not supported."); + if (_variableDeclaration.referenceLocation() != VariableDeclaration::Location::Unspecified) + m_errorReporter.syntaxError(0000_error, _variableDeclaration.location(), "Variables with reference location not supported."); + return true; +} diff --git a/libsolidity/experimental/analysis/SyntaxRestrictor.h b/libsolidity/experimental/analysis/SyntaxRestrictor.h new file mode 100644 index 000000000..86d2d1b49 --- /dev/null +++ b/libsolidity/experimental/analysis/SyntaxRestrictor.h @@ -0,0 +1,67 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include + +#include +#include + +namespace solidity::frontend::experimental +{ +class Analysis; + +class SyntaxRestrictor: public ASTConstVisitor +{ +public: + SyntaxRestrictor(Analysis& _analysis); + + bool analyze(ASTNode const& _astRoot); + +private: + /// Default visit will reject all AST nodes that are not explicitly allowed. + bool visitNode(ASTNode const& _node) override; + + bool visit(SourceUnit const&) override { return true; } + bool visit(PragmaDirective const&) override { return true; } + bool visit(ImportDirective const&) override { return true; } + bool visit(ContractDefinition const& _contractDefinition) override; + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(ExpressionStatement const&) override { return true; } + bool visit(FunctionCall const&) override { return true; } + bool visit(Assignment const&) override { return true; } + bool visit(Block const&) override { return true; } + bool visit(InlineAssembly const&) override { return true; } + bool visit(Identifier const&) override { return true; } + bool visit(IdentifierPath const&) override { return true; } + bool visit(IfStatement const&) override { return true; } + bool visit(VariableDeclarationStatement const&) override; + bool visit(VariableDeclaration const&) override; + bool visit(ElementaryTypeName const&) override { return true; } + bool visit(ParameterList const&) override { return true; } + bool visit(Return const&) override { return true; } + bool visit(MemberAccess const&) override { return true; } + bool visit(BinaryOperation const&) override { return true; } + bool visit(ElementaryTypeNameExpression const&) override { return true; } + bool visit(TupleExpression const&) override { return true; } + bool visit(Literal const&) override { return true; } + + langutil::ErrorReporter& m_errorReporter; +}; + +} diff --git a/libsolidity/experimental/analysis/TypeInference.cpp b/libsolidity/experimental/analysis/TypeInference.cpp new file mode 100644 index 000000000..52a0679da --- /dev/null +++ b/libsolidity/experimental/analysis/TypeInference.cpp @@ -0,0 +1,1247 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +TypeInference::TypeInference(Analysis& _analysis): +m_analysis(_analysis), +m_errorReporter(_analysis.errorReporter()), +m_typeSystem(_analysis.typeSystem()) +{ + auto declareBuiltinClass = [&](std::string _name, BuiltinClass _class, auto _memberCreator, Sort _sort = {}) -> TypeClass { + Type type = m_typeSystem.freshTypeVariable(std::move(_sort)); + auto result = m_typeSystem.declareTypeClass( + type, + _name, + nullptr + ); + if (auto error = get_if(&result)) + solAssert(!error, *error); + TypeClass resultClass = get(result); + // TODO: validation? + annotation().typeClassFunctions[resultClass] = _memberCreator(type); + solAssert(annotation().builtinClassesByName.emplace(_name, _class).second); + return annotation().builtinClasses.emplace(_class, resultClass).first->second; + }; + TypeSystemHelpers helper{m_typeSystem}; + using MemberList = std::map; + + declareBuiltinClass("integer", BuiltinClass::Integer, [&](Type _typeVar) -> MemberList { + return { + { + "fromInteger", + helper.functionType(m_typeSystem.type(PrimitiveType::Integer, {}), _typeVar) + } + }; + }); + + auto defineBinaryMonoidalOperator = [&](std::string _className, BuiltinClass _class, Token _token, std::string _functionName) { + TypeClass typeClass = declareBuiltinClass(_className, _class, [&](Type _typeVar) -> MemberList { + return { + { + _functionName, + helper.functionType(helper.tupleType({_typeVar, _typeVar}), _typeVar) + } + }; + }); + annotation().operators.emplace(_token, std::make_tuple(typeClass, _functionName)); + }; + + defineBinaryMonoidalOperator("*", BuiltinClass::Mul, Token::Mul, "mul"); + defineBinaryMonoidalOperator("+", BuiltinClass::Add, Token::Add, "add"); + + auto defineBinaryCompareOperator = [&](std::string _className, BuiltinClass _class, Token _token, std::string _functionName) { + TypeClass typeClass = declareBuiltinClass(_className, _class, [&](Type _typeVar) -> MemberList { + return { + { + _functionName, + helper.functionType(helper.tupleType({_typeVar, _typeVar}), m_typeSystem.type(PrimitiveType::Bool, {})) + } + }; + }); + annotation().operators.emplace(_token, std::make_tuple(typeClass, _functionName)); + }; + defineBinaryCompareOperator("==", BuiltinClass::Equal, Token::Equal, "eq"); + defineBinaryCompareOperator("<", BuiltinClass::Less, Token::LessThan, "lt"); + defineBinaryCompareOperator("<=", BuiltinClass::LessOrEqual, Token::LessThanOrEqual, "leq"); + defineBinaryCompareOperator(">", BuiltinClass::Greater, Token::GreaterThan, "gt"); + defineBinaryCompareOperator(">=", BuiltinClass::GreaterOrEqual, Token::GreaterThanOrEqual, "geq"); + + m_voidType = m_typeSystem.type(PrimitiveType::Void, {}); + m_wordType = m_typeSystem.type(PrimitiveType::Word, {}); + m_integerType = m_typeSystem.type(PrimitiveType::Integer, {}); + m_unitType = m_typeSystem.type(PrimitiveType::Unit, {}); + m_boolType = m_typeSystem.type(PrimitiveType::Bool, {}); + m_env = &m_typeSystem.env(); + + { + auto [members, newlyInserted] = annotation().members.emplace(m_typeSystem.constructor(PrimitiveType::Bool), map{}); + solAssert(newlyInserted); + members->second.emplace("abs", TypeMember{helper.functionType(m_wordType, m_boolType)}); + members->second.emplace("rep", TypeMember{helper.functionType(m_boolType, m_wordType)}); + } + { + Type first = m_typeSystem.freshTypeVariable({}); + Type second = m_typeSystem.freshTypeVariable({}); + Type pairType = m_typeSystem.type(PrimitiveType::Pair, {first, second}); + auto [members, newlyInserted] = annotation().members.emplace(m_typeSystem.constructor(PrimitiveType::Pair), map{}); + solAssert(newlyInserted); + members->second.emplace("first", TypeMember{helper.functionType(pairType, first)}); + members->second.emplace("second", TypeMember{helper.functionType(pairType, second)}); + } +} + +bool TypeInference::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool TypeInference::visit(FunctionDefinition const& _functionDefinition) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + auto& functionAnnotation = annotation(_functionDefinition); + if (functionAnnotation.type) + return false; + + ScopedSaveAndRestore signatureRestore(m_currentFunctionType, nullopt); + + Type argumentsType = m_typeSystem.freshTypeVariable({}); + Type returnType = m_typeSystem.freshTypeVariable({}); + Type functionType = TypeSystemHelpers{m_typeSystem}.functionType(argumentsType, returnType); + + m_currentFunctionType = functionType; + functionAnnotation.type = functionType; + + + _functionDefinition.parameterList().accept(*this); + unify(argumentsType, getType(_functionDefinition.parameterList()), _functionDefinition.parameterList().location()); + if (_functionDefinition.experimentalReturnExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _functionDefinition.experimentalReturnExpression()->accept(*this); + unify( + returnType, + getType(*_functionDefinition.experimentalReturnExpression()), + _functionDefinition.experimentalReturnExpression()->location() + ); + } + else + unify(returnType, m_unitType, _functionDefinition.location()); + + if (_functionDefinition.isImplemented()) + _functionDefinition.body().accept(*this); + + return false; +} + +void TypeInference::endVisit(Return const& _return) +{ + solAssert(m_currentFunctionType); + Type functionReturnType = get<1>(TypeSystemHelpers{m_typeSystem}.destFunctionType(*m_currentFunctionType)); + if (_return.expression()) + unify(functionReturnType, getType(*_return.expression()), _return.location()); + else + unify(functionReturnType, m_unitType, _return.location()); +} + +void TypeInference::endVisit(ParameterList const& _parameterList) +{ + auto& listAnnotation = annotation(_parameterList); + solAssert(!listAnnotation.type); + listAnnotation.type = TypeSystemHelpers{m_typeSystem}.tupleType( + _parameterList.parameters() | ranges::views::transform([&](auto _arg) { return getType(*_arg); }) | ranges::to> + ); +} + +bool TypeInference::visit(TypeClassDefinition const& _typeClassDefinition) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + auto& typeClassAnnotation = annotation(_typeClassDefinition); + if (typeClassAnnotation.type) + return false; + typeClassAnnotation.type = type(&_typeClassDefinition, {}); + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _typeClassDefinition.typeVariable().accept(*this); + } + + map functionTypes; + + Type typeVar = m_typeSystem.freshTypeVariable({}); + auto& typeMembers = annotation().members[typeConstructor(&_typeClassDefinition)]; + + for (auto subNode: _typeClassDefinition.subNodes()) + { + subNode->accept(*this); + auto const* functionDefinition = dynamic_cast(subNode.get()); + solAssert(functionDefinition); + // TODO: need polymorphicInstance? + auto functionType = polymorphicInstance(getType(*functionDefinition)); + if (!functionTypes.emplace(functionDefinition->name(), functionType).second) + m_errorReporter.fatalTypeError(0000_error, functionDefinition->location(), "Function in type class declared multiple times."); + auto typeVars = TypeEnvironmentHelpers{*m_env}.typeVars(functionType); + if(typeVars.size() != 1) + m_errorReporter.fatalTypeError(0000_error, functionDefinition->location(), "Function in type class may only depend on the type class variable."); + unify(typeVars.front(), typeVar, functionDefinition->location()); + typeMembers[functionDefinition->name()] = TypeMember{functionType}; + } + + TypeClass typeClass = std::visit(util::GenericVisitor{ + [](TypeClass _class) -> TypeClass { return _class; }, + [&](std::string _error) -> TypeClass { + m_errorReporter.fatalTypeError(0000_error, _typeClassDefinition.location(), _error); + util::unreachable(); + } + }, m_typeSystem.declareTypeClass(typeVar, _typeClassDefinition.name(), &_typeClassDefinition)); + annotation(_typeClassDefinition).typeClass = typeClass; + annotation().typeClassFunctions[typeClass] = std::move(functionTypes); + + for (auto [functionName, functionType]: functionTypes) + { + TypeEnvironmentHelpers helper{*m_env}; + auto typeVars = helper.typeVars(functionType); + if (typeVars.empty()) + m_errorReporter.typeError(0000_error, _typeClassDefinition.location(), "Function " + functionName + " does not depend on class variable."); + if (typeVars.size() > 2) + m_errorReporter.typeError(0000_error, _typeClassDefinition.location(), "Function " + functionName + " depends on multiple type variables."); + if (!m_env->typeEquals(typeVars.front(), typeVar)) + m_errorReporter.typeError(0000_error, _typeClassDefinition.location(), "Function " + functionName + " depends on invalid type variable."); + } + + unify(getType(_typeClassDefinition.typeVariable()), m_typeSystem.freshTypeVariable({{typeClass}}), _typeClassDefinition.location()); + for (auto instantiation: m_analysis.annotation(_typeClassDefinition).instantiations | ranges::views::values) + // TODO: recursion-safety? Order of instantiation? + instantiation->accept(*this); + + return false; +} + +bool TypeInference::visit(InlineAssembly const& _inlineAssembly) +{ + // External references have already been resolved in a prior stage and stored in the annotation. + // We run the resolve step again regardless. + yul::ExternalIdentifierAccess::Resolver identifierAccess = [&]( + yul::Identifier const& _identifier, + yul::IdentifierContext _context, + bool + ) -> bool + { + if (_context == yul::IdentifierContext::NonExternal) + { + // TODO: do we need this? + // Hack until we can disallow any shadowing: If we found an internal reference, + // clear the external references, so that codegen does not use it. + _inlineAssembly.annotation().externalReferences.erase(& _identifier); + return false; + } + InlineAssemblyAnnotation::ExternalIdentifierInfo* identifierInfo = util::valueOrNullptr(_inlineAssembly.annotation().externalReferences, &_identifier); + if (!identifierInfo) + return false; + Declaration const* declaration = identifierInfo->declaration; + solAssert(!!declaration, ""); + solAssert(identifierInfo->suffix == "", ""); + + unify(getType(*declaration), m_wordType, originLocationOf(_identifier)); + identifierInfo->valueSize = 1; + return true; + }; + solAssert(!_inlineAssembly.annotation().analysisInfo, ""); + _inlineAssembly.annotation().analysisInfo = make_shared(); + yul::AsmAnalyzer analyzer( + *_inlineAssembly.annotation().analysisInfo, + m_errorReporter, + _inlineAssembly.dialect(), + identifierAccess + ); + if (!analyzer.analyze(_inlineAssembly.operations())) + solAssert(m_errorReporter.hasErrors()); + return false; +} + +bool TypeInference::visit(ElementaryTypeNameExpression const& _expression) +{ + auto& expressionAnnotation = annotation(_expression); + solAssert(!expressionAnnotation.type); + + switch (m_expressionContext) + { + case ExpressionContext::Term: + case ExpressionContext::Type: + if (auto constructor = m_analysis.annotation(_expression).typeConstructor) + { + vector arguments; + std::generate_n(std::back_inserter(arguments), m_typeSystem.constructorInfo(*constructor).arguments(), [&]() { + return m_typeSystem.freshTypeVariable({}); + }); + // TODO: get rid of the distinction (assign a function on unit for the empty case)? Ambiguity? + if (arguments.empty() || m_expressionContext == ExpressionContext::Term) + expressionAnnotation.type = m_typeSystem.type(*constructor, arguments); + else + { + TypeSystemHelpers helper{m_typeSystem}; + expressionAnnotation.type = + helper.typeFunctionType( + helper.tupleType(arguments), + m_typeSystem.type(*constructor, arguments) + ); + } + } + else + { + m_errorReporter.typeError(0000_error, _expression.location(), "No type constructor registered for elementary type name."); + expressionAnnotation.type = m_typeSystem.freshTypeVariable({}); + } + break; + case ExpressionContext::Sort: + m_errorReporter.typeError(0000_error, _expression.location(), "Elementary type name expression not supported in sort context."); + expressionAnnotation.type = m_typeSystem.freshTypeVariable({}); + break; + } + return false; +} + +bool TypeInference::visit(BinaryOperation const& _binaryOperation) +{ + auto& operationAnnotation = annotation(_binaryOperation); + solAssert(!operationAnnotation.type); + TypeSystemHelpers helper{m_typeSystem}; + switch (m_expressionContext) + { + case ExpressionContext::Term: + if (auto* operatorInfo = util::valueOrNullptr(annotation().operators, _binaryOperation.getOperator())) + { + auto [typeClass, functionName] = *operatorInfo; + // TODO: error robustness? + Type functionType = m_env->fresh(annotation().typeClassFunctions.at(typeClass).at(functionName)); + + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + + Type argTuple = helper.tupleType({getType(_binaryOperation.leftExpression()), getType(_binaryOperation.rightExpression())}); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.functionType(argTuple, resultType); + unify(functionType, genericFunctionType, _binaryOperation.location()); + + operationAnnotation.type = resultType; + } + else if (_binaryOperation.getOperator() == Token::Colon) + { + _binaryOperation.leftExpression().accept(*this); + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _binaryOperation.rightExpression().accept(*this); + } + Type leftType = getType(_binaryOperation.leftExpression()); + unify(leftType, getType(_binaryOperation.rightExpression()), _binaryOperation.location()); + operationAnnotation.type = leftType; + } + else + { + m_errorReporter.typeError(0000_error, _binaryOperation.location(), "Binary operation in term context not yet supported."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + } + return false; + case ExpressionContext::Type: + if (_binaryOperation.getOperator() == Token::Colon) + { + _binaryOperation.leftExpression().accept(*this); + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Sort}; + _binaryOperation.rightExpression().accept(*this); + } + Type leftType = getType(_binaryOperation.leftExpression()); + unify(leftType, getType(_binaryOperation.rightExpression()), _binaryOperation.location()); + operationAnnotation.type = leftType; + } + else if (_binaryOperation.getOperator() == Token::RightArrow) + { + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + operationAnnotation.type = helper.functionType(getType(_binaryOperation.leftExpression()), getType(_binaryOperation.rightExpression())); + } + else if (_binaryOperation.getOperator() == Token::BitOr) + { + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + operationAnnotation.type = helper.sumType({getType(_binaryOperation.leftExpression()), getType(_binaryOperation.rightExpression())}); + } + else + { + m_errorReporter.typeError(0000_error, _binaryOperation.location(), "Invalid binary operations in type context."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + } + return false; + case ExpressionContext::Sort: + m_errorReporter.typeError(0000_error, _binaryOperation.location(), "Invalid binary operation in sort context."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + } + return false; +} + +void TypeInference::endVisit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + if (_variableDeclarationStatement.declarations().size () != 1) + { + m_errorReporter.typeError(0000_error, _variableDeclarationStatement.location(), "Multi variable declaration not supported."); + return; + } + Type variableType = getType(*_variableDeclarationStatement.declarations().front()); + if (_variableDeclarationStatement.initialValue()) + unify(variableType, getType(*_variableDeclarationStatement.initialValue()), _variableDeclarationStatement.location()); +} + +bool TypeInference::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(!_variableDeclaration.value()); + auto& variableAnnotation = annotation(_variableDeclaration); + solAssert(!variableAnnotation.type); + + switch (m_expressionContext) + { + case ExpressionContext::Term: + if (_variableDeclaration.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _variableDeclaration.typeExpression()->accept(*this); + variableAnnotation.type = getType(*_variableDeclaration.typeExpression()); + return false; + } + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + case ExpressionContext::Type: + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + if (_variableDeclaration.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Sort}; + _variableDeclaration.typeExpression()->accept(*this); + unify(*variableAnnotation.type, getType(*_variableDeclaration.typeExpression()), _variableDeclaration.typeExpression()->location()); + } + return false; + case ExpressionContext::Sort: + m_errorReporter.typeError(0000_error, _variableDeclaration.location(), "Variable declaration in sort context."); + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + } + util::unreachable(); +} + +void TypeInference::endVisit(IfStatement const& _ifStatement) +{ + auto& ifAnnotation = annotation(_ifStatement); + solAssert(!ifAnnotation.type); + + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(0000_error, _ifStatement.location(), "If statement outside term context."); + ifAnnotation.type = m_typeSystem.freshTypeVariable({}); + return; + } + + unify(getType(_ifStatement.condition()), m_boolType, _ifStatement.condition().location()); + + ifAnnotation.type = m_unitType; +} + +void TypeInference::endVisit(Assignment const& _assignment) +{ + auto& assignmentAnnotation = annotation(_assignment); + solAssert(!assignmentAnnotation.type); + + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(0000_error, _assignment.location(), "Assignment outside term context."); + assignmentAnnotation.type = m_typeSystem.freshTypeVariable({}); + return; + } + + Type leftType = getType(_assignment.leftHandSide()); + unify(leftType, getType(_assignment.rightHandSide()), _assignment.location()); + assignmentAnnotation.type = leftType; +} + +experimental::Type TypeInference::handleIdentifierByReferencedDeclaration(langutil::SourceLocation _location, Declaration const& _declaration) +{ + switch(m_expressionContext) + { + case ExpressionContext::Term: + { + if ( + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) + ) + { + SecondarySourceLocation ssl; + ssl.append("Referenced node.", _declaration.location()); + m_errorReporter.fatalTypeError(0000_error, _location, ssl, "Attempt to type identifier referring to unexpected node."); + } + + auto& declarationAnnotation = annotation(_declaration); + if (!declarationAnnotation.type) + _declaration.accept(*this); + + solAssert(declarationAnnotation.type); + + if (dynamic_cast(&_declaration)) + return *declarationAnnotation.type; + else if (dynamic_cast(&_declaration)) + return polymorphicInstance(*declarationAnnotation.type); + else if (dynamic_cast(&_declaration)) + return polymorphicInstance(*declarationAnnotation.type); + else if (dynamic_cast(&_declaration)) + { + // TODO: can we avoid this? + Type type = *declarationAnnotation.type; + if (TypeSystemHelpers{m_typeSystem}.isTypeFunctionType(type)) + type = std::get<1>(TypeSystemHelpers{m_typeSystem}.destTypeFunctionType(type)); + return polymorphicInstance(type); + } + else + solAssert(false); + break; + } + case ExpressionContext::Type: + { + if ( + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) + ) + { + SecondarySourceLocation ssl; + ssl.append("Referenced node.", _declaration.location()); + m_errorReporter.fatalTypeError(0000_error, _location, ssl, "Attempt to type identifier referring to unexpected node."); + } + + // TODO: Assert that this is a type class variable declaration? + auto& declarationAnnotation = annotation(_declaration); + if (!declarationAnnotation.type) + _declaration.accept(*this); + + solAssert(declarationAnnotation.type); + + if (dynamic_cast(&_declaration)) + return *declarationAnnotation.type; + else if (dynamic_cast(&_declaration)) + return polymorphicInstance(*declarationAnnotation.type); + else + solAssert(false); + break; + } + case ExpressionContext::Sort: + { + if (auto const* typeClass = dynamic_cast(&_declaration)) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Term}; + typeClass->accept(*this); + if (!annotation(*typeClass).typeClass) + { + m_errorReporter.typeError(0000_error, _location, "Unregistered type class."); + return m_typeSystem.freshTypeVariable({}); + } + return m_typeSystem.freshTypeVariable(Sort{{*annotation(*typeClass).typeClass}}); + } + else + { + m_errorReporter.typeError(0000_error, _location, "Expected type class."); + return m_typeSystem.freshTypeVariable({}); + } + break; + } + } + util::unreachable(); +} + +bool TypeInference::visit(Identifier const& _identifier) +{ + auto& identifierAnnotation = annotation(_identifier); + solAssert(!identifierAnnotation.type); + + if (auto const* referencedDeclaration = _identifier.annotation().referencedDeclaration) + { + identifierAnnotation.type = handleIdentifierByReferencedDeclaration(_identifier.location(), *referencedDeclaration); + return false; + } + + switch(m_expressionContext) + { + case ExpressionContext::Term: + // TODO: error handling + solAssert(false); + break; + case ExpressionContext::Type: + // TODO: register free type variable name! + identifierAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + case ExpressionContext::Sort: + // TODO: error handling + solAssert(false); + break; + } + + return false; +} + +void TypeInference::endVisit(TupleExpression const& _tupleExpression) +{ + auto& expressionAnnotation = annotation(_tupleExpression); + solAssert(!expressionAnnotation.type); + + TypeSystemHelpers helper{m_typeSystem}; + auto componentTypes = _tupleExpression.components() | ranges::views::transform([&](auto _expr) -> Type { + auto& componentAnnotation = annotation(*_expr); + solAssert(componentAnnotation.type); + return *componentAnnotation.type; + }) | ranges::to>; + switch (m_expressionContext) + { + case ExpressionContext::Term: + case ExpressionContext::Type: + expressionAnnotation.type = helper.tupleType(componentTypes); + break; + case ExpressionContext::Sort: + { + Type type = m_typeSystem.freshTypeVariable({}); + for (auto componentType: componentTypes) + unify(type, componentType, _tupleExpression.location()); + expressionAnnotation.type = type; + break; + } + } +} + +bool TypeInference::visit(IdentifierPath const& _identifierPath) +{ + auto& identifierAnnotation = annotation(_identifierPath); + solAssert(!identifierAnnotation.type); + + if (auto const* referencedDeclaration = _identifierPath.annotation().referencedDeclaration) + { + identifierAnnotation.type = handleIdentifierByReferencedDeclaration(_identifierPath.location(), *referencedDeclaration); + return false; + } + + // TODO: error handling + solAssert(false); +} + +bool TypeInference::visit(TypeClassInstantiation const& _typeClassInstantiation) +{ + ScopedSaveAndRestore activeInstantiations{m_activeInstantiations, m_activeInstantiations + set{&_typeClassInstantiation}}; + // Note: recursion is resolved due to special handling during unification. + auto& instantiationAnnotation = annotation(_typeClassInstantiation); + if (instantiationAnnotation.type) + return false; + instantiationAnnotation.type = m_voidType; + std::optional typeClass = std::visit(util::GenericVisitor{ + [&](ASTPointer _typeClassName) -> std::optional { + if (auto const* typeClass = dynamic_cast(_typeClassName->annotation().referencedDeclaration)) + { + // visiting the type class will re-visit this instantiation + typeClass->accept(*this); + // TODO: more error handling? Should be covered by the visit above. + if (!annotation(*typeClass).typeClass) + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeClass().location(), "Expected type class."); + return annotation(*typeClass).typeClass; + } + else + { + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeClass().location(), "Expected type class."); + return nullopt; + } + }, + [&](Token _token) -> std::optional { + if (auto builtinClass = builtinClassFromToken(_token)) + if (auto typeClass = util::valueOrNullptr(annotation().builtinClasses, *builtinClass)) + return *typeClass; + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), "Invalid type class name."); + return nullopt; + } + }, _typeClassInstantiation.typeClass().name()); + if (!typeClass) + return false; + + // TODO: _typeClassInstantiation.typeConstructor().accept(*this); ? + auto typeConstructor = m_analysis.annotation(_typeClassInstantiation.typeConstructor()).typeConstructor; + if (!typeConstructor) + { + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeConstructor().location(), "Invalid type constructor."); + return false; + } + + vector arguments; + Arity arity{ + {}, + *typeClass + }; + + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + if (_typeClassInstantiation.argumentSorts()) + { + _typeClassInstantiation.argumentSorts()->accept(*this); + auto& argumentSortAnnotation = annotation(*_typeClassInstantiation.argumentSorts()); + solAssert(argumentSortAnnotation.type); + arguments = TypeSystemHelpers{m_typeSystem}.destTupleType(*argumentSortAnnotation.type); + arity.argumentSorts = arguments | ranges::views::transform([&](Type _type) { + return m_env->sort(_type); + }) | ranges::to>; + } + } + + Type type{TypeConstant{*typeConstructor, arguments}}; + + map functionTypes; + + for (auto subNode: _typeClassInstantiation.subNodes()) + { + auto const* functionDefinition = dynamic_cast(subNode.get()); + solAssert(functionDefinition); + subNode->accept(*this); + if (!functionTypes.emplace(functionDefinition->name(), getType(*functionDefinition)).second) + m_errorReporter.typeError(0000_error, subNode->location(), "Duplicate definition of function " + functionDefinition->name() + " during type class instantiation."); + } + + if (auto error = m_typeSystem.instantiateClass(type, arity)) + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), *error); + + auto const& classFunctions = annotation().typeClassFunctions.at(*typeClass); + + TypeEnvironment newEnv = m_env->clone(); + if (!newEnv.unify(m_typeSystem.typeClassVariable(*typeClass), type).empty()) + { + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), "Unification of class and instance variable failed."); + return false; + } + + for (auto [name, classFunctionType]: classFunctions) + { + if (!functionTypes.count(name)) + { + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), "Missing function: " + name); + continue; + } + Type instanceFunctionType = functionTypes.at(name); + functionTypes.erase(name); + + if (!newEnv.typeEquals(instanceFunctionType, classFunctionType)) + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), "Type mismatch for function " + name + " " + TypeEnvironmentHelpers{newEnv}.typeToString(instanceFunctionType) + " != " + TypeEnvironmentHelpers{newEnv}.typeToString(classFunctionType)); + } + + if (!functionTypes.empty()) + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), "Additional functions in class instantiation."); + + return false; +} + +bool TypeInference::visit(MemberAccess const& _memberAccess) +{ + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(0000_error, _memberAccess.location(), "Member access outside term context."); + annotation(_memberAccess).type = m_typeSystem.freshTypeVariable({}); + return false; + } + return true; +} + +experimental::Type TypeInference::memberType(Type _type, std::string _memberName, langutil::SourceLocation _location) +{ + Type type = m_env->resolve(_type); + TypeSystemHelpers helper{m_typeSystem}; + if (helper.isTypeConstant(type)) + { + auto constructor = std::get<0>(helper.destTypeConstant(type)); + if (auto* typeMember = util::valueOrNullptr(annotation().members.at(constructor), _memberName)) + { + return polymorphicInstance(typeMember->type); + } + else + { + m_errorReporter.typeError(0000_error, _location, fmt::format("Member {} not found in type {}.", _memberName, TypeEnvironmentHelpers{*m_env}.typeToString(_type))); + return m_typeSystem.freshTypeVariable({}); + } + } + else + { + m_errorReporter.typeError(0000_error, _location, "Unsupported member access expression."); + return m_typeSystem.freshTypeVariable({}); + } +} + +void TypeInference::endVisit(MemberAccess const& _memberAccess) +{ + auto &memberAccessAnnotation = annotation(_memberAccess); + solAssert(!memberAccessAnnotation.type); + Type expressionType = getType(_memberAccess.expression()); + memberAccessAnnotation.type = memberType(expressionType, _memberAccess.memberName(), _memberAccess.location()); +} + +bool TypeInference::visit(TypeDefinition const& _typeDefinition) +{ + TypeSystemHelpers helper{m_typeSystem}; + auto& typeDefinitionAnnotation = annotation(_typeDefinition); + if (typeDefinitionAnnotation.type) + return false; + + if (_typeDefinition.arguments()) + _typeDefinition.arguments()->accept(*this); + + std::optional underlyingType; + if (_typeDefinition.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _typeDefinition.typeExpression()->accept(*this); + underlyingType = annotation(*_typeDefinition.typeExpression()).type; + } + + vector arguments; + if (_typeDefinition.arguments()) + for (size_t i = 0; i < _typeDefinition.arguments()->parameters().size(); ++i) + arguments.emplace_back(m_typeSystem.freshTypeVariable({})); + + Type definedType = type(&_typeDefinition, arguments); + if (arguments.empty()) + typeDefinitionAnnotation.type = definedType; + else + typeDefinitionAnnotation.type = helper.typeFunctionType(helper.tupleType(arguments), definedType); + + TypeConstructor constructor = typeConstructor(&_typeDefinition); + auto [members, newlyInserted] = annotation().members.emplace(constructor, map{}); + solAssert(newlyInserted); + if (underlyingType) + { + members->second.emplace("abs", TypeMember{helper.functionType(*underlyingType, definedType)}); + members->second.emplace("rep", TypeMember{helper.functionType(definedType, *underlyingType)}); + } + return false; +} + +bool TypeInference::visit(FunctionCall const&) { return true; } +void TypeInference::endVisit(FunctionCall const& _functionCall) +{ + auto& functionCallAnnotation = annotation(_functionCall); + solAssert(!functionCallAnnotation.type); + + Type functionType = getType(_functionCall.expression()); + + TypeSystemHelpers helper{m_typeSystem}; + std::vector argTypes; + for(auto arg: _functionCall.arguments()) + { + switch(m_expressionContext) + { + case ExpressionContext::Term: + case ExpressionContext::Type: + argTypes.emplace_back(getType(*arg)); + break; + case ExpressionContext::Sort: + m_errorReporter.typeError(0000_error, _functionCall.location(), "Function call in sort context."); + functionCallAnnotation.type = m_typeSystem.freshTypeVariable({}); + break; + } + } + + switch(m_expressionContext) + { + case ExpressionContext::Term: + { + Type argTuple = helper.tupleType(argTypes); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.functionType(argTuple, resultType); + unify(functionType, genericFunctionType, _functionCall.location()); + functionCallAnnotation.type = resultType; + break; + } + case ExpressionContext::Type: + { + Type argTuple = helper.tupleType(argTypes); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.typeFunctionType(argTuple, resultType); + unify(functionType, genericFunctionType, _functionCall.location()); + functionCallAnnotation.type = resultType; + break; + } + case ExpressionContext::Sort: + solAssert(false); + } +} + +// TODO: clean up rational parsing +namespace +{ + +optional parseRational(string const& _value) +{ + rational value; + try + { + auto radixPoint = find(_value.begin(), _value.end(), '.'); + + if (radixPoint != _value.end()) + { + if ( + !all_of(radixPoint + 1, _value.end(), util::isDigit) || + !all_of(_value.begin(), radixPoint, util::isDigit) + ) + return nullopt; + + // Only decimal notation allowed here, leading zeros would switch to octal. + auto fractionalBegin = find_if_not( + radixPoint + 1, + _value.end(), + [](char const& a) { return a == '0'; } + ); + + rational numerator; + rational denominator(1); + + denominator = bigint(string(fractionalBegin, _value.end())); + denominator /= boost::multiprecision::pow( + bigint(10), + static_cast(distance(radixPoint + 1, _value.end())) + ); + numerator = bigint(string(_value.begin(), radixPoint)); + value = numerator + denominator; + } + else + value = bigint(_value); + return value; + } + catch (...) + { + return nullopt; + } +} + +/// Checks whether _mantissa * (10 ** _expBase10) fits into 4096 bits. +bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10) +{ + double const log2Of10AwayFromZero = 3.3219280948873624; + return fitsPrecisionBaseX(_mantissa, log2Of10AwayFromZero, _expBase10); +} + +optional rationalValue(Literal const& _literal) +{ + rational value; + try + { + ASTString valueString = _literal.valueWithoutUnderscores(); + + auto expPoint = find(valueString.begin(), valueString.end(), 'e'); + if (expPoint == valueString.end()) + expPoint = find(valueString.begin(), valueString.end(), 'E'); + + if (boost::starts_with(valueString, "0x")) + { + // process as hex + value = bigint(valueString); + } + else if (expPoint != valueString.end()) + { + // Parse mantissa and exponent. Checks numeric limit. + optional mantissa = parseRational(string(valueString.begin(), expPoint)); + + if (!mantissa) + return nullopt; + value = *mantissa; + + // 0E... is always zero. + if (value == 0) + return nullopt; + + bigint exp = bigint(string(expPoint + 1, valueString.end())); + + if (exp > numeric_limits::max() || exp < numeric_limits::min()) + return nullopt; + + uint32_t expAbs = bigint(abs(exp)).convert_to(); + + if (exp < 0) + { + if (!fitsPrecisionBase10(abs(value.denominator()), expAbs)) + return nullopt; + value /= boost::multiprecision::pow( + bigint(10), + expAbs + ); + } + else if (exp > 0) + { + if (!fitsPrecisionBase10(abs(value.numerator()), expAbs)) + return nullopt; + value *= boost::multiprecision::pow( + bigint(10), + expAbs + ); + } + } + else + { + // parse as rational number + optional tmp = parseRational(valueString); + if (!tmp) + return nullopt; + value = *tmp; + } + } + catch (...) + { + return nullopt; + } + switch (_literal.subDenomination()) + { + case Literal::SubDenomination::None: + case Literal::SubDenomination::Wei: + case Literal::SubDenomination::Second: + break; + case Literal::SubDenomination::Gwei: + value *= bigint("1000000000"); + break; + case Literal::SubDenomination::Ether: + value *= bigint("1000000000000000000"); + break; + case Literal::SubDenomination::Minute: + value *= bigint("60"); + break; + case Literal::SubDenomination::Hour: + value *= bigint("3600"); + break; + case Literal::SubDenomination::Day: + value *= bigint("86400"); + break; + case Literal::SubDenomination::Week: + value *= bigint("604800"); + break; + case Literal::SubDenomination::Year: + value *= bigint("31536000"); + break; + } + + return value; +} +} + +bool TypeInference::visit(Literal const& _literal) +{ + auto& literalAnnotation = annotation(_literal); + if (_literal.token() != Token::Number) + { + m_errorReporter.typeError(0000_error, _literal.location(), "Only number literals are supported."); + return false; + } + optional value = rationalValue(_literal); + if (!value) + { + m_errorReporter.typeError(0000_error, _literal.location(), "Invalid number literals."); + return false; + } + if (value->denominator() != 1) + { + m_errorReporter.typeError(0000_error, _literal.location(), "Only integers are supported."); + return false; + } + literalAnnotation.type = m_typeSystem.freshTypeVariable(Sort{{annotation().builtinClasses.at(BuiltinClass::Integer)}}); + return false; +} + + +namespace +{ +// TODO: put at a nice place to deduplicate. +TypeRegistration::TypeClassInstantiations const& typeClassInstantiations(Analysis const& _analysis, TypeClass _class) +{ + auto const* typeClassDeclaration = _analysis.typeSystem().typeClassDeclaration(_class); + if (typeClassDeclaration) + return _analysis.annotation(*typeClassDeclaration).instantiations; + // TODO: better mechanism than fetching by name. + auto& annotation = _analysis.annotation(); + auto& inferenceAnnotation = _analysis.annotation(); + return annotation.builtinClassInstantiations.at(inferenceAnnotation.builtinClassesByName.at(_analysis.typeSystem().typeClassName(_class))); +} +} + +void TypeInference::unifyGeneralized(Type _type, Type _scheme, std::vector _monomorphicTypes, langutil::SourceLocation _location) +{ + solUnimplementedAssert(_monomorphicTypes.empty(), "unsupported"); + Type fresh = m_env->fresh(_scheme); + unify(_type, fresh, _location); +} + +experimental::Type TypeInference::polymorphicInstance(Type _scheme, langutil::SourceLocation _location) +{ + Type result = m_typeSystem.freshTypeVariable({}); + unifyGeneralized(result, _scheme, {}, _location); + return result; +} + +void TypeInference::unify(Type _a, Type _b, langutil::SourceLocation _location) +{ + TypeSystemHelpers helper{m_typeSystem}; + auto unificationFailures = m_env->unify(_a, _b); + + if (!m_activeInstantiations.empty()) + { + // Attempt to resolve interdependencies between type class instantiations. + std::vector missingInstantiations; + bool recursion = false; + bool onlyMissingInstantiations = [&]() { + for (auto failure: unificationFailures) + { + if (auto* sortMismatch = get_if(&failure)) + if (helper.isTypeConstant(sortMismatch->type)) + { + TypeConstructor constructor = std::get<0>(helper.destTypeConstant(sortMismatch->type)); + for (auto typeClass: sortMismatch->sort.classes) + { + if (auto const* instantiation = util::valueOrDefault(typeClassInstantiations(m_analysis, typeClass), constructor, nullptr)) + { + if (m_activeInstantiations.count(instantiation)) + { + langutil::SecondarySourceLocation ssl; + for (auto activeInstantiation: m_activeInstantiations) + ssl.append("Involved instantiation", activeInstantiation->location()); + m_errorReporter.typeError( + 0000_error, + _location, + ssl, + "Recursion during type class instantiation." + ); + recursion = true; + return false; + } + missingInstantiations.emplace_back(instantiation); + } + else + return false; + } + continue; + } + return false; + } + return true; + }(); + + if (recursion) + return; + + if (onlyMissingInstantiations) + { + for (auto instantiation: missingInstantiations) + instantiation->accept(*this); + unificationFailures = m_env->unify(_a, _b); + } + } + + for (auto failure: unificationFailures) + { + TypeEnvironmentHelpers envHelper{*m_env}; + std::visit(util::GenericVisitor{ + [&](TypeEnvironment::TypeMismatch _typeMismatch) { + m_errorReporter.typeError( + 0000_error, + _location, + fmt::format( + "Cannot unify {} and {}.", + envHelper.typeToString(_typeMismatch.a), + envHelper.typeToString(_typeMismatch.b)) + ); + }, + [&](TypeEnvironment::SortMismatch _sortMismatch) { + m_errorReporter.typeError(0000_error, _location, fmt::format( + "{} does not have sort {}", + envHelper.typeToString(_sortMismatch.type), + TypeSystemHelpers{m_typeSystem}.sortToString(_sortMismatch.sort) + )); + }, + [&](TypeEnvironment::RecursiveUnification _recursiveUnification) { + m_errorReporter.typeError( + 0000_error, + _location, + fmt::format( + "Recursive unification: {} occurs in {}.", + envHelper.typeToString(_recursiveUnification.var), + envHelper.typeToString(_recursiveUnification.type)) + ); + } + }, failure); + } +} + +experimental::Type TypeInference::getType(ASTNode const& _node) const +{ + auto result = annotation(_node).type; + solAssert(result); + return *result; +} +TypeConstructor TypeInference::typeConstructor(Declaration const* _type) const +{ + if (auto const& constructor = m_analysis.annotation(*_type).typeConstructor) + return *constructor; + m_errorReporter.fatalTypeError(0000_error, _type->location(), "Unregistered type."); + util::unreachable(); +} +experimental::Type TypeInference::type(Declaration const* _type, vector _arguments) const +{ + return m_typeSystem.type(typeConstructor(_type), std::move(_arguments)); +} + +bool TypeInference::visitNode(ASTNode const& _node) +{ + m_errorReporter.fatalTypeError(0000_error, _node.location(), "Unsupported AST node during type inference."); + return false; +} + +TypeInference::Annotation& TypeInference::annotation(ASTNode const& _node) +{ + return m_analysis.annotation(_node); +} + +TypeInference::Annotation const& TypeInference::annotation(ASTNode const& _node) const +{ + return m_analysis.annotation(_node); +} + +TypeInference::GlobalAnnotation& TypeInference::annotation() +{ + return m_analysis.annotation(); +} diff --git a/libsolidity/experimental/analysis/TypeInference.h b/libsolidity/experimental/analysis/TypeInference.h new file mode 100644 index 000000000..86686f556 --- /dev/null +++ b/libsolidity/experimental/analysis/TypeInference.h @@ -0,0 +1,126 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +class TypeInference: public ASTConstVisitor +{ +public: + TypeInference(Analysis& _analysis); + + bool analyze(SourceUnit const& _sourceUnit); + + struct Annotation + { + /// Expressions, variable declarations, function declarations. + std::optional type; + // Type classes. + std::optional typeClass; + }; + struct TypeMember + { + Type type; + }; + struct GlobalAnnotation + { + std::map builtinClasses; + std::map builtinClassesByName; + std::map> typeClassFunctions; + std::map> operators; + std::map> members; + }; + bool visit(Block const&) override { return true; } + bool visit(VariableDeclarationStatement const&) override { return true; } + void endVisit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(VariableDeclaration const& _variableDeclaration) override; + + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(ParameterList const&) override { return true; } + void endVisit(ParameterList const& _parameterList) override; + bool visit(SourceUnit const&) override { return true; } + bool visit(ContractDefinition const&) override { return true; } + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(PragmaDirective const&) override { return false; } + + bool visit(IfStatement const&) override { return true; } + void endVisit(IfStatement const& _ifStatement) override; + bool visit(ExpressionStatement const&) override { return true; } + bool visit(Assignment const&) override { return true; } + void endVisit(Assignment const& _assignment) override; + bool visit(Identifier const&) override; + bool visit(IdentifierPath const&) override; + bool visit(FunctionCall const& _functionCall) override; + void endVisit(FunctionCall const& _functionCall) override; + bool visit(Return const&) override { return true; } + void endVisit(Return const& _return) override; + + bool visit(MemberAccess const& _memberAccess) override; + void endVisit(MemberAccess const& _memberAccess) override; + bool visit(ElementaryTypeNameExpression const& _expression) override; + + bool visit(TypeClassDefinition const& _typeClassDefinition) override; + bool visit(TypeClassInstantiation const& _typeClassInstantiation) override; + bool visit(TupleExpression const&) override { return true; } + void endVisit(TupleExpression const& _tupleExpression) override; + bool visit(TypeDefinition const& _typeDefinition) override; + + bool visitNode(ASTNode const& _node) override; + + bool visit(BinaryOperation const& _operation) override; + + bool visit(Literal const& _literal) override; +private: + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + TypeSystem& m_typeSystem; + TypeEnvironment* m_env = nullptr; + Type m_voidType; + Type m_wordType; + Type m_integerType; + Type m_unitType; + Type m_boolType; + std::optional m_currentFunctionType; + + Type getType(ASTNode const& _node) const; + + Annotation& annotation(ASTNode const& _node); + Annotation const& annotation(ASTNode const& _node) const; + GlobalAnnotation& annotation(); + + void unify(Type _a, Type _b, langutil::SourceLocation _location = {}); + void unifyGeneralized(Type _type, Type _scheme, std::vector _monomorphicTypes, langutil::SourceLocation _location = {}); + Type polymorphicInstance(Type _scheme, langutil::SourceLocation _location = {}); + Type memberType(Type _type, std::string _memberName, langutil::SourceLocation _location = {}); + enum class ExpressionContext { Term, Type, Sort }; + Type handleIdentifierByReferencedDeclaration(langutil::SourceLocation _location, Declaration const& _declaration); + TypeConstructor typeConstructor(Declaration const* _type) const; + Type type(Declaration const* _type, std::vector _arguments) const; + ExpressionContext m_expressionContext = ExpressionContext::Term; + std::set> m_activeInstantiations; +}; + +} diff --git a/libsolidity/experimental/analysis/TypeRegistration.cpp b/libsolidity/experimental/analysis/TypeRegistration.cpp new file mode 100644 index 000000000..3cc46c438 --- /dev/null +++ b/libsolidity/experimental/analysis/TypeRegistration.cpp @@ -0,0 +1,188 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +TypeRegistration::TypeRegistration(Analysis& _analysis): +m_analysis(_analysis), +m_errorReporter(_analysis.errorReporter()), +m_typeSystem(_analysis.typeSystem()) +{ +} + +bool TypeRegistration::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool TypeRegistration::visit(TypeClassDefinition const& _typeClassDefinition) +{ + if (annotation(_typeClassDefinition).typeConstructor) + return false; + annotation(_typeClassDefinition).typeConstructor = m_typeSystem.declareTypeConstructor( + _typeClassDefinition.name(), + "t_" + *_typeClassDefinition.annotation().canonicalName + "_" + util::toString(_typeClassDefinition.id()), + 0, + &_typeClassDefinition + ); + return true; +} + +bool TypeRegistration::visit(ElementaryTypeName const& _typeName) +{ + if (annotation(_typeName).typeConstructor) + return false; + annotation(_typeName).typeConstructor = [&]() -> optional { + switch(_typeName.typeName().token()) + { + case Token::Void: + return m_typeSystem.constructor(PrimitiveType::Void); + case Token::Fun: + return m_typeSystem.constructor(PrimitiveType::Function); + case Token::Unit: + return m_typeSystem.constructor(PrimitiveType::Unit); + case Token::Pair: + return m_typeSystem.constructor(PrimitiveType::Pair); + case Token::Word: + return m_typeSystem.constructor(PrimitiveType::Word); + case Token::Integer: + return m_typeSystem.constructor(PrimitiveType::Integer); + case Token::Bool: + return m_typeSystem.constructor(PrimitiveType::Bool); + default: + m_errorReporter.fatalTypeError(0000_error, _typeName.location(), "Expected primitive type."); + return nullopt; + } + }(); + return true; +} + +void TypeRegistration::endVisit(ElementaryTypeNameExpression const & _typeNameExpression) +{ + if (annotation(_typeNameExpression).typeConstructor) + return; + + // TODO: this is not visited in the ElementaryTypeNameExpression visit - is that intentional? + _typeNameExpression.type().accept(*this); + if (auto constructor = annotation(_typeNameExpression.type()).typeConstructor) + annotation(_typeNameExpression).typeConstructor = constructor; + else + solAssert(m_errorReporter.hasErrors()); +} + +bool TypeRegistration::visit(UserDefinedTypeName const& _userDefinedTypeName) +{ + if (annotation(_userDefinedTypeName).typeConstructor) + return false; + auto const* declaration = _userDefinedTypeName.pathNode().annotation().referencedDeclaration; + if (!declaration) + { + // TODO: fatal/non-fatal + m_errorReporter.fatalTypeError(0000_error, _userDefinedTypeName.pathNode().location(), "Expected declaration."); + return false; + } + declaration->accept(*this); + if (!(annotation(_userDefinedTypeName).typeConstructor = annotation(*declaration).typeConstructor)) + { + // TODO: fatal/non-fatal + m_errorReporter.fatalTypeError(0000_error, _userDefinedTypeName.pathNode().location(), "Expected type declaration."); + return false; + } + return true; +} + +bool TypeRegistration::visit(TypeClassInstantiation const& _typeClassInstantiation) +{ + if (annotation(_typeClassInstantiation).typeConstructor) + return false; + _typeClassInstantiation.typeConstructor().accept(*this); + auto typeConstructor = annotation(_typeClassInstantiation.typeConstructor()).typeConstructor; + if (!typeConstructor) + { + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeConstructor().location(), "Invalid type name."); + return false; + } + auto* instantiations = std::visit(util::GenericVisitor{ + [&](ASTPointer _path) -> TypeClassInstantiations* + { + if (TypeClassDefinition const* classDefinition = dynamic_cast(_path->annotation().referencedDeclaration)) + return &annotation(*classDefinition).instantiations; + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeClass().location(), "Expected a type class."); + return nullptr; + }, + [&](Token _token) -> TypeClassInstantiations* + { + if (auto typeClass = builtinClassFromToken(_token)) + return &annotation().builtinClassInstantiations[*typeClass]; + m_errorReporter.typeError(0000_error, _typeClassInstantiation.typeClass().location(), "Expected a type class."); + return nullptr; + } + }, _typeClassInstantiation.typeClass().name()); + + if (!instantiations) + return false; + + if ( + auto [instantiation, newlyInserted] = instantiations->emplace(*typeConstructor, &_typeClassInstantiation); + !newlyInserted + ) + { + SecondarySourceLocation ssl; + ssl.append("Previous instantiation.", instantiation->second->location()); + m_errorReporter.typeError(0000_error, _typeClassInstantiation.location(), ssl, "Duplicate type class instantiation."); + } + + return true; +} + +bool TypeRegistration::visit(TypeDefinition const& _typeDefinition) +{ + if (annotation(_typeDefinition).typeConstructor) + return false; + annotation(_typeDefinition).typeConstructor = m_typeSystem.declareTypeConstructor( + _typeDefinition.name(), + "t_" + *_typeDefinition.annotation().canonicalName + "_" + util::toString(_typeDefinition.id()), + _typeDefinition.arguments() ? _typeDefinition.arguments()->parameters().size() : 0, + &_typeDefinition + ); + return true; +} + +TypeRegistration::Annotation& TypeRegistration::annotation(ASTNode const& _node) +{ + return m_analysis.annotation(_node); +} + +TypeRegistration::GlobalAnnotation& TypeRegistration::annotation() +{ + return m_analysis.annotation(); +} diff --git a/libsolidity/experimental/analysis/TypeRegistration.h b/libsolidity/experimental/analysis/TypeRegistration.h new file mode 100644 index 000000000..d6d534fa7 --- /dev/null +++ b/libsolidity/experimental/analysis/TypeRegistration.h @@ -0,0 +1,65 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +class TypeRegistration: public ASTConstVisitor +{ +public: + using TypeClassInstantiations = std::map; + struct Annotation + { + // For type class definitions. + TypeClassInstantiations instantiations; + // For type definitions, type class definitions, type names and type name expressions. + std::optional typeConstructor; + }; + struct GlobalAnnotation + { + std::map primitiveClassInstantiations; + std::map builtinClassInstantiations; + }; + TypeRegistration(Analysis& _analysis); + + bool analyze(SourceUnit const& _sourceUnit); +private: + bool visit(TypeClassDefinition const& _typeClassDefinition) override; + bool visit(TypeClassInstantiation const& _typeClassInstantiation) override; + bool visit(TypeDefinition const& _typeDefinition) override; + bool visit(UserDefinedTypeName const& _typeName) override; + void endVisit(ElementaryTypeNameExpression const& _typeName) override; + bool visit(ElementaryTypeName const& _typeName) override; + Annotation& annotation(ASTNode const& _node); + GlobalAnnotation& annotation(); + + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + TypeSystem& m_typeSystem; + std::set m_visitedClasses; +}; + +} diff --git a/libsolidity/experimental/ast/Type.cpp b/libsolidity/experimental/ast/Type.cpp new file mode 100644 index 000000000..9650a6411 --- /dev/null +++ b/libsolidity/experimental/ast/Type.cpp @@ -0,0 +1,63 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend::experimental; + +bool Sort::operator==(Sort const& _rhs) const +{ + if (classes.size() != _rhs.classes.size()) + return false; + for (auto [lhs, rhs]: ranges::zip_view(classes, _rhs.classes)) + if (lhs != rhs) + return false; + return true; +} + +bool Sort::operator<=(Sort const& _rhs) const +{ + for (auto c: classes) + if (!_rhs.classes.count(c)) + return false; + return true; +} + +Sort Sort::operator+(Sort const& _rhs) const +{ + Sort result { classes }; + result.classes += _rhs.classes; + return result; +} + + +Sort Sort::operator-(Sort const& _rhs) const +{ + Sort result { classes }; + result.classes -= _rhs.classes; + return result; +} diff --git a/libsolidity/experimental/ast/Type.h b/libsolidity/experimental/ast/Type.h new file mode 100644 index 000000000..f98925684 --- /dev/null +++ b/libsolidity/experimental/ast/Type.h @@ -0,0 +1,153 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include +#include + +namespace solidity::frontend::experimental +{ + +class TypeSystem; + +struct TypeConstant; +struct TypeVariable; + +using Type = std::variant; + +enum class PrimitiveType +{ + Void, + Function, + TypeFunction, + Itself, + Unit, + Pair, + Sum, + Word, + Bool, + Integer +}; + +enum class PrimitiveClass +{ + Type +}; + +// TODO: move elsewhere? +enum class BuiltinClass +{ + Integer, + Mul, + Add, + Equal, + Less, + LessOrEqual, + Greater, + GreaterOrEqual +}; + +struct TypeConstructor +{ +public: + TypeConstructor(TypeConstructor const& _typeConstructor): m_index(_typeConstructor.m_index) {} + TypeConstructor& operator=(TypeConstructor const& _typeConstructor) + { + m_index = _typeConstructor.m_index; + return *this; + } + bool operator<(TypeConstructor const& _rhs) const + { + return m_index < _rhs.m_index; + } + bool operator==(TypeConstructor const& _rhs) const + { + return m_index == _rhs.m_index; + } + bool operator!=(TypeConstructor const& _rhs) const + { + return m_index != _rhs.m_index; + } +private: + friend class TypeSystem; + TypeConstructor(size_t _index): m_index(_index) {} + size_t m_index = 0; +}; + +struct TypeConstant +{ + TypeConstructor constructor; + std::vector arguments; +}; + +struct TypeClass +{ +public: + TypeClass(TypeClass const& _typeClass): m_index(_typeClass.m_index) {} + TypeClass& operator=(TypeClass const& _typeConstructor) + { + m_index = _typeConstructor.m_index; + return *this; + } + bool operator<(TypeClass const& _rhs) const + { + return m_index < _rhs.m_index; + } + bool operator==(TypeClass const& _rhs) const + { + return m_index == _rhs.m_index; + } + bool operator!=(TypeClass const& _rhs) const + { + return m_index != _rhs.m_index; + } +private: + friend class TypeSystem; + TypeClass(size_t _index): m_index(_index) {} + size_t m_index = 0; +}; + +struct Sort +{ + std::set classes; + bool operator==(Sort const& _rhs) const; + bool operator!=(Sort const& _rhs) const { return !operator==(_rhs); } + bool operator<=(Sort const& _rhs) const; + Sort operator+(Sort const& _rhs) const; + Sort operator-(Sort const& _rhs) const; +}; + +struct Arity +{ + std::vector argumentSorts; + TypeClass typeClass; +}; + +struct TypeVariable +{ + size_t index() const { return m_index; } + Sort const& sort() const { return m_sort; } +private: + friend class TypeSystem; + size_t m_index = 0; + Sort m_sort; + TypeVariable(size_t _index, Sort _sort): m_index(_index), m_sort(std::move(_sort)) {} +}; + +} diff --git a/libsolidity/experimental/ast/TypeSystem.cpp b/libsolidity/experimental/ast/TypeSystem.cpp new file mode 100644 index 000000000..443946b8d --- /dev/null +++ b/libsolidity/experimental/ast/TypeSystem.cpp @@ -0,0 +1,347 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; + +vector TypeEnvironment::unify(Type _a, Type _b) +{ + vector failures; + auto unificationFailure = [&]() { + failures.emplace_back(UnificationFailure{TypeMismatch{_a, _b}}); + }; + _a = resolve(_a); + _b = resolve(_b); + std::visit(util::GenericVisitor{ + [&](TypeVariable _left, TypeVariable _right) { + if (_left.index() == _right.index()) + { + if (_left.sort() != _right.sort()) + unificationFailure(); + } + else + { + if (_left.sort() <= _right.sort()) + failures += instantiate(_left, _right); + else if (_right.sort() <= _left.sort()) + failures += instantiate(_right, _left); + else + { + Type newVar = m_typeSystem.freshVariable(_left.sort() + _right.sort()); + failures += instantiate(_left, newVar); + failures += instantiate(_right, newVar); + } + } + }, + [&](TypeVariable _var, auto) { + failures += instantiate(_var, _b); + }, + [&](auto, TypeVariable _var) { + failures += instantiate(_var, _a); + }, + [&](TypeConstant _left, TypeConstant _right) { + if(_left.constructor != _right.constructor) + return unificationFailure(); + if (_left.arguments.size() != _right.arguments.size()) + return unificationFailure(); + for (auto&& [left, right]: ranges::zip_view(_left.arguments, _right.arguments)) + failures += unify(left, right); + }, + [&](auto, auto) { + unificationFailure(); + } + }, _a, _b); + return failures; +} + +bool TypeEnvironment::typeEquals(Type _lhs, Type _rhs) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeVariable _left, TypeVariable _right) { + if (_left.index() == _right.index()) + { + solAssert(_left.sort() == _right.sort()); + return true; + } + return false; + }, + [&](TypeConstant _left, TypeConstant _right) { + if(_left.constructor != _right.constructor) + return false; + if (_left.arguments.size() != _right.arguments.size()) + return false; + for (auto&& [left, right]: ranges::zip_view(_left.arguments, _right.arguments)) + if (!typeEquals(left, right)) + return false; + return true; + }, + [&](auto, auto) { + return false; + } + }, resolve(_lhs), resolve(_rhs)); +} + +TypeEnvironment TypeEnvironment::clone() const +{ + TypeEnvironment result{m_typeSystem}; + result.m_typeVariables = m_typeVariables; + return result; +} + +TypeSystem::TypeSystem() +{ + auto declarePrimitiveClass = [&](std::string _name) { + return std::visit(util::GenericVisitor{ + [](std::string _error) -> TypeClass { + solAssert(false, _error); + }, + [](TypeClass _class) -> TypeClass { return _class; } + }, declareTypeClass(freshVariable({}), _name, nullptr)); + }; + + m_primitiveTypeClasses.emplace(PrimitiveClass::Type, declarePrimitiveClass("type")); + + for (auto [type, name, arity]: std::initializer_list> { + {PrimitiveType::TypeFunction, "tfun", 2}, + {PrimitiveType::Function, "fun", 2}, + {PrimitiveType::Function, "itself", 1}, + {PrimitiveType::Void, "void", 0}, + {PrimitiveType::Unit, "unit", 0}, + {PrimitiveType::Pair, "pair", 2}, + {PrimitiveType::Sum, "sum", 2}, + {PrimitiveType::Word, "word", 0}, + {PrimitiveType::Integer, "integer", 0}, + {PrimitiveType::Bool, "bool", 0}, + }) + m_primitiveTypeConstructors.emplace(type, declareTypeConstructor(name, name, arity, nullptr)); + + TypeClass classType = primitiveClass(PrimitiveClass::Type); + //TypeClass classKind = primitiveClass(PrimitiveClass::Kind); + Sort typeSort{{classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::TypeFunction).m_index).arities = {Arity{vector{{typeSort},{typeSort}}, classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::Function).m_index).arities = {Arity{vector{{typeSort, typeSort}}, classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::Function).m_index).arities = {Arity{vector{{typeSort, typeSort}}, classType}}; +} + +experimental::Type TypeSystem::freshVariable(Sort _sort) +{ + uint64_t index = m_numTypeVariables++; + return TypeVariable(index, std::move(_sort)); +} + +experimental::Type TypeSystem::freshTypeVariable(Sort _sort) +{ + _sort.classes.emplace(primitiveClass(PrimitiveClass::Type)); + return freshVariable(_sort); +} + +vector TypeEnvironment::instantiate(TypeVariable _variable, Type _type) +{ + for (auto typeVar: TypeEnvironmentHelpers{*this}.typeVars(_type)) + if (typeVar.index() == _variable.index()) + return {UnificationFailure{RecursiveUnification{_variable, _type}}}; + Sort typeSort = sort(_type); + if (!(_variable.sort() <= typeSort)) + { + return {UnificationFailure{SortMismatch{_type, _variable.sort() - typeSort}}}; + } + solAssert(m_typeVariables.emplace(_variable.index(), _type).second); + return {}; +} + +experimental::Type TypeEnvironment::resolve(Type _type) const +{ + Type result = _type; + while(auto const* var = std::get_if(&result)) + if (Type const* resolvedType = util::valueOrNullptr(m_typeVariables, var->index())) + result = *resolvedType; + else + break; + return result; +} + +experimental::Type TypeEnvironment::resolveRecursive(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) -> Type { + return TypeConstant{ + _type.constructor, + _type.arguments | ranges::views::transform([&](Type _argType) { + return resolveRecursive(_argType); + }) | ranges::to> + }; + }, + [&](TypeVariable const&) -> Type { + return _type; + }, + [&](std::monostate) -> Type { + return _type; + } + }, resolve(_type)); +} + +Sort TypeEnvironment::sort(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _expression) -> Sort + { + auto const& constructorInfo = m_typeSystem.constructorInfo(_expression.constructor); + auto argumentSorts = _expression.arguments | ranges::views::transform([&](Type _argumentType) { + return sort(resolve(_argumentType)); + }) | ranges::to>; + Sort sort; + for (auto const& arity: constructorInfo.arities) + { + solAssert(arity.argumentSorts.size() == argumentSorts.size()); + bool hasArity = true; + for (auto&& [argumentSort, arityArgumentSort]: ranges::zip_view(argumentSorts, arity.argumentSorts)) + { + if (!(arityArgumentSort <= argumentSort)) + { + hasArity = false; + break; + } + } + + if (hasArity) + sort.classes.insert(arity.typeClass); + } + return sort; + }, + [](TypeVariable const& _variable) -> Sort { return _variable.sort(); }, + [](std::monostate) -> Sort { solAssert(false); } + }, _type); +} + +TypeConstructor TypeSystem::declareTypeConstructor(string _name, string _canonicalName, size_t _arguments, Declaration const* _declaration) +{ + solAssert(m_canonicalTypeNames.insert(_canonicalName).second, "Duplicate canonical type name."); + Sort baseSort{{primitiveClass(PrimitiveClass::Type)}}; + size_t index = m_typeConstructors.size(); + m_typeConstructors.emplace_back(TypeConstructorInfo{ + _name, + _canonicalName, + {Arity{vector{_arguments, baseSort}, primitiveClass(PrimitiveClass::Type)}}, + _declaration + }); + TypeConstructor constructor{index}; + if (_arguments) + { + std::vector argumentSorts; + std::generate_n(std::back_inserter(argumentSorts), _arguments, [&](){ return Sort{{primitiveClass(PrimitiveClass::Type)}}; }); + std::vector argumentTypes; + std::generate_n(std::back_inserter(argumentTypes), _arguments, [&](){ return freshVariable({}); }); + auto error = instantiateClass(type(constructor, argumentTypes), Arity{argumentSorts, primitiveClass(PrimitiveClass::Type)}); + solAssert(!error, *error); + } + else + { + auto error = instantiateClass(type(constructor, {}), Arity{{}, primitiveClass(PrimitiveClass::Type)}); + solAssert(!error, *error); + } + return constructor; +} + +std::variant TypeSystem::declareTypeClass(Type _typeVariable, std::string _name, Declaration const* _declaration) +{ + TypeVariable const* typeVariable = get_if(&_typeVariable); + if (!typeVariable) + return "Invalid type variable."; + + size_t index = m_typeClasses.size(); + m_typeClasses.emplace_back(TypeClassInfo{ + _typeVariable, + _name, + _declaration + }); + TypeClass typeClass{index}; + + return typeClass; +} + +experimental::Type TypeSystem::type(TypeConstructor _constructor, std::vector _arguments) const +{ + // TODO: proper error handling + auto const& info = m_typeConstructors.at(_constructor.m_index); + solAssert(info.arguments() == _arguments.size(), "Invalid arity."); + return TypeConstant{_constructor, _arguments}; +} + +experimental::Type TypeEnvironment::fresh(Type _type) +{ + std::unordered_map mapping; + auto freshImpl = [&](Type _type, auto _recurse) -> Type { + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) -> Type { + return TypeConstant{ + _type.constructor, + _type.arguments | ranges::views::transform([&](Type _argType) { + return _recurse(_argType, _recurse); + }) | ranges::to> + }; + }, + [&](TypeVariable const& _var) -> Type { + if (auto* mapped = util::valueOrNullptr(mapping, _var.index())) + { + auto* typeVariable = get_if(mapped); + solAssert(typeVariable); + // TODO: can there be a mismatch? + solAssert(typeVariable->sort() == _var.sort()); + return *mapped; + } + return mapping[_var.index()] = m_typeSystem.freshTypeVariable(_var.sort()); + }, + [](std::monostate) -> Type { solAssert(false); } + }, resolve(_type)); + }; + return freshImpl(_type, freshImpl); +} + +std::optional TypeSystem::instantiateClass(Type _instanceVariable, Arity _arity) +{ + if (!TypeSystemHelpers{*this}.isTypeConstant(_instanceVariable)) + return "Invalid instance variable."; + auto [typeConstructor, typeArguments] = TypeSystemHelpers{*this}.destTypeConstant(_instanceVariable); + auto& typeConstructorInfo = m_typeConstructors.at(typeConstructor.m_index); + if (_arity.argumentSorts.size() != typeConstructorInfo.arguments()) + return "Invalid arity."; + if (typeArguments.size() != typeConstructorInfo.arguments()) + return "Invalid arity."; + + typeConstructorInfo.arities.emplace_back(_arity); + + return nullopt; +} diff --git a/libsolidity/experimental/ast/TypeSystem.h b/libsolidity/experimental/ast/TypeSystem.h new file mode 100644 index 000000000..17b427e85 --- /dev/null +++ b/libsolidity/experimental/ast/TypeSystem.h @@ -0,0 +1,154 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace solidity::frontend +{ +class Declaration; +} +namespace solidity::frontend::experimental +{ + +class TypeEnvironment +{ +public: + TypeEnvironment(TypeSystem& _typeSystem): m_typeSystem(_typeSystem) {} + TypeEnvironment(TypeEnvironment const&) = delete; + TypeEnvironment& operator=(TypeEnvironment const&) = delete; + TypeEnvironment clone() const; + Type resolve(Type _type) const; + Type resolveRecursive(Type _type) const; + Type fresh(Type _type); + struct TypeMismatch { Type a; Type b; }; + struct SortMismatch { Type type; Sort sort; }; + struct RecursiveUnification { Type var; Type type; }; + using UnificationFailure = std::variant; + [[nodiscard]] std::vector unify(Type _a, Type _b); + Sort sort(Type _type) const; + bool typeEquals(Type _lhs, Type _rhs) const; + TypeSystem& typeSystem() { return m_typeSystem; } + TypeSystem const& typeSystem() const { return m_typeSystem; } +private: + TypeEnvironment(TypeEnvironment&& _env): m_typeSystem(_env.m_typeSystem), m_typeVariables(std::move(_env.m_typeVariables)) {} + [[nodiscard]] std::vector instantiate(TypeVariable _variable, Type _type); + TypeSystem& m_typeSystem; + std::map m_typeVariables; +}; + +class TypeSystem +{ +public: + struct TypeConstructorInfo + { + std::string name; + std::string canonicalName; + std::vector arities; + Declaration const* typeDeclaration = nullptr; + size_t arguments() const + { + solAssert(!arities.empty()); + return arities.front().argumentSorts.size(); + } + }; + struct TypeClassInfo + { + Type typeVariable; + std::string name; + Declaration const* classDeclaration = nullptr; + }; + TypeSystem(); + TypeSystem(TypeSystem const&) = delete; + TypeSystem const& operator=(TypeSystem const&) = delete; + Type type(PrimitiveType _typeConstructor, std::vector _arguments) const + { + return type(m_primitiveTypeConstructors.at(_typeConstructor), std::move(_arguments)); + } + Type type(TypeConstructor _typeConstructor, std::vector _arguments) const; + std::string typeName(TypeConstructor _typeConstructor) const + { + // TODO: proper error handling + return m_typeConstructors.at(_typeConstructor.m_index).name; + } + std::string canonicalName(TypeConstructor _typeConstructor) const + { + // TODO: proper error handling + return m_typeConstructors.at(_typeConstructor.m_index).canonicalName; + } + TypeConstructor declareTypeConstructor(std::string _name, std::string _canonicalName, size_t _arguments, Declaration const* _declaration); + TypeConstructor constructor(PrimitiveType _type) const + { + return m_primitiveTypeConstructors.at(_type); + } + TypeClass primitiveClass(PrimitiveClass _class) const + { + return m_primitiveTypeClasses.at(_class); + } + size_t constructorArguments(TypeConstructor _typeConstructor) const + { + // TODO: error handling + return m_typeConstructors.at(_typeConstructor.m_index).arguments(); + } + TypeConstructorInfo const& constructorInfo(TypeConstructor _typeConstructor) const + { + // TODO: error handling + return m_typeConstructors.at(_typeConstructor.m_index); + } + TypeConstructorInfo const& constructorInfo(PrimitiveType _typeConstructor) const + { + return constructorInfo(constructor(_typeConstructor)); + } + + std::variant declareTypeClass(Type _typeVariable, std::string _name, Declaration const* _declaration); + [[nodiscard]] std::optional instantiateClass(Type _instanceVariable, Arity _arity); + + Type freshTypeVariable(Sort _sort); + + TypeEnvironment const& env() const { return m_globalTypeEnvironment; } + TypeEnvironment& env() { return m_globalTypeEnvironment; } + + Type freshVariable(Sort _sort); + std::string typeClassName(TypeClass _class) const { return m_typeClasses.at(_class.m_index).name; } + Declaration const* typeClassDeclaration(TypeClass _class) const { return m_typeClasses.at(_class.m_index).classDeclaration; } + Type typeClassVariable(TypeClass _class) const + { + return m_typeClasses.at(_class.m_index).typeVariable; + } +private: + friend class TypeEnvironment; + TypeClassInfo const& typeClassInfo(TypeClass _class) const + { + return m_typeClasses.at(_class.m_index); + } + size_t m_numTypeVariables = 0; + std::map m_primitiveTypeConstructors; + std::map m_primitiveTypeClasses; + std::set m_canonicalTypeNames; + std::vector m_typeConstructors; + std::vector m_typeClasses; + TypeEnvironment m_globalTypeEnvironment{*this}; +}; + +} diff --git a/libsolidity/experimental/ast/TypeSystemHelper.cpp b/libsolidity/experimental/ast/TypeSystemHelper.cpp new file mode 100644 index 000000000..b8d72b85b --- /dev/null +++ b/libsolidity/experimental/ast/TypeSystemHelper.cpp @@ -0,0 +1,403 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; + +/*std::optional experimental::typeConstructorFromTypeName(Analysis const& _analysis, TypeName const& _typeName) +{ + if (auto const* elementaryTypeName = dynamic_cast(&_typeName)) + { + if (auto constructor = typeConstructorFromToken(_analysis, elementaryTypeName->typeName().token())) + return *constructor; + } + else if (auto const* userDefinedType = dynamic_cast(&_typeName)) + { + if (auto const* referencedDeclaration = userDefinedType->pathNode().annotation().referencedDeclaration) + return _analysis.annotation(*referencedDeclaration).typeConstructor; + } + return nullopt; +}*/ +/* +std::optional experimental::typeConstructorFromToken(Analysis const& _analysis, langutil::Token _token) +{ + TypeSystem const& typeSystem = _analysis.typeSystem(); + switch(_token) + { + case Token::Void: + return typeSystem.builtinConstructor(BuiltinType::Void); + case Token::Fun: + return typeSystem.builtinConstructor(BuiltinType::Function); + case Token::Unit: + return typeSystem.builtinConstructor(BuiltinType::Unit); + case Token::Pair: + return typeSystem.builtinConstructor(BuiltinType::Pair); + case Token::Word: + return typeSystem.builtinConstructor(BuiltinType::Word); + case Token::Integer: + return typeSystem.builtinConstructor(BuiltinType::Integer); + case Token::Bool: + return typeSystem.builtinConstructor(BuiltinType::Bool); + default: + return nullopt; + } +}*/ + +std::optional experimental::builtinClassFromToken(langutil::Token _token) +{ + switch (_token) + { + case Token::Integer: + return BuiltinClass::Integer; + case Token::Mul: + return BuiltinClass::Mul; + case Token::Add: + return BuiltinClass::Add; + case Token::Equal: + return BuiltinClass::Equal; + case Token::LessThan: + return BuiltinClass::Less; + case Token::LessThanOrEqual: + return BuiltinClass::LessOrEqual; + case Token::GreaterThan: + return BuiltinClass::Greater; + case Token::GreaterThanOrEqual: + return BuiltinClass::GreaterOrEqual; + default: + return nullopt; + } +} +/* +std::optional experimental::typeClassFromTypeClassName(TypeClassName const& _typeClass) +{ + return std::visit(util::GenericVisitor{ + [&](ASTPointer _path) -> optional { + auto classDefinition = dynamic_cast(_path->annotation().referencedDeclaration); + if (!classDefinition) + return nullopt; + return TypeClass{classDefinition}; + }, + [&](Token _token) -> optional { + return typeClassFromToken(_token); + } + }, _typeClass.name()); +} +*/ +experimental::Type TypeSystemHelpers::tupleType(vector _elements) const +{ + if (_elements.empty()) + return typeSystem.type(PrimitiveType::Unit, {}); + if (_elements.size() == 1) + return _elements.front(); + Type result = _elements.back(); + for (Type type: _elements | ranges::views::reverse | ranges::views::drop_exactly(1)) + result = typeSystem.type(PrimitiveType::Pair, {type, result}); + return result; +} + +vector TypeSystemHelpers::destTupleType(Type _tupleType) const +{ + if (!isTypeConstant(_tupleType)) + return {_tupleType}; + TypeConstructor pairConstructor = typeSystem.constructor(PrimitiveType::Pair); + auto [constructor, arguments] = destTypeConstant(_tupleType); + if (constructor == typeSystem.constructor(PrimitiveType::Unit)) + return {}; + if (constructor != pairConstructor) + return {_tupleType}; + solAssert(arguments.size() == 2); + + vector result; + result.emplace_back(arguments.front()); + Type tail = arguments.back(); + while(true) + { + if (!isTypeConstant(tail)) + break; + auto [tailConstructor, tailArguments] = destTypeConstant(tail); + if (tailConstructor != pairConstructor) + break; + solAssert(tailArguments.size() == 2); + result.emplace_back(tailArguments.front()); + tail = tailArguments.back(); + } + result.emplace_back(tail); + return result; +} + +experimental::Type TypeSystemHelpers::sumType(vector _elements) const +{ + if (_elements.empty()) + return typeSystem.type(PrimitiveType::Void, {}); + if (_elements.size() == 1) + return _elements.front(); + Type result = _elements.back(); + for (Type type: _elements | ranges::views::reverse | ranges::views::drop_exactly(1)) + result = typeSystem.type(PrimitiveType::Sum, {type, result}); + return result; +} + +vector TypeSystemHelpers::destSumType(Type _tupleType) const +{ + if (!isTypeConstant(_tupleType)) + return {_tupleType}; + TypeConstructor sumConstructor = typeSystem.constructor(PrimitiveType::Sum); + auto [constructor, arguments] = destTypeConstant(_tupleType); + if (constructor == typeSystem.constructor(PrimitiveType::Void)) + return {}; + if (constructor != sumConstructor) + return {_tupleType}; + solAssert(arguments.size() == 2); + + vector result; + result.emplace_back(arguments.front()); + Type tail = arguments.back(); + while(true) + { + if (!isTypeConstant(tail)) + break; + auto [tailConstructor, tailArguments] = destTypeConstant(tail); + if (tailConstructor != sumConstructor) + break; + solAssert(tailArguments.size() == 2); + result.emplace_back(tailArguments.front()); + tail = tailArguments.back(); + } + result.emplace_back(tail); + return result; +} + +tuple> TypeSystemHelpers::destTypeConstant(Type _type) const +{ + using ResultType = tuple>; + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) -> ResultType { + return std::make_tuple(_type.constructor, _type.arguments); + }, + [](auto const&) -> ResultType { + solAssert(false); + } + }, _type); +} + +bool TypeSystemHelpers::isTypeConstant(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const&) -> bool { + return true; + }, + [](auto const&) -> bool { + return false; + } + }, _type); +} + +experimental::Type TypeSystemHelpers::functionType(experimental::Type _argType, experimental::Type _resultType) const +{ + return typeSystem.type(PrimitiveType::Function, {_argType, _resultType}); +} + +tuple TypeSystemHelpers::destFunctionType(Type _functionType) const +{ + auto [constructor, arguments] = destTypeConstant(_functionType); + solAssert(constructor == typeSystem.constructor(PrimitiveType::Function)); + solAssert(arguments.size() == 2); + return make_tuple(arguments.front(), arguments.back()); +} + +bool TypeSystemHelpers::isFunctionType(Type _type) const +{ + if (!isTypeConstant(_type)) + return false; + auto constructor = get<0>(destTypeConstant(_type)); + return constructor == typeSystem.constructor(PrimitiveType::Function); +} + +experimental::Type TypeSystemHelpers::typeFunctionType(experimental::Type _argType, experimental::Type _resultType) const +{ + return typeSystem.type(PrimitiveType::TypeFunction, {_argType, _resultType}); +} + +tuple TypeSystemHelpers::destTypeFunctionType(Type _functionType) const +{ + auto [constructor, arguments] = destTypeConstant(_functionType); + solAssert(constructor == typeSystem.constructor(PrimitiveType::TypeFunction)); + solAssert(arguments.size() == 2); + return make_tuple(arguments.front(), arguments.back()); +} + +bool TypeSystemHelpers::isTypeFunctionType(Type _type) const +{ + if (!isTypeConstant(_type)) + return false; + auto constructor = get<0>(destTypeConstant(_type)); + return constructor == typeSystem.constructor(PrimitiveType::TypeFunction); +} + +vector TypeEnvironmentHelpers::typeVars(Type _type) const +{ + set indices; + vector typeVars; + auto typeVarsImpl = [&](Type _type, auto _recurse) -> void { + std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) { + for (auto arg: _type.arguments) + _recurse(arg, _recurse); + }, + [&](TypeVariable const& _var) { + if (indices.emplace(_var.index()).second) + typeVars.emplace_back(_var); + }, + [](std::monostate) { solAssert(false); } + }, env.resolve(_type)); + }; + typeVarsImpl(_type, typeVarsImpl); + return typeVars; + +} + + +std::string TypeSystemHelpers::sortToString(Sort _sort) const +{ + switch (_sort.classes.size()) + { + case 0: + return "()"; + case 1: + return typeSystem.typeClassName(*_sort.classes.begin()); + default: + { + std::stringstream stream; + stream << "("; + for (auto typeClass: _sort.classes | ranges::views::drop_last(1)) + stream << typeSystem.typeClassName(typeClass) << ", "; + stream << typeSystem.typeClassName(*_sort.classes.rbegin()) << ")"; + return stream.str(); + } + } +} + +string TypeEnvironmentHelpers::canonicalTypeName(Type _type) const +{ + return visit(util::GenericVisitor{ + [&](TypeConstant _type) -> string { + std::stringstream stream; + stream << env.typeSystem().constructorInfo(_type.constructor).canonicalName; + if (!_type.arguments.empty()) + { + stream << "$"; + for (auto type: _type.arguments | ranges::views::drop_last(1)) + stream << canonicalTypeName(type) << "$"; + stream << canonicalTypeName(_type.arguments.back()); + stream << "$"; + } + return stream.str(); + }, + [](TypeVariable) -> string { + solAssert(false); + }, + [](std::monostate) -> string { + solAssert(false); + }, + }, env.resolve(_type)); +} + +std::string TypeEnvironmentHelpers::typeToString(Type const& _type) const +{ + std::map)>> formatters{ + {env.typeSystem().constructor(PrimitiveType::Function), [&](auto const& _args) { + solAssert(_args.size() == 2); + return fmt::format("{} -> {}", typeToString(_args.front()), typeToString(_args.back())); + }}, + {env.typeSystem().constructor(PrimitiveType::Unit), [&](auto const& _args) { + solAssert(_args.size() == 0); + return "()"; + }}, + {env.typeSystem().constructor(PrimitiveType::Pair), [&](auto const& _arguments) { + auto tupleTypes = TypeSystemHelpers{env.typeSystem()}.destTupleType(_arguments.back()); + string result = "("; + result += typeToString(_arguments.front()); + for (auto type: tupleTypes) + result += ", " + typeToString(type); + result += ")"; + return result; + }}, + }; + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) { + if (auto* formatter = util::valueOrNullptr(formatters, _type.constructor)) + return (*formatter)(_type.arguments); + std::stringstream stream; + stream << env.typeSystem().constructorInfo(_type.constructor).name; + if (!_type.arguments.empty()) + { + stream << "("; + for (auto type: _type.arguments | ranges::views::drop_last(1)) + stream << typeToString(type) << ", "; + stream << typeToString(_type.arguments.back()); + stream << ")"; + } + return stream.str(); + }, + [&](TypeVariable const& _type) { + std::stringstream stream; + std::string varName; + size_t index = _type.index(); + varName += 'a' + static_cast(index%26); + while (index /= 26) + varName += 'a' + static_cast(index%26); + reverse(varName.begin(), varName.end()); + stream << '\'' << varName; + switch (_type.sort().classes.size()) + { + case 0: + break; + case 1: + stream << ":" << env.typeSystem().typeClassName(*_type.sort().classes.begin()); + break; + default: + stream << ":("; + for (auto typeClass: _type.sort().classes | ranges::views::drop_last(1)) + stream << env.typeSystem().typeClassName(typeClass) << ", "; + stream << env.typeSystem().typeClassName(*_type.sort().classes.rbegin()); + stream << ")"; + break; + } + return stream.str(); + }, + [](std::monostate) -> string { solAssert(false); } + }, env.resolve(_type)); +} diff --git a/libsolidity/experimental/ast/TypeSystemHelper.h b/libsolidity/experimental/ast/TypeSystemHelper.h new file mode 100644 index 000000000..5149d34bd --- /dev/null +++ b/libsolidity/experimental/ast/TypeSystemHelper.h @@ -0,0 +1,59 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include +#include + +namespace solidity::frontend::experimental +{ +class Analysis; +enum class BuiltinClass; +//std::optional typeConstructorFromTypeName(Analysis const& _analysis, TypeName const& _typeName); +//std::optional typeConstructorFromToken(Analysis const& _analysis, langutil::Token _token); +//std::optional typeClassFromTypeClassName(TypeClassName const& _typeClass); +std::optional builtinClassFromToken(langutil::Token _token); + +struct TypeSystemHelpers +{ + TypeSystem const& typeSystem; + std::tuple> destTypeConstant(Type _type) const; + bool isTypeConstant(Type _type) const; + Type tupleType(std::vector _elements) const; + std::vector destTupleType(Type _tupleType) const; + Type sumType(std::vector _elements) const; + std::vector destSumType(Type _tupleType) const; + Type functionType(Type _argType, Type _resultType) const; + std::tuple destFunctionType(Type _functionType) const; + bool isFunctionType(Type _type) const; + Type typeFunctionType(Type _argType, Type _resultType) const; + std::tuple destTypeFunctionType(Type _functionType) const; + bool isTypeFunctionType(Type _type) const; + std::string sortToString(Sort _sort) const; +}; + +struct TypeEnvironmentHelpers +{ + TypeEnvironment const& env; + std::string typeToString(Type const& _type) const; + std::string canonicalTypeName(Type _type) const; + std::vector typeVars(Type _type) const; +}; + +} diff --git a/libsolidity/experimental/codegen/Common.cpp b/libsolidity/experimental/codegen/Common.cpp new file mode 100644 index 000000000..b14c51c67 --- /dev/null +++ b/libsolidity/experimental/codegen/Common.cpp @@ -0,0 +1,74 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::util; +using namespace solidity::yul; + +namespace solidity::frontend::experimental +{ + +string IRNames::function(TypeEnvironment const& _env, FunctionDefinition const& _function, Type _type) +{ + if (_function.isConstructor()) + return constructor(*_function.annotation().contract); + + return "fun_" + _function.name() + "_" + to_string(_function.id()) + "$" + TypeEnvironmentHelpers{_env}.canonicalTypeName(_type) + "$"; +} + +string IRNames::function(VariableDeclaration const& _varDecl) +{ + return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} + +string IRNames::creationObject(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + toString(_contract.id()); +} + +string IRNames::deployedObject(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + toString(_contract.id()) + "_deployed"; +} + +string IRNames::constructor(ContractDefinition const& _contract) +{ + return "constructor_" + _contract.name() + "_" + to_string(_contract.id()); +} + +string IRNames::localVariable(VariableDeclaration const& _declaration) +{ + return "var_" + _declaration.name() + '_' + std::to_string(_declaration.id()); +} + +string IRNames::localVariable(Expression const& _expression) +{ + return "expr_" + to_string(_expression.id()); +} + +} diff --git a/libsolidity/experimental/codegen/Common.h b/libsolidity/experimental/codegen/Common.h new file mode 100644 index 000000000..25f05efcb --- /dev/null +++ b/libsolidity/experimental/codegen/Common.h @@ -0,0 +1,41 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include +#include + +namespace solidity::frontend::experimental +{ + +struct IRNames +{ + static std::string function(TypeEnvironment const& _env, FunctionDefinition const& _function, Type _type); + static std::string function(VariableDeclaration const& _varDecl); + static std::string creationObject(ContractDefinition const& _contract); + static std::string deployedObject(ContractDefinition const& _contract); + static std::string constructor(ContractDefinition const& _contract); + static std::string localVariable(VariableDeclaration const& _declaration); + static std::string localVariable(Expression const& _expression); +}; + +} diff --git a/libsolidity/experimental/codegen/IRGenerationContext.h b/libsolidity/experimental/codegen/IRGenerationContext.h new file mode 100644 index 000000000..b0958cf67 --- /dev/null +++ b/libsolidity/experimental/codegen/IRGenerationContext.h @@ -0,0 +1,55 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include +#include + +#include +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +struct IRGenerationContext +{ + Analysis const& analysis; + TypeEnvironment const* env = nullptr; + void enqueueFunctionDefinition(FunctionDefinition const* _functionDefinition, Type _type) + { + QueuedFunction queue{_functionDefinition, env->resolve(_type)}; + for (auto type: generatedFunctions[_functionDefinition]) + if (env->typeEquals(type, _type)) + return; + functionQueue.emplace_back(queue); + } + struct QueuedFunction + { + FunctionDefinition const* function = nullptr; + Type type = std::monostate{}; + }; + std::list functionQueue; + std::map> generatedFunctions; +}; + +} diff --git a/libsolidity/experimental/codegen/IRGenerator.cpp b/libsolidity/experimental/codegen/IRGenerator.cpp new file mode 100644 index 000000000..a050ed07a --- /dev/null +++ b/libsolidity/experimental/codegen/IRGenerator.cpp @@ -0,0 +1,161 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; +using namespace solidity::util; + +IRGenerator::IRGenerator( + EVMVersion _evmVersion, + std::optional _eofVersion, + frontend::RevertStrings, std::map, + DebugInfoSelection const&, + CharStreamProvider const*, + Analysis const& _analysis +) +: +m_evmVersion(_evmVersion), +m_eofVersion(_eofVersion), +// m_debugInfoSelection(_debugInfoSelection), +// m_soliditySourceProvider(_soliditySourceProvider), +m_env(_analysis.typeSystem().env().clone()), +m_context{_analysis, &m_env, {}, {}} +{ +} + +string IRGenerator::run( + ContractDefinition const& _contract, + bytes const& /*_cborMetadata*/, + map const& /*_otherYulSources*/ +) +{ + + Whiskers t(R"( + object "" { + code { + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + } + object "" { + code { + + } + } + } + )"); + t("CreationObject", IRNames::creationObject(_contract)); + t("DeployedObject", IRNames::deployedObject(_contract)); + t("code", generate(_contract)); + + return t.render(); +} + +string IRGenerator::generate(ContractDefinition const& _contract) +{ + std::stringstream code; + code << "{\n"; + if (_contract.fallbackFunction()) + { + auto type = m_context.analysis.annotation(*_contract.fallbackFunction()).type; + solAssert(type); + type = m_context.env->resolve(*type); + code << IRNames::function(*m_context.env, *_contract.fallbackFunction(), *type) << "()\n"; + m_context.enqueueFunctionDefinition(_contract.fallbackFunction(), *type); + } + code << "revert(0,0)\n"; + code << "}\n"; + + while (!m_context.functionQueue.empty()) + { + auto queueEntry = m_context.functionQueue.front(); + m_context.functionQueue.pop_front(); + auto& generatedTypes = m_context.generatedFunctions.insert(std::make_pair(queueEntry.function, vector{})).first->second; + if (!util::contains_if(generatedTypes, [&](auto const& _generatedType) { return m_context.env->typeEquals(_generatedType, queueEntry.type); })) + { + generatedTypes.emplace_back(queueEntry.type); + code << generate(*queueEntry.function, queueEntry.type); + } + } + + return code.str(); +} + +string IRGenerator::generate(FunctionDefinition const& _function, Type _type) +{ + TypeEnvironment newEnv = m_context.env->clone(); + ScopedSaveAndRestore envRestore{m_context.env, &newEnv}; + auto type = m_context.analysis.annotation(_function).type; + solAssert(type); + for (auto err: newEnv.unify(*type, _type)) + { + TypeEnvironmentHelpers helper{newEnv}; + solAssert(false, helper.typeToString(*type) + " <-> " + helper.typeToString(_type)); + } + std::stringstream code; + code << "function " << IRNames::function(newEnv, _function, _type) << "("; + if (_function.parameters().size() > 1) + for (auto const& arg: _function.parameters() | ranges::views::drop_last(1)) + code << IRNames::localVariable(*arg) << ", "; + if (!_function.parameters().empty()) + code << IRNames::localVariable(*_function.parameters().back()); + code << ")"; + if (_function.experimentalReturnExpression()) + { + auto returnType = m_context.analysis.annotation(*_function.experimentalReturnExpression()).type; + solAssert(returnType); + if (!m_env.typeEquals(*returnType, m_context.analysis.typeSystem().type(PrimitiveType::Unit, {}))) + { + // TODO: destructure tuples. + code << " -> " << IRNames::localVariable(*_function.experimentalReturnExpression()) << " "; + } + } + code << "{\n"; + for (auto _statement: _function.body().statements()) + { + IRGeneratorForStatements statementGenerator{m_context}; + code << statementGenerator.generate(*_statement); + } + code << "}\n"; + return code.str(); +} diff --git a/libsolidity/codegen/experimental/IRGenerator.h b/libsolidity/experimental/codegen/IRGenerator.h similarity index 71% rename from libsolidity/codegen/experimental/IRGenerator.h rename to libsolidity/experimental/codegen/IRGenerator.h index b2d1a7247..cce8c7305 100644 --- a/libsolidity/codegen/experimental/IRGenerator.h +++ b/libsolidity/experimental/codegen/IRGenerator.h @@ -18,10 +18,12 @@ #pragma once +#include #include #include #include #include +#include #include #include @@ -34,7 +36,7 @@ namespace solidity::frontend::experimental { -class SourceUnit; +class Analysis; class IRGenerator { @@ -44,30 +46,27 @@ public: std::optional _eofVersion, RevertStrings /*_revertStrings*/, std::map /*_sourceIndices*/, - langutil::DebugInfoSelection const& _debugInfoSelection, - langutil::CharStreamProvider const* _soliditySourceProvider - ): - m_evmVersion(_evmVersion), - m_eofVersion(_eofVersion), - m_debugInfoSelection(_debugInfoSelection), - m_soliditySourceProvider(_soliditySourceProvider) - {} + langutil::DebugInfoSelection const& /*_debugInfoSelection*/, + langutil::CharStreamProvider const* /*_soliditySourceProvider*/, + Analysis const& _analysis + ); std::string run( ContractDefinition const& _contract, bytes const& _cborMetadata, std::map const& _otherYulSources - ) const; + ); - std::string generate(ContractDefinition const& _contract) const; - std::string generate(FunctionDefinition const& _function) const; - std::string generate(InlineAssembly const& _assembly) const; + std::string generate(ContractDefinition const& _contract); + std::string generate(FunctionDefinition const& _function, Type _type); private: langutil::EVMVersion const m_evmVersion; std::optional const m_eofVersion; OptimiserSettings const m_optimiserSettings; - langutil::DebugInfoSelection m_debugInfoSelection = {}; - langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; +// langutil::DebugInfoSelection m_debugInfoSelection = {}; +// langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; + TypeEnvironment m_env; + IRGenerationContext m_context; }; } diff --git a/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp b/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp new file mode 100644 index 000000000..911dc11f9 --- /dev/null +++ b/libsolidity/experimental/codegen/IRGeneratorForStatements.cpp @@ -0,0 +1,388 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::util; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace std::string_literals; + +std::string IRGeneratorForStatements::generate(ASTNode const& _node) +{ + _node.accept(*this); + return m_code.str(); +} + + +namespace { + +struct CopyTranslate: public yul::ASTCopier +{ + CopyTranslate( + IRGenerationContext const& _context, + yul::Dialect const& _dialect, + map _references + ): m_context(_context), m_dialect(_dialect), m_references(std::move(_references)) {} + + using ASTCopier::operator(); + + yul::Expression operator()(yul::Identifier const& _identifier) override + { + // The operator() function is only called in lvalue context. In rvalue context, + // only translate(yul::Identifier) is called. + if (m_references.count(&_identifier)) + return translateReference(_identifier); + else + return ASTCopier::operator()(_identifier); + } + + yul::YulString translateIdentifier(yul::YulString _name) override + { + if (m_dialect.builtin(_name)) + return _name; + else + return yul::YulString{"usr$" + _name.str()}; + } + + yul::Identifier translate(yul::Identifier const& _identifier) override + { + if (!m_references.count(&_identifier)) + return ASTCopier::translate(_identifier); + + yul::Expression translated = translateReference(_identifier); + solAssert(holds_alternative(translated)); + return get(std::move(translated)); + } + +private: + + /// Translates a reference to a local variable, potentially including + /// a suffix. Might return a literal, which causes this to be invalid in + /// lvalue-context. + yul::Expression translateReference(yul::Identifier const& _identifier) + { + auto const& reference = m_references.at(&_identifier); + auto const varDecl = dynamic_cast(reference.declaration); + solAssert(varDecl, "External reference in inline assembly to something that is not a variable declaration."); + auto type = m_context.analysis.annotation(*varDecl).type; + solAssert(type); + solAssert(m_context.env->typeEquals(*type, m_context.analysis.typeSystem().type(PrimitiveType::Word, {}))); + string value = IRNames::localVariable(*varDecl); + return yul::Identifier{_identifier.debugData, yul::YulString{value}}; + } + + IRGenerationContext const& m_context; + yul::Dialect const& m_dialect; + map m_references; +}; + +} + +bool IRGeneratorForStatements::visit(TupleExpression const& _tupleExpression) +{ + std::vector components; + for (auto const& component: _tupleExpression.components()) + { + solUnimplementedAssert(component); + component->accept(*this); + components.emplace_back(IRNames::localVariable(*component)); + } + + solUnimplementedAssert(false, "No support for tuples."); + + return false; +} + +bool IRGeneratorForStatements::visit(InlineAssembly const& _assembly) +{ + CopyTranslate bodyCopier{m_context, _assembly.dialect(), _assembly.annotation().externalReferences}; + yul::Statement modified = bodyCopier(_assembly.operations()); + solAssert(holds_alternative(modified)); + m_code << yul::AsmPrinter()(std::get(modified)) << "\n"; + return false; +} + +bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + if (_variableDeclarationStatement.initialValue()) + _variableDeclarationStatement.initialValue()->accept(*this); + solAssert(_variableDeclarationStatement.declarations().size() == 1, "multi variable declarations not supported"); + VariableDeclaration const* variableDeclaration = _variableDeclarationStatement.declarations().front().get(); + solAssert(variableDeclaration); + // TODO: check the type of the variable; register local variable; initialize + m_code << "let " << IRNames::localVariable(*variableDeclaration); + if (_variableDeclarationStatement.initialValue()) + m_code << " := " << IRNames::localVariable(*_variableDeclarationStatement.initialValue()); + m_code << "\n"; + return false; +} + +bool IRGeneratorForStatements::visit(ExpressionStatement const&) +{ + return true; +} + +bool IRGeneratorForStatements::visit(Identifier const& _identifier) +{ + if (auto const* var = dynamic_cast(_identifier.annotation().referencedDeclaration)) + { + m_code << "let " << IRNames::localVariable(_identifier) << " := " << IRNames::localVariable(*var) << "\n"; + } + else if (auto const* function = dynamic_cast(_identifier.annotation().referencedDeclaration)) + solAssert(m_expressionDeclaration.emplace(&_identifier, function).second); + else if (auto const* typeClass = dynamic_cast(_identifier.annotation().referencedDeclaration)) + solAssert(m_expressionDeclaration.emplace(&_identifier, typeClass).second); + else if (auto const* typeDefinition = dynamic_cast(_identifier.annotation().referencedDeclaration)) + solAssert(m_expressionDeclaration.emplace(&_identifier, typeDefinition).second); + else + solAssert(false, "Unsupported Identifier"); + return false; +} + +void IRGeneratorForStatements::endVisit(Return const& _return) +{ + if (Expression const* value = _return.expression()) + { + solAssert(_return.annotation().function, "Invalid return."); + solAssert(_return.annotation().function->experimentalReturnExpression(), "Invalid return."); + m_code << IRNames::localVariable(*_return.annotation().function->experimentalReturnExpression()) << " := " << IRNames::localVariable(*value) << "\n"; + } + + m_code << "leave\n"; +} + +experimental::Type IRGeneratorForStatements::type(ASTNode const& _node) const +{ + auto type = m_context.analysis.annotation(_node).type; + solAssert(type); + return *type; +} + +void IRGeneratorForStatements::endVisit(BinaryOperation const& _binaryOperation) +{ + TypeSystemHelpers helper{m_context.analysis.typeSystem()}; + Type leftType = type(_binaryOperation.leftExpression()); + Type rightType = type(_binaryOperation.rightExpression()); + Type resultType = type(_binaryOperation); + Type functionType = helper.functionType(helper.tupleType({leftType, rightType}), resultType); + auto [typeClass, memberName] = m_context.analysis.annotation().operators.at(_binaryOperation.getOperator()); + auto const& functionDefinition = resolveTypeClassFunction(typeClass, memberName, functionType); + // TODO: deduplicate with FunctionCall + // TODO: get around resolveRecursive by passing the environment further down? + functionType = m_context.env->resolveRecursive(functionType); + m_context.enqueueFunctionDefinition(&functionDefinition, functionType); + // TODO: account for return stack size + m_code << "let " << IRNames::localVariable(_binaryOperation) << " := " << IRNames::function(*m_context.env, functionDefinition, functionType) << "(" + << IRNames::localVariable(_binaryOperation.leftExpression()) << ", " << IRNames::localVariable(_binaryOperation.rightExpression()) << ")\n"; +} + +namespace +{ +TypeRegistration::TypeClassInstantiations const& typeClassInstantiations(IRGenerationContext const& _context, TypeClass _class) +{ + auto const* typeClassDeclaration = _context.analysis.typeSystem().typeClassDeclaration(_class); + if (typeClassDeclaration) + return _context.analysis.annotation(*typeClassDeclaration).instantiations; + // TODO: better mechanism than fetching by name. + auto& instantiations = _context.analysis.annotation().builtinClassInstantiations; + auto& builtinClassesByName = _context.analysis.annotation().builtinClassesByName; + return instantiations.at(builtinClassesByName.at(_context.analysis.typeSystem().typeClassName(_class))); +} +} + +FunctionDefinition const& IRGeneratorForStatements::resolveTypeClassFunction(TypeClass _class, string _name, Type _type) +{ + TypeSystemHelpers helper{m_context.analysis.typeSystem()}; + + TypeEnvironment env = m_context.env->clone(); + Type genericFunctionType = env.fresh(m_context.analysis.annotation().typeClassFunctions.at(_class).at(_name)); + auto typeVars = TypeEnvironmentHelpers{env}.typeVars(genericFunctionType); + solAssert(typeVars.size() == 1); + solAssert(env.unify(genericFunctionType, _type).empty()); + auto typeClassInstantiation = get<0>(helper.destTypeConstant(env.resolve(typeVars.front()))); + + auto const& instantiations = typeClassInstantiations(m_context, _class); + TypeClassInstantiation const* instantiation = instantiations.at(typeClassInstantiation); + FunctionDefinition const* functionDefinition = nullptr; + for (auto const& node: instantiation->subNodes()) + { + auto const* def = dynamic_cast(node.get()); + solAssert(def); + if (def->name() == _name) + { + functionDefinition = def; + break; + } + } + solAssert(functionDefinition); + return *functionDefinition; +} + +void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) +{ + TypeSystemHelpers helper{m_context.analysis.typeSystem()}; + // TODO: avoid resolve? + auto expressionType = m_context.env->resolve(type(_memberAccess.expression())); + auto constructor = std::get<0>(helper.destTypeConstant(expressionType)); + auto memberAccessType = type(_memberAccess); + // TODO: better mechanism + if (constructor == m_context.analysis.typeSystem().constructor(PrimitiveType::Bool)) + { + if (_memberAccess.memberName() == "abs") + solAssert(m_expressionDeclaration.emplace(&_memberAccess, Builtins::ToBool).second); + else if (_memberAccess.memberName() == "rep") + solAssert(m_expressionDeclaration.emplace(&_memberAccess, Builtins::FromBool).second); + return; + } + auto const* declaration = m_context.analysis.typeSystem().constructorInfo(constructor).typeDeclaration; + solAssert(declaration); + if (auto const* typeClassDefinition = dynamic_cast(declaration)) + { + optional typeClass = m_context.analysis.annotation(*typeClassDefinition).typeClass; + solAssert(typeClass); + solAssert(m_expressionDeclaration.emplace( + &_memberAccess, + &resolveTypeClassFunction(*typeClass, _memberAccess.memberName(), memberAccessType) + ).second); + } + else if (dynamic_cast(declaration)) + { + if (_memberAccess.memberName() == "abs" || _memberAccess.memberName() == "rep") + solAssert(m_expressionDeclaration.emplace(&_memberAccess, Builtins::Identity).second); + else + solAssert(false); + } + else + solAssert(false); +} + +bool IRGeneratorForStatements::visit(ElementaryTypeNameExpression const&) +{ + // TODO: is this always a no-op? + return false; +} + +void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) +{ + Type functionType = type(_functionCall.expression()); + auto declaration = m_expressionDeclaration.at(&_functionCall.expression()); + if (auto builtin = get_if(&declaration)) + { + switch(*builtin) + { + case Builtins::FromBool: + case Builtins::Identity: + solAssert(_functionCall.arguments().size() == 1); + m_code << "let " << IRNames::localVariable(_functionCall) << " := " << IRNames::localVariable(*_functionCall.arguments().front()) << "\n"; + return; + case Builtins::ToBool: + solAssert(_functionCall.arguments().size() == 1); + m_code << "let " << IRNames::localVariable(_functionCall) << " := iszero(iszero(" << IRNames::localVariable(*_functionCall.arguments().front()) << "))\n"; + return; + } + solAssert(false); + } + FunctionDefinition const* functionDefinition = dynamic_cast(get(declaration)); + solAssert(functionDefinition); + // TODO: get around resolveRecursive by passing the environment further down? + functionType = m_context.env->resolveRecursive(functionType); + m_context.enqueueFunctionDefinition(functionDefinition, functionType); + // TODO: account for return stack size + m_code << "let " << IRNames::localVariable(_functionCall) << " := " << IRNames::function(*m_context.env, *functionDefinition, functionType) << "("; + auto const& arguments = _functionCall.arguments(); + if (arguments.size() > 1) + for (auto arg: arguments | ranges::views::drop_last(1)) + m_code << IRNames::localVariable(*arg) << ", "; + if (!arguments.empty()) + m_code << IRNames::localVariable(*arguments.back()); + m_code << ")\n"; +} + +bool IRGeneratorForStatements::visit(FunctionCall const&) +{ + return true; +} + +bool IRGeneratorForStatements::visit(Block const& _block) +{ + m_code << "{\n"; + solAssert(!_block.unchecked()); + for (auto const& statement: _block.statements()) + statement->accept(*this); + m_code << "}\n"; + return false; +} + +bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) +{ + _ifStatement.condition().accept(*this); + if (_ifStatement.falseStatement()) + { + m_code << "switch " << IRNames::localVariable(_ifStatement.condition()) << " {\n"; + m_code << "case 0 {\n"; + _ifStatement.falseStatement()->accept(*this); + m_code << "}\n"; + m_code << "default {\n"; + _ifStatement.trueStatement().accept(*this); + m_code << "}\n"; + } + else + { + m_code << "if " << IRNames::localVariable(_ifStatement.condition()) << " {\n"; + _ifStatement.trueStatement().accept(*this); + m_code << "}\n"; + } + return false; +} + +bool IRGeneratorForStatements::visit(Assignment const& _assignment) +{ + _assignment.rightHandSide().accept(*this); + auto const* lhs = dynamic_cast(&_assignment.leftHandSide()); + solAssert(lhs, "Can only assign to identifiers."); + auto const* lhsVar = dynamic_cast(lhs->annotation().referencedDeclaration); + solAssert(lhsVar, "Can only assign to identifiers referring to variables."); + m_code << IRNames::localVariable(*lhsVar) << " := " << IRNames::localVariable(_assignment.rightHandSide()) << "\n"; + + m_code << "let " << IRNames::localVariable(_assignment) << " := " << IRNames::localVariable(*lhsVar) << "\n"; + return false; +} + + +bool IRGeneratorForStatements::visitNode(ASTNode const&) +{ + solAssert(false, "Unsupported AST node during statement code generation."); +} diff --git a/libsolidity/experimental/codegen/IRGeneratorForStatements.h b/libsolidity/experimental/codegen/IRGeneratorForStatements.h new file mode 100644 index 000000000..527a6abe8 --- /dev/null +++ b/libsolidity/experimental/codegen/IRGeneratorForStatements.h @@ -0,0 +1,71 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include + +#include +#include + +namespace solidity::frontend::experimental +{ +class Analysis; + +class IRGeneratorForStatements: public ASTConstVisitor +{ +public: + IRGeneratorForStatements(IRGenerationContext& _context): m_context(_context) {} + + std::string generate(ASTNode const& _node); +private: + bool visit(ExpressionStatement const& _expressionStatement) override; + bool visit(Block const& _block) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(Assignment const& _assignment) override; + bool visit(Identifier const& _identifier) override; + bool visit(FunctionCall const& _functionCall) override; + void endVisit(FunctionCall const& _functionCall) override; + bool visit(ElementaryTypeNameExpression const& _elementaryTypeNameExpression) override; + bool visit(MemberAccess const&) override { return true; } + bool visit(TupleExpression const&) override; + void endVisit(MemberAccess const& _memberAccess) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(BinaryOperation const&) override { return true; } + void endVisit(BinaryOperation const& _binaryOperation) override; + bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(Return const&) override { return true; } + void endVisit(Return const& _return) override; + /// Default visit will reject all AST nodes that are not explicitly supported. + bool visitNode(ASTNode const& _node) override; + IRGenerationContext& m_context; + std::stringstream m_code; + enum class Builtins + { + Identity, + FromBool, + ToBool + }; + std::map> m_expressionDeclaration; + Type type(ASTNode const& _node) const; + + FunctionDefinition const& resolveTypeClassFunction(TypeClass _class, std::string _name, Type _type); +}; + +} diff --git a/libsolidity/experimental/codegen/IRVariable.cpp b/libsolidity/experimental/codegen/IRVariable.cpp new file mode 100644 index 000000000..3f2c6f648 --- /dev/null +++ b/libsolidity/experimental/codegen/IRVariable.cpp @@ -0,0 +1,135 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend::experimental; +using namespace solidity::util; + +template +Type getType(IRGenerationContext const& _context, Node const& _node) +{ + auto& annotation = _context.analysis.annotation(_node); + solAssert(annotation.type); + return _context.env->resolve(*annotation.type); +} + +namespace +{ +size_t getTypeStackSlots(IRGenerationContext const& _context, Type _type) +{ +} +} + +IRVariable::IRVariable(IRGenerationContext const& _context, std::string _baseName, Type _type): + m_baseName(std::move(_baseName)), m_type(_type) +{ +} + +IRVariable::IRVariable(IRGenerationContext const& _context, VariableDeclaration const& _declaration): + IRVariable(_context, IRNames::localVariable(_declaration), getType(_context, _declaration)) +{ +} + +IRVariable::IRVariable(IRGenerationContext const& _context, Expression const& _expression): + IRVariable(_context, IRNames::localVariable(_expression), getType(_context, _expression)) +{ +} + +IRVariable IRVariable::part(string const& _name) const +{ + for (auto const& [itemName, itemType]: m_type.stackItems()) + if (itemName == _name) + { + solAssert(itemName.empty() || itemType, ""); + return IRVariable{suffixedName(itemName), itemType ? *itemType : m_type}; + } + solAssert(false, "Invalid stack item name: " + _name); +} + +bool IRVariable::hasPart(std::string const& _name) const +{ + for (auto const& [itemName, itemType]: m_type.stackItems()) + if (itemName == _name) + { + solAssert(itemName.empty() || itemType, ""); + return true; + } + return false; +} + +vector IRVariable::stackSlots() const +{ + vector result; + for (auto const& [itemName, itemType]: m_type.stackItems()) + if (itemType) + { + solAssert(!itemName.empty(), ""); + solAssert(m_type != *itemType, ""); + result += IRVariable{suffixedName(itemName), *itemType}.stackSlots(); + } + else + { + solAssert(itemName.empty(), ""); + result.emplace_back(m_baseName); + } + return result; +} + +string IRVariable::commaSeparatedList() const +{ + return joinHumanReadable(stackSlots()); +} + +string IRVariable::commaSeparatedListPrefixed() const +{ + return joinHumanReadablePrefixed(stackSlots()); +} + +string IRVariable::name() const +{ + solAssert(m_type.sizeOnStack() == 1, ""); + auto const& [itemName, type] = m_type.stackItems().front(); + solAssert(!type, "Expected null type for name " + itemName); + return suffixedName(itemName); +} + +IRVariable IRVariable::tupleComponent(size_t _i) const +{ + solAssert( + m_type.category() == Type::Category::Tuple, + "Requested tuple component of non-tuple IR variable." + ); + return part(IRNames::tupleComponent(_i)); +} + +string IRVariable::suffixedName(string const& _suffix) const +{ + if (_suffix.empty()) + return m_baseName; + else + return m_baseName + '_' + _suffix; +} diff --git a/libsolidity/experimental/codegen/IRVariable.h b/libsolidity/experimental/codegen/IRVariable.h new file mode 100644 index 000000000..02d6f7931 --- /dev/null +++ b/libsolidity/experimental/codegen/IRVariable.h @@ -0,0 +1,85 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include + +#include +#include +#include + +namespace solidity::frontend +{ +class VariableDeclaration; +class Expression; +} + +namespace solidity::frontend::experimental +{ + +class IRGenerationContext; + +class IRVariable +{ +public: + /// IR variable with explicit base name @a _baseName and type @a _type. + IRVariable(IRGenerationContext const& _analysis, std::string _baseName, Type _type); + /// IR variable referring to the declaration @a _decl. + IRVariable(IRGenerationContext const& _analysis, VariableDeclaration const& _decl); + /// IR variable referring to the expression @a _expr. + IRVariable(IRGenerationContext const& _analysis, Expression const& _expression); + + /// @returns the name of the variable, if it occupies a single stack slot (otherwise throws). + std::string name() const; + + /// @returns a comma-separated list of the stack slots of the variable. + std::string commaSeparatedList() const; + + /// @returns a comma-separated list of the stack slots of the variable that is + /// prefixed with a comma, unless it is empty. + std::string commaSeparatedListPrefixed() const; + + /// @returns an IRVariable referring to the tuple component @a _i of a tuple variable. + IRVariable tupleComponent(std::size_t _i) const; + + /// @returns the type of the variable. + Type const& type() const { return m_type; } + + /// @returns an IRVariable referring to the stack component @a _slot of the variable. + /// @a _slot must be among the stack slots in ``m_type.stackItems()``. + /// The returned IRVariable is itself typed with the type of the stack slot as defined + /// in ``m_type.stackItems()`` and may again occupy multiple stack slots. + IRVariable part(std::string const& _slot) const; + + /// @returns true if variable contains @a _name component + /// @a _name name of the component that is being checked + bool hasPart(std::string const& _name) const; + + /// @returns a vector containing the names of the stack slots of the variable. + std::vector stackSlots() const; + +private: + /// @returns a name consisting of the base name appended with an underscore and @æ _suffix, + /// unless @a _suffix is empty, in which case the base name itself is returned. + std::string suffixedName(std::string const& _suffix) const; + std::string m_baseName; + Type m_type; +}; + + +} diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e7d46892a..947329aff 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -126,7 +126,10 @@ ASTPointer Parser::parse(CharStream& _charStream) nodes.push_back(parseEnumDefinition()); break; case Token::Type: - nodes.push_back(parseUserDefinedValueTypeDefinition()); + if (m_experimentalSolidityEnabledInCurrentSourceUnit) + nodes.push_back(parseTypeDefinition()); + else + nodes.push_back(parseUserDefinedValueTypeDefinition()); break; case Token::Using: nodes.push_back(parseUsingDirective()); @@ -134,6 +137,14 @@ ASTPointer Parser::parse(CharStream& _charStream) case Token::Function: nodes.push_back(parseFunctionDefinition(true)); break; + case Token::Class: + solAssert(m_experimentalSolidityEnabledInCurrentSourceUnit); + nodes.push_back(parseTypeClassDefinition()); + break; + case Token::Instantiation: + solAssert(m_experimentalSolidityEnabledInCurrentSourceUnit); + nodes.push_back(parseTypeClassInstantiation()); + break; default: if ( // Workaround because `error` is not a keyword. @@ -591,18 +602,29 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVari else break; } - if (m_scanner->currentToken() == Token::Returns) + if (m_experimentalSolidityEnabledInCurrentSourceUnit) { - bool const permitEmptyParameterList = false; - advance(); - result.returnParameters = parseParameterList(options, permitEmptyParameterList); + if (m_scanner->currentToken() == Token::RightArrow) + { + advance(); + result.experimentalReturnExpression = parseBinaryExpression(); + } } else - result.returnParameters = createEmptyParameterList(); + { + if (m_scanner->currentToken() == Token::Returns) + { + bool const permitEmptyParameterList = m_experimentalSolidityEnabledInCurrentSourceUnit; + advance(); + result.returnParameters = parseParameterList(options, permitEmptyParameterList); + } + else + result.returnParameters = createEmptyParameterList(); + } return result; } -ASTPointer Parser::parseFunctionDefinition(bool _freeFunction) +ASTPointer Parser::parseFunctionDefinition(bool _freeFunction, bool _allowBody) { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); @@ -650,9 +672,16 @@ ASTPointer Parser::parseFunctionDefinition(bool _freeFunction) FunctionHeaderParserResult header = parseFunctionHeader(false); + if (m_experimentalSolidityEnabledInCurrentSourceUnit) + solAssert(!header.returnParameters); + else + solAssert(!header.experimentalReturnExpression); + ASTPointer block; nodeFactory.markEndPosition(); - if (m_scanner->currentToken() == Token::Semicolon) + if (!_allowBody) + expectToken(Token::Semicolon); + else if (m_scanner->currentToken() == Token::Semicolon) advance(); else { @@ -672,7 +701,8 @@ ASTPointer Parser::parseFunctionDefinition(bool _freeFunction) header.parameters, header.modifiers, header.returnParameters, - block + block, + header.experimentalReturnExpression ); } @@ -1190,10 +1220,12 @@ ASTPointer Parser::parseTypeName() ASTPointer Parser::parseFunctionType() { + solAssert(!m_experimentalSolidityEnabledInCurrentSourceUnit); RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); expectToken(Token::Function); FunctionHeaderParserResult header = parseFunctionHeader(true); + solAssert(!header.experimentalReturnExpression); return nodeFactory.createNode( header.parameters, header.returnParameters, @@ -1249,16 +1281,29 @@ ASTPointer Parser::parseParameterList( std::vector> parameters; VarDeclParserOptions options(_options); options.allowEmptyName = true; + if (m_experimentalSolidityEnabledInCurrentSourceUnit && m_scanner->currentToken() == Token::Identifier) + { + // Parses unary parameter lists without parentheses. TODO: is this a good idea in all cases? Including arguments? + parameters = {parsePostfixVariableDeclaration()}; + nodeFactory.setEndPositionFromNode(parameters.front()); + return nodeFactory.createNode(parameters); + } expectToken(Token::LParen); + auto parseSingleVariableDeclaration = [&]() { + if (m_experimentalSolidityEnabledInCurrentSourceUnit) + return parsePostfixVariableDeclaration(); + else + return parseVariableDeclaration(options); + }; if (!_allowEmpty || m_scanner->currentToken() != Token::RParen) { - parameters.push_back(parseVariableDeclaration(options)); + parameters.push_back(parseSingleVariableDeclaration()); while (m_scanner->currentToken() != Token::RParen) { if (m_scanner->currentToken() == Token::Comma && m_scanner->peekNextToken() == Token::RParen) fatalParserError(7591_error, "Unexpected trailing comma in parameter list."); expectToken(Token::Comma); - parameters.push_back(parseVariableDeclaration(options)); + parameters.push_back(parseSingleVariableDeclaration()); } } nodeFactory.markEndPosition(); @@ -1607,12 +1652,206 @@ ASTPointer Parser::parseRevertStatement(ASTPointer c return nodeFactory.createNode(_docString, errorCall); } +ASTPointer Parser::parsePostfixVariableDeclarationStatement( + ASTPointer const& _docString +) +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + + expectToken(Token::Let); + + std::vector> variables; + variables.emplace_back(parsePostfixVariableDeclaration()); + nodeFactory.setEndPositionFromNode(variables.back()); + + ASTPointer value; + if (m_scanner->currentToken() == Token::Assign) + { + advance(); + value = parseExpression(); + nodeFactory.setEndPositionFromNode(value); + } + return nodeFactory.createNode(_docString, variables, value); +} + +ASTPointer Parser::parsePostfixVariableDeclaration() +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + + ASTPointer const documentation = parseStructuredDocumentation(); + + nodeFactory.markEndPosition(); + auto [identifier, nameLocation] = expectIdentifierWithLocation(); + + ASTPointer type; + if (m_scanner->currentToken() == Token::Colon) + { + advance(); + type = parseBinaryExpression(); + nodeFactory.setEndPositionFromNode(type); + } + + return nodeFactory.createNode( + nullptr, + identifier, + nameLocation, + nullptr, + Visibility::Default, + documentation, + false, + VariableDeclaration::Mutability::Mutable, + nullptr, + VariableDeclaration::Location::Unspecified, + type + ); +} + +ASTPointer Parser::parseTypeClassDefinition() +{ + solAssert(m_experimentalSolidityEnabledInCurrentSourceUnit); + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + + std::vector> subNodes; + + ASTPointer const documentation = parseStructuredDocumentation(); + + expectToken(Token::Class); + // TODO: parseTypeVariable()? parseTypeVariableDeclaration()? + ASTPointer typeVariable; + { + ASTNodeFactory nodeFactory(*this); + nodeFactory.markEndPosition(); + auto [identifier, nameLocation] = expectIdentifierWithLocation(); + typeVariable = nodeFactory.createNode( + nullptr, + identifier, + nameLocation, + nullptr, + Visibility::Default, + nullptr + ); + } + expectToken(Token::Colon); + auto [name, nameLocation] = expectIdentifierWithLocation(); + expectToken(Token::LBrace); + while (true) + { + Token currentTokenValue = m_scanner->currentToken(); + if (currentTokenValue == Token::RBrace) + break; + expectToken(Token::Function, false); + subNodes.push_back(parseFunctionDefinition(false, false)); + } + nodeFactory.markEndPosition(); + expectToken(Token::RBrace); + + return nodeFactory.createNode( + typeVariable, + name, + nameLocation, + documentation, + subNodes + ); +} + +ASTPointer Parser::parseTypeClassName() +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + std::variant> name; + if (TokenTraits::isBuiltinTypeClassName(m_scanner->currentToken())) + { + nodeFactory.markEndPosition(); + name = m_scanner->currentToken(); + advance(); + } + else + { + auto identifierPath = parseIdentifierPath(); + name = identifierPath; + nodeFactory.setEndPositionFromNode(identifierPath); + } + return nodeFactory.createNode(name); +} + +ASTPointer Parser::parseTypeClassInstantiation() +{ + solAssert(m_experimentalSolidityEnabledInCurrentSourceUnit); + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + + std::vector> subNodes; + + expectToken(Token::Instantiation); + // TODO: parseTypeConstructor() + ASTPointer typeConstructor = parseTypeName(); + ASTPointer argumentSorts; + if (m_scanner->currentToken() == Token::LParen) + { + argumentSorts = parseParameterList(); + } + expectToken(Token::Colon); + ASTPointer typeClassName = parseTypeClassName(); + expectToken(Token::LBrace); + while (true) + { + Token currentTokenValue = m_scanner->currentToken(); + if (currentTokenValue == Token::RBrace) + break; + expectToken(Token::Function, false); + // TODO: require body already during parsing? + subNodes.push_back(parseFunctionDefinition(false, true)); + } + nodeFactory.markEndPosition(); + expectToken(Token::RBrace); + + return nodeFactory.createNode( + typeConstructor, + argumentSorts, + typeClassName, + subNodes + ); +} + +ASTPointer Parser::parseTypeDefinition() +{ + solAssert(m_experimentalSolidityEnabledInCurrentSourceUnit); + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Type); + auto&& [name, nameLocation] = expectIdentifierWithLocation(); + + ASTPointer arguments; + if (m_scanner->currentToken() == Token::LParen) + arguments = parseParameterList(); + + ASTPointer expression; + if (m_scanner->currentToken() == Token::Assign) + { + expectToken(Token::Assign); + expression = parseExpression(); + } + nodeFactory.markEndPosition(); + expectToken(Token::Semicolon); + return nodeFactory.createNode( + std::move(name), + std::move(nameLocation), + std::move(arguments), + std::move(expression) + ); +} + ASTPointer Parser::parseSimpleStatement(ASTPointer const& _docString) { RecursionGuard recursionGuard(*this); LookAheadInfo statementType; IndexAccessedPath iap; + if (m_experimentalSolidityEnabledInCurrentSourceUnit && m_scanner->currentToken() == Token::Let) + return parsePostfixVariableDeclarationStatement(_docString); + if (m_scanner->currentToken() == Token::LParen) { ASTNodeFactory nodeFactory(*this); @@ -1715,7 +1954,10 @@ std::pair Parser::tryParseInde { case LookAheadInfo::VariableDeclaration: case LookAheadInfo::Expression: - return std::make_pair(statementType, IndexAccessedPath()); + return std::make_pair( + m_experimentalSolidityEnabledInCurrentSourceUnit ? LookAheadInfo::Expression : statementType, + IndexAccessedPath() + ); default: break; } @@ -1726,6 +1968,9 @@ std::pair Parser::tryParseInde // VariableDeclarationStatement out of it. IndexAccessedPath iap = parseIndexAccessedPath(); + if (m_experimentalSolidityEnabledInCurrentSourceUnit) + return std::make_pair(LookAheadInfo::Expression, std::move(iap)); + if (m_scanner->currentToken() == Token::Identifier || TokenTraits::isLocationSpecifier(m_scanner->currentToken())) return std::make_pair(LookAheadInfo::VariableDeclaration, std::move(iap)); else @@ -1808,9 +2053,9 @@ ASTPointer Parser::parseBinaryExpression( RecursionGuard recursionGuard(*this); ASTPointer expression = parseUnaryExpression(_partiallyParsedExpression); ASTNodeFactory nodeFactory(*this, expression); - int precedence = TokenTraits::precedence(m_scanner->currentToken()); + int precedence = tokenPrecedence(m_scanner->currentToken()); for (; precedence >= _minPrecedence; --precedence) - while (TokenTraits::precedence(m_scanner->currentToken()) == precedence) + while (tokenPrecedence(m_scanner->currentToken()) == precedence) { Token op = m_scanner->currentToken(); advance(); @@ -1827,6 +2072,23 @@ ASTPointer Parser::parseBinaryExpression( return expression; } +int Parser::tokenPrecedence(Token _token) const +{ + if (m_experimentalSolidityEnabledInCurrentSourceUnit) + { + switch(_token) + { + case Token::Colon: + return 1000; + case Token::RightArrow: + return 999; + default: + break; + } + } + return TokenTraits::precedence(m_scanner->currentToken()); +} + ASTPointer Parser::parseUnaryExpression( ASTPointer const& _partiallyParsedExpression ) @@ -2285,7 +2547,14 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath() while (m_scanner->currentToken() == Token::Period) { advance(); - iap.path.push_back(parseIdentifierOrAddress()); + if (m_experimentalSolidityEnabledInCurrentSourceUnit && m_scanner->currentToken() == Token::Number) + { + ASTNodeFactory nodeFactory(*this); + nodeFactory.markEndPosition(); + iap.path.push_back(nodeFactory.createNode(getLiteralAndAdvance())); + } + else + iap.path.push_back(parseIdentifierOrAddress()); } } else diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index df282cb99..d4c48c7a6 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -48,6 +48,8 @@ public: ASTPointer parse(langutil::CharStream& _charStream); + /// Returns the maximal AST node ID assigned so far + int64_t maxID() const { return m_currentNodeID; } private: class ASTNodeFactory; @@ -74,6 +76,7 @@ private: Visibility visibility = Visibility::Default; StateMutability stateMutability = StateMutability::NonPayable; std::vector> modifiers; + ASTPointer experimentalReturnExpression; }; /// Struct to share parsed function call arguments. @@ -99,7 +102,7 @@ private: ASTPointer parseOverrideSpecifier(); StateMutability parseStateMutability(); FunctionHeaderParserResult parseFunctionHeader(bool _isStateVariable); - ASTPointer parseFunctionDefinition(bool _freeFunction = false); + ASTPointer parseFunctionDefinition(bool _freeFunction = false, bool _allowBody = true); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); ASTPointer parseUserDefinedValueTypeDefinition(); @@ -167,6 +170,18 @@ private: std::pair, langutil::SourceLocation> expectIdentifierWithLocation(); ///@} + ///@{ + ///@name Specialized parsing functions for the AST nodes of experimental solidity. + ASTPointer parsePostfixVariableDeclarationStatement( + ASTPointer const& _docString + ); + ASTPointer parsePostfixVariableDeclaration(); + ASTPointer parseTypeClassDefinition(); + ASTPointer parseTypeClassInstantiation(); + ASTPointer parseTypeDefinition(); + ASTPointer parseTypeClassName(); + ///@} + ///@{ ///@name Helper functions @@ -197,8 +212,6 @@ private: /// Returns the next AST node ID int64_t nextID() { return ++m_currentNodeID; } - /// Returns the maximal AST node ID assigned so far - int64_t maxID() const { return m_currentNodeID; } std::pair tryParseIndexAccessedPath(); /// Performs limited look-ahead to distinguish between variable declaration and expression statement. @@ -223,6 +236,8 @@ private: bool isQuotedPath() const; bool isStdlibPath() const; + int tokenPrecedence(Token _token) const; + ASTPointer getStdlibImportPathAndAdvance(); /// Creates an empty ParameterList at the current location (used if parameters can be omitted). diff --git a/libstdlib/src/stub.sol b/libstdlib/src/stub.sol index 46ae21dcf..bd34e0475 100644 --- a/libstdlib/src/stub.sol +++ b/libstdlib/src/stub.sol @@ -3,7 +3,6 @@ pragma solidity >=0.0; pragma experimental solidity; -function identity(uint256 x) pure returns (uint256) +function identity() { - return x; } diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index dcfe49ab4..ccad7d49d 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -64,7 +64,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _sourceCode) Scoper::assignScopes(*sourceUnit); BOOST_REQUIRE(SyntaxChecker(errorReporter, false).checkSyntax(*sourceUnit)); GlobalContext globalContext; - NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter); + NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter, false); DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion()); solAssert(!Error::containsErrors(errorReporter.errors()), ""); resolver.registerDeclarations(*sourceUnit); diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index b05d28a84..a171a5be0 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -127,7 +127,7 @@ bytes compileFirstExpression( GlobalContext globalContext; Scoper::assignScopes(*sourceUnit); BOOST_REQUIRE(SyntaxChecker(errorReporter, false).checkSyntax(*sourceUnit)); - NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter); + NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter, false); resolver.registerDeclarations(*sourceUnit); BOOST_REQUIRE_MESSAGE(resolver.resolveNamesAndTypes(*sourceUnit), "Resolving names failed"); DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion()); diff --git a/test/libsolidity/semanticTests/experimental/stub.sol b/test/libsolidity/semanticTests/experimental/stub.sol new file mode 100644 index 000000000..7f3061fe7 --- /dev/null +++ b/test/libsolidity/semanticTests/experimental/stub.sol @@ -0,0 +1,91 @@ +pragma experimental solidity; + +type uint256 = word; + +instantiation uint256: + { + function add(x, y) -> uint256 { + let a = uint256.rep(x); + let b = uint256.rep(y); + assembly { + a := add(a,b) + } + return uint256.abs(a); + } +} + + +instantiation uint256: * { + function mul(x, y) -> uint256 { + let a = uint256.rep(x); + let b = uint256.rep(y); + assembly { + a := mul(a,b) + } + return uint256.abs(a); + } +} +instantiation word: * { + function mul(x, y) -> word { + let z: word; + assembly { + z := mul(x,y) + } + return z; + } +} + +instantiation word: integer { + function fromInteger(x:integer) -> word { + //x + x; + } +} + +instantiation word: == { + function eq(x, y) -> bool { + assembly { + x := eq(x, y) + } + } +} + + +function f(x:uint256->uint256,y:uint256) -> uint256 +{ + return x(y); +} + +function g(x:uint256) -> uint256 +{ + return x; +} + +contract C { + fallback() external { + let arg; + assembly { + arg := calldataload(0) + } + let x : word; + if (bool.abs(arg)) { + assembly { + x := 0x10 + } + } + let w = uint256.abs(x); +// w = f(g, w); + w = w * w + w; + let y : word; + let z : (uint256,uint256); + assembly { y := 2 } + y = uint256.rep(w) * y; + assembly { + mstore(0, y) + return(0, 32) + } + } +} +// ==== +// compileViaYul: true +// ---- +// (): 0 -> 0 +// (): 1 -> 544 diff --git a/test/libsolidity/syntaxTests/experimental_keywords.sol b/test/libsolidity/syntaxTests/experimental_keywords.sol new file mode 100644 index 000000000..b5920aed0 --- /dev/null +++ b/test/libsolidity/syntaxTests/experimental_keywords.sol @@ -0,0 +1,4 @@ +function f() pure { + uint word; word; + uint static_assert; static_assert; +} \ No newline at end of file