From b9a166061bc94cd06458c945d66bf52e76e84b70 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 8 Oct 2015 18:01:12 +0200 Subject: [PATCH] Refactoring to allow multi-variable declarations. --- libsolidity/AST.h | 21 +++++-- libsolidity/AST_accept.h | 16 +++++- libsolidity/Compiler.cpp | 9 ++- libsolidity/NameAndTypeResolver.cpp | 4 +- libsolidity/Parser.cpp | 36 +++++++++--- libsolidity/TypeChecker.cpp | 88 +++++++++++++++++------------ libsolidity/TypeChecker.h | 1 + 7 files changed, 120 insertions(+), 55 deletions(-) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 075c1ff5e..354beb7c8 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -963,20 +963,31 @@ public: * Definition of a variable as a statement inside a function. It requires a type name (which can * also be "var") but the actual assignment can be missing. * Examples: var a = 2; uint256 a; + * As a second form, multiple variables can be declared, cannot have a type and must be assigned + * right away. If the first or last component is unnamed, it can "consume" an arbitrary number + * of components. + * Examples: var (a, b) = f(); var (a,,,c) = g(); var (a,) = d(); */ class VariableDeclarationStatement: public Statement { public: - VariableDeclarationStatement(SourceLocation const& _location, ASTPointer _variable): - Statement(_location), m_variable(_variable) {} + VariableDeclarationStatement( + SourceLocation const& _location, + std::vector> const& _variables, + ASTPointer const& _initialValue + ): + Statement(_location), m_variables(_variables), m_initialValue(_initialValue) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - VariableDeclaration const& declaration() const { return *m_variable; } - Expression const* expression() const { return m_variable->value().get(); } + std::vector> const& declarations() const { return m_variables; } + Expression const* initialValue() const { return m_initialValue.get(); } private: - ASTPointer m_variable; + /// List of variables, some of which can be empty pointers (unnamed components). + std::vector> m_variables; + /// The assigned expression / initial value. + ASTPointer m_initialValue; }; /** diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index dfa9c4255..994bfc8d3 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -516,14 +516,26 @@ void ExpressionStatement::accept(ASTConstVisitor& _visitor) const void VariableDeclarationStatement::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) - m_variable->accept(_visitor); + { + for (ASTPointer const& var: m_variables) + if (var) + var->accept(_visitor); + if (m_initialValue) + m_initialValue->accept(_visitor); + } _visitor.endVisit(*this); } void VariableDeclarationStatement::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) - m_variable->accept(_visitor); + { + for (ASTPointer const& var: m_variables) + if (var) + var->accept(_visitor); + if (m_initialValue) + m_initialValue->accept(_visitor); + } _visitor.endVisit(*this); } diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 7ce2121e8..64a67a4f9 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -623,10 +623,13 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta { StackHeightChecker checker(m_context); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); - if (Expression const* expression = _variableDeclarationStatement.expression()) + solAssert(_variableDeclarationStatement.declarations().size() == 1, "To be implemented."); + solAssert(!!_variableDeclarationStatement.declarations().front(), ""); + VariableDeclaration const& varDecl = *_variableDeclarationStatement.declarations().front(); + if (Expression const* expression = _variableDeclarationStatement.initialValue()) { - compileExpression(*expression, _variableDeclarationStatement.declaration().annotation().type); - CompilerUtils(m_context).moveToStackVariable(_variableDeclarationStatement.declaration()); + compileExpression(*expression, varDecl.annotation().type); + CompilerUtils(m_context).moveToStackVariable(varDecl); } checker.check(); return false; diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 0ce20f3c3..0d2c3b027 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -350,7 +350,9 @@ void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _vari // Register the local variables with the function // This does not fit here perfectly, but it saves us another AST visit. solAssert(m_currentFunction, "Variable declaration without function."); - m_currentFunction->addLocalVariable(_variableDeclarationStatement.declaration()); + for (ASTPointer const& var: _variableDeclarationStatement.declarations()) + if (var) + m_currentFunction->addLocalVariable(*var); } bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 94e9c0ea6..c6b74e1cc 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -771,13 +771,35 @@ ASTPointer Parser::parseVariableDeclarationStateme ASTPointer const& _lookAheadArrayType ) { - VarDeclParserOptions options; - options.allowVar = true; - options.allowInitialValue = true; - options.allowLocationSpecifier = true; - ASTPointer variable = parseVariableDeclaration(options, _lookAheadArrayType); - ASTNodeFactory nodeFactory(*this, variable); - return nodeFactory.createNode(variable); + ASTNodeFactory nodeFactory(*this); + if (_lookAheadArrayType) + nodeFactory.setLocation(_lookAheadArrayType->location()); + vector> variables; + ASTPointer value; + if ( + !_lookAheadArrayType && + m_scanner->currentToken() == Token::Var && + m_scanner->peekNextToken() == Token::LParen + ) + { + // Parse `var (a, b, ,, c) = ...` into a single VariableDeclarationStatement with multiple variables. + solAssert(false, "To be implemented."); + } + else + { + VarDeclParserOptions options; + options.allowVar = true; + options.allowLocationSpecifier = true; + options.allowInitialValue = false; + variables.push_back(parseVariableDeclaration(options, _lookAheadArrayType)); + } + if (m_scanner->currentToken() == Token::Assign) + { + m_scanner->next(); + value = parseExpression(); + nodeFactory.setEndPositionFromNode(value); + } + return nodeFactory.createNode(variables, value); } ASTPointer Parser::parseExpressionStatement( diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index 5bbbd0729..8207e1ffb 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -424,16 +424,17 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) // Note that assignments before the first declaration are legal because of the special scoping // rules inherited from JavaScript. - // This only infers the type from its type name. - // If an explicit type is required, it throws, otherwise it returns TypePointer(); + // type is filled either by ReferencesResolver directly from the type name or by + // TypeChecker at the VariableDeclarationStatement level. TypePointer varType = _variable.annotation().type; + solAssert(!!varType, "Failed to infer variable type."); if (_variable.isConstant()) { if (!dynamic_cast(_variable.scope())) typeError(_variable, "Illegal use of \"constant\" specifier."); if (!_variable.value()) typeError(_variable, "Uninitialized \"constant\" variable."); - if (varType && !varType->isValueType()) + if (!varType->isValueType()) { bool constImplemented = false; if (auto arrayType = dynamic_cast(varType.get())) @@ -446,43 +447,20 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) ); } } - if (varType) - { - if (_variable.value()) - expectType(*_variable.value(), *varType); - else - { - if (auto ref = dynamic_cast(varType.get())) - if (ref->dataStoredIn(DataLocation::Storage) && _variable.isLocalVariable() && !_variable.isCallableParameter()) - { - auto err = make_shared(); - *err << - errinfo_sourceLocation(_variable.location()) << - errinfo_comment("Uninitialized storage pointer. Did you mean ' memory " + _variable.name() + "'?"); - m_errors.push_back(err); - } - } - } + if (_variable.value()) + expectType(*_variable.value(), *varType); else { - // Infer type from value. - if (!_variable.value()) - fatalTypeError(_variable, "Assignment necessary for type detection."); - _variable.value()->accept(*this); - - TypePointer const& valueType = type(*_variable.value()); - solAssert(!!valueType, ""); - if ( - valueType->category() == Type::Category::IntegerConstant && - !dynamic_pointer_cast(valueType)->integerType() - ) - fatalTypeError(*_variable.value(), "Invalid integer constant " + valueType->toString() + "."); - else if (valueType->category() == Type::Category::Void) - fatalTypeError(_variable, "Variable cannot have void type."); - varType = valueType->mobileType(); + if (auto ref = dynamic_cast(varType.get())) + if (ref->dataStoredIn(DataLocation::Storage) && _variable.isLocalVariable() && !_variable.isCallableParameter()) + { + auto err = make_shared(); + *err << + errinfo_sourceLocation(_variable.location()) << + errinfo_comment("Uninitialized storage pointer. Did you mean ' memory " + _variable.name() + "'?"); + m_errors.push_back(err); + } } - solAssert(!!varType, ""); - _variable.annotation().type = varType; if (!_variable.isStateVariable()) { if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData)) @@ -621,6 +599,42 @@ void TypeChecker::endVisit(Return const& _return) } } +bool TypeChecker::visit(VariableDeclarationStatement const& _statement) +{ + solAssert(_statement.declarations().size() == 1, "To be implemented."); + solAssert(!!_statement.declarations().front(), ""); + VariableDeclaration const& var = *_statement.declarations().front(); + solAssert(!var.value(), "Value has to be tied to statement."); + if (!var.annotation().type) + { + solAssert(!var.typeName(), ""); + // Infer type from value. + if (!_statement.initialValue()) + fatalTypeError(_statement, "Assignment necessary for type detection."); + _statement.initialValue()->accept(*this); + + TypePointer const& valueType = type(*_statement.initialValue()); + solAssert(!!valueType, ""); + if ( + valueType->category() == Type::Category::IntegerConstant && + !dynamic_pointer_cast(valueType)->integerType() + ) + fatalTypeError(*_statement.initialValue(), "Invalid integer constant " + valueType->toString() + "."); + else if (valueType->category() == Type::Category::Void) + fatalTypeError(_statement, "Variable cannot have void type."); + var.annotation().type = valueType->mobileType(); + var.accept(*this); + return false; + } + else + { + var.accept(*this); + if (_statement.initialValue()) + expectType(*_statement.initialValue(), *var.annotation().type); + } + return false; +} + void TypeChecker::endVisit(ExpressionStatement const& _statement) { if (type(_statement.expression())->category() == Type::Category::IntegerConstant) diff --git a/libsolidity/TypeChecker.h b/libsolidity/TypeChecker.h index 97262ed06..cb65d5a8a 100644 --- a/libsolidity/TypeChecker.h +++ b/libsolidity/TypeChecker.h @@ -87,6 +87,7 @@ private: virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; virtual void endVisit(Return const& _return) override; + virtual bool visit(VariableDeclarationStatement const& _variable) override; virtual void endVisit(ExpressionStatement const& _statement) override; virtual bool visit(Assignment const& _assignment) override; virtual void endVisit(BinaryOperation const& _operation) override;