Merge pull request #326 from guanqun/cond-expr

support conditional expression _ ? _ : _
This commit is contained in:
chriseth 2016-01-24 21:43:48 +01:00
commit 194679f77a
17 changed files with 586 additions and 7 deletions

View File

@ -9,7 +9,7 @@ Control Structures
Most of the control structures from C/JavaScript are available in Solidity
except for `switch` and `goto`. So
there is: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, with
there is: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `? :`, with
the usual semantics known from C / JavaScript.
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted

View File

@ -744,6 +744,43 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
typeError(_statement.expression().location(), "Invalid integer constant.");
}
bool TypeChecker::visit(Conditional const& _conditional)
{
expectType(_conditional.condition(), BoolType());
_conditional.trueExpression().accept(*this);
_conditional.falseExpression().accept(*this);
TypePointer trueType = type(_conditional.trueExpression())->mobileType();
TypePointer falseType = type(_conditional.falseExpression())->mobileType();
TypePointer commonType = Type::commonType(trueType, falseType);
if (!commonType)
{
typeError(
_conditional.location(),
"True expression's type " +
trueType->toString() +
" doesn't match false expression's type " +
falseType->toString() +
"."
);
// even we can't find a common type, we have to set a type here,
// otherwise the upper statement will not be able to check the type.
commonType = trueType;
}
_conditional.annotation().type = commonType;
if (_conditional.annotation().lValueRequested)
typeError(
_conditional.location(),
"Conditional expression as left value is not supported yet."
);
return false;
}
bool TypeChecker::visit(Assignment const& _assignment)
{
requireLValue(_assignment.leftHandSide());

View File

@ -90,6 +90,7 @@ private:
virtual void endVisit(Return const& _return) override;
virtual bool visit(VariableDeclarationStatement const& _variable) override;
virtual void endVisit(ExpressionStatement const& _statement) override;
virtual bool visit(Conditional const& _conditional) override;
virtual bool visit(Assignment const& _assignment) override;
virtual bool visit(TupleExpression const& _tuple) override;
virtual void endVisit(BinaryOperation const& _operation) override;

View File

@ -1119,6 +1119,33 @@ public:
ExpressionAnnotation& annotation() const override;
};
class Conditional: public Expression
{
public:
Conditional(
SourceLocation const& _location,
ASTPointer<Expression> const& _condition,
ASTPointer<Expression> const& _trueExpression,
ASTPointer<Expression> const& _falseExpression
):
Expression(_location),
m_condition(_condition),
m_trueExpression(_trueExpression),
m_falseExpression(_falseExpression)
{}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
Expression const& condition() const { return *m_condition; }
Expression const& trueExpression() const { return *m_trueExpression; }
Expression const& falseExpression() const { return *m_falseExpression; }
private:
ASTPointer<Expression> m_condition;
ASTPointer<Expression> m_trueExpression;
ASTPointer<Expression> m_falseExpression;
};
/// Assignment, can also be a compound assignment.
/// Examples: (a = 7 + 8) or (a *= 2)
class Assignment: public Expression

View File

@ -69,6 +69,7 @@ class Throw;
class VariableDeclarationStatement;
class ExpressionStatement;
class Expression;
class Conditional;
class Assignment;
class TupleExpression;
class UnaryOperation;

View File

