mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Extract typing.
This commit is contained in:
parent
accd8d7667
commit
3af43fd350
@ -12,6 +12,8 @@ set(sources
|
||||
analysis/ControlFlowGraph.h
|
||||
analysis/DeclarationContainer.cpp
|
||||
analysis/DeclarationContainer.h
|
||||
analysis/DeclarationTypeChecker.cpp
|
||||
analysis/DeclarationTypeChecker.h
|
||||
analysis/DocStringAnalyser.cpp
|
||||
analysis/DocStringAnalyser.h
|
||||
analysis/ImmutableValidator.cpp
|
||||
|
299
libsolidity/analysis/DeclarationTypeChecker.cpp
Normal file
299
libsolidity/analysis/DeclarationTypeChecker.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
|
||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
bool DeclarationTypeChecker::visit(ElementaryTypeName const& _typeName)
|
||||
{
|
||||
if (!_typeName.annotation().type)
|
||||
{
|
||||
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
|
||||
if (_typeName.stateMutability().has_value())
|
||||
{
|
||||
// for non-address types this was already caught by the parser
|
||||
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
|
||||
switch (*_typeName.stateMutability())
|
||||
{
|
||||
case StateMutability::Payable:
|
||||
_typeName.annotation().type = TypeProvider::payableAddress();
|
||||
break;
|
||||
case StateMutability::NonPayable:
|
||||
_typeName.annotation().type = TypeProvider::address();
|
||||
break;
|
||||
default:
|
||||
typeError(
|
||||
_typeName.location(),
|
||||
"Address types can only be payable or non-payable."
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
|
||||
{
|
||||
Declaration const* declaration = _typeName.annotation().referencedDeclaration;
|
||||
solAssert(declaration, "");
|
||||
|
||||
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
|
||||
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
||||
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::contract(*contract);
|
||||
else
|
||||
{
|
||||
_typeName.annotation().type = TypeProvider::emptyTuple();
|
||||
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
|
||||
}
|
||||
}
|
||||
void DeclarationTypeChecker::endVisit(FunctionTypeName const& _typeName)
|
||||
{
|
||||
switch (_typeName.visibility())
|
||||
{
|
||||
case Visibility::Internal:
|
||||
case Visibility::External:
|
||||
break;
|
||||
default:
|
||||
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
|
||||
{
|
||||
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_typeName.visibility() == Visibility::External)
|
||||
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
|
||||
{
|
||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||
if (!t->annotation().type->interfaceType(false).get())
|
||||
{
|
||||
fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_typeName.annotation().type = TypeProvider::function(_typeName);
|
||||
}
|
||||
void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
|
||||
{
|
||||
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
||||
{
|
||||
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
|
||||
{
|
||||
if (contractType->contractDefinition().isLibrary())
|
||||
m_errorReporter.fatalTypeError(
|
||||
typeName->location(),
|
||||
"Library types cannot be used as mapping keys."
|
||||
);
|
||||
}
|
||||
else if (typeName->annotation().type->category() != Type::Category::Enum)
|
||||
m_errorReporter.fatalTypeError(
|
||||
typeName->location(),
|
||||
"Only elementary types, contract types or enums are allowed as mapping keys."
|
||||
);
|
||||
}
|
||||
else
|
||||
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
|
||||
|
||||
TypePointer keyType = _mapping.keyType().annotation().type;
|
||||
TypePointer valueType = _mapping.valueType().annotation().type;
|
||||
|
||||
// Convert key type to memory.
|
||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||
|
||||
// Convert value type to storage reference.
|
||||
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
||||
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
|
||||
{
|
||||
TypePointer baseType = _typeName.baseType().annotation().type;
|
||||
if (!baseType)
|
||||
{
|
||||
solAssert(!m_errorReporter.errors().empty(), "");
|
||||
return;
|
||||
}
|
||||
if (baseType->storageBytes() == 0)
|
||||
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
|
||||
if (Expression const* length = _typeName.length())
|
||||
{
|
||||
TypePointer& lengthTypeGeneric = length->annotation().type;
|
||||
if (!lengthTypeGeneric)
|
||||
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
|
||||
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
|
||||
u256 lengthValue = 0;
|
||||
if (!lengthType || !lengthType->mobileType())
|
||||
typeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
|
||||
else if (lengthType->isZero())
|
||||
typeError(length->location(), "Array with zero length specified.");
|
||||
else if (lengthType->isFractional())
|
||||
typeError(length->location(), "Array with fractional length specified.");
|
||||
else if (lengthType->isNegative())
|
||||
typeError(length->location(), "Array with negative length specified.");
|
||||
else
|
||||
lengthValue = lengthType->literalValue(nullptr);
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue);
|
||||
}
|
||||
else
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
|
||||
}
|
||||
void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (_variable.annotation().type)
|
||||
return;
|
||||
|
||||
if (_variable.isConstant() && !_variable.isStateVariable())
|
||||
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
|
||||
if (_variable.immutable() && !_variable.isStateVariable())
|
||||
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
|
||||
|
||||
if (!_variable.typeName())
|
||||
{
|
||||
// This can still happen in very unusual cases where a developer uses constructs, such as
|
||||
// `var a;`, however, such code will have generated errors already.
|
||||
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
|
||||
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
|
||||
// after this step.
|
||||
return;
|
||||
}
|
||||
using Location = VariableDeclaration::Location;
|
||||
Location varLoc = _variable.referenceLocation();
|
||||
DataLocation typeLoc = DataLocation::Memory;
|
||||
|
||||
set<Location> allowedDataLocations = _variable.allowedDataLocations();
|
||||
if (!allowedDataLocations.count(varLoc))
|
||||
{
|
||||
auto locationToString = [](VariableDeclaration::Location _location) -> string
|
||||
{
|
||||
switch (_location)
|
||||
{
|
||||
case Location::Memory: return "\"memory\"";
|
||||
case Location::Storage: return "\"storage\"";
|
||||
case Location::CallData: return "\"calldata\"";
|
||||
case Location::Unspecified: return "none";
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
string errorString;
|
||||
if (!_variable.hasReferenceOrMappingType())
|
||||
errorString = "Data location can only be specified for array, struct or mapping types";
|
||||
else
|
||||
{
|
||||
errorString = "Data location must be " +
|
||||
util::joinHumanReadable(
|
||||
allowedDataLocations | boost::adaptors::transformed(locationToString),
|
||||
", ",
|
||||
" or "
|
||||
);
|
||||
if (_variable.isCallableOrCatchParameter())
|
||||
errorString +=
|
||||
" for " +
|
||||
string(_variable.isReturnParameter() ? "return " : "") +
|
||||
"parameter in" +
|
||||
string(_variable.isExternalCallableParameter() ? " external" : "") +
|
||||
" function";
|
||||
else
|
||||
errorString += " for variable";
|
||||
}
|
||||
errorString += ", but " + locationToString(varLoc) + " was given.";
|
||||
typeError(_variable.location(), errorString);
|
||||
|
||||
solAssert(!allowedDataLocations.empty(), "");
|
||||
varLoc = *allowedDataLocations.begin();
|
||||
}
|
||||
|
||||
// Find correct data location.
|
||||
if (_variable.isEventParameter())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = DataLocation::Memory;
|
||||
}
|
||||
else if (_variable.isStateVariable())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
|
||||
}
|
||||
else if (
|
||||
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
|
||||
dynamic_cast<EnumDefinition const*>(_variable.scope())
|
||||
)
|
||||
// The actual location will later be changed depending on how the type is used.
|
||||
typeLoc = DataLocation::Storage;
|
||||
else
|
||||
switch (varLoc)
|
||||
{
|
||||
case Location::Memory:
|
||||
typeLoc = DataLocation::Memory;
|
||||
break;
|
||||
case Location::Storage:
|
||||
typeLoc = DataLocation::Storage;
|
||||
break;
|
||||
case Location::CallData:
|
||||
typeLoc = DataLocation::CallData;
|
||||
break;
|
||||
case Location::Unspecified:
|
||||
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
|
||||
}
|
||||
|
||||
TypePointer type = _variable.typeName()->annotation().type;
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type))
|
||||
{
|
||||
bool isPointer = !_variable.isStateVariable();
|
||||
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
|
||||
}
|
||||
|
||||
_variable.annotation().type = type;
|
||||
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::typeError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
m_errorReporter.typeError(_location, _description);
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
m_errorReporter.fatalTypeError(_location, _description);
|
||||
}
|
||||
|
||||
bool DeclarationTypeChecker::check(ASTNode const& _node)
|
||||
{
|
||||
_node.accept(*this);
|
||||
return !m_errorOccurred;
|
||||
}
|
72
libsolidity/analysis/DeclarationTypeChecker.h
Normal file
72
libsolidity/analysis/DeclarationTypeChecker.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
}
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Assigns types to declarations.
|
||||
*/
|
||||
class DeclarationTypeChecker: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
DeclarationTypeChecker(
|
||||
langutil::ErrorReporter& _errorReporter,
|
||||
langutil::EVMVersion _evmVersion
|
||||
):
|
||||
m_errorReporter(_errorReporter),
|
||||
m_evmVersion(_evmVersion)
|
||||
{}
|
||||
|
||||
bool check(ASTNode const& _contract);
|
||||
|
||||
private:
|
||||
|
||||
bool visit(ElementaryTypeName const& _typeName) override;
|
||||
void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||
void endVisit(FunctionTypeName const& _typeName) override;
|
||||
void endVisit(Mapping const& _mapping) override;
|
||||
void endVisit(ArrayTypeName const& _typeName) override;
|
||||
void endVisit(VariableDeclaration const& _variable) override;
|
||||
|
||||
/// Adds a new error to the list of errors.
|
||||
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
/// Adds a new error to the list of errors and throws to abort reference resolving.
|
||||
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
bool m_errorOccurred = false;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
};
|
||||
|
||||
}
|
@ -195,51 +195,6 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> c
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
|
||||
Identifier const& _identifier,
|
||||
vector<Declaration const*> const& _declarations
|
||||
)
|
||||
{
|
||||
solAssert(_declarations.size() > 1, "");
|
||||
vector<Declaration const*> uniqueFunctions;
|
||||
|
||||
for (Declaration const* declaration: _declarations)
|
||||
{
|
||||
solAssert(declaration, "");
|
||||
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
|
||||
solAssert(
|
||||
dynamic_cast<FunctionDefinition const*>(declaration) ||
|
||||
dynamic_cast<EventDefinition const*>(declaration) ||
|
||||
dynamic_cast<VariableDeclaration const*>(declaration) ||
|
||||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
|
||||
"Found overloading involving something not a function, event or a (magic) variable."
|
||||
);
|
||||
|
||||
FunctionTypePointer functionType { declaration->functionType(false) };
|
||||
if (!functionType)
|
||||
functionType = declaration->functionType(true);
|
||||
solAssert(functionType, "Failed to determine the function type of the overloaded.");
|
||||
|
||||
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
|
||||
if (!parameter)
|
||||
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
|
||||
|
||||
if (uniqueFunctions.end() == find_if(
|
||||
uniqueFunctions.begin(),
|
||||
uniqueFunctions.end(),
|
||||
[&](Declaration const* d)
|
||||
{
|
||||
FunctionType const* newFunctionType = d->functionType(false);
|
||||
if (!newFunctionType)
|
||||
newFunctionType = d->functionType(true);
|
||||
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
|
||||
}
|
||||
))
|
||||
uniqueFunctions.push_back(declaration);
|
||||
}
|
||||
return uniqueFunctions;
|
||||
}
|
||||
|
||||
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
|
||||
{
|
||||
for (auto const& instruction: evmasm::c_instructions)
|
||||
|
@ -95,12 +95,6 @@ public:
|
||||
/// @note Returns a null pointer if any component in the path was not unique or not found.
|
||||
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const;
|
||||
|
||||
/// returns the vector of declarations without repetitions
|
||||
std::vector<Declaration const*> cleanedDeclarations(
|
||||
Identifier const& _identifier,
|
||||
std::vector<Declaration const*> const& _declarations
|
||||
);
|
||||
|
||||
/// Generate and store warnings about variables that are named like instructions.
|
||||
void warnVariablesNamedLikeInstructions();
|
||||
|
||||
|
@ -22,9 +22,7 @@
|
||||
|
||||
#include <libsolidity/analysis/ReferencesResolver.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
@ -37,7 +35,6 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::langutil;
|
||||
@ -126,40 +123,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
|
||||
else if (declarations.size() == 1)
|
||||
_identifier.annotation().referencedDeclaration = declarations.front();
|
||||
else
|
||||
_identifier.annotation().overloadedDeclarations =
|
||||
m_resolver.cleanedDeclarations(_identifier, declarations);
|
||||
_identifier.annotation().candidateDeclarations = declarations;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
||||
{
|
||||
if (!_typeName.annotation().type)
|
||||
{
|
||||
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
|
||||
if (_typeName.stateMutability().has_value())
|
||||
{
|
||||
// for non-address types this was already caught by the parser
|
||||
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
|
||||
switch (*_typeName.stateMutability())
|
||||
{
|
||||
case StateMutability::Payable:
|
||||
_typeName.annotation().type = TypeProvider::payableAddress();
|
||||
break;
|
||||
case StateMutability::NonPayable:
|
||||
_typeName.annotation().type = TypeProvider::address();
|
||||
break;
|
||||
default:
|
||||
m_errorReporter.typeError(
|
||||
_typeName.location(),
|
||||
"Address types can only be payable or non-payable."
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||
@ -194,113 +161,6 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
|
||||
}
|
||||
|
||||
_typeName.annotation().referencedDeclaration = declaration;
|
||||
|
||||
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
|
||||
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
||||
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::contract(*contract);
|
||||
else
|
||||
{
|
||||
_typeName.annotation().type = TypeProvider::emptyTuple();
|
||||
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
|
||||
}
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
||||
{
|
||||
switch (_typeName.visibility())
|
||||
{
|
||||
case Visibility::Internal:
|
||||
case Visibility::External:
|
||||
break;
|
||||
default:
|
||||
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
|
||||
{
|
||||
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_typeName.visibility() == Visibility::External)
|
||||
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
|
||||
{
|
||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||
if (!t->annotation().type->interfaceType(false).get())
|
||||
{
|
||||
fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_typeName.annotation().type = TypeProvider::function(_typeName);
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(Mapping const& _mapping)
|
||||
{
|
||||
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
||||
{
|
||||
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
|
||||
{
|
||||
if (contractType->contractDefinition().isLibrary())
|
||||
m_errorReporter.fatalTypeError(
|
||||
typeName->location(),
|
||||
"Library types cannot be used as mapping keys."
|
||||
);
|
||||
}
|
||||
else if (typeName->annotation().type->category() != Type::Category::Enum)
|
||||
m_errorReporter.fatalTypeError(
|
||||
typeName->location(),
|
||||
"Only elementary types, contract types or enums are allowed as mapping keys."
|
||||
);
|
||||
}
|
||||
else
|
||||
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
|
||||
|
||||
TypePointer keyType = _mapping.keyType().annotation().type;
|
||||
TypePointer valueType = _mapping.valueType().annotation().type;
|
||||
|
||||
// Convert key type to memory.
|
||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||
|
||||
// Convert value type to storage reference.
|
||||
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
||||
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||
{
|
||||
TypePointer baseType = _typeName.baseType().annotation().type;
|
||||
if (!baseType)
|
||||
{
|
||||
solAssert(!m_errorReporter.errors().empty(), "");
|
||||
return;
|
||||
}
|
||||
if (baseType->storageBytes() == 0)
|
||||
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
|
||||
if (Expression const* length = _typeName.length())
|
||||
{
|
||||
TypePointer& lengthTypeGeneric = length->annotation().type;
|
||||
if (!lengthTypeGeneric)
|
||||
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
|
||||
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
|
||||
if (!lengthType || !lengthType->mobileType())
|
||||
fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
|
||||
else if (lengthType->isZero())
|
||||
fatalTypeError(length->location(), "Array with zero length specified.");
|
||||
else if (lengthType->isFractional())
|
||||
fatalTypeError(length->location(), "Array with fractional length specified.");
|
||||
else if (lengthType->isNegative())
|
||||
fatalTypeError(length->location(), "Array with negative length specified.");
|
||||
else
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
|
||||
}
|
||||
else
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
||||
@ -321,115 +181,6 @@ bool ReferencesResolver::visit(Return const& _return)
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (_variable.annotation().type)
|
||||
return;
|
||||
|
||||
if (_variable.isConstant() && !_variable.isStateVariable())
|
||||
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
|
||||
if (_variable.immutable() && !_variable.isStateVariable())
|
||||
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
|
||||
|
||||
if (!_variable.typeName())
|
||||
{
|
||||
// This can still happen in very unusual cases where a developer uses constructs, such as
|
||||
// `var a;`, however, such code will have generated errors already.
|
||||
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
|
||||
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
|
||||
// after this step.
|
||||
return;
|
||||
}
|
||||
using Location = VariableDeclaration::Location;
|
||||
Location varLoc = _variable.referenceLocation();
|
||||
DataLocation typeLoc = DataLocation::Memory;
|
||||
|
||||
set<Location> allowedDataLocations = _variable.allowedDataLocations();
|
||||
if (!allowedDataLocations.count(varLoc))
|
||||
{
|
||||
auto locationToString = [](VariableDeclaration::Location _location) -> string
|
||||
{
|
||||
switch (_location)
|
||||
{
|
||||
case Location::Memory: return "\"memory\"";
|
||||
case Location::Storage: return "\"storage\"";
|
||||
case Location::CallData: return "\"calldata\"";
|
||||
case Location::Unspecified: return "none";
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
string errorString;
|
||||
if (!_variable.hasReferenceOrMappingType())
|
||||
errorString = "Data location can only be specified for array, struct or mapping types";
|
||||
else
|
||||
{
|
||||
errorString = "Data location must be " +
|
||||
util::joinHumanReadable(
|
||||
allowedDataLocations | boost::adaptors::transformed(locationToString),
|
||||
", ",
|
||||
" or "
|
||||
);
|
||||
if (_variable.isCallableOrCatchParameter())
|
||||
errorString +=
|
||||
" for " +
|
||||
string(_variable.isReturnParameter() ? "return " : "") +
|
||||
"parameter in" +
|
||||
string(_variable.isExternalCallableParameter() ? " external" : "") +
|
||||
" function";
|
||||
else
|
||||
errorString += " for variable";
|
||||
}
|
||||
errorString += ", but " + locationToString(varLoc) + " was given.";
|
||||
typeError(_variable.location(), errorString);
|
||||
|
||||
solAssert(!allowedDataLocations.empty(), "");
|
||||
varLoc = *allowedDataLocations.begin();
|
||||
}
|
||||
|
||||
// Find correct data location.
|
||||
if (_variable.isEventParameter())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = DataLocation::Memory;
|
||||
}
|
||||
else if (_variable.isStateVariable())
|
||||
{
|
||||
solAssert(varLoc == Location::Unspecified, "");
|
||||
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
|
||||
}
|
||||
else if (
|
||||
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
|
||||
dynamic_cast<EnumDefinition const*>(_variable.scope())
|
||||
)
|
||||
// The actual location will later be changed depending on how the type is used.
|
||||
typeLoc = DataLocation::Storage;
|
||||
else
|
||||
switch (varLoc)
|
||||
{
|
||||
case Location::Memory:
|
||||
typeLoc = DataLocation::Memory;
|
||||
break;
|
||||
case Location::Storage:
|
||||
typeLoc = DataLocation::Storage;
|
||||
break;
|
||||
case Location::CallData:
|
||||
typeLoc = DataLocation::CallData;
|
||||
break;
|
||||
case Location::Unspecified:
|
||||
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
|
||||
}
|
||||
|
||||
TypePointer type = _variable.typeName()->annotation().type;
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type))
|
||||
{
|
||||
bool isPointer = !_variable.isStateVariable();
|
||||
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
|
||||
}
|
||||
|
||||
_variable.annotation().type = type;
|
||||
}
|
||||
|
||||
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
||||
{
|
||||
bool wasInsideFunction = m_yulInsideFunction;
|
||||
@ -514,18 +265,6 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
||||
visit(*_varDecl.value);
|
||||
}
|
||||
|
||||
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
m_errorReporter.typeError(_location, _description);
|
||||
}
|
||||
|
||||
void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
m_errorReporter.fatalTypeError(_location, _description);
|
||||
}
|
||||
|
||||
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
|
@ -76,29 +76,18 @@ private:
|
||||
void endVisit(ForStatement const& _for) override;
|
||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(ElementaryTypeName const& _typeName) override;
|
||||
bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||
void endVisit(FunctionDefinition const& _functionDefinition) override;
|
||||
bool visit(ModifierDefinition const& _modifierDefinition) override;
|
||||
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
||||
void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||
void endVisit(FunctionTypeName const& _typeName) override;
|
||||
void endVisit(Mapping const& _mapping) override;
|
||||
void endVisit(ArrayTypeName const& _typeName) override;
|
||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||
bool visit(Return const& _return) override;
|
||||
void endVisit(VariableDeclaration const& _variable) override;
|
||||
|
||||
void operator()(yul::FunctionDefinition const& _function) override;
|
||||
void operator()(yul::Identifier const& _identifier) override;
|
||||
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
||||
|
||||
/// Adds a new error to the list of errors.
|
||||
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
/// Adds a new error to the list of errors and throws to abort reference resolving.
|
||||
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
/// Adds a new error to the list of errors.
|
||||
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
|
@ -2776,11 +2776,57 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<Declaration const*> TypeChecker::cleanOverloadedDeclarations(
|
||||
Identifier const& _identifier,
|
||||
vector<Declaration const*> const& _candidates
|
||||
)
|
||||
{
|
||||
solAssert(_candidates.size() > 1, "");
|
||||
vector<Declaration const*> uniqueDeclarations;
|
||||
|
||||
for (Declaration const* declaration: _candidates)
|
||||
{
|
||||
solAssert(declaration, "");
|
||||
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
|
||||
solAssert(
|
||||
dynamic_cast<FunctionDefinition const*>(declaration) ||
|
||||
dynamic_cast<EventDefinition const*>(declaration) ||
|
||||
dynamic_cast<VariableDeclaration const*>(declaration) ||
|
||||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
|
||||
"Found overloading involving something not a function, event or a (magic) variable."
|
||||
);
|
||||
|
||||
FunctionTypePointer functionType {declaration->functionType(false)};
|
||||
if (!functionType)
|
||||
functionType = declaration->functionType(true);
|
||||
solAssert(functionType, "Failed to determine the function type of the overloaded.");
|
||||
|
||||
for (TypePointer parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
|
||||
if (!parameter)
|
||||
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
|
||||
|
||||
if (uniqueDeclarations.end() == find_if(
|
||||
uniqueDeclarations.begin(),
|
||||
uniqueDeclarations.end(),
|
||||
[&](Declaration const* d)
|
||||
{
|
||||
FunctionType const* newFunctionType = d->functionType(false);
|
||||
if (!newFunctionType)
|
||||
newFunctionType = d->functionType(true);
|
||||
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
|
||||
}
|
||||
))
|
||||
uniqueDeclarations.push_back(declaration);
|
||||
}
|
||||
return uniqueDeclarations;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(Identifier const& _identifier)
|
||||
{
|
||||
IdentifierAnnotation& annotation = _identifier.annotation();
|
||||
if (!annotation.referencedDeclaration)
|
||||
{
|
||||
annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations);
|
||||
if (!annotation.arguments)
|
||||
{
|
||||
// The identifier should be a public state variable shadowing other functions
|
||||
|
@ -154,6 +154,11 @@ private:
|
||||
/// @returns the referenced declaration and throws on error.
|
||||
Declaration const& dereference(UserDefinedTypeName const& _typeName) const;
|
||||
|
||||
std::vector<Declaration const*> cleanOverloadedDeclarations(
|
||||
Identifier const& _reference,
|
||||
std::vector<Declaration const*> const& _candidates
|
||||
);
|
||||
|
||||
/// Runs type checks on @a _expression to infer its type and then checks that it is implicitly
|
||||
/// convertible to @a _expectedType.
|
||||
bool expectType(Expression const& _expression, Type const& _expectedType);
|
||||
|
@ -248,6 +248,8 @@ struct IdentifierAnnotation: ExpressionAnnotation
|
||||
{
|
||||
/// Referenced declaration, set at latest during overload resolution stage.
|
||||
Declaration const* referencedDeclaration = nullptr;
|
||||
/// List of possible declarations it could refer to (can contain duplicates).
|
||||
std::vector<Declaration const*> candidateDeclarations;
|
||||
/// List of possible declarations it could refer to.
|
||||
std::vector<Declaration const*> overloadedDeclarations;
|
||||
};
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||
#include <libsolidity/analysis/GlobalContext.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
@ -348,6 +349,11 @@ bool CompilerStack::analyze()
|
||||
|
||||
}
|
||||
|
||||
DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !declarationTypeChecker.check(*source->ast))
|
||||
return false;
|
||||
|
||||
// Next, we check inheritance, overrides, function collisions and other things at
|
||||
// contract or function level.
|
||||
// This also calculates whether a contract is abstract, which is needed by the
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <libsolidity/parsing/Parser.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/codegen/Compiler.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
@ -60,6 +61,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
|
||||
GlobalContext globalContext;
|
||||
NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), scopes, errorReporter);
|
||||
DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion());
|
||||
solAssert(Error::containsOnlyWarnings(errorReporter.errors()), "");
|
||||
resolver.registerDeclarations(*sourceUnit);
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
@ -69,6 +71,12 @@ evmasm::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
|
||||
if (!Error::containsOnlyWarnings(errorReporter.errors()))
|
||||
return AssemblyItems();
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
{
|
||||
BOOST_REQUIRE_NO_THROW(declarationTypeChecker.check(*node));
|
||||
if (!Error::containsOnlyWarnings(errorReporter.errors()))
|
||||
return AssemblyItems();
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <libsolidity/parsing/Parser.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
@ -118,10 +119,12 @@ bytes compileFirstExpression(
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
|
||||
NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), scopes, errorReporter);
|
||||
resolver.registerDeclarations(*sourceUnit);
|
||||
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
BOOST_REQUIRE_MESSAGE(resolver.resolveNamesAndTypes(*contract), "Resolving names failed");
|
||||
DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion());
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
BOOST_REQUIRE(declarationTypeChecker.check(*node));
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
|
@ -6,3 +6,4 @@ contract C {
|
||||
// ----
|
||||
// DeclarationError: (28-45): The "constant" keyword can only be used for state variables.
|
||||
// TypeError: (69-72): Invalid array length, expected integer literal or constant expression.
|
||||
// TypeError: (64-75): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -8,4 +8,3 @@ library L
|
||||
}
|
||||
// ----
|
||||
// DeclarationError: (32-35): Identifier not found or not unique.
|
||||
// TypeError: (63-76): Internal type cannot be used for external function type.
|
||||
|
@ -3,3 +3,4 @@ contract c {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (51-52): Invalid array length, expected integer literal or constant expression.
|
||||
// TypeError: (45-55): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -3,3 +3,4 @@ contract c {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (51-53): Array with negative length specified.
|
||||
// TypeError: (45-56): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -5,3 +5,4 @@ contract test {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (55-58): Array with fractional length specified.
|
||||
// TypeError: (50-61): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -5,3 +5,4 @@ contract test {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (55-65): Invalid array length, expected integer literal or constant expression.
|
||||
// TypeError: (50-68): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -5,3 +5,4 @@ contract test {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (55-66): Invalid array length, expected integer literal or constant expression.
|
||||
// TypeError: (50-69): Data location must be "storage" or "memory" for variable, but none was given.
|
||||
|
@ -18,9 +18,9 @@ contract C {
|
||||
// TypeError: (33-45): Address types can only be payable or non-payable.
|
||||
// TypeError: (52-64): Address types can only be payable or non-payable.
|
||||
// TypeError: (89-101): Address types can only be payable or non-payable.
|
||||
// TypeError: (138-150): Address types can only be payable or non-payable.
|
||||
// TypeError: (156-168): Address types can only be payable or non-payable.
|
||||
// TypeError: (195-207): Address types can only be payable or non-payable.
|
||||
// TypeError: (236-248): Address types can only be payable or non-payable.
|
||||
// TypeError: (300-312): Address types can only be payable or non-payable.
|
||||
// TypeError: (352-364): Address types can only be payable or non-payable.
|
||||
// TypeError: (138-150): Address types can only be payable or non-payable.
|
||||
// TypeError: (156-168): Address types can only be payable or non-payable.
|
||||
|
@ -4,4 +4,3 @@ contract C {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (25-26): Name has to refer to a struct, enum or contract.
|
||||
// TypeError: (53-61): Internal type cannot be used for external function type.
|
||||
|
@ -5,4 +5,3 @@ contract C {
|
||||
}
|
||||
// ----
|
||||
// TypeError: (50-51): Name has to refer to a struct, enum or contract.
|
||||
// TypeError: (78-86): Internal type cannot be used for external function type.
|
||||
|
@ -1,12 +1,11 @@
|
||||
contract C {
|
||||
function f() {
|
||||
function f() public {
|
||||
(((((((((((,2),)),)),),))=4)));
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// SyntaxError: (15-69): No visibility specified. Did you intend to add "public"?
|
||||
// TypeError: (46-47): Expression has to be an lvalue.
|
||||
// TypeError: (60-61): Type int_const 4 is not implicitly convertible to expected type tuple(tuple(tuple(tuple(tuple(,int_const 2),),),),).
|
||||
// TypeError: (37-61): Tuple component cannot be empty.
|
||||
// TypeError: (36-62): Tuple component cannot be empty.
|
||||
// TypeError: (35-63): Tuple component cannot be empty.
|
||||
// TypeError: (53-54): Expression has to be an lvalue.
|
||||
// TypeError: (67-68): Type int_const 4 is not implicitly convertible to expected type tuple(tuple(tuple(tuple(tuple(,int_const 2),),),),).
|
||||
// TypeError: (44-68): Tuple component cannot be empty.
|
||||
// TypeError: (43-69): Tuple component cannot be empty.
|
||||
// TypeError: (42-70): Tuple component cannot be empty.
|
||||
|
@ -1,6 +1,6 @@
|
||||
contract n
|
||||
{
|
||||
fallback()
|
||||
fallback() external
|
||||
{
|
||||
// Used to cause a segfault
|
||||
var (x,y) = (1);
|
||||
@ -12,5 +12,4 @@ contract n
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// SyntaxError: (14-129): No visibility specified. Did you intend to add "external"?
|
||||
// TypeError: (60-75): Different number of components on the left hand side (2) than on the right hand side (1).
|
||||
// TypeError: (69-84): Different number of components on the left hand side (2) than on the right hand side (1).
|
||||
|
Loading…
Reference in New Issue
Block a user