Browse Source

Merge pull request #1634 from chriseth/sol_overloadingFunctions

Function overloading.
cl-refactor
chriseth 10 years ago
parent
commit
6245e42e9b
  1. 258
      libsolidity/AST.cpp
  2. 61
      libsolidity/AST.h
  3. 6
      libsolidity/Compiler.cpp
  4. 38
      libsolidity/CompilerContext.cpp
  5. 11
      libsolidity/CompilerContext.h
  6. 38
      libsolidity/DeclarationContainer.cpp
  7. 8
      libsolidity/DeclarationContainer.h
  8. 37
      libsolidity/ExpressionCompiler.cpp
  9. 8
      libsolidity/LValue.cpp
  10. 115
      libsolidity/NameAndTypeResolver.cpp
  11. 21
      libsolidity/NameAndTypeResolver.h
  12. 117
      libsolidity/Types.cpp
  13. 41
      libsolidity/Types.h
  14. 4
      mix/CodeModel.cpp
  15. 109
      test/libsolidity/SolidityEndToEndTest.cpp
  16. 4
      test/libsolidity/SolidityExpressionCompiler.cpp
  17. 105
      test/libsolidity/SolidityNameAndTypeResolution.cpp
  18. 25
      test/libsolidity/SolidityParser.cpp

258
libsolidity/AST.cpp