@ -217,6 +217,12 @@ bool ASTJsonConverter::visit(ExpressionStatement const&)
return true;
}
bool ASTJsonConverter::visit(Conditional const&)
{
addJsonNode("Conditional", {}, true);
return true;
}
bool ASTJsonConverter::visit(Assignment const& _node)
{
addJsonNode("Assignment",
@ -397,6 +403,11 @@ void ASTJsonConverter::endVisit(ExpressionStatement const&)
goUp();
}
void ASTJsonConverter::endVisit(Conditional const&)
{
goUp();
}
void ASTJsonConverter::endVisit(Assignment const&)
{
goUp();

View File

@ -67,6 +67,7 @@ public:
bool visit(Throw const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;
bool visit(Assignment const& _node) override;
bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
@ -99,6 +100,7 @@ public:
void endVisit(Throw const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;
void endVisit(Assignment const&) override;
void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;

View File

@ -248,6 +248,14 @@ bool ASTPrinter::visit(ExpressionStatement const& _node)
return goDeeper();
}
bool ASTPrinter::visit(Conditional const& _node)
{
writeLine("Conditional");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Assignment const& _node)
{
writeLine(string("Assignment using operator ") + Token::toString(_node.assignmentOperator()));
@ -480,6 +488,11 @@ void ASTPrinter::endVisit(ExpressionStatement const&)
m_indentation--;
}
void ASTPrinter::endVisit(Conditional const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Assignment const&)
{
m_indentation--;

View File

@ -75,6 +75,7 @@ public:
bool visit(Throw const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;
bool visit(Assignment const& _node) override;
bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
@ -115,6 +116,7 @@ public:
void endVisit(Throw const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;
void endVisit(Assignment const&) override;
void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;

View File

@ -73,6 +73,7 @@ public:
virtual bool visit(Throw& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); }
virtual bool visit(Conditional& _node) { return visitNode(_node); }
virtual bool visit(Assignment& _node) { return visitNode(_node); }
virtual bool visit(TupleExpression& _node) { return visitNode(_node); }
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); }
@ -115,6 +116,7 @@ public:
virtual void endVisit(Throw& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclarationStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Conditional& _node) { endVisitNode(_node); }
virtual void endVisit(Assignment& _node) { endVisitNode(_node); }
virtual void endVisit(TupleExpression& _node) { endVisitNode(_node); }
virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); }
@ -169,6 +171,7 @@ public:
virtual bool visit(Throw const& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclarationStatement const& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement const& _node) { return visitNode(_node); }
virtual bool visit(Conditional const& _node) { return visitNode(_node); }
virtual bool visit(Assignment const& _node) { return visitNode(_node); }
virtual bool visit(TupleExpression const& _node) { return visitNode(_node); }
virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); }
@ -211,6 +214,7 @@ public:
virtual void endVisit(Throw const& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclarationStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Conditional const& _node) { endVisitNode(_node); }
virtual void endVisit(Assignment const& _node) { endVisitNode(_node); }
virtual void endVisit(TupleExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); }

View File

@ -551,6 +551,28 @@ void VariableDeclarationStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void Conditional::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_condition->accept(_visitor);
m_trueExpression->accept(_visitor);
m_falseExpression->accept(_visitor);
}
_visitor.endVisit(*this);
}
void Conditional::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_condition->accept(_visitor);
m_trueExpression->accept(_visitor);
m_falseExpression->accept(_visitor);
}
_visitor.endVisit(*this);
}
void Assignment::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

View File

