mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
2886 lines
100 KiB
C++
2886 lines
100 KiB
C++
/*
|
|
This file is part of solidity.
|
|
|
|
solidity is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
solidity is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/**
|
|
* @author Christian <c@ethdev.com>
|
|
* @date 2015
|
|
* Type analyzer and checker.
|
|
*/
|
|
|
|
#include <libsolidity/analysis/TypeChecker.h>
|
|
#include <libsolidity/ast/AST.h>
|
|
#include <libsolidity/ast/ASTUtils.h>
|
|
#include <libsolidity/ast/TypeProvider.h>
|
|
|
|
#include <libyul/AsmAnalysis.h>
|
|
#include <libyul/AsmAnalysisInfo.h>
|
|
#include <libyul/AsmData.h>
|
|
|
|
#include <liblangutil/ErrorReporter.h>
|
|
|
|
#include <libsolutil/Algorithms.h>
|
|
#include <libsolutil/StringUtils.h>
|
|
|
|
#include <boost/algorithm/cxx11/all_of.hpp>
|
|
#include <boost/algorithm/string/join.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
using namespace solidity;
|
|
using namespace solidity::util;
|
|
using namespace solidity::langutil;
|
|
using namespace solidity::frontend;
|
|
|
|
bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall)
|
|
{
|
|
if (_isLibraryCall && _type.dataStoredIn(DataLocation::Storage))
|
|
return true;
|
|
if (_type.category() == Type::Category::Struct)
|
|
return false;
|
|
if (_type.category() == Type::Category::Array)
|
|
{
|
|
auto const& arrayType = dynamic_cast<ArrayType const&>(_type);
|
|
auto base = arrayType.baseType();
|
|
if (!typeSupportedByOldABIEncoder(*base, _isLibraryCall) || (base->category() == Type::Category::Array && base->isDynamicallySized()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
|
|
{
|
|
_contract.accept(*this);
|
|
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
|
}
|
|
|
|
TypePointer const& TypeChecker::type(Expression const& _expression) const
|
|
{
|
|
solAssert(!!_expression.annotation().type, "Type requested but not present.");
|
|
return _expression.annotation().type;
|
|
}
|
|
|
|
TypePointer const& TypeChecker::type(VariableDeclaration const& _variable) const
|
|
{
|
|
solAssert(!!_variable.annotation().type, "Type requested but not present.");
|
|
return _variable.annotation().type;
|
|
}
|
|
|
|
bool TypeChecker::visit(ContractDefinition const& _contract)
|
|
{
|
|
m_scope = &_contract;
|
|
|
|
ASTNode::listAccept(_contract.baseContracts(), *this);
|
|
|
|
for (auto const& n: _contract.subNodes())
|
|
n->accept(*this);
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
|
|
{
|
|
TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide()));
|
|
TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide()));
|
|
|
|
if (lhs.components().size() != rhs.components().size())
|
|
{
|
|
solAssert(m_errorReporter.hasErrors(), "");
|
|
return;
|
|
}
|
|
|
|
size_t storageToStorageCopies = 0;
|
|
size_t toStorageCopies = 0;
|
|
for (size_t i = 0; i < lhs.components().size(); ++i)
|
|
{
|
|
ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i]);
|
|
if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer())
|
|
continue;
|
|
toStorageCopies++;
|
|
if (rhs.components()[i]->dataStoredIn(DataLocation::Storage))
|
|
storageToStorageCopies++;
|
|
}
|
|
if (storageToStorageCopies >= 1 && toStorageCopies >= 2)
|
|
m_errorReporter.warning(
|
|
_assignment.location(),
|
|
"This assignment performs two copies to storage. Since storage copies do not first "
|
|
"copy to a temporary location, one of them might be overwritten before the second "
|
|
"is executed and thus may have unexpected effects. It is safer to perform the copies "
|
|
"separately or assign to storage pointers first."
|
|
);
|
|
}
|
|
|
|
TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2)
|
|
{
|
|
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
|
|
if (arguments.size() != 2)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"This function takes two arguments, but " +
|
|
toString(arguments.size()) +
|
|
" were provided."
|
|
);
|
|
|
|
if (arguments.size() >= 1)
|
|
if (
|
|
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) &&
|
|
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata())
|
|
)
|
|
m_errorReporter.typeError(
|
|
arguments.front()->location(),
|
|
"The first argument to" + quoteSpace("abi.decode") + "must be implicitly convertible to" +
|
|
quoteSpace("bytes memory") + "or " + quote("bytes calldata") + ", but is of type " +
|
|
quote(type(*arguments.front())->toString()) +
|
|
"."
|
|
);
|
|
|
|
if (arguments.size() < 2)
|
|
return {};
|
|
|
|
// The following is a rather syntactic restriction, but we check it here anyway:
|
|
// The second argument has to be a tuple expression containing type names.
|
|
TupleExpression const* tupleExpression = dynamic_cast<TupleExpression const*>(arguments[1].get());
|
|
if (!tupleExpression)
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments[1]->location(),
|
|
"The second argument to" + quoteSpace("abi.decode") + "has to be a tuple of types."
|
|
);
|
|
return {};
|
|
}
|
|
|
|
TypePointers components;
|
|
for (auto const& typeArgument: tupleExpression->components())
|
|
{
|
|
solAssert(typeArgument, "");
|
|
if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument)))
|
|
{
|
|
TypePointer actualType = argTypeType->actualType();
|
|
solAssert(actualType, "");
|
|
// We force memory because the parser currently cannot handle
|
|
// data locations. Furthermore, storage can be a little dangerous and
|
|
// calldata is not really implemented anyway.
|
|
actualType = TypeProvider::withLocationIfReference(DataLocation::Memory, actualType);
|
|
// We force address payable for address types.
|
|
if (actualType->category() == Type::Category::Address)
|
|
actualType = TypeProvider::payableAddress();
|
|
solAssert(
|
|
!actualType->dataStoredIn(DataLocation::CallData) &&
|
|
!actualType->dataStoredIn(DataLocation::Storage),
|
|
""
|
|
);
|
|
if (!actualType->fullEncodingType(false, _abiEncoderV2, false))
|
|
m_errorReporter.typeError(
|
|
typeArgument->location(),
|
|
"Decoding type" + quoteSpace(actualType->toString(false)) + "not supported."
|
|
);
|
|
components.push_back(actualType);
|
|
}
|
|
else
|
|
{
|
|
m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name.");
|
|
components.push_back(TypeProvider::emptyTuple());
|
|
}
|
|
}
|
|
return components;
|
|
}
|
|
|
|
TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall)
|
|
{
|
|
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
|
|
if (arguments.size() != 1)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"This function takes one argument, but " +
|
|
toString(arguments.size()) +
|
|
" were provided."
|
|
);
|
|
return {};
|
|
}
|
|
TypePointer firstArgType = type(*arguments.front());
|
|
if (
|
|
firstArgType->category() != Type::Category::TypeType ||
|
|
dynamic_cast<TypeType const&>(*firstArgType).actualType()->category() != TypeType::Category::Contract
|
|
)
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments.front()->location(),
|
|
"Invalid type for argument in function call. "
|
|
"Contract type required, but" +
|
|
quoteSpace(type(*arguments.front())->toString(true)) +
|
|
"provided."
|
|
);
|
|
return {};
|
|
}
|
|
|
|
return {TypeProvider::meta(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
|
|
}
|
|
|
|
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
|
{
|
|
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
|
|
solAssert(base, "Base contract not available.");
|
|
|
|
if (m_scope->isInterface())
|
|
m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit.");
|
|
|
|
if (base->isLibrary())
|
|
m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from.");
|
|
|
|
auto const& arguments = _inheritance.arguments();
|
|
TypePointers parameterTypes;
|
|
if (!base->isInterface())
|
|
// Interfaces do not have constructors, so there are zero parameters.
|
|
parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
|
|
|
|
if (arguments)
|
|
{
|
|
if (parameterTypes.size() != arguments->size())
|
|
{
|
|
m_errorReporter.typeError(
|
|
_inheritance.location(),
|
|
"Wrong argument count for constructor call: " +
|
|
toString(arguments->size()) +
|
|
" arguments given but expected " +
|
|
toString(parameterTypes.size()) +
|
|
". Remove parentheses if you do not want to provide arguments here."
|
|
);
|
|
}
|
|
for (size_t i = 0; i < std::min(arguments->size(), parameterTypes.size()); ++i)
|
|
{
|
|
BoolResult result = type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i]);
|
|
if (!result)
|
|
m_errorReporter.typeErrorConcatenateDescriptions(
|
|
(*arguments)[i]->location(),
|
|
"Invalid type for argument in constructor call. "
|
|
"Invalid implicit conversion from" +
|
|
quoteSpace(type(*(*arguments)[i])->toString()) +
|
|
"to" +
|
|
quoteSpace(parameterTypes[i]->toString()) +
|
|
"requested.",
|
|
result.message()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|
{
|
|
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
|
|
_usingFor.libraryName().annotation().referencedDeclaration
|
|
);
|
|
if (!library || !library->isLibrary())
|
|
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
|
|
}
|
|
|
|
bool TypeChecker::visit(StructDefinition const& _struct)
|
|
{
|
|
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
|
solAssert(type(*member)->canBeStored(), "Type cannot be used in struct.");
|
|
|
|
// Check recursion, fatal error if detected.
|
|
auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t _depth)
|
|
{
|
|
if (_depth >= 256)
|
|
m_errorReporter.fatalDeclarationError(_struct.location(), "Struct definition exhausting cyclic dependency validator.");
|
|
|
|
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
|
{
|
|
Type const* memberType = type(*member);
|
|
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
|
|
{
|
|
if (arrayType->isDynamicallySized())
|
|
break;
|
|
memberType = arrayType->baseType();
|
|
}
|
|
if (auto structType = dynamic_cast<StructType const*>(memberType))
|
|
if (_cycleDetector.run(structType->structDefinition()))
|
|
return;
|
|
}
|
|
};
|
|
if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
|
|
m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition.");
|
|
|
|
bool insideStruct = true;
|
|
swap(insideStruct, m_insideStruct);
|
|
ASTNode::listAccept(_struct.members(), *this);
|
|
m_insideStruct = insideStruct;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(FunctionDefinition const& _function)
|
|
{
|
|
bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library;
|
|
|
|
if (_function.markedVirtual())
|
|
{
|
|
if (_function.annotation().contract->isInterface())
|
|
m_errorReporter.warning(_function.location(), "Interface functions are implicitly " + quote("virtual") + ".");
|
|
if (_function.visibility() == Visibility::Private)
|
|
m_errorReporter.typeError(_function.location(), quote("virtual") + " and" + quoteSpace("private") + "cannot be used together.");
|
|
}
|
|
|
|
if (_function.isPayable())
|
|
{
|
|
if (isLibraryFunction)
|
|
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
|
|
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
|
|
m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
|
|
}
|
|
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
|
|
if (type(var)->category() == Type::Category::Mapping)
|
|
{
|
|
if (var.referenceLocation() != VariableDeclaration::Location::Storage)
|
|
{
|
|
if (!isLibraryFunction && _function.isPublic())
|
|
m_errorReporter.typeError(var.location(), "Mapping types can only have a data location of" + quoteSpace("storage") + "and thus only be parameters or return variables for internal or library functions.");
|
|
else
|
|
m_errorReporter.typeError(var.location(), "Mapping types can only have a data location of " + quote("storage") + ".");
|
|
}
|
|
else
|
|
{
|
|
solAssert(isLibraryFunction || !_function.isPublic(), "Mapping types for parameters or return variables can only be used in internal or library functions.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!type(var)->canLiveOutsideStorage() && _function.isPublic())
|
|
m_errorReporter.typeError(var.location(), "Type is required to live outside storage.");
|
|
if (_function.isPublic())
|
|
{
|
|
auto iType = type(var)->interfaceType(isLibraryFunction);
|
|
|
|
if (!iType)
|
|
{
|
|
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
|
m_errorReporter.fatalTypeError(var.location(), iType.message());
|
|
}
|
|
}
|
|
}
|
|
if (
|
|
_function.isPublic() &&
|
|
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
|
!typeSupportedByOldABIEncoder(*type(var), isLibraryFunction)
|
|
)
|
|
m_errorReporter.typeError(
|
|
var.location(),
|
|
"This type is only supported in ABIEncoderV2. "
|
|
"Use" + quoteSpace("pragma experimental ABIEncoderV2;") + "to enable the feature."
|
|
);
|
|
};
|
|
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
|
{
|
|
checkArgumentAndReturnParameter(*var);
|
|
var->accept(*this);
|
|
}
|
|
for (ASTPointer<VariableDeclaration> const& var: _function.returnParameters())
|
|
{
|
|
checkArgumentAndReturnParameter(*var);
|
|
var->accept(*this);
|
|
}
|
|
set<Declaration const*> modifiers;
|
|
for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers())
|
|
{
|
|
auto baseContracts = dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts;
|
|
// Delete first base which is just the main contract itself
|
|
baseContracts.erase(baseContracts.begin());
|
|
|
|
visitManually(
|
|
*modifier,
|
|
_function.isConstructor() ? baseContracts : vector<ContractDefinition const*>()
|
|
);
|
|
Declaration const* decl = &dereference(*modifier->name());
|
|
if (modifiers.count(decl))
|
|
{
|
|
if (dynamic_cast<ContractDefinition const*>(decl))
|
|
m_errorReporter.declarationError(modifier->location(), "Base constructor already provided.");
|
|
}
|
|
else
|
|
modifiers.insert(decl);
|
|
}
|
|
if (m_scope->isInterface())
|
|
{
|
|
if (_function.isImplemented())
|
|
m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
|
|
|
|
if (_function.visibility() != Visibility::External)
|
|
m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external.");
|
|
|
|
if (_function.isConstructor())
|
|
m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
|
|
}
|
|
else if (m_scope->contractKind() == ContractDefinition::ContractKind::Library)
|
|
if (_function.isConstructor())
|
|
m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in libraries.");
|
|
if (_function.isImplemented())
|
|
_function.body().accept(*this);
|
|
else if (_function.isConstructor())
|
|
m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared.");
|
|
else if (isLibraryFunction)
|
|
m_errorReporter.typeError(_function.location(), "Library functions must be implemented if declared.");
|
|
else if (!_function.virtualSemantics())
|
|
m_errorReporter.typeError(_function.location(), "Functions without implementation must be marked virtual.");
|
|
|
|
|
|
if (_function.isFallback())
|
|
typeCheckFallbackFunction(_function);
|
|
else if (_function.isReceive())
|
|
typeCheckReceiveFunction(_function);
|
|
else if (_function.isConstructor())
|
|
typeCheckConstructor(_function);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|
{
|
|
// Forbid any variable declarations inside interfaces unless they are part of
|
|
// * a function's input/output parameters,
|
|
// * or inside of a struct definition.
|
|
if (
|
|
m_scope->isInterface()
|
|
&& !_variable.isCallableOrCatchParameter()
|
|
&& !m_insideStruct
|
|
)
|
|
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
|
|
|
|
if (_variable.typeName())
|
|
_variable.typeName()->accept(*this);
|
|
|
|
// type is filled either by ReferencesResolver directly from the type name or by
|
|
// TypeChecker at the VariableDeclarationStatement level.
|
|
TypePointer varType = _variable.annotation().type;
|
|
solAssert(!!varType, "Variable type not provided.");
|
|
|
|
if (auto contractType = dynamic_cast<ContractType const*>(varType))
|
|
if (contractType->contractDefinition().isLibrary())
|
|
m_errorReporter.typeError(_variable.location(), "The type of a variable cannot be a library.");
|
|
if (_variable.value())
|
|
expectType(*_variable.value(), *varType);
|
|
if (_variable.isConstant())
|
|
{
|
|
if (!_variable.type()->isValueType())
|
|
{
|
|
bool allowed = false;
|
|
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type()))
|
|
allowed = arrayType->isByteArray();
|
|
if (!allowed)
|
|
m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
|
|
}
|
|
|
|
if (!_variable.value())
|
|
m_errorReporter.typeError(_variable.location(), "Uninitialized" + quoteSpace("constant") + "variable.");
|
|
else if (!_variable.value()->annotation().isPure)
|
|
m_errorReporter.typeError(
|
|
_variable.value()->location(),
|
|
"Initial value for constant variable has to be compile-time constant."
|
|
);
|
|
}
|
|
if (!_variable.isStateVariable())
|
|
{
|
|
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
|
|
if (!varType->canLiveOutsideStorage())
|
|
m_errorReporter.typeError(_variable.location(), "Type" + quoteSpace(varType->toString()) + "is only valid in storage.");
|
|
}
|
|
else if (_variable.visibility() >= Visibility::Public)
|
|
{
|
|
FunctionType getter(_variable);
|
|
if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
|
|
{
|
|
vector<string> unsupportedTypes;
|
|
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
|
if (!typeSupportedByOldABIEncoder(*param, false /* isLibrary */))
|
|
unsupportedTypes.emplace_back(param->toString());
|
|
if (!unsupportedTypes.empty())
|
|
m_errorReporter.typeError(_variable.location(),
|
|
"The following types are only supported for getters in ABIEncoderV2: " +
|
|
joinHumanReadable(unsupportedTypes) +
|
|
". Either remove" + quoteSpace("public") + "or use" + quoteSpace("pragma experimental ABIEncoderV2;") + "to enable the feature."
|
|
);
|
|
}
|
|
if (!getter.interfaceFunctionType())
|
|
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
|
|
}
|
|
|
|
switch (varType->category())
|
|
{
|
|
case Type::Category::Array:
|
|
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
|
|
if (
|
|
((arrayType->location() == DataLocation::Memory) ||
|
|
(arrayType->location() == DataLocation::CallData)) &&
|
|
!arrayType->validForCalldata()
|
|
)
|
|
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::visitManually(
|
|
ModifierInvocation const& _modifier,
|
|
vector<ContractDefinition const*> const& _bases
|
|
)
|
|
{
|
|
std::vector<ASTPointer<Expression>> const& arguments =
|
|
_modifier.arguments() ? *_modifier.arguments() : std::vector<ASTPointer<Expression>>();
|
|
for (ASTPointer<Expression> const& argument: arguments)
|
|
argument->accept(*this);
|
|
|
|
{
|
|
m_insideModifierInvocation = true;
|
|
ScopeGuard resetFlag{[&] () { m_insideModifierInvocation = false; }};
|
|
_modifier.name()->accept(*this);
|
|
}
|
|
|
|
auto const* declaration = &dereference(*_modifier.name());
|
|
vector<ASTPointer<VariableDeclaration>> emptyParameterList;
|
|
vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
|
|
if (auto modifierDecl = dynamic_cast<ModifierDefinition const*>(declaration))
|
|
parameters = &modifierDecl->parameters();
|
|
else
|
|
// check parameters for Base constructors
|
|
for (ContractDefinition const* base: _bases)
|
|
if (declaration == base)
|
|
{
|
|
if (auto referencedConstructor = base->constructor())
|
|
parameters = &referencedConstructor->parameters();
|
|
else
|
|
parameters = &emptyParameterList;
|
|
break;
|
|
}
|
|
if (!parameters)
|
|
{
|
|
m_errorReporter.typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class.");
|
|
return;
|
|
}
|
|
if (parameters->size() != arguments.size())
|
|
{
|
|
m_errorReporter.typeError(
|
|
_modifier.location(),
|
|
"Wrong argument count for modifier invocation: " +
|
|
toString(arguments.size()) +
|
|
" arguments given but expected " +
|
|
toString(parameters->size()) +
|
|
"."
|
|
);
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < arguments.size(); ++i)
|
|
{
|
|
BoolResult result = type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i]));
|
|
if (!result)
|
|
m_errorReporter.typeErrorConcatenateDescriptions(
|
|
arguments[i]->location(),
|
|
"Invalid type for argument in modifier invocation. "
|
|
"Invalid implicit conversion from" +
|
|
quoteSpace(type(*arguments[i])->toString()) +
|
|
"to" +
|
|
quoteSpace(type(*(*parameters)[i])->toString()) +
|
|
"requested.",
|
|
result.message()
|
|
);
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(EventDefinition const& _eventDef)
|
|
{
|
|
solAssert(_eventDef.visibility() > Visibility::Internal, "");
|
|
unsigned numIndexed = 0;
|
|
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
|
|
{
|
|
if (var->isIndexed())
|
|
numIndexed++;
|
|
if (!type(*var)->canLiveOutsideStorage())
|
|
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
|
if (!type(*var)->interfaceType(false))
|
|
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
|
if (
|
|
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
|
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
|
)
|
|
m_errorReporter.typeError(
|
|
var->location(),
|
|
"This type is only supported in ABIEncoderV2. "
|
|
"Use" + quoteSpace("pragma experimental ABIEncoderV2;") + "to enable the feature."
|
|
);
|
|
}
|
|
if (_eventDef.isAnonymous() && numIndexed > 4)
|
|
m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
|
|
else if (!_eventDef.isAnonymous() && numIndexed > 3)
|
|
m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
|
{
|
|
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
|
if (fun.kind() == FunctionType::Kind::External)
|
|
solAssert(fun.interfaceType(false), "External function type uses internal types.");
|
|
}
|
|
|
|
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|
{
|
|
// External references have already been resolved in a prior stage and stored in the annotation.
|
|
// We run the resolve step again regardless.
|
|
yul::ExternalIdentifierAccess::Resolver identifierAccess = [&](
|
|
yul::Identifier const& _identifier,
|
|
yul::IdentifierContext _context,
|
|
bool
|
|
)
|
|
{
|
|
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
|
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
|
return size_t(-1);
|
|
Declaration const* declaration = ref->second.declaration;
|
|
solAssert(!!declaration, "");
|
|
bool requiresStorage = ref->second.isSlot || ref->second.isOffset;
|
|
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
|
{
|
|
solAssert(var->type(), "Expected variable type!");
|
|
if (var->isConstant())
|
|
{
|
|
var = rootVariableDeclaration(*var);
|
|
|
|
if (!var->value())
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Constant has no value.");
|
|
return size_t(-1);
|
|
}
|
|
else if (!type(*var)->isValueType() || (
|
|
dynamic_cast<Literal const*>(var->value().get()) == nullptr &&
|
|
type(*var->value())->category() != Type::Category::RationalNumber
|
|
))
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Only direct number constants and references to such constants are supported by inline assembly.");
|
|
return size_t(-1);
|
|
}
|
|
else if (_context == yul::IdentifierContext::LValue)
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Constant variables cannot be assigned to.");
|
|
return size_t(-1);
|
|
}
|
|
else if (requiresStorage)
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "The suffixes" + quoteSpace("_offset") + "and" + quoteSpace("_slot") + "can only be used on non-constant storage variables.");
|
|
return size_t(-1);
|
|
}
|
|
}
|
|
|
|
if (requiresStorage)
|
|
{
|
|
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "The suffixes" + quoteSpace("_offset") + "and" + quoteSpace("_slot") + "can only be used on storage variables.");
|
|
return size_t(-1);
|
|
}
|
|
else if (_context != yul::IdentifierContext::RValue)
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to.");
|
|
return size_t(-1);
|
|
}
|
|
}
|
|
else if (!var->isConstant() && var->isStateVariable())
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the" + quoteSpace("_slot") + "and" + quoteSpace("_offset") + "suffixes.");
|
|
return size_t(-1);
|
|
}
|
|
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "You have to use the" + quoteSpace("_slot") + "or" + quoteSpace("_offset") + "suffix to access storage reference variables.");
|
|
return size_t(-1);
|
|
}
|
|
else if (var->type()->sizeOnStack() != 1)
|
|
{
|
|
if (var->type()->dataStoredIn(DataLocation::CallData))
|
|
m_errorReporter.typeError(_identifier.location, "Call data elements cannot be accessed directly. Copy to a local variable first or use" + quoteSpace("calldataload") + "or" + quoteSpace("calldatacopy") + "with manually determined offsets and sizes.");
|
|
else
|
|
m_errorReporter.typeError(_identifier.location, "Only types that use one stack slot are supported.");
|
|
return size_t(-1);
|
|
}
|
|
}
|
|
else if (requiresStorage)
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "The suffixes" + quoteSpace("_offset") + "and" + quoteSpace("_slot") + "can only be used on storage variables.");
|
|
return size_t(-1);
|
|
}
|
|
else if (_context == yul::IdentifierContext::LValue)
|
|
{
|
|
if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
|
return size_t(-1);
|
|
|
|
m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
|
|
return size_t(-1);
|
|
}
|
|
|
|
if (_context == yul::IdentifierContext::RValue)
|
|
{
|
|
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
|
|
if (dynamic_cast<FunctionDefinition const*>(declaration))
|
|
{
|
|
}
|
|
else if (dynamic_cast<VariableDeclaration const*>(declaration))
|
|
{
|
|
}
|
|
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
|
{
|
|
if (!contract->isLibrary())
|
|
{
|
|
m_errorReporter.typeError(_identifier.location, "Expected a library.");
|
|
return size_t(-1);
|
|
}
|
|
}
|
|
else
|
|
return size_t(-1);
|
|
}
|
|
ref->second.valueSize = 1;
|
|
return size_t(1);
|
|
};
|
|
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
|
|
_inlineAssembly.annotation().analysisInfo = make_shared<yul::AsmAnalysisInfo>();
|
|
yul::AsmAnalyzer analyzer(
|
|
*_inlineAssembly.annotation().analysisInfo,
|
|
m_errorReporter,
|
|
_inlineAssembly.dialect(),
|
|
identifierAccess
|
|
);
|
|
if (!analyzer.analyze(_inlineAssembly.operations()))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
|
{
|
|
expectType(_ifStatement.condition(), *TypeProvider::boolean());
|
|
_ifStatement.trueStatement().accept(*this);
|
|
if (_ifStatement.falseStatement())
|
|
_ifStatement.falseStatement()->accept(*this);
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(TryStatement const& _tryStatement)
|
|
{
|
|
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
|
|
if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_tryStatement.externalCall().location(),
|
|
"Try can only be used with external function calls and contract creation calls."
|
|
);
|
|
return;
|
|
}
|
|
|
|
FunctionType const& functionType = dynamic_cast<FunctionType const&>(*externalCall->expression().annotation().type);
|
|
if (
|
|
functionType.kind() != FunctionType::Kind::External &&
|
|
functionType.kind() != FunctionType::Kind::Creation &&
|
|
functionType.kind() != FunctionType::Kind::DelegateCall
|
|
)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_tryStatement.externalCall().location(),
|
|
"Try can only be used with external function calls and contract creation calls."
|
|
);
|
|
return;
|
|
}
|
|
|
|
externalCall->annotation().tryCall = true;
|
|
|
|
solAssert(_tryStatement.clauses().size() >= 2, "");
|
|
solAssert(_tryStatement.clauses().front(), "");
|
|
|
|
TryCatchClause const& successClause = *_tryStatement.clauses().front();
|
|
if (successClause.parameters())
|
|
{
|
|
TypePointers returnTypes =
|
|
m_evmVersion.supportsReturndata() ?
|
|
functionType.returnParameterTypes() :
|
|
functionType.returnParameterTypesWithoutDynamicTypes();
|
|
std::vector<ASTPointer<VariableDeclaration>> const& parameters =
|
|
successClause.parameters()->parameters();
|
|
if (returnTypes.size() != parameters.size())
|
|
m_errorReporter.typeError(
|
|
successClause.location(),
|
|
"Function returns " +
|
|
to_string(functionType.returnParameterTypes().size()) +
|
|
" values, but returns clause has " +
|
|
to_string(parameters.size()) +
|
|
" variables."
|
|
);
|
|
size_t len = min(returnTypes.size(), parameters.size());
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
solAssert(returnTypes[i], "");
|
|
if (parameters[i] && *parameters[i]->annotation().type != *returnTypes[i])
|
|
m_errorReporter.typeError(
|
|
parameters[i]->location(),
|
|
"Invalid type, expected" +
|
|
quoteSpace(returnTypes[i]->toString(false)) +
|
|
"but got " +
|
|
quote(parameters[i]->annotation().type->toString()) +
|
|
"."
|
|
);
|
|
}
|
|
}
|
|
|
|
TryCatchClause const* errorClause = nullptr;
|
|
TryCatchClause const* lowLevelClause = nullptr;
|
|
for (size_t i = 1; i < _tryStatement.clauses().size(); ++i)
|
|
{
|
|
TryCatchClause const& clause = *_tryStatement.clauses()[i];
|
|
if (clause.errorName() == "")
|
|
{
|
|
if (lowLevelClause)
|
|
m_errorReporter.typeError(
|
|
clause.location(),
|
|
SecondarySourceLocation{}.append("The first clause is here:", lowLevelClause->location()),
|
|
"This try statement already has a low-level catch clause."
|
|
);
|
|
lowLevelClause = &clause;
|
|
if (clause.parameters() && !clause.parameters()->parameters().empty())
|
|
{
|
|
if (
|
|
clause.parameters()->parameters().size() != 1 ||
|
|
*clause.parameters()->parameters().front()->type() != *TypeProvider::bytesMemory()
|
|
)
|
|
m_errorReporter.typeError(clause.location(), "Expected" + quoteSpace("catch (bytes memory ...) { ... }") + "or " + quote("catch { ... }") + ".");
|
|
if (!m_evmVersion.supportsReturndata())
|
|
m_errorReporter.typeError(
|
|
clause.location(),
|
|
"This catch clause type cannot be used on the selected EVM version (" +
|
|
m_evmVersion.name() +
|
|
"). You need at least a Byzantium-compatible EVM or use " + quote("catch { ... }") + "."
|
|
);
|
|
}
|
|
}
|
|
else if (clause.errorName() == "Error")
|
|
{
|
|
if (!m_evmVersion.supportsReturndata())
|
|
m_errorReporter.typeError(
|
|
clause.location(),
|
|
"This catch clause type cannot be used on the selected EVM version (" +
|
|
m_evmVersion.name() +
|
|
"). You need at least a Byzantium-compatible EVM or use " + quote("catch { ... }") + "."
|
|
);
|
|
|
|
if (errorClause)
|
|
m_errorReporter.typeError(
|
|
clause.location(),
|
|
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
|
|
"This try statement already has an" + quoteSpace("Error") + "catch clause."
|
|
);
|
|
errorClause = &clause;
|
|
if (
|
|
!clause.parameters() ||
|
|
clause.parameters()->parameters().size() != 1 ||
|
|
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
|
|
)
|
|
m_errorReporter.typeError(clause.location(), "Expected " + quote("catch Error(string memory ...) { ... }") + ".");
|
|
}
|
|
else
|
|
m_errorReporter.typeError(
|
|
clause.location(),
|
|
"Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`."
|
|
);
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(WhileStatement const& _whileStatement)
|
|
{
|
|
expectType(_whileStatement.condition(), *TypeProvider::boolean());
|
|
_whileStatement.body().accept(*this);
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(ForStatement const& _forStatement)
|
|
{
|
|
if (_forStatement.initializationExpression())
|
|
_forStatement.initializationExpression()->accept(*this);
|
|
if (_forStatement.condition())
|
|
expectType(*_forStatement.condition(), *TypeProvider::boolean());
|
|
if (_forStatement.loopExpression())
|
|
_forStatement.loopExpression()->accept(*this);
|
|
_forStatement.body().accept(*this);
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(Return const& _return)
|
|
{
|
|
ParameterList const* params = _return.annotation().functionReturnParameters;
|
|
if (!_return.expression())
|
|
{
|
|
if (params && !params->parameters().empty())
|
|
m_errorReporter.typeError(_return.location(), "Return arguments required.");
|
|
return;
|
|
}
|
|
if (!params)
|
|
{
|
|
m_errorReporter.typeError(_return.location(), "Return arguments not allowed.");
|
|
return;
|
|
}
|
|
TypePointers returnTypes;
|
|
for (auto const& var: params->parameters())
|
|
returnTypes.push_back(type(*var));
|
|
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression())))
|
|
{
|
|
if (tupleType->components().size() != params->parameters().size())
|
|
m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
|
|
else
|
|
{
|
|
BoolResult result = tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes));
|
|
if (!result)
|
|
m_errorReporter.typeErrorConcatenateDescriptions(
|
|
_return.expression()->location(),
|
|
"Return argument type" +
|
|
quoteSpace(type(*_return.expression())->toString()) +
|
|
"is not implicitly convertible to expected type " +
|
|
quote(TupleType(returnTypes).toString(false)) + ".",
|
|
result.message()
|
|
);
|
|
}
|
|
}
|
|
else if (params->parameters().size() != 1)
|
|
m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
|
|
else
|
|
{
|
|
TypePointer const& expected = type(*params->parameters().front());
|
|
BoolResult result = type(*_return.expression())->isImplicitlyConvertibleTo(*expected);
|
|
if (!result)
|
|
m_errorReporter.typeErrorConcatenateDescriptions(
|
|
_return.expression()->location(),
|
|
"Return argument type" +
|
|
quoteSpace(type(*_return.expression())->toString()) +
|
|
"is not implicitly convertible to expected type (type of first return variable) " +
|
|
quote(expected->toString()) + ".",
|
|
result.message()
|
|
);
|
|
}
|
|
}
|
|
|
|
void TypeChecker::endVisit(EmitStatement const& _emit)
|
|
{
|
|
if (
|
|
_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall ||
|
|
type(_emit.eventCall().expression())->category() != Type::Category::Function ||
|
|
dynamic_cast<FunctionType const&>(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event
|
|
)
|
|
m_errorReporter.typeError(_emit.eventCall().expression().location(), "Expression has to be an event invocation.");
|
|
m_insideEmitStatement = false;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/**
|
|
* @returns a suggested left-hand-side of a multi-variable declaration contairing
|
|
* the variable declarations given in @a _decls.
|
|
*/
|
|
string createTupleDecl(vector<ASTPointer<VariableDeclaration>> const& _decls)
|
|
{
|
|
vector<string> components;
|
|
for (ASTPointer<VariableDeclaration> const& decl: _decls)
|
|
if (decl)
|
|
{
|
|
solAssert(decl->annotation().type, "");
|
|
components.emplace_back(decl->annotation().type->toString(false) + " " + decl->name());
|
|
}
|
|
else
|
|
components.emplace_back();
|
|
|
|
if (_decls.size() == 1)
|
|
return components.front();
|
|
else
|
|
return "(" + boost::algorithm::join(components, ", ") + ")";
|
|
}
|
|
|
|
bool typeCanBeExpressed(vector<ASTPointer<VariableDeclaration>> const& decls)
|
|
{
|
|
for (ASTPointer<VariableDeclaration> const& decl: decls)
|
|
{
|
|
// skip empty tuples (they can be expressed of course)
|
|
if (!decl)
|
|
continue;
|
|
|
|
if (!decl->annotation().type)
|
|
return false;
|
|
|
|
if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type))
|
|
if (
|
|
functionType->kind() != FunctionType::Kind::Internal &&
|
|
functionType->kind() != FunctionType::Kind::External
|
|
)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
|
{
|
|
if (!_statement.initialValue())
|
|
{
|
|
// No initial value is only permitted for single variables with specified type.
|
|
if (_statement.declarations().size() != 1 || !_statement.declarations().front())
|
|
{
|
|
if (boost::algorithm::all_of_equal(_statement.declarations(), nullptr))
|
|
{
|
|
// The syntax checker has already generated an error for this case (empty LHS tuple).
|
|
solAssert(m_errorReporter.hasErrors(), "");
|
|
|
|
// It is okay to return here, as there are no named components on the
|
|
// left-hand-side that could cause any damage later.
|
|
return false;
|
|
}
|
|
else
|
|
// Bailing out *fatal* here, as those (untyped) vars may be used later, and diagnostics wouldn't be helpful then.
|
|
m_errorReporter.fatalTypeError(_statement.location(), "Use of the" + quoteSpace("var") + "keyword is disallowed.");
|
|
}
|
|
|
|
VariableDeclaration const& varDecl = *_statement.declarations().front();
|
|
if (!varDecl.annotation().type)
|
|
m_errorReporter.fatalTypeError(_statement.location(), "Use of the" + quoteSpace("var") + "keyword is disallowed.");
|
|
|
|
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl)))
|
|
{
|
|
if (ref->dataStoredIn(DataLocation::Storage))
|
|
{
|
|
string errorText{"Uninitialized storage pointer."};
|
|
solAssert(varDecl.referenceLocation() != VariableDeclaration::Location::Unspecified, "Expected a specified location at this point");
|
|
solAssert(m_scope, "");
|
|
m_errorReporter.declarationError(varDecl.location(), errorText);
|
|
}
|
|
}
|
|
else if (dynamic_cast<MappingType const*>(type(varDecl)))
|
|
m_errorReporter.typeError(
|
|
varDecl.location(),
|
|
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
|
|
);
|
|
varDecl.accept(*this);
|
|
return false;
|
|
}
|
|
|
|
// Here we have an initial value and might have to derive some types before we can visit
|
|
// the variable declaration(s).
|
|
|
|
_statement.initialValue()->accept(*this);
|
|
TypePointers valueTypes;
|
|
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_statement.initialValue())))
|
|
valueTypes = tupleType->components();
|
|
else
|
|
valueTypes = TypePointers{type(*_statement.initialValue())};
|
|
|
|
vector<ASTPointer<VariableDeclaration>> const& variables = _statement.declarations();
|
|
if (variables.empty())
|
|
// We already have an error for this in the SyntaxChecker.
|
|
solAssert(m_errorReporter.hasErrors(), "");
|
|
else if (valueTypes.size() != variables.size())
|
|
m_errorReporter.typeError(
|
|
_statement.location(),
|
|
"Different number of components on the left hand side (" +
|
|
toString(variables.size()) +
|
|
") than on the right hand side (" +
|
|
toString(valueTypes.size()) +
|
|
")."
|
|
);
|
|
|
|
bool autoTypeDeductionNeeded = false;
|
|
|
|
for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i)
|
|
{
|
|
if (!variables[i])
|
|
continue;
|
|
VariableDeclaration const& var = *variables[i];
|
|
solAssert(!var.value(), "Value has to be tied to statement.");
|
|
TypePointer const& valueComponentType = valueTypes[i];
|
|
solAssert(!!valueComponentType, "");
|
|
if (!var.annotation().type)
|
|
{
|
|
autoTypeDeductionNeeded = true;
|
|
|
|
// Infer type from value.
|
|
solAssert(!var.typeName(), "");
|
|
var.annotation().type = valueComponentType->mobileType();
|
|
if (!var.annotation().type)
|
|
{
|
|
if (valueComponentType->category() == Type::Category::RationalNumber)
|
|
m_errorReporter.fatalTypeError(
|
|
_statement.initialValue()->location(),
|
|
"Invalid rational" +
|
|
quoteSpace(valueComponentType->toString()) +
|
|
"(absolute value too large or division by zero)."
|
|
);
|
|
else
|
|
solAssert(false, "");
|
|
}
|
|
else if (*var.annotation().type == *TypeProvider::emptyTuple())
|
|
solAssert(false, "Cannot declare variable with void (empty tuple) type.");
|
|
else if (valueComponentType->category() == Type::Category::RationalNumber)
|
|
{
|
|
string typeName = var.annotation().type->toString(true);
|
|
string extension;
|
|
if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type))
|
|
{
|
|
unsigned numBits = type->numBits();
|
|
bool isSigned = type->isSigned();
|
|
solAssert(numBits > 0, "");
|
|
string minValue;
|
|
string maxValue;
|
|
if (isSigned)
|
|
{
|
|
numBits--;
|
|
minValue = "-" + bigint(bigint(1) << numBits).str();
|
|
}
|
|
else
|
|
minValue = "0";
|
|
maxValue = bigint((bigint(1) << numBits) - 1).str();
|
|
extension = ", which can hold values between " + minValue + " and " + maxValue;
|
|
}
|
|
else
|
|
solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type), "Unknown type.");
|
|
}
|
|
|
|
var.accept(*this);
|
|
}
|
|
else
|
|
{
|
|
var.accept(*this);
|
|
BoolResult result = valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type);
|
|
if (!result)
|
|
{
|
|
auto errorMsg = "Type" +
|
|
quoteSpace(valueComponentType->toString()) +
|
|
"is not implicitly convertible to expected type " +
|
|
quote(var.annotation().type->toString());
|
|
if (
|
|
valueComponentType->category() == Type::Category::RationalNumber &&
|
|
dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() &&
|
|
valueComponentType->mobileType()
|
|
)
|
|
{
|
|
if (var.annotation().type->operator==(*valueComponentType->mobileType()))
|
|
m_errorReporter.typeError(
|
|
_statement.location(),
|
|
errorMsg + ", but it can be explicitly converted."
|
|
);
|
|
else
|
|
m_errorReporter.typeError(
|
|
_statement.location(),
|
|
errorMsg +
|
|
". Try converting to type" +
|
|
quoteSpace(valueComponentType->mobileType()->toString()) +
|
|
"or use an explicit conversion."
|
|
);
|
|
}
|
|
else
|
|
m_errorReporter.typeErrorConcatenateDescriptions(
|
|
_statement.location(),
|
|
errorMsg + ".",
|
|
result.message()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valueTypes.size() != variables.size())
|
|
{
|
|
solAssert(m_errorReporter.hasErrors(), "Should have errors!");
|
|
for (auto const& var: variables)
|
|
if (var && !var->annotation().type)
|
|
BOOST_THROW_EXCEPTION(FatalError());
|
|
}
|
|
|
|
if (autoTypeDeductionNeeded)
|
|
{
|
|
if (!typeCanBeExpressed(variables))
|
|
m_errorReporter.syntaxError(
|
|
_statement.location(),
|
|
"Use of the" + quoteSpace("var") + "keyword is disallowed. "
|
|
"Type cannot be expressed in syntax."
|
|
);
|
|
else
|
|
m_errorReporter.syntaxError(
|
|
_statement.location(),
|
|
"Use of the" + quoteSpace("var") + "keyword is disallowed. "
|
|
"Use explicit declaration" + quoteSpace(createTupleDecl(variables) + " = ...") + "instead."
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(ExpressionStatement const& _statement)
|
|
{
|
|
if (type(_statement.expression())->category() == Type::Category::RationalNumber)
|
|
if (!dynamic_cast<RationalNumberType const&>(*type(_statement.expression())).mobileType())
|
|
m_errorReporter.typeError(_statement.expression().location(), "Invalid rational number.");
|
|
|
|
if (auto call = dynamic_cast<FunctionCall const*>(&_statement.expression()))
|
|
{
|
|
if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression())))
|
|
{
|
|
auto kind = callType->kind();
|
|
if (
|
|
kind == FunctionType::Kind::BareCall ||
|
|
kind == FunctionType::Kind::BareCallCode ||
|
|
kind == FunctionType::Kind::BareDelegateCall ||
|
|
kind == FunctionType::Kind::BareStaticCall
|
|
)
|
|
m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used.");
|
|
else if (kind == FunctionType::Kind::Send)
|
|
m_errorReporter.warning(_statement.location(), "Failure condition of" + quoteSpace("send") + "ignored. Consider using" + quoteSpace("transfer") + "instead.");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(Conditional const& _conditional)
|
|
{
|
|
expectType(_conditional.condition(), *TypeProvider::boolean());
|
|
|
|
_conditional.trueExpression().accept(*this);
|
|
_conditional.falseExpression().accept(*this);
|
|
|
|
TypePointer trueType = type(_conditional.trueExpression())->mobileType();
|
|
TypePointer falseType = type(_conditional.falseExpression())->mobileType();
|
|
|
|
TypePointer commonType = nullptr;
|
|
|
|
if (!trueType)
|
|
m_errorReporter.typeError(_conditional.trueExpression().location(), "Invalid mobile type in true expression.");
|
|
else
|
|
commonType = trueType;
|
|
|
|
if (!falseType)
|
|
m_errorReporter.typeError(_conditional.falseExpression().location(), "Invalid mobile type in false expression.");
|
|
else
|
|
commonType = falseType;
|
|
|
|
if (!trueType && !falseType)
|
|
BOOST_THROW_EXCEPTION(FatalError());
|
|
else if (trueType && falseType)
|
|
{
|
|
commonType = Type::commonType(trueType, falseType);
|
|
|
|
if (!commonType)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_conditional.location(),
|
|
"True expression's type" +
|
|
quoteSpace(trueType->toString()) +
|
|
"doesn't match false expression's type " +
|
|
quote(falseType->toString()) +
|
|
"."
|
|
);
|
|
// even we can't find a common type, we have to set a type here,
|
|
// otherwise the upper statement will not be able to check the type.
|
|
commonType = trueType;
|
|
}
|
|
}
|
|
|
|
_conditional.annotation().type = commonType;
|
|
_conditional.annotation().isPure =
|
|
_conditional.condition().annotation().isPure &&
|
|
_conditional.trueExpression().annotation().isPure &&
|
|
_conditional.falseExpression().annotation().isPure;
|
|
|
|
if (_conditional.annotation().lValueRequested)
|
|
m_errorReporter.typeError(
|
|
_conditional.location(),
|
|
"Conditional expression as left value is not supported yet."
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& _expression)
|
|
{
|
|
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
|
|
{
|
|
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
|
|
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { &_type };
|
|
|
|
solAssert(
|
|
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
|
|
"Array sizes don't match or no errors generated."
|
|
);
|
|
|
|
for (size_t i = 0; i < min(tupleExpression->components().size(), types.size()); i++)
|
|
if (types[i])
|
|
{
|
|
solAssert(!!tupleExpression->components()[i], "");
|
|
checkExpressionAssignment(*types[i], *tupleExpression->components()[i]);
|
|
}
|
|
}
|
|
else if (_type.category() == Type::Category::Mapping)
|
|
{
|
|
bool isLocalOrReturn = false;
|
|
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expression))
|
|
if (auto const *variableDeclaration = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
|
|
if (variableDeclaration->isLocalOrReturn())
|
|
isLocalOrReturn = true;
|
|
if (!isLocalOrReturn)
|
|
m_errorReporter.typeError(_expression.location(), "Mappings cannot be assigned to.");
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(Assignment const& _assignment)
|
|
{
|
|
requireLValue(_assignment.leftHandSide());
|
|
TypePointer t = type(_assignment.leftHandSide());
|
|
_assignment.annotation().type = t;
|
|
|
|
checkExpressionAssignment(*t, _assignment.leftHandSide());
|
|
|
|
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t))
|
|
{
|
|
if (_assignment.assignmentOperator() != Token::Assign)
|
|
m_errorReporter.typeError(
|
|
_assignment.location(),
|
|
"Compound assignment is not allowed for tuple types."
|
|
);
|
|
// Sequenced assignments of tuples is not valid, make the result a "void" type.
|
|
_assignment.annotation().type = TypeProvider::emptyTuple();
|
|
|
|
expectType(_assignment.rightHandSide(), *tupleType);
|
|
|
|
// expectType does not cause fatal errors, so we have to check again here.
|
|
if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide())))
|
|
checkDoubleStorageAssignment(_assignment);
|
|
}
|
|
else if (_assignment.assignmentOperator() == Token::Assign)
|
|
expectType(_assignment.rightHandSide(), *t);
|
|
else
|
|
{
|
|
// compound assignment
|
|
_assignment.rightHandSide().accept(*this);
|
|
TypePointer resultType = t->binaryOperatorResult(
|
|
TokenTraits::AssignmentToBinaryOp(_assignment.assignmentOperator()),
|
|
type(_assignment.rightHandSide())
|
|
);
|
|
if (!resultType || *resultType != *t)
|
|
m_errorReporter.typeError(
|
|
_assignment.location(),
|
|
"Operator" +
|
|
quoteSpace(string(TokenTraits::toString(_assignment.assignmentOperator()))) +
|
|
"not compatible with types" +
|
|
quoteSpace(t->toString()) +
|
|
"and " +
|
|
quote(type(_assignment.rightHandSide())->toString()) +
|
|
"."
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(TupleExpression const& _tuple)
|
|
{
|
|
vector<ASTPointer<Expression>> const& components = _tuple.components();
|
|
TypePointers types;
|
|
|
|
if (_tuple.annotation().lValueRequested)
|
|
{
|
|
if (_tuple.isInlineArray())
|
|
m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
|
|
for (auto const& component: components)
|
|
if (component)
|
|
{
|
|
requireLValue(*component);
|
|
types.push_back(type(*component));
|
|
}
|
|
else
|
|
types.push_back(TypePointer());
|
|
if (components.size() == 1)
|
|
_tuple.annotation().type = type(*components[0]);
|
|
else
|
|
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
|
// If some of the components are not LValues, the error is reported above.
|
|
_tuple.annotation().isLValue = true;
|
|
}
|
|
else
|
|
{
|
|
bool isPure = true;
|
|
TypePointer inlineArrayType = nullptr;
|
|
|
|
for (size_t i = 0; i < components.size(); ++i)
|
|
{
|
|
if (!components[i])
|
|
m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
|
|
else if (components[i])
|
|
{
|
|
components[i]->accept(*this);
|
|
types.push_back(type(*components[i]));
|
|
|
|
if (types[i]->category() == Type::Category::Tuple)
|
|
if (dynamic_cast<TupleType const&>(*types[i]).components().empty())
|
|
{
|
|
if (_tuple.isInlineArray())
|
|
m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty.");
|
|
m_errorReporter.typeError(components[i]->location(), "Tuple component cannot be empty.");
|
|
}
|
|
|
|
// Note: code generation will visit each of the expression even if they are not assigned from.
|
|
if (types[i]->category() == Type::Category::RationalNumber && components.size() > 1)
|
|
if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType())
|
|
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid rational number.");
|
|
|
|
if (_tuple.isInlineArray())
|
|
{
|
|
solAssert(!!types[i], "Inline array cannot have empty components");
|
|
|
|
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
|
|
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type.");
|
|
|
|
if (i == 0)
|
|
inlineArrayType = types[i]->mobileType();
|
|
else if (inlineArrayType)
|
|
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
|
|
}
|
|
if (!components[i]->annotation().isPure)
|
|
isPure = false;
|
|
}
|
|
else
|
|
types.push_back(TypePointer());
|
|
}
|
|
_tuple.annotation().isPure = isPure;
|
|
if (_tuple.isInlineArray())
|
|
{
|
|
if (!inlineArrayType)
|
|
m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
|
|
else if (!inlineArrayType->canLiveOutsideStorage())
|
|
m_errorReporter.fatalTypeError(_tuple.location(), "Type" + quoteSpace(inlineArrayType->toString()) + "is only valid in storage.");
|
|
|
|
_tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size());
|
|
}
|
|
else
|
|
{
|
|
if (components.size() == 1)
|
|
_tuple.annotation().type = type(*components[0]);
|
|
else
|
|
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(UnaryOperation const& _operation)
|
|
{
|
|
// Inc, Dec, Add, Sub, Not, BitNot, Delete
|
|
Token op = _operation.getOperator();
|
|
bool const modifying = (op == Token::Inc || op == Token::Dec || op == Token::Delete);
|
|
if (modifying)
|
|
requireLValue(_operation.subExpression());
|
|
else
|
|
_operation.subExpression().accept(*this);
|
|
TypePointer const& subExprType = type(_operation.subExpression());
|
|
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
|
|
if (!t)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_operation.location(),
|
|
"Unary operator" +
|
|
quoteSpace(string(TokenTraits::toString(op))) +
|
|
"cannot be applied to type " +
|
|
quote(subExprType->toString()) +
|
|
"."
|
|
);
|
|
t = subExprType;
|
|
}
|
|
_operation.annotation().type = t;
|
|
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(BinaryOperation const& _operation)
|
|
{
|
|
TypePointer const& leftType = type(_operation.leftExpression());
|
|
TypePointer const& rightType = type(_operation.rightExpression());
|
|
TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
|
|
TypePointer commonType = result.get();
|
|
if (!commonType)
|
|
{
|
|
m_errorReporter.typeError(
|
|
_operation.location(),
|
|
"Operator" +
|
|
quoteSpace(string(TokenTraits::toString(_operation.getOperator()))) +
|
|
"not compatible with types" +
|
|
quoteSpace(leftType->toString()) +
|
|
"and " +
|
|
quote(rightType->toString()) +
|
|
"." +
|
|
(!result.message().empty() ? " " + result.message() : "")
|
|
);
|
|
commonType = leftType;
|
|
}
|
|
_operation.annotation().commonType = commonType;
|
|
_operation.annotation().type =
|
|
TokenTraits::isCompareOp(_operation.getOperator()) ?
|
|
TypeProvider::boolean() :
|
|
commonType;
|
|
_operation.annotation().isPure =
|
|
_operation.leftExpression().annotation().isPure &&
|
|
_operation.rightExpression().annotation().isPure;
|
|
|
|
if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
|
|
{
|
|
string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift";
|
|
if (
|
|
leftType->category() == Type::Category::RationalNumber &&
|
|
rightType->category() != Type::Category::RationalNumber
|
|
)
|
|
if ((
|
|
commonType->category() == Type::Category::Integer &&
|
|
dynamic_cast<IntegerType const&>(*commonType).numBits() != 256
|
|
) || (
|
|
commonType->category() == Type::Category::FixedPoint &&
|
|
dynamic_cast<FixedPointType const&>(*commonType).numBits() != 256
|
|
))
|
|
m_errorReporter.warning(
|
|
_operation.location(),
|
|
"Result of " + operation + " has type" + quoteSpace(commonType->toString()) + "and thus "
|
|
"might overflow. Silence this warning by converting the literal to the "
|
|
"expected type."
|
|
);
|
|
if (
|
|
commonType->category() == Type::Category::Integer &&
|
|
rightType->category() == Type::Category::Integer &&
|
|
dynamic_cast<IntegerType const&>(*commonType).numBits() <
|
|
dynamic_cast<IntegerType const&>(*rightType).numBits()
|
|
)
|
|
m_errorReporter.warning(
|
|
_operation.location(),
|
|
"The result type of the " +
|
|
operation +
|
|
"operation is equal to the type of the first operand (" +
|
|
quote(commonType->toString()) +
|
|
") ignoring the (larger) type of the second operand (" +
|
|
quote(rightType->toString()) +
|
|
") which might be unexpected. Silence this warning by either converting "
|
|
"the first or the second operand to the type of the other."
|
|
);
|
|
}
|
|
}
|
|
|
|
TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
|
FunctionCall const& _functionCall
|
|
)
|
|
{
|
|
solAssert(_functionCall.annotation().kind == FunctionCallKind::TypeConversion, "");
|
|
TypePointer const& expressionType = type(_functionCall.expression());
|
|
|
|
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
|
bool const isPositionalCall = _functionCall.names().empty();
|
|
|
|
TypePointer resultType = dynamic_cast<TypeType const&>(*expressionType).actualType();
|
|
if (arguments.size() != 1)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Exactly one argument expected for explicit type conversion."
|
|
);
|
|
else if (!isPositionalCall)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Type conversion cannot allow named arguments."
|
|
);
|
|
else
|
|
{
|
|
Type const* argType = type(*arguments.front());
|
|
// Resulting data location is memory unless we are converting from a reference
|
|
// type with a different data location.
|
|
// (data location cannot yet be specified for type conversions)
|
|
DataLocation dataLoc = DataLocation::Memory;
|
|
if (auto argRefType = dynamic_cast<ReferenceType const*>(argType))
|
|
dataLoc = argRefType->location();
|
|
if (auto type = dynamic_cast<ReferenceType const*>(resultType))
|
|
resultType = TypeProvider::withLocation(type, dataLoc, type->isPointer());
|
|
if (argType->isExplicitlyConvertibleTo(*resultType))
|
|
{
|
|
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
|
{
|
|
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType);
|
|
solAssert(!!resultArrayType, "");
|
|
solAssert(
|
|
argArrayType->location() != DataLocation::Storage ||
|
|
(
|
|
(
|
|
resultArrayType->isPointer() ||
|
|
(argArrayType->isByteArray() && resultArrayType->isByteArray())
|
|
) &&
|
|
resultArrayType->location() == DataLocation::Storage
|
|
),
|
|
"Invalid explicit conversion to storage type."
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (
|
|
resultType->category() == Type::Category::Contract &&
|
|
argType->category() == Type::Category::Address
|
|
)
|
|
{
|
|
solAssert(dynamic_cast<ContractType const*>(resultType)->isPayable(), "");
|
|
solAssert(
|
|
dynamic_cast<AddressType const*>(argType)->stateMutability() <
|
|
StateMutability::Payable,
|
|
""
|
|
);
|
|
SecondarySourceLocation ssl;
|
|
if (
|
|
auto const* identifier = dynamic_cast<Identifier const*>(arguments.front().get())
|
|
)
|
|
if (
|
|
auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(
|
|
identifier->annotation().referencedDeclaration
|
|
)
|
|
)
|
|
ssl.append(
|
|
"Did you mean to declare this variable as " + quote("address payable") + "?",
|
|
variableDeclaration->location()
|
|
);
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
ssl,
|
|
"Explicit type conversion not allowed from non-payable" + quoteSpace("address") + "to " +
|
|
quote(resultType->toString()) +
|
|
", which has a payable fallback function."
|
|
);
|
|
}
|
|
else if (
|
|
auto const* functionType = dynamic_cast<FunctionType const*>(argType);
|
|
functionType &&
|
|
functionType->kind() == FunctionType::Kind::External &&
|
|
resultType->category() == Type::Category::Address
|
|
)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Explicit type conversion not allowed from" + quoteSpace(argType->toString()) + "to " +
|
|
quote(resultType->toString()) +
|
|
". To obtain the address of the contract of the function, " +
|
|
"you can use the" + quoteSpace(".address") + "member of the function."
|
|
);
|
|
else
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Explicit type conversion not allowed from" +
|
|
quoteSpace(argType->toString()) +
|
|
"to " +
|
|
quote(resultType->toString()) +
|
|
"."
|
|
);
|
|
}
|
|
if (auto addressType = dynamic_cast<AddressType const*>(resultType))
|
|
if (addressType->stateMutability() != StateMutability::Payable)
|
|
{
|
|
bool payable = false;
|
|
if (argType->category() != Type::Category::Address)
|
|
payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
|
|
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address();
|
|
}
|
|
}
|
|
return resultType;
|
|
}
|
|
|
|
void TypeChecker::typeCheckFunctionCall(
|
|
FunctionCall const& _functionCall,
|
|
FunctionTypePointer _functionType
|
|
)
|
|
{
|
|
// Actual function call or struct constructor call.
|
|
|
|
solAssert(!!_functionType, "");
|
|
solAssert(_functionType->kind() != FunctionType::Kind::ABIDecode, "");
|
|
|
|
// Check for unsupported use of bare static call
|
|
if (
|
|
_functionType->kind() == FunctionType::Kind::BareStaticCall &&
|
|
!m_evmVersion.hasStaticCall()
|
|
)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
quote("staticcall") + " is not supported by the VM version."
|
|
);
|
|
|
|
// Check for event outside of emit statement
|
|
if (!m_insideEmitStatement && _functionType->kind() == FunctionType::Kind::Event)
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Event invocations have to be prefixed by " + quote("emit") + "."
|
|
);
|
|
|
|
// Perform standard function call type checking
|
|
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
|
|
}
|
|
|
|
void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
|
|
{
|
|
solAssert(_function.isFallback(), "");
|
|
|
|
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
|
|
m_errorReporter.typeError(_function.location(), "Libraries cannot have fallback functions.");
|
|
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
|
|
m_errorReporter.typeError(
|
|
_function.location(),
|
|
"Fallback function must be payable or non-payable, but is " +
|
|
quote(stateMutabilityToString(_function.stateMutability())) +
|
|
"."
|
|
);
|
|
if (_function.visibility() != Visibility::External)
|
|
m_errorReporter.typeError(_function.location(), "Fallback function must be defined as " + quote("external") + ".");
|
|
if (!_function.returnParameters().empty())
|
|
{
|
|
if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory())
|
|
m_errorReporter.typeError(_function.returnParameterList()->location(), "Fallback function can only have a single" + quoteSpace("bytes memory") + "return value.");
|
|
else
|
|
m_errorReporter.typeError(_function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented.");
|
|
}
|
|
if (!_function.parameters().empty())
|
|
m_errorReporter.typeError(_function.parameterList().location(), "Fallback function cannot take parameters.");
|
|
}
|
|
|
|
void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
|
|
{
|
|
solAssert(_function.isReceive(), "");
|
|
|
|
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
|
|
m_errorReporter.typeError(_function.location(), "Libraries cannot have receive ether functions.");
|
|
|
|
if (_function.stateMutability() != StateMutability::Payable)
|
|
m_errorReporter.typeError(
|
|
_function.location(),
|
|
"Receive ether function must be" + quoteSpace("payable") + "but is " +
|
|
quote(stateMutabilityToString(_function.stateMutability())) +
|
|
"."
|
|
);
|
|
if (_function.visibility() != Visibility::External)
|
|
m_errorReporter.typeError(_function.location(), "Receive ether function must be defined as " + quote("external") + ".");
|
|
if (!_function.returnParameters().empty())
|
|
m_errorReporter.typeError(_function.returnParameterList()->location(), "Receive ether function cannot return values.");
|
|
if (!_function.parameters().empty())
|
|
m_errorReporter.typeError(_function.parameterList().location(), "Receive ether function cannot take parameters.");
|
|
}
|
|
|
|
|
|
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
|
|
{
|
|
solAssert(_function.isConstructor(), "");
|
|
if (!_function.returnParameters().empty())
|
|
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty" + quoteSpace("returns") + "directive for constructor.");
|
|
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
|
|
m_errorReporter.typeError(
|
|
_function.location(),
|
|
"Constructor must be payable or non-payable, but is " +
|
|
quote(stateMutabilityToString(_function.stateMutability())) +
|
|
"."
|
|
);
|
|
if (_function.visibility() != Visibility::Public && _function.visibility() != Visibility::Internal)
|
|
m_errorReporter.typeError(_function.location(), "Constructor must be public or internal.");
|
|
}
|
|
|
|
void TypeChecker::typeCheckABIEncodeFunctions(
|
|
FunctionCall const& _functionCall,
|
|
FunctionTypePointer _functionType
|
|
)
|
|
{
|
|
solAssert(!!_functionType, "");
|
|
solAssert(
|
|
_functionType->kind() == FunctionType::Kind::ABIEncode ||
|
|
_functionType->kind() == FunctionType::Kind::ABIEncodePacked ||
|
|
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
|
|
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature,
|
|
"ABI function has unexpected FunctionType::Kind."
|
|
);
|
|
solAssert(_functionType->takesArbitraryParameters(), "ABI functions should be variadic.");
|
|
|
|
bool const isPacked = _functionType->kind() == FunctionType::Kind::ABIEncodePacked;
|
|
solAssert(_functionType->padArguments() != isPacked, "ABI function with unexpected padding");
|
|
|
|
bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(
|
|
ExperimentalFeature::ABIEncoderV2
|
|
);
|
|
|
|
// Check for named arguments
|
|
if (!_functionCall.names().empty())
|
|
{
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Named arguments cannot be used for functions that take arbitrary parameters."
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Perform standard function call type checking
|
|
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
|
|
|
|
// Check additional arguments for variadic functions
|
|
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
|
for (size_t i = 0; i < arguments.size(); ++i)
|
|
{
|
|
auto const& argType = type(*arguments[i]);
|
|
|
|
if (argType->category() == Type::Category::RationalNumber)
|
|
{
|
|
auto const& rationalType = dynamic_cast<RationalNumberType const&>(*argType);
|
|
if (rationalType.isFractional())
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"Fractional numbers cannot yet be encoded."
|
|
);
|
|
continue;
|
|
}
|
|
else if (!argType->mobileType())
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"Invalid rational number (too large or division by zero)."
|
|
);
|
|
continue;
|
|
}
|
|
else if (isPacked)
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"Cannot perform packed encoding for a literal."
|
|
" Please convert it to an explicit type first."
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (isPacked && !typeSupportedByOldABIEncoder(*argType, false /* isLibrary */))
|
|
{
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"Type not supported in packed mode."
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments()))
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"This type cannot be encoded."
|
|
);
|
|
}
|
|
}
|
|
|
|
void TypeChecker::typeCheckFunctionGeneralChecks(
|
|
FunctionCall const& _functionCall,
|
|
FunctionTypePointer _functionType
|
|
)
|
|
{
|
|
// Actual function call or struct constructor call.
|
|
|
|
solAssert(!!_functionType, "");
|
|
solAssert(_functionType->kind() != FunctionType::Kind::ABIDecode, "");
|
|
|
|
bool const isPositionalCall = _functionCall.names().empty();
|
|
bool const isVariadic = _functionType->takesArbitraryParameters();
|
|
|
|
solAssert(
|
|
!isVariadic || _functionCall.annotation().kind == FunctionCallKind::FunctionCall,
|
|
"Struct constructor calls cannot be variadic."
|
|
);
|
|
|
|
TypePointers const& parameterTypes = _functionType->parameterTypes();
|
|
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
|
vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names();
|
|
|
|
// Check number of passed in arguments
|
|
if (
|
|
arguments.size() < parameterTypes.size() ||
|
|
(!isVariadic && arguments.size() > parameterTypes.size())
|
|
)
|
|
{
|
|
bool const isStructConstructorCall =
|
|
_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
|
|
|
|
string msg;
|
|
|
|
if (isVariadic)
|
|
msg +=
|
|
"Need at least " +
|
|
toString(parameterTypes.size()) +
|
|
" arguments for " +
|
|
string(isStructConstructorCall ? "struct constructor" : "function call") +
|
|
", but provided only " +
|
|
toString(arguments.size()) +
|
|
".";
|
|
else
|
|
msg +=
|
|
"Wrong argument count for " +
|
|
string(isStructConstructorCall ? "struct constructor" : "function call") +
|
|
": " +
|
|
toString(arguments.size()) +
|
|
" arguments given but " +
|
|
string(isVariadic ? "need at least " : "expected ") +
|
|
toString(parameterTypes.size()) +
|
|
".";
|
|
|
|
// Extend error message in case we try to construct a struct with mapping member.
|
|
if (isStructConstructorCall)
|
|
{
|
|
/// For error message: Struct members that were removed during conversion to memory.
|
|
TypePointer const expressionType = type(_functionCall.expression());
|
|
TypeType const& t = dynamic_cast<TypeType const&>(*expressionType);
|
|
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
|
|
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
|
|
|
|
if (!membersRemovedForStructConstructor.empty())
|
|
{
|
|
msg += " Members that have to be skipped in memory:";
|
|
for (auto const& member: membersRemovedForStructConstructor)
|
|
msg += " " + quote(member);
|
|
msg += ".";
|
|
}
|
|
}
|
|
else if (
|
|
_functionType->kind() == FunctionType::Kind::BareCall ||
|
|
_functionType->kind() == FunctionType::Kind::BareCallCode ||
|
|
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
|
|
_functionType->kind() == FunctionType::Kind::BareStaticCall
|
|
)
|
|
{
|
|
if (arguments.empty())
|
|
msg +=
|
|
" This function requires a single bytes argument."
|
|
" Use \"\" as argument to provide empty calldata.";
|
|
else
|
|
msg +=
|
|
" This function requires a single bytes argument."
|
|
" If all your arguments are value types, you can use" +
|
|
quoteSpace("abi.encode(...)") + "to properly generate it.";
|
|
}
|
|
else if (
|
|
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
|
|
_functionType->kind() == FunctionType::Kind::SHA256 ||
|
|
_functionType->kind() == FunctionType::Kind::RIPEMD160
|
|
)
|
|
msg +=
|
|
" This function requires a single bytes argument."
|
|
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
|
|
" behaviour or" + quoteSpace("abi.encode(...)") + "to use ABI encoding.";
|
|
m_errorReporter.typeError(_functionCall.location(), msg);
|
|
return;
|
|
}
|
|
|
|
// Parameter to argument map
|
|
std::vector<Expression const*> paramArgMap(parameterTypes.size());
|
|
|
|
// Map parameters to arguments - trivially for positional calls, less so for named calls
|
|
if (isPositionalCall)
|
|
for (size_t i = 0; i < paramArgMap.size(); ++i)
|
|
paramArgMap[i] = arguments[i].get();
|
|
else
|
|
{
|
|
auto const& parameterNames = _functionType->parameterNames();
|
|
|
|
solAssert(
|
|
parameterNames.size() == argumentNames.size(),
|
|
"Unexpected parameter length mismatch!"
|
|
);
|
|
|
|
// Check for duplicate argument names
|
|
{
|
|
bool duplication = false;
|
|
for (size_t i = 0; i < argumentNames.size(); i++)
|
|
for (size_t j = i + 1; j < argumentNames.size(); j++)
|
|
if (*argumentNames[i] == *argumentNames[j])
|
|
{
|
|
duplication = true;
|
|
m_errorReporter.typeError(
|
|
arguments[i]->location(),
|
|
"Duplicate named argument " + quote(*argumentNames[i]) + "."
|
|
);
|
|
}
|
|
if (duplication)
|
|
return;
|
|
}
|
|
|
|
// map parameter names to argument names
|
|
{
|
|
bool not_all_mapped = false;
|
|
|
|
for (size_t i = 0; i < paramArgMap.size(); i++)
|
|
{
|
|
size_t j;
|
|
for (j = 0; j < argumentNames.size(); j++)
|
|
if (parameterNames[i] == *argumentNames[j])
|
|
break;
|
|
|
|
if (j < argumentNames.size())
|
|
paramArgMap[i] = arguments[j].get();
|
|
else
|
|
{
|
|
paramArgMap[i] = nullptr;
|
|
not_all_mapped = true;
|
|
m_errorReporter.typeError(
|
|
_functionCall.location(),
|
|
"Named argument" +
|
|
quoteSpace(*argumentNames[i]) +
|
|
"does not match function declaration."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (not_all_mapped)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for compatible types between arguments and parameters
|
|
for (size_t i = 0; i < paramArgMap.size(); ++i)
|
|
{
|
|
solAssert(!!paramArgMap[i], "unmapped parameter");
|
|
if (!type(*paramArgMap[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
|
|
{
|
|
string msg =
|
|
"Invalid type for argument in function call. "
|
|
"Invalid implicit conversion from" +
|
|
quoteSpace(type(*paramArgMap[i])->toString()) +
|
|
"to " +
|
|
quote(parameterTypes[i]->toString()) +
|
|
" requested.";
|
|
if (
|
|
_functionType->kind() == FunctionType::Kind::BareCall ||
|
|
_functionType->kind() == FunctionType::Kind::BareCallCode ||
|
|
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
|
|
_functionType->kind() == FunctionType::Kind::BareStaticCall
|
|
)
|
|
msg +=
|
|
" This function requires a single" + quoteSpace("bytes") + "argument."
|
|
" If all your arguments are value types, you can"
|
|
" use" + quoteSpace("abi.encode(...)") + "to properly generate it.";
|
|
else if (
|
|
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
|
|
_functionType->kind() == FunctionType::Kind::SHA256 ||
|
|
_functionType->kind() == FunctionType::Kind::RIPEMD160
|
|
)
|
|
msg +=
|
|
" This function requires a single" + quoteSpace("bytes") + "argument."
|
|
" Use" + quoteSpace("abi.encodePacked(...)") + "to obtain the pre-0.5.0"
|
|
" behaviour or" + quoteSpace("abi.encode(...)") + "to use ABI encoding.";
|
|
m_errorReporter.typeError(paramArgMap[i]->location(), msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|
{
|
|
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
|
bool argumentsArePure = true;
|
|
|
|
// We need to check arguments' type first as they will be needed for overload resolution.
|
|
for (ASTPointer<Expression const> const& argument: arguments)
|
|
{
|
|
argument->accept(*this);
|
|
if (!argument->annotation().isPure)
|
|
argumentsArePure = false;
|
|
}
|
|
|
|
// Store argument types - and names if given - for overload resolution
|
|
{
|
|
FuncCallArguments funcCallArgs;
|
|
|
|
funcCallArgs.names = _functionCall.names();
|
|
|
|
for (ASTPointer<Expression const> const& argument: arguments)
|
|
funcCallArgs.types.push_back(type(*argument));
|
|
|
|
_functionCall.expression().annotation().arguments = std::move(funcCallArgs);
|
|
}
|
|
|
|
_functionCall.expression().accept(*this);
|
|
|
|
Type const* expressionType = type(_functionCall.expression());
|
|
|
|
// Determine function call kind and function type for this FunctionCall node
|
|
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
|
|
FunctionTypePointer functionType = nullptr;
|
|
|
|
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
|
|
switch (expressionType->category())
|
|
{
|
|
case Type::Category::Function:
|
|
functionType = dynamic_cast<FunctionType const*>(expressionType);
|
|
funcCallAnno.kind = FunctionCallKind::FunctionCall;
|
|
|
|
// Purity for function calls also depends upon the callee and its FunctionType
|
|
funcCallAnno.isPure =
|
|
argumentsArePure &&
|
|
_functionCall.expression().annotation().isPure &&
|
|
functionType &&
|
|
functionType->isPure();
|
|
|
|
if (
|
|
functionType->kind() == FunctionType::Kind::ArrayPush ||
|
|
functionType->kind() == FunctionType::Kind::ByteArrayPush
|
|
)
|
|
funcCallAnno.isLValue = functionType->parameterTypes().empty();
|
|
|
|
break;
|
|
|
|
case Type::Category::TypeType:
|
|
{
|
|
// Determine type for type conversion or struct construction expressions
|
|
TypePointer const& actualType = dynamic_cast<TypeType const&>(*expressionType).actualType();
|
|
solAssert(!!actualType, "");
|
|
|
|
if (actualType->category() == Type::Category::Struct)
|
|
{
|
|
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
|
|
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
|
|
funcCallAnno.isPure = argumentsArePure;
|
|
}
|
|
else
|
|
{
|
|
funcCallAnno.kind = FunctionCallKind::TypeConversion;
|
|
funcCallAnno.isPure = argumentsArePure;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
m_errorReporter.typeError(_functionCall.location(), "Type is not callable.");
|
|
funcCallAnno.kind = FunctionCallKind::Unset;
|
|
funcCallAnno.isPure = argumentsArePure;
|
|
break;
|
|
}
|
|
|
|
// Determine return types
|
|
switch (funcCallAnno.kind)
|
|
{
|
|
case FunctionCallKind::TypeConversion:
|
|
funcCallAnno.type = typeCheckTypeConversionAndRetrieveReturnType(_functionCall);
|
|
break;
|
|
|
|
case FunctionCallKind::StructConstructorCall: // fall-through
|
|
case FunctionCallKind::FunctionCall:
|
|
{
|
|
TypePointers returnTypes;
|
|
|
|
switch (functionType->kind())
|
|
{
|
|
case FunctionType::Kind::ABIDecode:
|
|
{
|
|
bool const abiEncoderV2 =
|
|
m_scope->sourceUnit().annotation().experimentalFeatures.count(
|
|
ExperimentalFeature::ABIEncoderV2
|
|
);
|
|
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2);
|
|
break;
|
|
}
|
|
case FunctionType::Kind::ABIEncode:
|
|
case FunctionType::Kind::ABIEncodePacked:
|
|
case FunctionType::Kind::ABIEncodeWithSelector:
|
|
case FunctionType::Kind::ABIEncodeWithSignature:
|
|
{
|
|
typeCheckABIEncodeFunctions(_functionCall, functionType);
|
|
returnTypes = functionType->returnParameterTypes();
|
|
break;
|
|
}
|
|
case FunctionType::Kind::MetaType:
|
|
returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall);
|
|
break;
|
|
default:
|
|
{
|
|
typeCheckFunctionCall(_functionCall, functionType);
|
|
returnTypes = m_evmVersion.supportsReturndata() ?
|
|
functionType->returnParameterTypes() :
|
|
functionType->returnParameterTypesWithoutDynamicTypes();
|
|
break;
|
|
}
|
|
}
|
|
|
|
funcCallAnno.type = returnTypes.size() == 1 ?
|
|
move(returnTypes.front()) :
|
|
TypeProvider::tuple(move(returnTypes));
|
|
|
|
break;
|
|
}
|
|
|
|
case FunctionCallKind::Unset: // fall-through
|
|
default:
|
|
// for non-callables, ensure error reported and annotate node to void function
|
|
solAssert(m_errorReporter.hasErrors(), "");
|
|
funcCallAnno.kind = FunctionCallKind::FunctionCall;
|
|
funcCallAnno.type = TypeProvider::emptyTuple();
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(NewExpression const& _newExpression)
|
|
{
|
|
TypePointer type = _newExpression.typeName().annotation().type;
|
|
solAssert(!!type, "Type name not resolved.");
|
|
|
|
if (auto contractName = dynamic_cast<UserDefinedTypeName const*>(&_newExpression.typeName()))
|
|
{
|
|
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
|
|
|
|
if (!contract)
|
|
m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
|
|
if (contract->isInterface())
|
|
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
|
|
if (!contract->constructorIsPublic())
|
|
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
|
|
if (contract->abstract())
|
|
m_errorReporter.typeError(_newExpression.location(), "Cannot instantiate an abstract contract.");
|
|
|
|
solAssert(!!m_scope, "");
|
|
m_scope->annotation().contractDependencies.insert(contract);
|
|
solAssert(
|
|
!contract->annotation().linearizedBaseContracts.empty(),
|
|
"Linearized base contracts not yet available."
|
|
);
|
|
if (contractDependenciesAreCyclic(*m_scope))
|
|
m_errorReporter.typeError(
|
|
_newExpression.location(),
|
|
"Circular reference for contract creation (cannot create instance of derived or same contract)."
|
|
);
|
|
|
|
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
|
|
}
|
|
else if (type->category() == Type::Category::Array)
|
|
{
|
|
if (!type->canLiveOutsideStorage())
|
|
m_errorReporter.fatalTypeError(
|
|
_newExpression.typeName().location(),
|
|
"Type cannot live outside storage."
|
|
);
|
|
if (!type->isDynamicallySized())
|
|
m_errorReporter.typeError(
|
|
_newExpression.typeName().location(),
|
|
"Length has to be placed in parentheses after the array type for new expression."
|
|
);
|
|
type = TypeProvider::withLocationIfReference(DataLocation::Memory, type);
|
|
_newExpression.annotation().type = TypeProvider::function(
|
|
TypePointers{TypeProvider::uint256()},
|
|
TypePointers{type},
|
|
strings(1, ""),
|
|
strings(1, ""),
|
|
FunctionType::Kind::ObjectCreation,
|
|
false,
|
|
StateMutability::Pure
|
|
);
|
|
_newExpression.annotation().isPure = true;
|
|
}
|
|
else
|
|
m_errorReporter.fatalTypeError(_newExpression.location(), "Contract or array type expected.");
|
|
}
|
|
|
|
bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|
{
|
|
_memberAccess.expression().accept(*this);
|
|
TypePointer exprType = type(_memberAccess.expression());
|
|
ASTString const& memberName = _memberAccess.memberName();
|
|
|
|
// Retrieve the types of the arguments if this is used to call a function.
|
|
auto const& arguments = _memberAccess.annotation().arguments;
|
|
MemberList::MemberMap possibleMembers = exprType->members(m_scope).membersByName(memberName);
|
|
size_t const initialMemberCount = possibleMembers.size();
|
|
if (initialMemberCount > 1 && arguments)
|
|
{
|
|
// do overload resolution
|
|
for (auto it = possibleMembers.begin(); it != possibleMembers.end();)
|
|
if (
|
|
it->type->category() == Type::Category::Function &&
|
|
!dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*arguments, exprType)
|
|
)
|
|
it = possibleMembers.erase(it);
|
|
else
|
|
++it;
|
|
}
|
|
|
|
auto& annotation = _memberAccess.annotation();
|
|
|
|
if (possibleMembers.empty())
|
|
{
|
|
if (initialMemberCount == 0)
|
|
{
|
|
// Try to see if the member was removed because it is only available for storage types.
|
|
auto storageType = TypeProvider::withLocationIfReference(
|
|
DataLocation::Storage,
|
|
exprType
|
|
);
|
|
if (!storageType->members(m_scope).membersByName(memberName).empty())
|
|
m_errorReporter.fatalTypeError(
|
|
_memberAccess.location(),
|
|
"Member" + quoteSpace(memberName) + "is not available in" +
|
|
quoteSpace(exprType->toString()) +
|
|
"outside of storage."
|
|
);
|
|
}
|
|
string errorMsg = "Member" + quoteSpace(memberName) + "not found or not visible "
|
|
"after argument-dependent lookup in " + quote(exprType->toString()) + ".";
|
|
|
|
if (auto const& funType = dynamic_cast<FunctionType const*>(exprType))
|
|
{
|
|
auto const& t = funType->returnParameterTypes();
|
|
|
|
if (memberName == "value")
|
|
{
|
|
if (funType->kind() == FunctionType::Kind::Creation)
|
|
errorMsg = "Constructor for" + quoteSpace(t.front()->toString()) + "must be payable for member" + quoteSpace("value") + "to be available.";
|
|
else if (
|
|
funType->kind() == FunctionType::Kind::DelegateCall ||
|
|
funType->kind() == FunctionType::Kind::BareDelegateCall
|
|
)
|
|
errorMsg = "Member" + quoteSpace("value") + "is not allowed in delegated calls due to" + quoteSpace("msg.value") + "persisting.";
|
|
else
|
|
errorMsg = "Member" + quoteSpace("value") + "is only available for payable functions.";
|
|
}
|
|
else if (
|
|
t.size() == 1 &&
|
|
(t.front()->category() == Type::Category::Struct ||
|
|
t.front()->category() == Type::Category::Contract)
|
|
)
|
|
errorMsg += " Did you intend to call the function?";
|
|
}
|
|
else if (exprType->category() == Type::Category::Contract)
|
|
{
|
|
for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
|
|
if (addressMember.name == memberName)
|
|
{
|
|
Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
|
string varName = var ? var->name() : "...";
|
|
errorMsg += " Use" + quoteSpace("address(" + varName + ")." + memberName) + "to access this address member.";
|
|
break;
|
|
}
|
|
}
|
|
else if (auto addressType = dynamic_cast<AddressType const*>(exprType))
|
|
{
|
|
// Trigger error when using send or transfer with a non-payable fallback function.
|
|
if (memberName == "send" || memberName == "transfer")
|
|
{
|
|
solAssert(
|
|
addressType->stateMutability() != StateMutability::Payable,
|
|
"Expected address not-payable as members were not found"
|
|
);
|
|
|
|
errorMsg = quote("send") + " and" + quoteSpace("transfer") + "are only available for objects of type " + quote("address payable") + ", not " + quote(exprType->toString()) + ".";
|
|
}
|
|
}
|
|
|
|
m_errorReporter.fatalTypeError(
|
|
_memberAccess.location(),
|
|
errorMsg
|
|
);
|
|
}
|
|
else if (possibleMembers.size() > 1)
|
|
m_errorReporter.fatalTypeError(
|
|
_memberAccess.location(),
|
|
"Member" + quoteSpace(memberName) + "not unique "
|
|
"after argument-dependent lookup in " + quote(exprType->toString()) +
|
|
(memberName == "value" ? " - did you forget the" + quoteSpace("payable") + "modifier?" : ".")
|
|
);
|
|
|
|
annotation.referencedDeclaration = possibleMembers.front().declaration;
|
|
annotation.type = possibleMembers.front().type;
|
|
|
|
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
|
solAssert(
|
|
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
|
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
|
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
|
);
|
|
|
|
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
|
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
|
else if (exprType->category() == Type::Category::Array)
|
|
annotation.isLValue = false;
|
|
else if (exprType->category() == Type::Category::FixedBytes)
|
|
annotation.isLValue = false;
|
|
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
|
|
{
|
|
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
|
|
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
|
}
|
|
|
|
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
|
// although every subexpression is, so leaving this limited for now.
|
|
if (auto tt = dynamic_cast<TypeType const*>(exprType))
|
|
if (tt->actualType()->category() == Type::Category::Enum)
|
|
annotation.isPure = true;
|
|
if (auto magicType = dynamic_cast<MagicType const*>(exprType))
|
|
{
|
|
if (magicType->kind() == MagicType::Kind::ABI)
|
|
annotation.isPure = true;
|
|
else if (magicType->kind() == MagicType::Kind::MetaType && (
|
|
memberName == "creationCode" || memberName == "runtimeCode"
|
|
))
|
|
{
|
|
annotation.isPure = true;
|
|
m_scope->annotation().contractDependencies.insert(
|
|
&dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition()
|
|
);
|
|
if (contractDependenciesAreCyclic(*m_scope))
|
|
m_errorReporter.typeError(
|
|
_memberAccess.location(),
|
|
"Circular reference for contract code access."
|
|
);
|
|
}
|
|
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
|
|
annotation.isPure = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(IndexAccess const& _access)
|
|
{
|
|
_access.baseExpression().accept(*this);
|
|
TypePointer baseType = type(_access.baseExpression());
|
|
TypePointer resultType = nullptr;
|
|
bool isLValue = false;
|
|
bool isPure = _access.baseExpression().annotation().isPure;
|
|
Expression const* index = _access.indexExpression();
|
|
switch (baseType->category())
|
|
{
|
|
case Type::Category::ArraySlice:
|
|
{
|
|
auto const& arrayType = dynamic_cast<ArraySliceType const&>(*baseType).arrayType();
|
|
if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized())
|
|
m_errorReporter.typeError(_access.location(), "Index access is only implemented for slices of dynamic calldata arrays.");
|
|
baseType = &arrayType;
|
|
[[fallthrough]];
|
|
}
|
|
case Type::Category::Array:
|
|
{
|
|
ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType);
|
|
if (!index)
|
|
m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
|
|
else if (actualType.isString())
|
|
{
|
|
m_errorReporter.typeError(_access.location(), "Index access for string is not possible.");
|
|
index->accept(*this);
|
|
}
|
|
else
|
|
{
|
|
expectType(*index, *TypeProvider::uint256());
|
|
if (!m_errorReporter.hasErrors())
|
|
if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index)))
|
|
{
|
|
solAssert(!numberType->isFractional(), "");
|
|
if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr))
|
|
m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
|
|
}
|
|
}
|
|
resultType = actualType.baseType();
|
|
isLValue = actualType.location() != DataLocation::CallData;
|
|
break;
|
|
}
|
|
case Type::Category::Mapping:
|
|
{
|
|
MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType);
|
|
if (!index)
|
|
m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
|
|
else
|
|
expectType(*index, *actualType.keyType());
|
|
resultType = actualType.valueType();
|
|
isLValue = true;
|
|
break;
|
|
}
|
|
case Type::Category::TypeType:
|
|
{
|
|
TypeType const& typeType = dynamic_cast<TypeType const&>(*baseType);
|
|
if (dynamic_cast<ContractType const*>(typeType.actualType()))
|
|
m_errorReporter.typeError(_access.location(), "Index access for contracts or libraries is not possible.");
|
|
if (!index)
|
|
resultType = TypeProvider::typeType(TypeProvider::array(DataLocation::Memory, typeType.actualType()));
|
|
else
|
|
{
|
|
u256 length = 1;
|
|
if (expectType(*index, *TypeProvider::uint256()))
|
|
{
|
|
if (auto indexValue = dynamic_cast<RationalNumberType const*>(type(*index)))
|
|
length = indexValue->literalValue(nullptr);
|
|
else
|
|
m_errorReporter.fatalTypeError(index->location(), "Integer constant expected.");
|
|
}
|
|
else
|
|
solAssert(m_errorReporter.hasErrors(), "Expected errors as expectType returned false");
|
|
|
|
resultType = TypeProvider::typeType(TypeProvider::array(
|
|
DataLocation::Memory,
|
|
typeType.actualType(),
|
|
length
|
|
));
|
|
}
|
|
break;
|
|
}
|
|
case Type::Category::FixedBytes:
|
|
{
|
|
FixedBytesType const& bytesType = dynamic_cast<FixedBytesType const&>(*baseType);
|
|
if (!index)
|
|
m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
|
|
else
|
|
{
|
|
if (!expectType(*index, *TypeProvider::uint256()))
|
|
m_errorReporter.fatalTypeError(_access.location(), "Index expression cannot be represented as an unsigned integer.");
|
|
if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index)))
|
|
if (bytesType.numBytes() <= integerType->literalValue(nullptr))
|
|
m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
|
|
}
|
|
resultType = TypeProvider::fixedBytes(1);
|
|
isLValue = false; // @todo this heavily depends on how it is embedded
|
|
break;
|
|
}
|
|
default:
|
|
m_errorReporter.fatalTypeError(
|
|
_access.baseExpression().location(),
|
|
"Indexed expression has to be a type, mapping or array (is " + quote(baseType->toString()) + ")."
|
|
);
|
|
}
|
|
_access.annotation().type = resultType;
|
|
_access.annotation().isLValue = isLValue;
|
|
if (index && !index->annotation().isPure)
|
|
isPure = false;
|
|
_access.annotation().isPure = isPure;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(IndexRangeAccess const& _access)
|
|
{
|
|
_access.baseExpression().accept(*this);
|
|
|
|
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
|
|
bool isPure = _access.baseExpression().annotation().isPure;
|
|
|
|
if (Expression const* start = _access.startExpression())
|
|
{
|
|
expectType(*start, *TypeProvider::uint256());
|
|
if (!start->annotation().isPure)
|
|
isPure = false;
|
|
}
|
|
if (Expression const* end = _access.endExpression())
|
|
{
|
|
expectType(*end, *TypeProvider::uint256());
|
|
if (!end->annotation().isPure)
|
|
isPure = false;
|
|
}
|
|
|
|
TypePointer exprType = type(_access.baseExpression());
|
|
if (exprType->category() == Type::Category::TypeType)
|
|
{
|
|
m_errorReporter.typeError(_access.location(), "Types cannot be sliced.");
|
|
_access.annotation().type = exprType;
|
|
return false;
|
|
}
|
|
|
|
ArrayType const* arrayType = nullptr;
|
|
if (auto const* arraySlice = dynamic_cast<ArraySliceType const*>(exprType))
|
|
arrayType = &arraySlice->arrayType();
|
|
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
|
|
m_errorReporter.fatalTypeError(_access.location(), "Index range access is only possible for arrays and array slices.");
|
|
|
|
|
|
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
|
|
m_errorReporter.typeError(_access.location(), "Index range access is only supported for dynamic calldata arrays.");
|
|
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
|
|
_access.annotation().isLValue = isLValue;
|
|
_access.annotation().isPure = isPure;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TypeChecker::visit(Identifier const& _identifier)
|
|
{
|
|
IdentifierAnnotation& annotation = _identifier.annotation();
|
|
if (!annotation.referencedDeclaration)
|
|
{
|
|
if (!annotation.arguments)
|
|
{
|
|
// The identifier should be a public state variable shadowing other functions
|
|
vector<Declaration const*> candidates;
|
|
|
|
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
|
{
|
|
if (VariableDeclaration const* variableDeclaration = dynamic_cast<decltype(variableDeclaration)>(declaration))
|
|
candidates.push_back(declaration);
|
|
}
|
|
if (candidates.empty())
|
|
m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup.");
|
|
else if (candidates.size() == 1)
|
|
annotation.referencedDeclaration = candidates.front();
|
|
else
|
|
m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup.");
|
|
}
|
|
else if (annotation.overloadedDeclarations.empty())
|
|
m_errorReporter.fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
|
|
else if (annotation.overloadedDeclarations.size() == 1)
|
|
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
|
|
else
|
|
{
|
|
vector<Declaration const*> candidates;
|
|
|
|
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
|
{
|
|
FunctionTypePointer functionType = declaration->functionType(true);
|
|
solAssert(!!functionType, "Requested type not present.");
|
|
if (functionType->canTakeArguments(*annotation.arguments))
|
|
candidates.push_back(declaration);
|
|
}
|
|
if (candidates.size() == 1)
|
|
annotation.referencedDeclaration = candidates.front();
|
|
else
|
|
{
|
|
SecondarySourceLocation ssl;
|
|
|
|
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
|
if (declaration->location().isEmpty())
|
|
{
|
|
// Try to re-construct function definition
|
|
string description;
|
|
for (auto const& param: declaration->functionType(true)->parameterTypes())
|
|
description += (description.empty() ? "" : ", ") + param->toString(false);
|
|
description = "function " + _identifier.name() + "(" + description + ")";
|
|
|
|
ssl.append("Candidate: " + description, declaration->location());
|
|
}
|
|
else
|
|
ssl.append("Candidate:", declaration->location());
|
|
if (candidates.empty())
|
|
m_errorReporter.fatalTypeError(_identifier.location(), ssl, "No matching declaration found after argument-dependent lookup.");
|
|
else
|
|
m_errorReporter.fatalTypeError(_identifier.location(), ssl, "No unique declaration found after argument-dependent lookup.");
|
|
}
|
|
}
|
|
}
|
|
solAssert(
|
|
!!annotation.referencedDeclaration,
|
|
"Referenced declaration is null after overload resolution."
|
|
);
|
|
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
|
annotation.type = annotation.referencedDeclaration->type();
|
|
if (!annotation.type)
|
|
m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
|
|
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
|
|
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
|
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
|
{
|
|
if (dynamic_cast<FunctionType const*>(annotation.type))
|
|
annotation.isPure = true;
|
|
}
|
|
else if (dynamic_cast<TypeType const*>(annotation.type))
|
|
annotation.isPure = true;
|
|
|
|
|
|
// Check for deprecated function names.
|
|
// The check is done here for the case without an actual function call.
|
|
if (FunctionType const* fType = dynamic_cast<FunctionType const*>(_identifier.annotation().type))
|
|
{
|
|
if (_identifier.name() == "sha3" && fType->kind() == FunctionType::Kind::KECCAK256)
|
|
m_errorReporter.typeError(
|
|
_identifier.location(),
|
|
quote("sha3") + " has been deprecated in favour of " + quote("keccak256") + "."
|
|
);
|
|
else if (_identifier.name() == "suicide" && fType->kind() == FunctionType::Kind::Selfdestruct)
|
|
m_errorReporter.typeError(
|
|
_identifier.location(),
|
|
quote("suicide") + " has been deprecated in favour of " + quote("selfdestruct") + "."
|
|
);
|
|
}
|
|
|
|
if (!m_insideModifierInvocation)
|
|
if (ModifierType const* type = dynamic_cast<decltype(type)>(_identifier.annotation().type))
|
|
{
|
|
m_errorReporter.typeError(
|
|
_identifier.location(),
|
|
"Modifier can only be referenced in function headers."
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
|
|
{
|
|
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
|
|
_expr.annotation().isPure = true;
|
|
}
|
|
|
|
void TypeChecker::endVisit(Literal const& _literal)
|
|
{
|
|
if (_literal.looksLikeAddress())
|
|
{
|
|
// Assign type here if it even looks like an address. This prevents double errors for invalid addresses
|
|
_literal.annotation().type = TypeProvider::payableAddress();
|
|
|
|
string msg;
|
|
if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits
|
|
// looksLikeAddress enforces that it is a hex literal starting with "0x"
|
|
msg =
|
|
"This looks like an address but is not exactly 40 hex digits. It is " +
|
|
to_string(_literal.valueWithoutUnderscores().length() - 2) +
|
|
" hex digits.";
|
|
else if (!_literal.passesAddressChecksum())
|
|
{
|
|
msg = "This looks like an address but has an invalid checksum.";
|
|
if (!_literal.getChecksummedAddress().empty())
|
|
msg += " Correct checksummed address: " + quote(_literal.getChecksummedAddress()) + ".";
|
|
}
|
|
|
|
if (!msg.empty())
|
|
m_errorReporter.syntaxError(
|
|
_literal.location(),
|
|
msg +
|
|
" If this is not used as an address, please prepend " + quote("00") + ". " +
|
|
"Please see https://solidity.readthedocs.io/en/develop/types.html#address-literals for more information."
|
|
);
|
|
}
|
|
|
|
if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None)
|
|
m_errorReporter.fatalTypeError(
|
|
_literal.location(),
|
|
"Hexadecimal numbers cannot be used with unit denominations. "
|
|
"You can use an expression of the form" + quoteSpace("0x1234 * 1 day") + "instead."
|
|
);
|
|
|
|
if (_literal.subDenomination() == Literal::SubDenomination::Year)
|
|
m_errorReporter.typeError(
|
|
_literal.location(),
|
|
"Using" + quoteSpace("years") + "as a unit denomination is deprecated."
|
|
);
|
|
|
|
if (!_literal.annotation().type)
|
|
_literal.annotation().type = TypeProvider::forLiteral(_literal);
|
|
|
|
if (!_literal.annotation().type)
|
|
m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
|
|
|
|
_literal.annotation().isPure = true;
|
|
}
|
|
|
|
bool TypeChecker::contractDependenciesAreCyclic(
|
|
ContractDefinition const& _contract,
|
|
std::set<ContractDefinition const*> const& _seenContracts
|
|
) const
|
|
{
|
|
// Naive depth-first search that remembers nodes already seen.
|
|
if (_seenContracts.count(&_contract))
|
|
return true;
|
|
set<ContractDefinition const*> seen(_seenContracts);
|
|
seen.insert(&_contract);
|
|
for (auto const* c: _contract.annotation().contractDependencies)
|
|
if (contractDependenciesAreCyclic(*c, seen))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
Declaration const& TypeChecker::dereference(Identifier const& _identifier) const
|
|
{
|
|
solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored.");
|
|
return *_identifier.annotation().referencedDeclaration;
|
|
}
|
|
|
|
Declaration const& TypeChecker::dereference(UserDefinedTypeName const& _typeName) const
|
|
{
|
|
solAssert(!!_typeName.annotation().referencedDeclaration, "Declaration not stored.");
|
|
return *_typeName.annotation().referencedDeclaration;
|
|
}
|
|
|
|
bool TypeChecker::expectType(Expression const& _expression, Type const& _expectedType)
|
|
{
|
|
_expression.accept(*this);
|
|
if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType))
|
|
{
|
|
auto errorMsg = "Type" +
|
|
quoteSpace(type(_expression)->toString()) +
|
|
"is not implicitly convertible to expected type " +
|
|
quote(_expectedType.toString());
|
|
if (
|
|
type(_expression)->category() == Type::Category::RationalNumber &&
|
|
dynamic_cast<RationalNumberType const*>(type(_expression))->isFractional() &&
|
|
type(_expression)->mobileType()
|
|
)
|
|
{
|
|
if (_expectedType.operator==(*type(_expression)->mobileType()))
|
|
m_errorReporter.typeError(
|
|
_expression.location(),
|
|
errorMsg + ", but it can be explicitly converted."
|
|
);
|
|
else
|
|
m_errorReporter.typeError(
|
|
_expression.location(),
|
|
errorMsg +
|
|
". Try converting to type" +
|
|
quoteSpace(type(_expression)->mobileType()->toString()) +
|
|
"or use an explicit conversion."
|
|
);
|
|
}
|
|
else
|
|
m_errorReporter.typeError(_expression.location(), errorMsg + ".");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TypeChecker::requireLValue(Expression const& _expression)
|
|
{
|
|
_expression.annotation().lValueRequested = true;
|
|
_expression.accept(*this);
|
|
|
|
if (_expression.annotation().isLValue)
|
|
return;
|
|
|
|
string errMsg;
|
|
|
|
return m_errorReporter.typeError(_expression.location(), [&]() {
|
|
if (_expression.annotation().isConstant)
|
|
return "Cannot assign to a constant variable.";
|
|
|
|
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
|
|
{
|
|
if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes)
|
|
return "Single bytes in fixed bytes arrays cannot be modified.";
|
|
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(indexAccess->baseExpression())))
|
|
if (arrayType->dataStoredIn(DataLocation::CallData))
|
|
return "Calldata arrays are read-only.";
|
|
}
|
|
|
|
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
|
|
{
|
|
if (auto structType = dynamic_cast<StructType const*>(type(memberAccess->expression())))
|
|
{
|
|
if (structType->dataStoredIn(DataLocation::CallData))
|
|
return "Calldata structs are read-only.";
|
|
}
|
|
else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
|
|
if (memberAccess->memberName() == "length")
|
|
return (errMsg = string("Member") + quoteSpace("length") + string("is read-only and cannot be used to resize arrays.")).c_str();
|
|
}
|
|
|
|
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))
|
|
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
|
|
if (varDecl->isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(identifier->annotation().type))
|
|
return "External function arguments of reference type are read-only.";
|
|
|
|
return "Expression has to be an lvalue.";
|
|
}());
|
|
}
|