@ -52,6 +52,7 @@ void ContractDefinition::checkTypeRequirements()
for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: getBaseContracts()) for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: getBaseContracts())
baseSpecifier->checkTypeRequirements(); baseSpecifier->checkTypeRequirements();
checkDuplicateFunctions();
checkIllegalOverrides(); checkIllegalOverrides();
checkAbstractFunctions(); checkAbstractFunctions();
checkAbstractConstructors(); checkAbstractConstructors();
@ -87,6 +88,7 @@ void ContractDefinition::checkTypeRequirements()
for (ASTPointer<VariableDeclaration> const& variable: m_stateVariables) for (ASTPointer<VariableDeclaration> const& variable: m_stateVariables)
variable->checkTypeRequirements(); variable->checkTypeRequirements();
checkExternalTypeClashes();
// check for hash collisions in function signatures // check for hash collisions in function signatures
set<FixedHash<4>> hashes; set<FixedHash<4>> hashes;
for (auto const& it: getInterfaceFunctionList()) for (auto const& it: getInterfaceFunctionList())
@ -131,6 +133,33 @@ FunctionDefinition const* ContractDefinition::getFallbackFunction() const
return nullptr; return nullptr;
} }
void ContractDefinition::checkDuplicateFunctions() const
{
/// Checks that two functions with the same name defined in this contract have different
/// argument types and that there is at most one constructor.
map<string, vector<FunctionDefinition const*>> functions;
for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
functions[function->getName()].push_back(function.get());
if (functions[getName()].size() > 1)
BOOST_THROW_EXCEPTION(
DeclarationError() <<
errinfo_sourceLocation(getLocation()) <<
errinfo_comment("More than one constructor defined.")
);
for (auto const& it: functions)
{
vector<FunctionDefinition const*> const& overloads = it.second;
for (size_t i = 0; i < overloads.size(); ++i)
for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
BOOST_THROW_EXCEPTION(
DeclarationError() <<
errinfo_sourceLocation(overloads[j]->getLocation()) <<
errinfo_comment("Function with same name and arguments already defined.")
);
}
}
void ContractDefinition::checkAbstractFunctions() void ContractDefinition::checkAbstractFunctions()
{ {
map<string, bool> functions; map<string, bool> functions;
@ -171,7 +200,7 @@ void ContractDefinition::checkAbstractConstructors()
for (auto const& modifier: constructor->getModifiers()) for (auto const& modifier: constructor->getModifiers())
{ {
auto baseContract = dynamic_cast<ContractDefinition const*>( auto baseContract = dynamic_cast<ContractDefinition const*>(
modifier->getName()->getReferencedDeclaration() &modifier->getName()->getReferencedDeclaration()
); );
if (baseContract) if (baseContract)
argumentsNeeded.erase(baseContract); argumentsNeeded.erase(baseContract);
@ -181,7 +210,7 @@ void ContractDefinition::checkAbstractConstructors()
for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts()) for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts())
{ {
auto baseContract = dynamic_cast<ContractDefinition const*>( auto baseContract = dynamic_cast<ContractDefinition const*>(
base->getName()->getReferencedDeclaration() &base->getName()->getReferencedDeclaration()
); );
solAssert(baseContract, ""); solAssert(baseContract, "");
if (!base->getArguments().empty()) if (!base->getArguments().empty())
@ -196,7 +225,7 @@ void ContractDefinition::checkIllegalOverrides() const
{ {
// TODO unify this at a later point. for this we need to put the constness and the access specifier // TODO unify this at a later point. for this we need to put the constness and the access specifier
// into the types // into the types
map<string, FunctionDefinition const*> functions; map<string, vector<FunctionDefinition const*>> functions;
map<string, ModifierDefinition const*> modifiers; map<string, ModifierDefinition const*> modifiers;
// We search from derived to base, so the stored item causes the error. // We search from derived to base, so the stored item causes the error.
@ -209,26 +238,65 @@ void ContractDefinition::checkIllegalOverrides() const
string const& name = function->getName(); string const& name = function->getName();
if (modifiers.count(name)) if (modifiers.count(name))
BOOST_THROW_EXCEPTION(modifiers[name]->createTypeError("Override changes function to modifier.")); BOOST_THROW_EXCEPTION(modifiers[name]->createTypeError("Override changes function to modifier."));
FunctionDefinition const*& override = functions[name]; FunctionType functionType(*function);
if (!override) // function should not change the return type
override = function.get(); for (FunctionDefinition const* overriding: functions[name])
else if (override->getVisibility() != function->getVisibility() || {
override->isDeclaredConst() != function->isDeclaredConst() || FunctionType overridingType(*overriding);
FunctionType(*override) != FunctionType(*function)) if (!overridingType.hasEqualArgumentTypes(functionType))
BOOST_THROW_EXCEPTION(override->createTypeError("Override changes extended function signature.")); continue;
if (
overriding->getVisibility() != function->getVisibility() ||
overriding->isDeclaredConst() != function->isDeclaredConst() ||
overridingType != functionType
)
BOOST_THROW_EXCEPTION(overriding->createTypeError("Override changes extended function signature."));
}
functions[name].push_back(function.get());
} }
for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers()) for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers())
{ {
string const& name = modifier->getName(); string const& name = modifier->getName();
if (functions.count(name))
BOOST_THROW_EXCEPTION(functions[name]->createTypeError("Override changes modifier to function."));
ModifierDefinition const*& override = modifiers[name]; ModifierDefinition const*& override = modifiers[name];
if (!override) if (!override)
override = modifier.get(); override = modifier.get();
else if (ModifierType(*override) != ModifierType(*modifier)) else if (ModifierType(*override) != ModifierType(*modifier))
BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier signature.")); BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier signature."));
if (!functions[name].empty())
BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier to function."));
}
}
}
void ContractDefinition::checkExternalTypeClashes() const
{
map<string, vector<pair<Declaration const*, shared_ptr<FunctionType>>>> externalDeclarations;
for (ContractDefinition const* contract: getLinearizedBaseContracts())
{
for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
if (f->isPartOfExternalInterface())
{
auto functionType = make_shared<FunctionType>(*f);
externalDeclarations[functionType->externalSignature(f->getName())].push_back(
make_pair(f.get(), functionType)
);
}
for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
if (v->isPartOfExternalInterface())
{
auto functionType = make_shared<FunctionType>(*v);
externalDeclarations[functionType->externalSignature(v->getName())].push_back(
make_pair(v.get(), functionType)
);
} }
} }
for (auto const& it: externalDeclarations)
for (size_t i = 0; i < it.second.size(); ++i)
for (size_t j = i + 1; j < it.second.size(); ++j)
if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second))
BOOST_THROW_EXCEPTION(it.second[j].first->createTypeError(
"Function overload clash during conversion to external types for arguments."
));
} }
std::vector<ASTPointer<EventDefinition>> const& ContractDefinition::getInterfaceEvents() const std::vector<ASTPointer<EventDefinition>> const& ContractDefinition::getInterfaceEvents() const
@ -253,16 +321,21 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
if (!m_interfaceFunctionList) if (!m_interfaceFunctionList)
{ {
set<string> functionsSeen; set<string> functionsSeen;
set<string> signaturesSeen;
m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>()); m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>());
for (ContractDefinition const* contract: getLinearizedBaseContracts()) for (ContractDefinition const* contract: getLinearizedBaseContracts())
{ {
for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
if (functionsSeen.count(f->getName()) == 0 && f->isPartOfExternalInterface()) {
string functionSignature = f->externalSignature();
if (f->isPartOfExternalInterface() && signaturesSeen.count(functionSignature) == 0)
{ {
functionsSeen.insert(f->getName()); functionsSeen.insert(f->getName());
FixedHash<4> hash(dev::sha3(f->externalSignature())); signaturesSeen.insert(functionSignature);
FixedHash<4> hash(dev::sha3(functionSignature));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false))); m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false)));
} }
}
for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables()) for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface()) if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
@ -314,11 +387,11 @@ TypePointer EnumValue::getType(ContractDefinition const*) const
void InheritanceSpecifier::checkTypeRequirements() void InheritanceSpecifier::checkTypeRequirements()
{ {
m_baseName->checkTypeRequirements(); m_baseName->checkTypeRequirements(nullptr);
for (ASTPointer<Expression> const& argument: m_arguments) for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements(); argument->checkTypeRequirements(nullptr);
ContractDefinition const* base = dynamic_cast<ContractDefinition const*>(m_baseName->getReferencedDeclaration()); ContractDefinition const* base = dynamic_cast<ContractDefinition const*>(&m_baseName->getReferencedDeclaration());
solAssert(base, "Base contract not available."); solAssert(base, "Base contract not available.");
TypePointers parameterTypes = ContractType(*base).getConstructorType()->getParameterTypes(); TypePointers parameterTypes = ContractType(*base).getConstructorType()->getParameterTypes();
if (!m_arguments.empty() && parameterTypes.size() != m_arguments.size()) if (!m_arguments.empty() && parameterTypes.size() != m_arguments.size())
@ -428,7 +501,8 @@ void VariableDeclaration::checkTypeRequirements()
if (!m_value) if (!m_value)
// This feature might be extended in the future. // This feature might be extended in the future.
BOOST_THROW_EXCEPTION(createTypeError("Assignment necessary for type detection.")); BOOST_THROW_EXCEPTION(createTypeError("Assignment necessary for type detection."));
m_value->checkTypeRequirements(); m_value->checkTypeRequirements(nullptr);
TypePointer type = m_value->getType(); TypePointer type = m_value->getType();
if (type->getCategory() == Type::Category::IntegerConstant) if (type->getCategory() == Type::Category::IntegerConstant)
{ {
@ -468,18 +542,22 @@ void ModifierDefinition::checkTypeRequirements()
void ModifierInvocation::checkTypeRequirements(vector<ContractDefinition const*> const& _bases) void ModifierInvocation::checkTypeRequirements(vector<ContractDefinition const*> const& _bases)
{ {
m_modifierName->checkTypeRequirements(); TypePointers argumentTypes;
for (ASTPointer<Expression> const& argument: m_arguments) for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements(); {
argument->checkTypeRequirements(nullptr);
argumentTypes.push_back(argument->getType());
}
m_modifierName->checkTypeRequirements(&argumentTypes);
auto declaration = m_modifierName->getReferencedDeclaration(); auto const* declaration = &m_modifierName->getReferencedDeclaration();
vector<ASTPointer<VariableDeclaration>> emptyParameterList; vector<ASTPointer<VariableDeclaration>> emptyParameterList;
vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr; vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
if (auto modifier = dynamic_cast<ModifierDefinition const*>(declaration)) if (auto modifier = dynamic_cast<ModifierDefinition const*>(declaration))
parameters = &modifier->getParameters(); parameters = &modifier->getParameters();
else else
// check parameters for Base constructors // check parameters for Base constructors
for (auto const* base: _bases) for (ContractDefinition const* base: _bases)
if (declaration == base) if (declaration == base)
{ {
if (auto referencedConstructor = base->getConstructor()) if (auto referencedConstructor = base->getConstructor())
@ -563,9 +641,9 @@ void VariableDeclarationStatement::checkTypeRequirements()
m_variable->checkTypeRequirements(); m_variable->checkTypeRequirements();
} }
void Assignment::checkTypeRequirements() void Assignment::checkTypeRequirements(TypePointers const*)
{ {
m_leftHandSide->checkTypeRequirements(); m_leftHandSide->checkTypeRequirements(nullptr);
m_leftHandSide->requireLValue(); m_leftHandSide->requireLValue();
if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping) if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping)
BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to.")); BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to."));
@ -575,7 +653,7 @@ void Assignment::checkTypeRequirements()
else else
{ {
// compound assignment // compound assignment
m_rightHandSide->checkTypeRequirements(); m_rightHandSide->checkTypeRequirements(nullptr);
TypePointer resultType = m_type->binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator), TypePointer resultType = m_type->binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator),
m_rightHandSide->getType()); m_rightHandSide->getType());
if (!resultType || *resultType != *m_type) if (!resultType || *resultType != *m_type)
@ -588,7 +666,7 @@ void Assignment::checkTypeRequirements()
void ExpressionStatement::checkTypeRequirements() void ExpressionStatement::checkTypeRequirements()
{ {
m_expression->checkTypeRequirements(); m_expression->checkTypeRequirements(nullptr);
if (m_expression->getType()->getCategory() == Type::Category::IntegerConstant) if (m_expression->getType()->getCategory() == Type::Category::IntegerConstant)
if (!dynamic_pointer_cast<IntegerConstantType const>(m_expression->getType())->getIntegerType()) if (!dynamic_pointer_cast<IntegerConstantType const>(m_expression->getType())->getIntegerType())
BOOST_THROW_EXCEPTION(m_expression->createTypeError("Invalid integer constant.")); BOOST_THROW_EXCEPTION(m_expression->createTypeError("Invalid integer constant."));
@ -596,7 +674,7 @@ void ExpressionStatement::checkTypeRequirements()
void Expression::expectType(Type const& _expectedType) void Expression::expectType(Type const& _expectedType)
{ {
checkTypeRequirements(); checkTypeRequirements(nullptr);
Type const& type = *getType(); Type const& type = *getType();
if (!type.isImplicitlyConvertibleTo(_expectedType)) if (!type.isImplicitlyConvertibleTo(_expectedType))
BOOST_THROW_EXCEPTION(createTypeError("Type " + type.toString() + BOOST_THROW_EXCEPTION(createTypeError("Type " + type.toString() +
@ -611,10 +689,10 @@ void Expression::requireLValue()
m_lvalueRequested = true; m_lvalueRequested = true;
} }
void UnaryOperation::checkTypeRequirements() void UnaryOperation::checkTypeRequirements(TypePointers const*)
{ {
// Inc, Dec, Add, Sub, Not, BitNot, Delete // Inc, Dec, Add, Sub, Not, BitNot, Delete
m_subExpression->checkTypeRequirements(); m_subExpression->checkTypeRequirements(nullptr);
if (m_operator == Token::Value::Inc || m_operator == Token::Value::Dec || m_operator == Token::Value::Delete) if (m_operator == Token::Value::Inc || m_operator == Token::Value::Dec || m_operator == Token::Value::Delete)
m_subExpression->requireLValue(); m_subExpression->requireLValue();
m_type = m_subExpression->getType()->unaryOperatorResult(m_operator); m_type = m_subExpression->getType()->unaryOperatorResult(m_operator);
@ -622,10 +700,10 @@ void UnaryOperation::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type."));
} }
void BinaryOperation::checkTypeRequirements() void BinaryOperation::checkTypeRequirements(TypePointers const*)
{ {
m_left->checkTypeRequirements(); m_left->checkTypeRequirements(nullptr);
m_right->checkTypeRequirements(); m_right->checkTypeRequirements(nullptr);
m_commonType = m_left->getType()->binaryOperatorResult(m_operator, m_right->getType()); m_commonType = m_left->getType()->binaryOperatorResult(m_operator, m_right->getType());
if (!m_commonType) if (!m_commonType)
BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) + BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) +
@ -635,11 +713,22 @@ void BinaryOperation::checkTypeRequirements()
m_type = Token::isCompareOp(m_operator) ? make_shared<BoolType>() : m_commonType; m_type = Token::isCompareOp(m_operator) ? make_shared<BoolType>() : m_commonType;
} }
void FunctionCall::checkTypeRequirements() void FunctionCall::checkTypeRequirements(TypePointers const*)
{ {
m_expression->checkTypeRequirements(); bool isPositionalCall = m_names.empty();
// we need to check arguments' type first as they will be forwarded to
// m_expression->checkTypeRequirements
TypePointers argumentTypes;
for (ASTPointer<Expression> const& argument: m_arguments) for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements(); {
argument->checkTypeRequirements(nullptr);
// only store them for positional calls
if (isPositionalCall)
argumentTypes.push_back(argument->getType());
}
m_expression->checkTypeRequirements(isPositionalCall ? &argumentTypes : nullptr);
Type const* expressionType = m_expression->getType().get(); Type const* expressionType = m_expression->getType().get();
if (isTypeConversion()) if (isTypeConversion())
@ -649,7 +738,7 @@ void FunctionCall::checkTypeRequirements()
// number of non-mapping members // number of non-mapping members
if (m_arguments.size() != 1) if (m_arguments.size() != 1)
BOOST_THROW_EXCEPTION(createTypeError("More than one argument for explicit type conversion.")); BOOST_THROW_EXCEPTION(createTypeError("More than one argument for explicit type conversion."));
if (!m_names.empty()) if (!isPositionalCall)
BOOST_THROW_EXCEPTION(createTypeError("Type conversion cannot allow named arguments.")); BOOST_THROW_EXCEPTION(createTypeError("Type conversion cannot allow named arguments."));
if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType())) if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType()))
BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed."));
@ -664,8 +753,9 @@ void FunctionCall::checkTypeRequirements()
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size()) if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size())
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call."));
if (m_names.empty()) if (isPositionalCall)
{ {
// call by positional arguments
for (size_t i = 0; i < m_arguments.size(); ++i) for (size_t i = 0; i < m_arguments.size(); ++i)
if (!functionType->takesArbitraryParameters() && if (!functionType->takesArbitraryParameters() &&
!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) !m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i]))
@ -673,6 +763,7 @@ void FunctionCall::checkTypeRequirements()
} }
else else
{ {
// call by named arguments
if (functionType->takesArbitraryParameters()) if (functionType->takesArbitraryParameters())
BOOST_THROW_EXCEPTION(createTypeError("Named arguments cannnot be used for functions " BOOST_THROW_EXCEPTION(createTypeError("Named arguments cannnot be used for functions "
"that take arbitrary parameters.")); "that take arbitrary parameters."));
@ -719,10 +810,10 @@ bool FunctionCall::isTypeConversion() const
return m_expression->getType()->getCategory() == Type::Category::TypeType; return m_expression->getType()->getCategory() == Type::Category::TypeType;
} }
void NewExpression::checkTypeRequirements() void NewExpression::checkTypeRequirements(TypePointers const*)
{ {
m_contractName->checkTypeRequirements(); m_contractName->checkTypeRequirements(nullptr);
m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration()); m_contract = dynamic_cast<ContractDefinition const*>(&m_contractName->getReferencedDeclaration());
if (!m_contract) if (!m_contract)
BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
if (!m_contract->isFullyImplemented()) if (!m_contract->isFullyImplemented())
@ -733,15 +824,37 @@ void NewExpression::checkTypeRequirements()
FunctionType::Location::Creation); FunctionType::Location::Creation);
} }
void MemberAccess::checkTypeRequirements() void MemberAccess::checkTypeRequirements(TypePointers const* _argumentTypes)
{ {
m_expression->checkTypeRequirements(); m_expression->checkTypeRequirements(nullptr);
Type const& type = *m_expression->getType(); Type const& type = *m_expression->getType();
m_type = type.getMemberType(*m_memberName);
if (!m_type) MemberList::MemberMap possibleMembers = type.getMembers().membersByName(*m_memberName);
BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found or not " if (possibleMembers.size() > 1 && _argumentTypes)
"visible in " + type.toString())); {
// This should probably move somewhere else. // do override resolution
for (auto it = possibleMembers.begin(); it != possibleMembers.end();)
if (
it->type->getCategory() == Type::Category::Function &&
!dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*_argumentTypes)
)
it = possibleMembers.erase(it);
else
++it;
}
if (possibleMembers.size() == 0)
BOOST_THROW_EXCEPTION(createTypeError(
"Member \"" + *m_memberName + "\" not found or not visible "
"after argument-dependent lookup in " + type.toString()
));
else if (possibleMembers.size() > 1)
BOOST_THROW_EXCEPTION(createTypeError(
"Member \"" + *m_memberName + "\" not unique "
"after argument-dependent lookup in " + type.toString()
));
m_referencedDeclaration = possibleMembers.front().declaration;
m_type = possibleMembers.front().type;
if (type.getCategory() == Type::Category::Struct) if (type.getCategory() == Type::Category::Struct)
m_isLValue = true; m_isLValue = true;
else if (type.getCategory() == Type::Category::Array) else if (type.getCategory() == Type::Category::Array)
@ -754,9 +867,9 @@ void MemberAccess::checkTypeRequirements()
m_isLValue = false; m_isLValue = false;
} }
void IndexAccess::checkTypeRequirements() void IndexAccess::checkTypeRequirements(TypePointers const*)
{ {
m_base->checkTypeRequirements(); m_base->checkTypeRequirements(nullptr);
switch (m_base->getType()->getCategory()) switch (m_base->getType()->getCategory())
{ {
case Type::Category::Array: case Type::Category::Array:
@ -789,7 +902,7 @@ void IndexAccess::checkTypeRequirements()
m_type = make_shared<TypeType>(make_shared<ArrayType>(ArrayType::Location::Memory, type.getActualType())); m_type = make_shared<TypeType>(make_shared<ArrayType>(ArrayType::Location::Memory, type.getActualType()));
else else
{ {
m_index->checkTypeRequirements(); m_index->checkTypeRequirements(nullptr);
auto length = dynamic_cast<IntegerConstantType const*>(m_index->getType().get()); auto length = dynamic_cast<IntegerConstantType const*>(m_index->getType().get());
if (!length) if (!length)
BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected.")); BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected."));
@ -804,22 +917,57 @@ void IndexAccess::checkTypeRequirements()
} }
} }
void Identifier::checkTypeRequirements() void Identifier::checkTypeRequirements(TypePointers const* _argumentTypes)
{ {
solAssert(m_referencedDeclaration, "Identifier not resolved."); if (!m_referencedDeclaration)
{
if (!_argumentTypes)
BOOST_THROW_EXCEPTION(createTypeError("Unable to determine overloaded type."));
overloadResolution(*_argumentTypes);
}
solAssert(!!m_referencedDeclaration, "Referenced declaration is null after overload resolution.");
m_isLValue = m_referencedDeclaration->isLValue(); m_isLValue = m_referencedDeclaration->isLValue();
m_type = m_referencedDeclaration->getType(m_currentContract); m_type = m_referencedDeclaration->getType(m_currentContract);
if (!m_type) if (!m_type)
BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined.")); BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined."));
} }
void ElementaryTypeNameExpression::checkTypeRequirements() Declaration const& Identifier::getReferencedDeclaration() const
{
solAssert(!!m_referencedDeclaration, "Identifier not resolved.");
return *m_referencedDeclaration;
}
void Identifier::overloadResolution(TypePointers const& _argumentTypes)
{
solAssert(!m_referencedDeclaration, "Referenced declaration should be null before overload resolution.");
solAssert(!m_overloadedDeclarations.empty(), "No candidates for overload resolution found.");
std::vector<Declaration const*> possibles;
if (m_overloadedDeclarations.size() == 1)
m_referencedDeclaration = *m_overloadedDeclarations.begin();
for (Declaration const* declaration: m_overloadedDeclarations)
{
TypePointer const& function = declaration->getType();
auto const* functionType = dynamic_cast<FunctionType const*>(function.get());
if (functionType && functionType->canTakeArguments(_argumentTypes))
possibles.push_back(declaration);
}
if (possibles.size() == 1)
m_referencedDeclaration = possibles.front();
else if (possibles.empty())
BOOST_THROW_EXCEPTION(createTypeError("No matching declaration found after argument-dependent lookup."));
else
BOOST_THROW_EXCEPTION(createTypeError("No unique declaration found after argument-dependent lookup."));
}
void ElementaryTypeNameExpression::checkTypeRequirements(TypePointers const*)
{ {
m_type = make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken)); m_type = make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken));
} }
void Literal::checkTypeRequirements() void Literal::checkTypeRequirements(TypePointers const*)
{ {
m_type = Type::forLiteral(*this); m_type = Type::forLiteral(*this);
if (!m_type) if (!m_type)

61
libsolidity/AST.h

@ -282,9 +282,15 @@ public:
FunctionDefinition const* getFallbackFunction() const; FunctionDefinition const* getFallbackFunction() const;
private: private:
/// Checks that two functions defined in this contract with the same name have different
/// arguments and that there is at most one constructor.
void checkDuplicateFunctions() const;
void checkIllegalOverrides() const; void checkIllegalOverrides() const;
void checkAbstractFunctions(); void checkAbstractFunctions();
void checkAbstractConstructors(); void checkAbstractConstructors();
/// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature).
void checkExternalTypeClashes() const;
std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const; std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const;
@ -968,7 +974,10 @@ class Expression: public ASTNode
{ {
public: public:
Expression(SourceLocation const& _location): ASTNode(_location) {} Expression(SourceLocation const& _location): ASTNode(_location) {}
virtual void checkTypeRequirements() = 0; /// Performs type checking after which m_type should be set.
/// @arg _argumentTypes if set, provides the argument types for the case that this expression
/// is used in the context of a call, used for function overload resolution.
virtual void checkTypeRequirements(TypePointers const* _argumentTypes) = 0;
std::shared_ptr<Type const> const& getType() const { return m_type; } std::shared_ptr<Type const> const& getType() const { return m_type; }
bool isLValue() const { return m_isLValue; } bool isLValue() const { return m_isLValue; }
@ -1007,7 +1016,7 @@ public:
} }
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Expression const& getLeftHandSide() const { return *m_leftHandSide; } Expression const& getLeftHandSide() const { return *m_leftHandSide; }
Token::Value getAssignmentOperator() const { return m_assigmentOperator; } Token::Value getAssignmentOperator() const { return m_assigmentOperator; }
@ -1035,7 +1044,7 @@ public:
} }
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Token::Value getOperator() const { return m_operator; } Token::Value getOperator() const { return m_operator; }
bool isPrefixOperation() const { return m_isPrefix; } bool isPrefixOperation() const { return m_isPrefix; }
@ -1062,7 +1071,7 @@ public:
} }
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Expression const& getLeftExpression() const { return *m_left; } Expression const& getLeftExpression() const { return *m_left; }
Expression const& getRightExpression() const { return *m_right; } Expression const& getRightExpression() const { return *m_right; }
@ -1090,7 +1099,7 @@ public:
Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Expression const& getExpression() const { return *m_expression; } Expression const& getExpression() const { return *m_expression; }
std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; }
@ -1116,7 +1125,7 @@ public:
Expression(_location), m_contractName(_contractName) {} Expression(_location), m_contractName(_contractName) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
/// Returns the referenced contract. Can only be called after type checking. /// Returns the referenced contract. Can only be called after type checking.
ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; } ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; }
@ -1140,11 +1149,18 @@ public:
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
Expression const& getExpression() const { return *m_expression; } Expression const& getExpression() const { return *m_expression; }
ASTString const& getMemberName() const { return *m_memberName; } ASTString const& getMemberName() const { return *m_memberName; }
virtual void checkTypeRequirements() override; /// @returns the declaration referenced by this expression. Might return nullptr even if the
/// expression is valid, e.g. if the member does not correspond to an AST node.
Declaration const* referencedDeclaration() const { return m_referencedDeclaration; }
virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
private: private:
ASTPointer<Expression> m_expression; ASTPointer<Expression> m_expression;
ASTPointer<ASTString> m_memberName; ASTPointer<ASTString> m_memberName;
/// Pointer to the referenced declaration, this is sometimes needed to resolve function over
/// loads in the type-checking phase.
Declaration const* m_referencedDeclaration = nullptr;
}; };
/** /**
@ -1158,7 +1174,7 @@ public:
Expression(_location), m_base(_base), m_index(_index) {} Expression(_location), m_base(_base), m_index(_index) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Expression const& getBaseExpression() const { return *m_base; } Expression const& getBaseExpression() const { return *m_base; }
Expression const* getIndexExpression() const { return m_index.get(); } Expression const* getIndexExpression() const { return m_index.get(); }
@ -1188,18 +1204,31 @@ public:
PrimaryExpression(_location), m_name(_name) {} PrimaryExpression(_location), m_name(_name) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
ASTString const& getName() const { return *m_name; } ASTString const& getName() const { return *m_name; }
void setReferencedDeclaration(Declaration const& _referencedDeclaration, void setReferencedDeclaration(
ContractDefinition const* _currentContract = nullptr) Declaration const& _referencedDeclaration,
ContractDefinition const* _currentContract = nullptr
)
{ {
m_referencedDeclaration = &_referencedDeclaration; m_referencedDeclaration = &_referencedDeclaration;
m_currentContract = _currentContract; m_currentContract = _currentContract;
} }
Declaration const* getReferencedDeclaration() const { return m_referencedDeclaration; } Declaration const& getReferencedDeclaration() const;
ContractDefinition const* getCurrentContract() const { return m_currentContract; }
/// Stores a set of possible declarations referenced by this identifier. Has to be resolved
/// providing argument types using overloadResolution before the referenced declaration
/// is accessed.
void setOverloadedDeclarations(std::set<Declaration const*> const& _declarations)
{
m_overloadedDeclarations = _declarations;
}
/// Tries to find exactly one of the possible referenced declarations provided the given
/// argument types in a call context.
void overloadResolution(TypePointers const& _argumentTypes);
private: private:
ASTPointer<ASTString> m_name; ASTPointer<ASTString> m_name;
@ -1209,6 +1238,8 @@ private:
/// Stores a reference to the current contract. This is needed because types of base contracts /// Stores a reference to the current contract. This is needed because types of base contracts
/// change depending on the context. /// change depending on the context.
ContractDefinition const* m_currentContract = nullptr; ContractDefinition const* m_currentContract = nullptr;
/// A set of overloaded declarations, right now only FunctionDefinition has overloaded declarations.
std::set<Declaration const*> m_overloadedDeclarations;
}; };
/** /**
@ -1226,7 +1257,7 @@ public:
} }
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Token::Value getTypeToken() const { return m_typeToken; } Token::Value getTypeToken() const { return m_typeToken; }
@ -1260,7 +1291,7 @@ public:
PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override; virtual void checkTypeRequirements(TypePointers const* _argumentTypes) override;
Token::Value getToken() const { return m_token; } Token::Value getToken() const { return m_token; }
/// @returns the non-parsed value of the literal /// @returns the non-parsed value of the literal

6
libsolidity/Compiler.cpp

@ -90,7 +90,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp
for (auto const& modifier: constructor->getModifiers()) for (auto const& modifier: constructor->getModifiers())
{ {
auto baseContract = dynamic_cast<ContractDefinition const*>( auto baseContract = dynamic_cast<ContractDefinition const*>(
modifier->getName()->getReferencedDeclaration()); &modifier->getName()->getReferencedDeclaration());
if (baseContract) if (baseContract)
if (m_baseArguments.count(baseContract->getConstructor()) == 0) if (m_baseArguments.count(baseContract->getConstructor()) == 0)
m_baseArguments[baseContract->getConstructor()] = &modifier->getArguments(); m_baseArguments[baseContract->getConstructor()] = &modifier->getArguments();
@ -99,7 +99,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp
for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts()) for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts())
{ {
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>( ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
base->getName()->getReferencedDeclaration()); &base->getName()->getReferencedDeclaration());
solAssert(baseContract, ""); solAssert(baseContract, "");
if (m_baseArguments.count(baseContract->getConstructor()) == 0) if (m_baseArguments.count(baseContract->getConstructor()) == 0)
@ -542,7 +542,7 @@ void Compiler::appendModifierOrFunctionCode()
ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth];
// constructor call should be excluded // constructor call should be excluded
if (dynamic_cast<ContractDefinition const*>(modifierInvocation->getName()->getReferencedDeclaration())) if (dynamic_cast<ContractDefinition const*>(&modifierInvocation->getName()->getReferencedDeclaration()))
{ {
++m_modifierDepth; ++m_modifierDepth;
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();

38
libsolidity/CompilerContext.cpp

@ -102,23 +102,13 @@ eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _dec
eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function)
{ {
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy) return getVirtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin());
for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions())
if (!function->isConstructor() && function->getName() == _function.getName())
return getFunctionEntryLabel(*function);
solAssert(false, "Virtual function " + _function.getName() + " not found.");
return m_asm.newTag(); // not reached
} }
eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base) eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base)
{ {
auto it = getSuperContract(_base); solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (; it != m_inheritanceHierarchy.end(); ++it) return getVirtualFunctionEntryLabel(_function, getSuperContract(_base));
for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions())
if (!function->isConstructor() && function->getName() == _name)
return getFunctionEntryLabel(*function);
solAssert(false, "Super function " + _name + " not found.");
return m_asm.newTag(); // not reached
} }
FunctionDefinition const* CompilerContext::getNextConstructor(ContractDefinition const& _contract) const FunctionDefinition const* CompilerContext::getNextConstructor(ContractDefinition const& _contract) const
@ -190,6 +180,26 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node)
updateSourceLocation(); updateSourceLocation();
} }
eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(
FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart
)
{
string name = _function.getName();
FunctionType functionType(_function);
auto it = _searchStart;
for (; it != m_inheritanceHierarchy.end(); ++it)
for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions())
if (
function->getName() == name &&
!function->isConstructor() &&
FunctionType(*function).hasEqualArgumentTypes(functionType)
)
return getFunctionEntryLabel(*function);
solAssert(false, "Super function " + name + " not found.");
return m_asm.newTag(); // not reached
}
vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const
{ {
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");

11
libsolidity/CompilerContext.h

@ -63,9 +63,9 @@ public:
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
/// @returns the entry label of the given function and takes overrides into account. /// @returns the entry label of the given function and takes overrides into account.
eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function); eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function);
/// @returns the entry label of function with the given name from the most derived class just /// @returns the entry label of a function that overrides the given declaration from the most derived class just
/// above _base in the current inheritance hierarchy. /// above _base in the current inheritance hierarchy.
eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base); eth::AssemblyItem getSuperFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base);
FunctionDefinition const* getNextConstructor(ContractDefinition const& _contract) const; FunctionDefinition const* getNextConstructor(ContractDefinition const& _contract) const;
/// @returns the set of functions for which we still need to generate code /// @returns the set of functions for which we still need to generate code
@ -141,6 +141,13 @@ public:
}; };
private: private:
/// @returns the entry label of the given function - searches the inheritance hierarchy
/// startig from the given point towards the base.
eth::AssemblyItem getVirtualFunctionEntryLabel(
FunctionDefinition const& _function,
std::vector<ContractDefinition const*>::const_iterator _searchStart
);
/// @returns an iterator to the contract directly above the given contract.
std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const; std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const;
/// Updates source location set in the assembly. /// Updates source location set in the assembly.
void updateSourceLocation(); void updateSourceLocation();

38
libsolidity/DeclarationContainer.cpp

@ -22,11 +22,11 @@
#include <libsolidity/DeclarationContainer.h> #include <libsolidity/DeclarationContainer.h>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
#include <libsolidity/Types.h>
namespace dev using namespace std;
{ using namespace dev;
namespace solidity using namespace dev::solidity;
{
bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update) bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update)
{ {
@ -34,17 +34,34 @@ bool DeclarationContainer::registerDeclaration(Declaration const& _declaration,
if (name.empty()) if (name.empty())
return true; return true;
if (!_update && (m_declarations.count(name) || m_invisibleDeclarations.count(name))) if (_update)
{
solAssert(!dynamic_cast<FunctionDefinition const*>(&_declaration), "Attempt to update function definition.");
m_declarations[name].clear();
m_invisibleDeclarations[name].clear();
}
else
{
if (dynamic_cast<FunctionDefinition const*>(&_declaration))
{
// check that all other declarations with the same name are functions
for (auto&& declaration: m_invisibleDeclarations[name] + m_declarations[name])
if (!dynamic_cast<FunctionDefinition const*>(declaration))
return false;
}
else if (m_declarations.count(name) > 0 || m_invisibleDeclarations.count(name) > 0)
return false; return false;
}
if (_invisible) if (_invisible)
m_invisibleDeclarations.insert(name); m_invisibleDeclarations[name].insert(&_declaration);
else else
m_declarations[name] = &_declaration; m_declarations[name].insert(&_declaration);
return true; return true;
} }
Declaration const* DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const set<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const
{ {
solAssert(!_name.empty(), "Attempt to resolve empty name."); solAssert(!_name.empty(), "Attempt to resolve empty name.");
auto result = m_declarations.find(_name); auto result = m_declarations.find(_name);
@ -52,8 +69,5 @@ Declaration const* DeclarationContainer::resolveName(ASTString const& _name, boo
return result->second; return result->second;
if (_recursive && m_enclosingContainer) if (_recursive && m_enclosingContainer)
return m_enclosingContainer->resolveName(_name, true); return m_enclosingContainer->resolveName(_name, true);
return nullptr; return set<Declaration const*>({});
}
}
} }

8
libsolidity/DeclarationContainer.h

@ -48,15 +48,15 @@ public:
/// @param _update if true, replaces a potential declaration that is already present /// @param _update if true, replaces a potential declaration that is already present
/// @returns false if the name was already declared. /// @returns false if the name was already declared.
bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false); bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false);
Declaration const* resolveName(ASTString const& _name, bool _recursive = false) const; std::set<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const;
Declaration const* getEnclosingDeclaration() const { return m_enclosingDeclaration; } Declaration const* getEnclosingDeclaration() const { return m_enclosingDeclaration; }
std::map<ASTString, Declaration const*> const& getDeclarations() const { return m_declarations; } std::map<ASTString, std::set<Declaration const*>> const& getDeclarations() const { return m_declarations; }
private: private:
Declaration const* m_enclosingDeclaration; Declaration const* m_enclosingDeclaration;
DeclarationContainer const* m_enclosingContainer; DeclarationContainer const* m_enclosingContainer;
std::map<ASTString, Declaration const*> m_declarations; std::map<ASTString, std::set<Declaration const*>> m_declarations;
std::set<ASTString> m_invisibleDeclarations; std::map<ASTString, std::set<Declaration const*>> m_invisibleDeclarations;
}; };
} }

37
libsolidity/ExpressionCompiler.cpp

@ -626,13 +626,25 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
bool alsoSearchInteger = false; bool alsoSearchInteger = false;
ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType()); ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType());
if (type.isSuper()) if (type.isSuper())
m_context << m_context.getSuperFunctionEntryLabel(member, type.getContractDefinition()).pushTag(); {
solAssert(!!_memberAccess.referencedDeclaration(), "Referenced declaration not resolved.");
m_context << m_context.getSuperFunctionEntryLabel(
dynamic_cast<FunctionDefinition const&>(*_memberAccess.referencedDeclaration()),
type.getContractDefinition()
).pushTag();
}
else else
{ {
// ordinary contract type // ordinary contract type
u256 identifier = type.getFunctionIdentifier(member); if (Declaration const* declaration = _memberAccess.referencedDeclaration())
if (identifier != Invalid256)
{ {
u256 identifier;
if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
identifier = FunctionType(*variable).externalIdentifier();
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
identifier = FunctionType(*function).externalIdentifier();
else
solAssert(false, "Contract member is neither variable nor function.");
appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true); appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true);
m_context << identifier; m_context << identifier;
} }
@ -708,19 +720,16 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
case Type::Category::TypeType: case Type::Category::TypeType:
{ {
TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.getExpression().getType()); TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.getExpression().getType());
if (!type.getMembers().getMemberType(member)) solAssert(
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to " + type.toString())); !type.getMembers().membersByName(_memberAccess.getMemberName()).empty(),
"Invalid member access to " + type.toString()
);
if (auto contractType = dynamic_cast<ContractType const*>(type.getActualType().get())) if (dynamic_cast<ContractType const*>(type.getActualType().get()))
{
ContractDefinition const& contract = contractType->getContractDefinition();
for (ASTPointer<FunctionDefinition> const& function: contract.getDefinedFunctions())
if (function->getName() == member)
{ {
auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.referencedDeclaration());
solAssert(!!function, "Function not found in member access");
m_context << m_context.getFunctionEntryLabel(*function).pushTag(); m_context << m_context.getFunctionEntryLabel(*function).pushTag();
return;
}
solAssert(false, "Function not found in member access.");
} }
else if (auto enumType = dynamic_cast<EnumType const*>(type.getActualType().get())) else if (auto enumType = dynamic_cast<EnumType const*>(type.getActualType().get()))
m_context << enumType->getMemberValue(_memberAccess.getMemberName()); m_context << enumType->getMemberValue(_memberAccess.getMemberName());
@ -805,7 +814,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Identifier const& _identifier)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _identifier); CompilerContext::LocationSetter locationSetter(m_context, _identifier);
Declaration const* declaration = _identifier.getReferencedDeclaration(); Declaration const* declaration = &_identifier.getReferencedDeclaration();
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
{ {
switch (magicVar->getType()->getCategory()) switch (magicVar->getType()->getCategory())

8
libsolidity/LValue.cpp

@ -193,10 +193,10 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
// assign each member that is not a mapping // assign each member that is not a mapping
TypePointer const& memberType = member.second; TypePointer const& memberType = member.type;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first); pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.name);
m_context m_context
<< offsets.first << u256(offsets.second) << offsets.first << u256(offsets.second)
<< eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::DUP6 << eth::Instruction::DUP3
@ -247,10 +247,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
for (auto const& member: structType.getMembers()) for (auto const& member: structType.getMembers())
{ {
// zero each member that is not a mapping // zero each member that is not a mapping
TypePointer const& memberType = member.second; TypePointer const& memberType = member.type;
if (memberType->getCategory() == Type::Category::Mapping) if (memberType->getCategory() == Type::Category::Mapping)
continue; continue;
pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first); pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.name);
m_context m_context
<< offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD
<< u256(offsets.second); << u256(offsets.second);

115
libsolidity/NameAndTypeResolver.cpp

@ -31,7 +31,7 @@ namespace dev
namespace solidity namespace solidity
{ {
NameAndTypeResolver::NameAndTypeResolver(std::vector<Declaration const*> const& _globals) NameAndTypeResolver::NameAndTypeResolver(vector<Declaration const*> const& _globals)
{ {
for (Declaration const* declaration: _globals) for (Declaration const* declaration: _globals)
m_scopes[nullptr].registerDeclaration(*declaration); m_scopes[nullptr].registerDeclaration(*declaration);
@ -53,8 +53,9 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
m_currentScope = &m_scopes[&_contract]; m_currentScope = &m_scopes[&_contract];
linearizeBaseContracts(_contract); linearizeBaseContracts(_contract);
// we first import non-functions only as we do not yet know the argument types
for (ContractDefinition const* base: _contract.getLinearizedBaseContracts()) for (ContractDefinition const* base: _contract.getLinearizedBaseContracts())
importInheritedScope(*base); importInheritedScope(*base, false); // import non-functions
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs()) for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
ReferencesResolver resolver(*structDef, *this, &_contract, nullptr); ReferencesResolver resolver(*structDef, *this, &_contract, nullptr);
@ -64,6 +65,8 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
ReferencesResolver resolver(*variable, *this, &_contract, nullptr); ReferencesResolver resolver(*variable, *this, &_contract, nullptr);
for (ASTPointer<EventDefinition> const& event: _contract.getEvents()) for (ASTPointer<EventDefinition> const& event: _contract.getEvents())
ReferencesResolver resolver(*event, *this, &_contract, nullptr); ReferencesResolver resolver(*event, *this, &_contract, nullptr);
// these can contain code, only resolve parameters for now
for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers()) for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers())
{ {
m_currentScope = &m_scopes[modifier.get()]; m_currentScope = &m_scopes[modifier.get()];
@ -75,6 +78,28 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
ReferencesResolver referencesResolver(*function, *this, &_contract, ReferencesResolver referencesResolver(*function, *this, &_contract,
function->getReturnParameterList().get()); function->getReturnParameterList().get());
} }
m_currentScope = &m_scopes[&_contract];
for (ContractDefinition const* base: _contract.getLinearizedBaseContracts())
importInheritedScope(*base, true); // import functions
// now resolve references inside the code
for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers())
{
m_currentScope = &m_scopes[modifier.get()];
ReferencesResolver resolver(*modifier, *this, &_contract, nullptr, true);
}
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
{
m_currentScope = &m_scopes[function.get()];
ReferencesResolver referencesResolver(
*function,
*this,
&_contract,
function->getReturnParameterList().get(),
true
);
}
} }
void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract)
@ -90,29 +115,49 @@ void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope."); solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope.");
} }
Declaration const* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const set<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
{ {
auto iterator = m_scopes.find(_scope); auto iterator = m_scopes.find(_scope);
if (iterator == end(m_scopes)) if (iterator == end(m_scopes))
return nullptr; return set<Declaration const*>({});
return iterator->second.resolveName(_name, false); return iterator->second.resolveName(_name, false);
} }
Declaration const* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) set<Declaration const*> NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive)
{ {
return m_currentScope->resolveName(_name, _recursive); return m_currentScope->resolveName(_name, _recursive);
} }
void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base, bool _importFunctions)
{ {
auto iterator = m_scopes.find(&_base); auto iterator = m_scopes.find(&_base);
solAssert(iterator != end(m_scopes), ""); solAssert(iterator != end(m_scopes), "");
for (auto const& nameAndDeclaration: iterator->second.getDeclarations()) for (auto const& nameAndDeclaration: iterator->second.getDeclarations())
{ for (auto const& declaration: nameAndDeclaration.second)
Declaration const* declaration = nameAndDeclaration.second;
// Import if it was declared in the base, is not the constructor and is visible in derived classes // Import if it was declared in the base, is not the constructor and is visible in derived classes
if (declaration->getScope() == &_base && declaration->getName() != _base.getName() && if (declaration->getScope() == &_base && declaration->isVisibleInDerivedContracts())
declaration->isVisibleInDerivedContracts()) {
auto function = dynamic_cast<FunctionDefinition const*>(declaration);
if ((function == nullptr) == _importFunctions)
continue;
if (!!function)
{
FunctionType functionType(*function);
// only import if a function with the same arguments does not exist yet
bool functionWithEqualArgumentsFound = false;
for (auto knownDeclaration: m_currentScope->resolveName(nameAndDeclaration.first))
{
auto knownFunction = dynamic_cast<FunctionDefinition const*>(knownDeclaration);
if (!knownFunction)
continue; // this is not legal, but will be caught later
if (!FunctionType(*knownFunction).hasEqualArgumentTypes(functionType))
continue;
functionWithEqualArgumentsFound = true;
break;
}
if (functionWithEqualArgumentsFound)
continue;
}
m_currentScope->registerDeclaration(*declaration); m_currentScope->registerDeclaration(*declaration);
} }
} }
@ -125,8 +170,7 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: _contract.getBaseContracts()) for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: _contract.getBaseContracts())
{ {
ASTPointer<Identifier> baseName = baseSpecifier->getName(); ASTPointer<Identifier> baseName = baseSpecifier->getName();
ContractDefinition const* base = dynamic_cast<ContractDefinition const*>( auto base = dynamic_cast<ContractDefinition const*>(&baseName->getReferencedDeclaration());
baseName->getReferencedDeclaration());
if (!base) if (!base)
BOOST_THROW_EXCEPTION(baseName->createTypeError("Contract expected.")); BOOST_THROW_EXCEPTION(baseName->createTypeError("Contract expected."));
// "push_front" has the effect that bases mentioned later can overwrite members of bases // "push_front" has the effect that bases mentioned later can overwrite members of bases
@ -318,11 +362,19 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
enterNewSubScope(_declaration); enterNewSubScope(_declaration);
} }
ReferencesResolver::ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, ReferencesResolver::ReferencesResolver(
ASTNode& _root,
NameAndTypeResolver& _resolver,
ContractDefinition const* _currentContract, ContractDefinition const* _currentContract,
ParameterList const* _returnParameters, bool _allowLazyTypes): ParameterList const* _returnParameters,
m_resolver(_resolver), m_currentContract(_currentContract), bool _resolveInsideCode,
m_returnParameters(_returnParameters), m_allowLazyTypes(_allowLazyTypes) bool _allowLazyTypes
):
m_resolver(_resolver),
m_currentContract(_currentContract),
m_returnParameters(_returnParameters),
m_resolveInsideCode(_resolveInsideCode),
m_allowLazyTypes(_allowLazyTypes)
{ {
_root.accept(*this); _root.accept(*this);
} }
@ -361,24 +413,37 @@ bool ReferencesResolver::visit(Mapping&)
bool ReferencesResolver::visit(UserDefinedTypeName& _typeName) bool ReferencesResolver::visit(UserDefinedTypeName& _typeName)
{ {
Declaration const* declaration = m_resolver.getNameFromCurrentScope(_typeName.getName()); auto declarations = m_resolver.getNameFromCurrentScope(_typeName.getName());
if (!declaration) if (declarations.empty())
BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_typeName.getLocation()) BOOST_THROW_EXCEPTION(
<< errinfo_comment("Undeclared identifier.")); DeclarationError() <<
_typeName.setReferencedDeclaration(*declaration); errinfo_sourceLocation(_typeName.getLocation()) <<
errinfo_comment("Undeclared identifier.")
);
else if (declarations.size() > 1)
BOOST_THROW_EXCEPTION(
DeclarationError() <<
errinfo_sourceLocation(_typeName.getLocation()) <<
errinfo_comment("Duplicate identifier.")
);
else
_typeName.setReferencedDeclaration(**declarations.begin());
return false; return false;
} }
bool ReferencesResolver::visit(Identifier& _identifier) bool ReferencesResolver::visit(Identifier& _identifier)
{ {
Declaration const* declaration = m_resolver.getNameFromCurrentScope(_identifier.getName()); auto declarations = m_resolver.getNameFromCurrentScope(_identifier.getName());
if (!declaration) if (declarations.empty())
BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_identifier.getLocation()) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_identifier.getLocation())
<< errinfo_comment("Undeclared identifier.")); << errinfo_comment("Undeclared identifier."));
_identifier.setReferencedDeclaration(*declaration, m_currentContract); else if (declarations.size() == 1)
_identifier.setReferencedDeclaration(**declarations.begin(), m_currentContract);
else
// Duplicate declaration will be checked in checkTypeRequirements()
_identifier.setOverloadedDeclarations(declarations);
return false; return false;
} }
} }
} }

21
libsolidity/NameAndTypeResolver.h

@ -56,18 +56,19 @@ public:
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
/// the global scope is used (i.e. the one containing only the contract). /// the global scope is used (i.e. the one containing only the contract).
/// @returns a pointer to the declaration on success or nullptr on failure. /// @returns a pointer to the declaration on success or nullptr on failure.
Declaration const* resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const; std::set<Declaration const*> resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const;
/// Resolves a name in the "current" scope. Should only be called during the initial /// Resolves a name in the "current" scope. Should only be called during the initial
/// resolving phase. /// resolving phase.
Declaration const* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); std::set<Declaration const*> getNameFromCurrentScope(ASTString const& _name, bool _recursive = true);
private: private:
void reset(); void reset();
/// Imports all members declared directly in the given contract (i.e. does not import inherited /// Either imports all non-function members or all function members declared directly in the
/// members) into the current scope if they are not present already. /// given contract (i.e. does not import inherited members) into the current scope if they are
void importInheritedScope(ContractDefinition const& _base); ///not present already.
void importInheritedScope(ContractDefinition const& _base, bool _importFunctions);
/// Computes "C3-Linearization" of base contracts and stores it inside the contract. /// Computes "C3-Linearization" of base contracts and stores it inside the contract.
void linearizeBaseContracts(ContractDefinition& _contract) const; void linearizeBaseContracts(ContractDefinition& _contract) const;
@ -126,13 +127,18 @@ private:
class ReferencesResolver: private ASTVisitor class ReferencesResolver: private ASTVisitor
{ {
public: public:
ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, ReferencesResolver(
ASTNode& _root,
NameAndTypeResolver& _resolver,
ContractDefinition const* _currentContract, ContractDefinition const* _currentContract,
ParameterList const* _returnParameters, ParameterList const* _returnParameters,
bool _allowLazyTypes = true); bool _resolveInsideCode = false,
bool _allowLazyTypes = true
);
private: private:
virtual void endVisit(VariableDeclaration& _variable) override; virtual void endVisit(VariableDeclaration& _variable) override;
virtual bool visit(Block&) override { return m_resolveInsideCode; }
virtual bool visit(Identifier& _identifier) override; virtual bool visit(Identifier& _identifier) override;
virtual bool visit(UserDefinedTypeName& _typeName) override; virtual bool visit(UserDefinedTypeName& _typeName) override;
virtual bool visit(Mapping&) override; virtual bool visit(Mapping&) override;
@ -141,6 +147,7 @@ private:
NameAndTypeResolver& m_resolver; NameAndTypeResolver& m_resolver;
ContractDefinition const* m_currentContract; ContractDefinition const* m_currentContract;
ParameterList const* m_returnParameters; ParameterList const* m_returnParameters;
bool m_resolveInsideCode;
bool m_allowLazyTypes; bool m_allowLazyTypes;
}; };

117
libsolidity/Types.cpp

@ -25,6 +25,7 @@
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <libdevcore/CommonIO.h> #include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libdevcrypto/SHA3.h>
#include <libsolidity/Utils.h> #include <libsolidity/Utils.h>
#include <libsolidity/AST.h> #include <libsolidity/AST.h>
@ -92,13 +93,13 @@ std::pair<u256, unsigned> const* MemberList::getMemberStorageOffset(string const
{ {
TypePointers memberTypes; TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size()); memberTypes.reserve(m_memberTypes.size());
for (auto const& nameAndType: m_memberTypes) for (auto const& member: m_memberTypes)
memberTypes.push_back(nameAndType.second); memberTypes.push_back(member.type);
m_storageOffsets.reset(new StorageOffsets()); m_storageOffsets.reset(new StorageOffsets());
m_storageOffsets->computeOffsets(memberTypes); m_storageOffsets->computeOffsets(memberTypes);
} }
for (size_t index = 0; index < m_memberTypes.size(); ++index) for (size_t index = 0; index < m_memberTypes.size(); ++index)
if (m_memberTypes[index].first == _name) if (m_memberTypes[index].name == _name)
return m_storageOffsets->getOffset(index); return m_storageOffsets->getOffset(index);
return nullptr; return nullptr;
} }
@ -189,7 +190,7 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length
if (_length) if (_length)
{ {
if (!_length->getType()) if (!_length->getType())
_length->checkTypeRequirements(); _length->checkTypeRequirements(nullptr);
auto const* length = dynamic_cast<IntegerConstantType const*>(_length->getType().get()); auto const* length = dynamic_cast<IntegerConstantType const*>(_length->getType().get());
if (!length) if (!length)
BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length.")); BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length."));
@ -793,18 +794,46 @@ MemberList const& ContractType::getMembers() const
if (!m_members) if (!m_members)
{ {
// All address members and all interface functions // All address members and all interface functions
vector<pair<string, TypePointer>> members(IntegerType::AddressMemberList.begin(), MemberList::MemberMap members(
IntegerType::AddressMemberList.end()); IntegerType::AddressMemberList.begin(),
IntegerType::AddressMemberList.end()
);
if (m_super) if (m_super)
{ {
// add the most derived of all functions which are visible in derived contracts
for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts())
for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions()) for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions())
if (function->isVisibleInDerivedContracts()) {
members.push_back(make_pair(function->getName(), make_shared<FunctionType>(*function, true))); if (!function->isVisibleInDerivedContracts())
continue;
auto functionType = make_shared<FunctionType>(*function, true);
bool functionWithEqualArgumentsFound = false;
for (auto const& member: members)
{
if (member.name != function->getName())
continue;
auto memberType = dynamic_cast<FunctionType const*>(member.type.get());
solAssert(!!memberType, "Override changes type.");
if (!memberType->hasEqualArgumentTypes(*functionType))
continue;
functionWithEqualArgumentsFound = true;
break;
}
if (!functionWithEqualArgumentsFound)
members.push_back(MemberList::Member(
function->getName(),
functionType,
function.get()
));
}
} }
else else
for (auto const& it: m_contract.getInterfaceFunctions()) for (auto const& it: m_contract.getInterfaceFunctions())
members.push_back(make_pair(it.second->getDeclaration().getName(), it.second)); members.push_back(MemberList::Member(
it.second->getDeclaration().getName(),
it.second,
&it.second->getDeclaration()
));
m_members.reset(new MemberList(members)); m_members.reset(new MemberList(members));
} }
return *m_members; return *m_members;
@ -823,16 +852,6 @@ shared_ptr<FunctionType const> const& ContractType::getConstructorType() const
return m_constructorType; return m_constructorType;
} }
u256 ContractType::getFunctionIdentifier(string const& _functionName) const
{
auto interfaceFunctions = m_contract.getInterfaceFunctions();
for (auto const& it: m_contract.getInterfaceFunctions())
if (it.second->getDeclaration().getName() == _functionName)
return FixedHash<4>::Arith(it.first);
return Invalid256;
}
vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::getStateVariables() const vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::getStateVariables() const
{ {
vector<VariableDeclaration const*> variables; vector<VariableDeclaration const*> variables;
@ -873,8 +892,8 @@ u256 StructType::getStorageSize() const
bool StructType::canLiveOutsideStorage() const bool StructType::canLiveOutsideStorage() const
{ {
for (pair<string, TypePointer> const& member: getMembers()) for (auto const& member: getMembers())
if (!member.second->canLiveOutsideStorage()) if (!member.type->canLiveOutsideStorage())
return false; return false;
return true; return true;
} }
@ -891,7 +910,7 @@ MemberList const& StructType::getMembers() const
{ {
MemberList::MemberMap members; MemberList::MemberMap members;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers()) for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers())
members.push_back(make_pair(variable->getName(), variable->getType())); members.push_back(MemberList::Member(variable->getName(), variable->getType(), variable.get()));
m_members.reset(new MemberList(members)); m_members.reset(new MemberList(members));
} }
return *m_members; return *m_members;
@ -1007,11 +1026,11 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
vector<string> retParamNames; vector<string> retParamNames;
if (auto structType = dynamic_cast<StructType const*>(returnType.get())) if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
{ {
for (pair<string, TypePointer> const& member: structType->getMembers()) for (auto const& member: structType->getMembers())
if (member.second->getCategory() != Category::Mapping && member.second->getCategory() != Category::Array) if (member.type->getCategory() != Category::Mapping && member.type->getCategory() != Category::Array)
{ {
retParamNames.push_back(member.first); retParamNames.push_back(member.name);
retParams.push_back(member.second); retParams.push_back(member.type);
} }
} }
else else
@ -1141,12 +1160,12 @@ MemberList const& FunctionType::getMembers() const
case Location::Bare: case Location::Bare:
if (!m_members) if (!m_members)
{ {
vector<pair<string, TypePointer>> members{ MemberList::MemberMap members{
{"value", make_shared<FunctionType>(parseElementaryTypeVector({"uint"}), {"value", make_shared<FunctionType>(parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(false, true)}, TypePointers{copyAndSetGasOrValue(false, true)},
Location::SetValue, false, m_gasSet, m_valueSet)}}; Location::SetValue, false, m_gasSet, m_valueSet)}};
if (m_location != Location::Creation) if (m_location != Location::Creation)
members.push_back(make_pair("gas", make_shared<FunctionType>( members.push_back(MemberList::Member("gas", make_shared<FunctionType>(
parseElementaryTypeVector({"uint"}), parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(true, false)}, TypePointers{copyAndSetGasOrValue(true, false)},
Location::SetGas, false, m_gasSet, m_valueSet))); Location::SetGas, false, m_gasSet, m_valueSet)));
@ -1158,6 +1177,37 @@ MemberList const& FunctionType::getMembers() const
} }
} }
bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes) const
{
TypePointers const& parameterTypes = getParameterTypes();
if (takesArbitraryParameters())
return true;
else if (_argumentTypes.size() != parameterTypes.size())
return false;
else
return std::equal(
_argumentTypes.cbegin(),
_argumentTypes.cend(),
parameterTypes.cbegin(),
[](TypePointer const& argumentType, TypePointer const& parameterType)
{
return argumentType->isImplicitlyConvertibleTo(*parameterType);
}
);
}
bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
{
if (m_parameterTypes.size() != _other.m_parameterTypes.size())
return false;
return equal(
m_parameterTypes.cbegin(),
m_parameterTypes.cend(),
_other.m_parameterTypes.cbegin(),
[](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }
);
}
string FunctionType::externalSignature(std::string const& _name) const string FunctionType::externalSignature(std::string const& _name) const
{ {
std::string funcName = _name; std::string funcName = _name;
@ -1178,6 +1228,11 @@ string FunctionType::externalSignature(std::string const& _name) const
return ret + ")"; return ret + ")";
} }
u256 FunctionType::externalIdentifier() const
{
return FixedHash<4>::Arith(FixedHash<4>(dev::sha3(externalSignature())));
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
{ {
TypePointers pointers; TypePointers pointers;
@ -1261,7 +1316,7 @@ MemberList const& TypeType::getMembers() const
// We need to lazy-initialize it because of recursive references. // We need to lazy-initialize it because of recursive references.
if (!m_members) if (!m_members)
{ {
vector<pair<string, TypePointer>> members; MemberList::MemberMap members;
if (m_actualType->getCategory() == Category::Contract && m_currentContract != nullptr) if (m_actualType->getCategory() == Category::Contract && m_currentContract != nullptr)
{ {
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).getContractDefinition(); ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).getContractDefinition();
@ -1270,14 +1325,14 @@ MemberList const& TypeType::getMembers() const
// We are accessing the type of a base contract, so add all public and protected // We are accessing the type of a base contract, so add all public and protected
// members. Note that this does not add inherited functions on purpose. // members. Note that this does not add inherited functions on purpose.
for (Declaration const* decl: contract.getInheritableMembers()) for (Declaration const* decl: contract.getInheritableMembers())
members.push_back(make_pair(decl->getName(), decl->getType())); members.push_back(MemberList::Member(decl->getName(), decl->getType(), decl));
} }
else if (m_actualType->getCategory() == Category::Enum) else if (m_actualType->getCategory() == Category::Enum)
{ {
EnumDefinition const& enumDef = dynamic_cast<EnumType const&>(*m_actualType).getEnumDefinition(); EnumDefinition const& enumDef = dynamic_cast<EnumType const&>(*m_actualType).getEnumDefinition();
auto enumType = make_shared<EnumType>(enumDef); auto enumType = make_shared<EnumType>(enumDef);
for (ASTPointer<EnumValue> const& enumValue: enumDef.getMembers()) for (ASTPointer<EnumValue> const& enumValue: enumDef.getMembers())
members.push_back(make_pair(enumValue->getName(), enumType)); members.push_back(MemberList::Member(enumValue->getName(), enumType));
} }
m_members.reset(new MemberList(members)); m_members.reset(new MemberList(members));
} }

41
libsolidity/Types.h

@ -69,17 +69,43 @@ private:
class MemberList class MemberList
{ {
public: public:
using MemberMap = std::vector<std::pair<std::string, TypePointer>>; struct Member
{
Member(std::string const& _name, TypePointer const& _type, Declaration const* _declaration = nullptr):
name(_name),
type(_type),
declaration(_declaration)
{
}
std::string name;
TypePointer type;
Declaration const* declaration = nullptr;
};
using MemberMap = std::vector<Member>;
MemberList() {} MemberList() {}
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
MemberList& operator=(MemberList&& _other); MemberList& operator=(MemberList&& _other);
TypePointer getMemberType(std::string const& _name) const TypePointer getMemberType(std::string const& _name) const
{ {
TypePointer type;
for (auto const& it: m_memberTypes) for (auto const& it: m_memberTypes)
if (it.first == _name) if (it.name == _name)
return it.second; {
return TypePointer(); solAssert(!type, "Requested member type by non-unique name.");
type = it.type;
}
return type;
}
MemberMap membersByName(std::string const& _name) const
{
MemberMap members;
for (auto const& it: m_memberTypes)
if (it.name == _name)
members.push_back(it);
return members;
} }
/// @returns the offset of the given member in storage slots and bytes inside a slot or /// @returns the offset of the given member in storage slots and bytes inside a slot or
/// a nullptr if the member is not part of storage. /// a nullptr if the member is not part of storage.
@ -554,11 +580,18 @@ public:
virtual unsigned getSizeOnStack() const override; virtual unsigned getSizeOnStack() const override;
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
/// @returns true if this function can take the given argument types (possibly
/// after implicit conversion).
bool canTakeArguments(TypePointers const& _arguments) const;
bool hasEqualArgumentTypes(FunctionType const& _other) const;
Location const& getLocation() const { return m_location; } Location const& getLocation() const { return m_location; }
/// @returns the external signature of this function type given the function name /// @returns the external signature of this function type given the function name
/// If @a _name is not provided (empty string) then the @c m_declaration member of the /// If @a _name is not provided (empty string) then the @c m_declaration member of the
/// function type is used /// function type is used
std::string externalSignature(std::string const& _name = "") const; std::string externalSignature(std::string const& _name = "") const;
/// @returns the external identifier of this function (the hash of the signature).
u256 externalIdentifier() const;
Declaration const& getDeclaration() const Declaration const& getDeclaration() const
{ {
solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); solAssert(m_declaration, "Requested declaration from a FunctionType that has none");

4
mix/CodeModel.cpp

@ -398,8 +398,8 @@ SolidityType CodeModel::nodeType(dev::solidity::Type const* _type)
StructType const* s = dynamic_cast<StructType const*>(_type); StructType const* s = dynamic_cast<StructType const*>(_type);
for(auto const& structMember: s->getMembers()) for(auto const& structMember: s->getMembers())
{ {
auto slotAndOffset = s->getStorageOffsetsOfMember(structMember.first); auto slotAndOffset = s->getStorageOffsetsOfMember(structMember.name);
r.members.push_back(SolidityDeclaration { QString::fromStdString(structMember.first), nodeType(structMember.second.get()), slotAndOffset.first, slotAndOffset.second }); r.members.push_back(SolidityDeclaration { QString::fromStdString(structMember.name), nodeType(structMember.type.get()), slotAndOffset.first, slotAndOffset.second });
} }
} }
break; break;

109
test/libsolidity/SolidityEndToEndTest.cpp

@ -302,7 +302,6 @@ BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr)
BOOST_AUTO_TEST_CASE(calling_other_functions) BOOST_AUTO_TEST_CASE(calling_other_functions)
{ {
// note that the index of a function is its index in the sorted sequence of functions
char const* sourceCode = "contract collatz {\n" char const* sourceCode = "contract collatz {\n"
" function run(uint x) returns(uint y) {\n" " function run(uint x) returns(uint y) {\n"
" while ((y = x) > 1) {\n" " while ((y = x) > 1) {\n"
@ -1147,26 +1146,6 @@ BOOST_AUTO_TEST_CASE(now)
BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true));
} }
BOOST_AUTO_TEST_CASE(function_types)
{
char const* sourceCode = "contract test {\n"
" function a(bool selector) returns (uint b) {\n"
" var f = fun1;\n"
" if (selector) f = fun2;\n"
" return f(9);\n"
" }\n"
" function fun1(uint x) returns (uint b) {\n"
" return 11;\n"
" }\n"
" function fun2(uint x) returns (uint b) {\n"
" return 12;\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("a(bool)", false) == encodeArgs(11));
BOOST_CHECK(callContractFunction("a(bool)", true) == encodeArgs(12));
}
BOOST_AUTO_TEST_CASE(type_conversions_cleanup) BOOST_AUTO_TEST_CASE(type_conversions_cleanup)
{ {
// 22-byte integer converted to a contract (i.e. address, 20 bytes), converted to a 32 byte // 22-byte integer converted to a contract (i.e. address, 20 bytes), converted to a 32 byte
@ -3674,6 +3653,94 @@ BOOST_AUTO_TEST_CASE(packed_storage_structs_with_bytes0)
BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("test()") == encodeArgs(true));
} }
BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_first)
{
char const* sourceCode = R"(
contract test {
function f(uint k) returns(uint d) { return k; }
function f(uint a, uint b) returns(uint d) { return a + b; }
function g() returns(uint d) { return f(3); }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("g()") == encodeArgs(3));
}
BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_second)
{
char const* sourceCode = R"(
contract test {
function f(uint a, uint b) returns(uint d) { return a + b; }
function f(uint k) returns(uint d) { return k; }
function g() returns(uint d) { return f(3, 7); }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("g()") == encodeArgs(10));
}
BOOST_AUTO_TEST_CASE(overloaded_function_call_with_if_else)
{
char const* sourceCode = R"(
contract test {
function f(uint a, uint b) returns(uint d) { return a + b; }
function f(uint k) returns(uint d) { return k; }
function g(bool flag) returns(uint d) {
if (flag)
return f(3);
else
return f(3, 7);
}
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(3));
BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs(10));
}
BOOST_AUTO_TEST_CASE(derived_overload_base_function_direct)
{
char const* sourceCode = R"(
contract B { function f() returns(uint) { return 10; } }
contract C is B {
function f(uint i) returns(uint) { return 2 * i; }
function g() returns(uint) { return f(1); }
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("g()") == encodeArgs(2));
}
BOOST_AUTO_TEST_CASE(derived_overload_base_function_indirect)
{
char const* sourceCode = R"(
contract A { function f(uint a) returns(uint) { return 2 * a; } }
contract B { function f() returns(uint) { return 10; } }
contract C is A, B {
function g() returns(uint) { return f(); }
function h() returns(uint) { return f(1); }
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("g()") == encodeArgs(10));
BOOST_CHECK(callContractFunction("h()") == encodeArgs(2));
}
BOOST_AUTO_TEST_CASE(super_overload)
{
char const* sourceCode = R"(
contract A { function f(uint a) returns(uint) { return 2 * a; } }
contract B { function f(bool b) returns(uint) { return 10; } }
contract C is A, B {
function g() returns(uint) { return super.f(true); }
function h() returns(uint) { return super.f(1); }
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("g()") == encodeArgs(10));
BOOST_CHECK(callContractFunction("h()") == encodeArgs(2));
}
BOOST_AUTO_TEST_CASE(packed_storage_signed) BOOST_AUTO_TEST_CASE(packed_storage_signed)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(

4
test/libsolidity/SolidityExpressionCompiler.cpp

@ -78,7 +78,9 @@ Declaration const& resolveDeclaration(
// bracers are required, cause msvc couldnt handle this macro in for statement // bracers are required, cause msvc couldnt handle this macro in for statement
for (string const& namePart: _namespacedName) for (string const& namePart: _namespacedName)
{ {
BOOST_REQUIRE(declaration = _resolver.resolveName(namePart, declaration)); auto declarations = _resolver.resolveName(namePart, declaration);
BOOST_REQUIRE(!declarations.empty());
BOOST_REQUIRE(declaration = *declarations.begin());
} }
BOOST_REQUIRE(declaration); BOOST_REQUIRE(declaration);
return *declaration; return *declaration;

105
test/libsolidity/SolidityNameAndTypeResolution.cpp

@ -623,23 +623,23 @@ BOOST_AUTO_TEST_CASE(cyclic_inheritance)
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
} }
BOOST_AUTO_TEST_CASE(illegal_override_direct) BOOST_AUTO_TEST_CASE(legal_override_direct)
{ {
char const* text = R"( char const* text = R"(
contract B { function f() {} } contract B { function f() {} }
contract C is B { function f(uint i) {} } contract C is B { function f(uint i) {} }
)"; )";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
} }
BOOST_AUTO_TEST_CASE(illegal_override_indirect) BOOST_AUTO_TEST_CASE(legal_override_indirect)
{ {
char const* text = R"( char const* text = R"(
contract A { function f(uint a) {} } contract A { function f(uint a) {} }
contract B { function f() {} } contract B { function f() {} }
contract C is A, B { } contract C is A, B { }
)"; )";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
} }
BOOST_AUTO_TEST_CASE(illegal_override_visibility) BOOST_AUTO_TEST_CASE(illegal_override_visibility)
@ -1654,6 +1654,103 @@ BOOST_AUTO_TEST_CASE(bytes0_array)
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
} }
BOOST_AUTO_TEST_CASE(overloaded_function_cannot_resolve)
{
char const* sourceCode = R"(
contract test {
function f() returns(uint) { return 1; }
function f(uint a) returns(uint) { return a; }
function g() returns(uint) { return f(3, 5); }
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError);
}
BOOST_AUTO_TEST_CASE(ambiguous_overloaded_function)
{
// literal 1 can be both converted to uint and uint8, so the call is ambiguous.
char const* sourceCode = R"(
contract test {
function f(uint8 a) returns(uint) { return a; }
function f(uint a) returns(uint) { return 2*a; }
function g() returns(uint) { return f(1); }
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError);
}
BOOST_AUTO_TEST_CASE(assignment_of_nonoverloaded_function)
{
char const* sourceCode = R"(
contract test {
function f(uint a) returns(uint) { return 2 * a; }
function g() returns(uint) { var x = f; return x(7); }
}
)";
ETH_TEST_REQUIRE_NO_THROW(parseTextAndResolveNames(sourceCode), "Type resolving failed");
}
BOOST_AUTO_TEST_CASE(assignment_of_overloaded_function)
{
char const* sourceCode = R"(
contract test {
function f() returns(uint) { return 1; }
function f(uint a) returns(uint) { return 2 * a; }
function g() returns(uint) { var x = f; return x(7); }
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError);
}
BOOST_AUTO_TEST_CASE(external_types_clash)
{
char const* sourceCode = R"(
contract base {
enum a { X }
function f(a) { }
}
contract test is base {
function f(uint8 a) { }
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError);
}
BOOST_AUTO_TEST_CASE(override_changes_return_types)
{
char const* sourceCode = R"(
contract base {
function f(uint a) returns (uint) { }
}
contract test is base {
function f(uint a) returns (uint8) { }
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), TypeError);
}
BOOST_AUTO_TEST_CASE(multiple_constructors)
{
char const* sourceCode = R"(
contract test {
function test(uint a) { }
function test() {}
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), DeclarationError);
}
BOOST_AUTO_TEST_CASE(equal_overload)
{
char const* sourceCode = R"(
contract test {
function test(uint a) returns (uint b) { }
function test(uint a) external {}
}
)";
BOOST_CHECK_THROW(parseTextAndResolveNames(sourceCode), DeclarationError);
}
BOOST_AUTO_TEST_CASE(uninitialized_var) BOOST_AUTO_TEST_CASE(uninitialized_var)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(

25
test/libsolidity/SolidityParser.cpp

@ -134,6 +134,31 @@ BOOST_AUTO_TEST_CASE(missing_argument_in_named_args)
BOOST_CHECK_THROW(parseText(text), ParserError); BOOST_CHECK_THROW(parseText(text), ParserError);
} }
BOOST_AUTO_TEST_CASE(two_exact_functions)
{
char const* text = R"(
contract test {
function fun(uint a) returns(uint r) { return a; }
function fun(uint a) returns(uint r) { return a; }
}
)";
// with support of overloaded functions, during parsing,
// we can't determine whether they match exactly, however
// it will throw DeclarationError in following stage.
BOOST_CHECK_NO_THROW(parseText(text));
}
BOOST_AUTO_TEST_CASE(overloaded_functions)
{
char const* text = R"(
contract test {
function fun(uint a) returns(uint r) { return a; }
function fun(uint a, uint b) returns(uint r) { return a + b; }
}
)";
BOOST_CHECK_NO_THROW(parseText(text));
}
BOOST_AUTO_TEST_CASE(function_natspec_documentation) BOOST_AUTO_TEST_CASE(function_natspec_documentation)
{ {
ASTPointer<ContractDefinition> contract; ASTPointer<ContractDefinition> contract;

Loading…
Cancel
Save