@ -176,6 +176,22 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
}
bool ExpressionCompiler::visit(Conditional const& _condition)
{
CompilerContext::LocationSetter locationSetter(m_context, _condition);
_condition.condition().accept(*this);
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
_condition.falseExpression().accept(*this);
utils().convertType(*_condition.falseExpression().annotation().type, *_condition.annotation().type);
eth::AssemblyItem endTag = m_context.appendJumpToNew();
m_context << trueTag;
m_context.adjustStackOffset(-_condition.annotation().type->sizeOnStack());
_condition.trueExpression().accept(*this);
utils().convertType(*_condition.trueExpression().annotation().type, *_condition.annotation().type);
m_context << endTag;
return false;
}
bool ExpressionCompiler::visit(Assignment const& _assignment)
{
CompilerContext::LocationSetter locationSetter(m_context, _assignment);

View File

@ -71,6 +71,7 @@ public:
void appendConstStateVariableAccessor(const VariableDeclaration& _varDecl);
private:
virtual bool visit(Conditional const& _condition) override;
virtual bool visit(Assignment const& _assignment) override;
virtual bool visit(TupleExpression const& _tuple) override;
virtual bool visit(UnaryOperation const& _unaryOperation) override;

View File

@ -932,13 +932,26 @@ ASTPointer<Expression> Parser::parseExpression(
)
{
ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure);
if (!Token::isAssignmentOp(m_scanner->currentToken()))
if (Token::isAssignmentOp(m_scanner->currentToken()))
{
Token::Value assignmentOperator = expectAssignmentOperator();
ASTPointer<Expression> rightHandSide = parseExpression();
ASTNodeFactory nodeFactory(*this, expression);
nodeFactory.setEndPositionFromNode(rightHandSide);
return nodeFactory.createNode<Assignment>(expression, assignmentOperator, rightHandSide);
}
else if (m_scanner->currentToken() == Token::Value::Conditional)
{
m_scanner->next();
ASTPointer<Expression> trueExpression = parseExpression();
expectToken(Token::Colon);
ASTPointer<Expression> falseExpression = parseExpression();
ASTNodeFactory nodeFactory(*this, expression);
nodeFactory.setEndPositionFromNode(falseExpression);
return nodeFactory.createNode<Conditional>(expression, trueExpression, falseExpression);
}
else
return expression;
Token::Value assignmentOperator = expectAssignmentOperator();
ASTPointer<Expression> rightHandSide = parseExpression();
ASTNodeFactory nodeFactory(*this, expression);
nodeFactory.setEndPositionFromNode(rightHandSide);
return nodeFactory.createNode<Assignment>(expression, assignmentOperator, rightHandSide);
}
ASTPointer<Expression> Parser::parseBinaryExpression(

View File

@ -86,6 +86,192 @@ BOOST_AUTO_TEST_CASE(exp_operator_const_signed)
BOOST_CHECK(callContractFunction("f()", bytes()) == toBigEndian(u256(-8)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_true_literal)
{
char const* sourceCode = R"(
contract test {
function f() returns(uint d) {
return true ? 5 : 10;
}
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f()", bytes()) == toBigEndian(u256(5)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_false_literal)
{
char const* sourceCode = R"(
contract test {
function f() returns(uint d) {
return false ? 5 : 10;
}
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f()", bytes()) == toBigEndian(u256(10)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_multiple)
{
char const* sourceCode = R"(
contract test {
function f(uint x) returns(uint d) {
return x > 100 ?
x > 1000 ? 1000 : 100
:
x > 50 ? 50 : 10;
}
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(uint256)", u256(1001)) == toBigEndian(u256(1000)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(500)) == toBigEndian(u256(100)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(80)) == toBigEndian(u256(50)));
BOOST_CHECK(callContractFunction("f(uint256)", u256(40)) == toBigEndian(u256(10)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_return_values)
{
char const* sourceCode = R"(
contract test {
function f(bool cond, uint v) returns (uint a, uint b) {
cond ? a = v : b = v;
}
})";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool,uint256)", true, u256(20)) == encodeArgs(u256(20), u256(0)));
BOOST_CHECK(callContractFunction("f(bool,uint256)", false, u256(20)) == encodeArgs(u256(0), u256(20)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_storage_memory_1)
{
char const* sourceCode = R"(
contract test {
bytes2[2] data1;
function f(bool cond) returns (uint) {
bytes2[2] memory x;
x[0] = "aa";
bytes2[2] memory y;
y[0] = "bb";
data1 = cond ? x : y;
uint ret = 0;
if (data1[0] == "aa")
{
ret = 1;
}
if (data1[0] == "bb")
{
ret = 2;
}
return ret;
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(u256(1)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_storage_memory_2)
{
char const* sourceCode = R"(
contract test {
bytes2[2] data1;
function f(bool cond) returns (uint) {
data1[0] = "cc";
bytes2[2] memory x;
bytes2[2] memory y;
y[0] = "bb";
x = cond ? y : data1;
uint ret = 0;
if (x[0] == "bb")
{
ret = 1;
}
if (x[0] == "cc")
{
ret = 2;
}
return ret;
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(u256(1)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_different_types)
{
char const* sourceCode = R"(
contract test {
function f(bool cond) returns (uint) {
uint8 x = 0xcd;
uint16 y = 0xabab;
return cond ? x : y;
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(u256(0xcd)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(u256(0xabab)));
}
/* let's add this back when I figure out the correct type conversion.
BOOST_AUTO_TEST_CASE(conditional_expression_string_literal)
{
char const* sourceCode = R"(
contract test {
function f(bool cond) returns (bytes32) {
return cond ? "true" : "false";
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(string("true", 4)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(string("false", 5)));
}
*/
BOOST_AUTO_TEST_CASE(conditional_expression_tuples)
{
char const* sourceCode = R"(
contract test {
function f(bool cond) returns (uint, uint) {
return cond ? (1, 2) : (3, 4);
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(u256(1), u256(2)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(u256(3), u256(4)));
}
BOOST_AUTO_TEST_CASE(conditional_expression_functions)
{
char const* sourceCode = R"(
contract test {
function x() returns (uint) { return 1; }
function y() returns (uint) { return 2; }
function f(bool cond) returns (uint) {
var z = cond ? x : y;
return z();
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(u256(1)));
BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(recursive_calls)
{
char const* sourceCode = "contract test {\n"

View File

@ -2960,6 +2960,182 @@ BOOST_AUTO_TEST_CASE(continue_not_in_loop_2)
BOOST_CHECK(expectError(text) == Error::Type::SyntaxError);
}
BOOST_AUTO_TEST_CASE(invalid_different_types_for_conditional_expression)
{
char const* text = R"(
contract C {
function f() {
true ? true : 2;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(left_value_in_conditional_expression_not_supported_yet)
{
char const* text = R"(
contract C {
function f() {
uint x;
uint y;
(true ? x : y) = 1;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_different_struct)
{
char const* text = R"(
contract C {
struct s1 {
uint x;
}
struct s2 {
uint x;
}
function f() {
s1 x;
s2 y;
true ? x : y;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_different_function_type)
{
char const* text = R"(
contract C {
function x(bool) {}
function y() {}
function f() {
true ? x : y;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_different_enum)
{
char const* text = R"(
contract C {
enum small { A, B, C, D }
enum big { A, B, C, D }
function f() {
small x;
big y;
true ? x : y;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_different_mapping)
{
char const* text = R"(
contract C {
mapping(uint8 => uint8) table1;
mapping(uint32 => uint8) table2;
function f() {
true ? table1 : table2;
}
}
)";
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
}
BOOST_AUTO_TEST_CASE(conditional_with_all_types)
{
char const* text = R"(
contract C {
struct s1 {
uint x;
}
s1 struct_x;
s1 struct_y;
function fun_x() {}
function fun_y() {}
enum small { A, B, C, D }
mapping(uint8 => uint8) table1;
mapping(uint8 => uint8) table2;
function f() {
// integers
uint x;
uint y;
true ? x : y;
// integer constants
true ? 1 : 3;
// string literal
true ? "hello" : "world";
// bool
true ? true : false;
// real is not there yet.
// array
byte[2] memory a;
byte[2] memory b;
true ? a : b;
bytes memory e;
bytes memory f;
true ? e : f;
// fixed bytes
bytes2 c;
bytes2 d;
true ? c : d;
// contract doesn't fit in here
// struct
true ? struct_x : struct_y;
// function
true ? fun_x : fun_y;
// enum
small enum_x;
small enum_y;
true ? enum_x : enum_y;
// tuple
true ? (1, 2) : (3, 4);
// mapping
true ? table1 : table2;
// typetype
true ? uint32(1) : uint32(2);
// modifier doesn't fit in here
// magic doesn't fit in here
// module doesn't fit in here
}
}
)";
BOOST_CHECK(success(text));
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -1111,6 +1111,73 @@ BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_without_lvalue)
BOOST_CHECK(!successParse(text));
}
BOOST_AUTO_TEST_CASE(conditional_true_false_literal)
{
char const* text = R"(
contract A {
function f() {
uint x = true ? 1 : 0;
uint y = false ? 0 : 1;
}
}
)";
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_CASE(conditional_with_constants)
{
char const* text = R"(
contract A {
function f() {
uint x = 3 > 0 ? 3 : 0;
uint y = (3 > 0) ? 3 : 0;
}
}
)";
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_CASE(conditional_with_variables)
{
char const* text = R"(
contract A {
function f() {
uint x = 3;
uint y = 1;
uint z = (x > y) ? x : y;
uint w = x > y ? x : y;
}
}
)";
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_CASE(conditional_multiple)
{
char const* text = R"(
contract A {
function f() {
uint x = 3 < 0 ? 2 > 1 ? 2 : 1 : 7 > 2 ? 7 : 6;
}
}
)";
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_CASE(conditional_with_assignment)
{
char const* text = R"(
contract A {
function f() {
uint y = 1;
uint x = 3 < 0 ? x = 3 : 6;
true ? x = 3 : 4;
}
}
)";
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_SUITE_END()
}