/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see .
*/
/**
* @author Christian
* @date 2015
* Type analyzer and checker.
*/
#include
#include
#include
#include
#include // needed for inline assembly
#include
using namespace std;
using namespace dev;
using namespace dev::solidity;
bool TypeChecker::checkTypeRequirements(ContractDefinition const& _contract)
{
try
{
visit(_contract);
}
catch (FatalError const&)
{
// We got a fatal error which required to stop further type checking, but we can
// continue normally from here.
if (m_errors.empty())
throw; // Something is weird here, rather throw again.
}
return Error::containsOnlyWarnings(m_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;
// We force our own visiting order here.
//@TODO structs will be visited again below, but it is probably fine.
ASTNode::listAccept(_contract.definedStructs(), *this);
ASTNode::listAccept(_contract.baseContracts(), *this);
checkContractDuplicateFunctions(_contract);
checkContractIllegalOverrides(_contract);
checkContractAbstractFunctions(_contract);
checkContractAbstractConstructors(_contract);
FunctionDefinition const* function = _contract.constructor();
if (function) {
if (!function->returnParameters().empty())
typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (function->isDeclaredConst())
typeError(function->location(), "Constructor cannot be defined as constant.");
if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal)
typeError(function->location(), "Constructor must be public or internal.");
}
FunctionDefinition const* fallbackFunction = nullptr;
for (FunctionDefinition const* function: _contract.definedFunctions())
{
if (function->name().empty())
{
if (fallbackFunction)
{
auto err = make_shared(Error::Type::DeclarationError);
*err << errinfo_comment("Only one fallback function is allowed.");
m_errors.push_back(err);
}
else
{
fallbackFunction = function;
if (_contract.isLibrary())
typeError(fallbackFunction->location(), "Libraries cannot have fallback functions.");
if (fallbackFunction->isDeclaredConst())
typeError(fallbackFunction->location(), "Fallback function cannot be declared constant.");
if (!fallbackFunction->parameters().empty())
typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallbackFunction->returnParameters().empty())
typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values.");
}
}
if (!function->isImplemented())
_contract.annotation().isFullyImplemented = false;
}
ASTNode::listAccept(_contract.subNodes(), *this);
checkContractExternalTypeClashes(_contract);
// check for hash collisions in function signatures
set> hashes;
for (auto const& it: _contract.interfaceFunctionList())
{
FixedHash<4> const& hash = it.first;
if (hashes.count(hash))
typeError(
_contract.location(),
string("Function signature hash collision for ") + it.second->externalSignature()
);
hashes.insert(hash);
}
if (_contract.isLibrary())
checkLibraryRequirements(_contract);
return false;
}
void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _contract)
{
/// Checks that two functions with the same name defined in this contract have different
/// argument types and that there is at most one constructor.
map> functions;
for (FunctionDefinition const* function: _contract.definedFunctions())
functions[function->name()].push_back(function);
// Constructor
if (functions[_contract.name()].size() > 1)
{
SecondarySourceLocation ssl;
auto it = ++functions[_contract.name()].begin();
for (; it != functions[_contract.name()].end(); ++it)
ssl.append("Another declaration is here:", (*it)->location());
auto err = make_shared(Error(Error::Type::DeclarationError));
*err <<
errinfo_sourceLocation(functions[_contract.name()].front()->location()) <<
errinfo_comment("More than one constructor defined.") <<
errinfo_secondarySourceLocation(ssl);
m_errors.push_back(err);
}
for (auto const& it: functions)
{
vector const& overloads = it.second;
for (size_t i = 0; i < overloads.size(); ++i)
for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
{
auto err = make_shared(Error(Error::Type::DeclarationError));
*err <<
errinfo_sourceLocation(overloads[j]->location()) <<
errinfo_comment("Function with same name and arguments defined twice.") <<
errinfo_secondarySourceLocation(SecondarySourceLocation().append(
"Other declaration is here:", overloads[i]->location()));
m_errors.push_back(err);
}
}
}
void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _contract)
{
// Mapping from name to function definition (exactly one per argument type equality class) and
// flag to indicate whether it is fully implemented.
using FunTypeAndFlag = std::pair;
map> functions;
// Search from base to derived
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
for (FunctionDefinition const* function: contract->definedFunctions())
{
// Take constructors out of overload hierarchy
if (function->isConstructor())
continue;
auto& overloads = functions[function->name()];
FunctionTypePointer funType = make_shared(*function);
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
{
return funType->hasEqualArgumentTypes(*_funAndFlag.first);
});
if (it == overloads.end())
overloads.push_back(make_pair(funType, function->isImplemented()));
else if (it->second)
{
if (!function->isImplemented())
typeError(function->location(), "Redeclaring an already implemented function as abstract");
}
else if (function->isImplemented())
it->second = true;
}
// Set to not fully implemented if at least one flag is false.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
if (!funAndFlag.second)
{
_contract.annotation().isFullyImplemented = false;
return;
}
}
void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _contract)
{
set argumentsNeeded;
// check that we get arguments for all base constructors that need it.
// If not mark the contract as abstract (not fully implemented)
vector const& bases = _contract.annotation().linearizedBaseContracts;
for (ContractDefinition const* contract: bases)
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
argumentsNeeded.insert(contract);
for (ContractDefinition const* contract: bases)
{
if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers())
{
auto baseContract = dynamic_cast(
&dereference(*modifier->name())
);
if (baseContract)
argumentsNeeded.erase(baseContract);
}
for (ASTPointer const& base: contract->baseContracts())
{
auto baseContract = dynamic_cast(&dereference(base->name()));
solAssert(baseContract, "");
if (!base->arguments().empty())
argumentsNeeded.erase(baseContract);
}
}
if (!argumentsNeeded.empty())
_contract.annotation().isFullyImplemented = false;
}
void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract)
{
// TODO unify this at a later point. for this we need to put the constness and the access specifier
// into the types
map> functions;
map modifiers;
// We search from derived to base, so the stored item causes the error.
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
{
for (FunctionDefinition const* function: contract->definedFunctions())
{
if (function->isConstructor())
continue; // constructors can neither be overridden nor override anything
string const& name = function->name();
if (modifiers.count(name))
typeError(modifiers[name]->location(), "Override changes function to modifier.");
FunctionType functionType(*function);
// function should not change the return type
for (FunctionDefinition const* overriding: functions[name])
{
FunctionType overridingType(*overriding);
if (!overridingType.hasEqualArgumentTypes(functionType))
continue;
if (
overriding->visibility() != function->visibility() ||
overriding->isDeclaredConst() != function->isDeclaredConst() ||
overriding->isPayable() != function->isPayable() ||
overridingType != functionType
)
typeError(overriding->location(), "Override changes extended function signature.");
}
functions[name].push_back(function);
}
for (ModifierDefinition const* modifier: contract->functionModifiers())
{
string const& name = modifier->name();
ModifierDefinition const*& override = modifiers[name];
if (!override)
override = modifier;
else if (ModifierType(*override) != ModifierType(*modifier))
typeError(override->location(), "Override changes modifier signature.");
if (!functions[name].empty())
typeError(override->location(), "Override changes modifier to function.");
}
}
}
void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract)
{
map>> externalDeclarations;
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
{
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isPartOfExternalInterface())
{
auto functionType = make_shared(*f);
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].push_back(
make_pair(f, functionType)
);
}
for (VariableDeclaration const* v: contract->stateVariables())
if (v->isPartOfExternalInterface())
{
auto functionType = make_shared(*v);
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].push_back(
make_pair(v, functionType)
);
}
}
for (auto const& it: externalDeclarations)
for (size_t i = 0; i < it.second.size(); ++i)
for (size_t j = i + 1; j < it.second.size(); ++j)
if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second))
typeError(
it.second[j].first->location(),
"Function overload clash during conversion to external types for arguments."
);
}
void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract)
{
solAssert(_contract.isLibrary(), "");
if (!_contract.baseContracts().empty())
typeError(_contract.location(), "Library is not allowed to inherit.");
for (auto const& var: _contract.stateVariables())
if (!var->isConstant())
typeError(var->location(), "Library cannot have non-constant state variables");
}
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
auto base = dynamic_cast(&dereference(_inheritance.name()));
solAssert(base, "Base contract not available.");
if (base->isLibrary())
typeError(_inheritance.location(), "Libraries cannot be inherited from.");
auto const& arguments = _inheritance.arguments();
TypePointers parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
if (!arguments.empty() && parameterTypes.size() != arguments.size())
{
typeError(
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
"."
);
return;
}
for (size_t i = 0; i < arguments.size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
typeError(
arguments[i]->location(),
"Invalid type for argument in constructor call. "
"Invalid implicit conversion from " +
type(*arguments[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested."
);
}
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{
ContractDefinition const* library = dynamic_cast(
_usingFor.libraryName().annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
typeError(_usingFor.libraryName().location(), "Library name expected.");
}
bool TypeChecker::visit(StructDefinition const& _struct)
{
for (ASTPointer const& member: _struct.members())
if (!type(*member)->canBeStored())
typeError(member->location(), "Type cannot be used in struct.");
// Check recursion, fatal error if detected.
using StructPointer = StructDefinition const*;
using StructPointersSet = set;
function check = [&](StructPointer _struct, StructPointersSet const& _parents)
{
if (_parents.count(_struct))
fatalTypeError(_struct->location(), "Recursive struct definition.");
StructPointersSet parents = _parents;
parents.insert(_struct);
for (ASTPointer const& member: _struct->members())
if (type(*member)->category() == Type::Category::Struct)
{
auto const& typeName = dynamic_cast(*member->typeName());
check(&dynamic_cast(*typeName.annotation().referencedDeclaration), parents);
}
};
check(&_struct, StructPointersSet{});
ASTNode::listAccept(_struct.members(), *this);
return false;
}
bool TypeChecker::visit(FunctionDefinition const& _function)
{
bool isLibraryFunction = dynamic_cast(*_function.scope()).isLibrary();
if (_function.isPayable())
{
if (isLibraryFunction)
typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.name().empty() && !_function.isPartOfExternalInterface())
typeError(_function.location(), "Internal functions cannot be payable.");
if (_function.isDeclaredConst())
typeError(_function.location(), "Functions cannot be constant and payable at the same time.");
}
for (ASTPointer const& var: _function.parameters() + _function.returnParameters())
{
if (!type(*var)->canLiveOutsideStorage())
typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
fatalTypeError(var->location(), "Internal type is not allowed for public or external functions.");
}
for (ASTPointer const& modifier: _function.modifiers())
visitManually(
*modifier,
_function.isConstructor() ?
dynamic_cast(*_function.scope()).annotation().linearizedBaseContracts :
vector()
);
if (_function.isImplemented())
_function.body().accept(*this);
return false;
}
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
// Variables can be declared without type (with "var"), in which case the first assignment
// sets the type.
// Note that assignments before the first declaration are legal because of the special scoping
// rules inherited from JavaScript.
// type is filled either by ReferencesResolver directly from the type name or by
// TypeChecker at the VariableDeclarationStatement level.
TypePointer varType = _variable.annotation().type;
solAssert(!!varType, "Failed to infer variable type.");
if (_variable.isConstant())
{
if (!dynamic_cast(_variable.scope()))
typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
if (!_variable.value())
typeError(_variable.location(), "Uninitialized \"constant\" variable.");
if (!varType->isValueType())
{
bool constImplemented = false;
if (auto arrayType = dynamic_cast(varType.get()))
constImplemented = arrayType->isByteArray();
if (!constImplemented)
typeError(
_variable.location(),
"Illegal use of \"constant\" specifier. \"constant\" "
"is not yet implemented for this type."
);
}
}
if (_variable.value())
expectType(*_variable.value(), *varType);
if (!_variable.isStateVariable())
{
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
if (!varType->canLiveOutsideStorage())
typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage.");
}
else if (
_variable.visibility() >= VariableDeclaration::Visibility::Public &&
!FunctionType(_variable).interfaceFunctionType()
)
typeError(_variable.location(), "Internal type is not allowed for public state variables.");
return false;
}
void TypeChecker::visitManually(
ModifierInvocation const& _modifier,
vector const& _bases
)
{
std::vector> const& arguments = _modifier.arguments();
for (ASTPointer const& argument: arguments)
argument->accept(*this);
_modifier.name()->accept(*this);
auto const* declaration = &dereference(*_modifier.name());
vector> emptyParameterList;
vector> const* parameters = nullptr;
if (auto modifierDecl = dynamic_cast(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)
{
typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class.");
return;
}
if (parameters->size() != arguments.size())
{
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 < _modifier.arguments().size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i])))
typeError(
arguments[i]->location(),
"Invalid type for argument in modifier invocation. "
"Invalid implicit conversion from " +
type(*arguments[i])->toString() +
" to " +
type(*(*parameters)[i])->toString() +
" requested."
);
}
bool TypeChecker::visit(EventDefinition const& _eventDef)
{
unsigned numIndexed = 0;
for (ASTPointer const& var: _eventDef.parameters())
{
if (var->isIndexed())
numIndexed++;
if (_eventDef.isAnonymous() && numIndexed > 4)
typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
if (!type(*var)->canLiveOutsideStorage())
typeError(var->location(), "Type is required to live outside storage.");
if (!type(*var)->interfaceType(false))
typeError(var->location(), "Internal type is not allowed as event parameter type.");
}
return false;
}
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
// Inline assembly does not have its own type-checking phase, so we just run the
// code-generator and see whether it produces any errors.
// External references have already been resolved in a prior stage and stored in the annotation.
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
return false;
Declaration const* declaration = ref->second;
solAssert(!!declaration, "");
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
{
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
unsigned pushes = 0;
if (dynamic_cast(declaration))
pushes = 1;
else if (auto var = dynamic_cast(declaration))
{
if (var->isConstant())
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
if (var->isLocalVariable())
pushes = var->type()->sizeOnStack();
else if (var->type()->isValueType())
pushes = 1;
else
pushes = 2; // slot number, intra slot offset
}
else if (auto contract = dynamic_cast(declaration))
{
if (!contract->isLibrary())
return false;
pushes = 1;
}
else
return false;
for (unsigned i = 0; i < pushes; ++i)
_assembly.append(u256(0)); // just to verify the stack height
}
else
{
// lvalue context
if (auto varDecl = dynamic_cast(declaration))
{
if (!varDecl->isLocalVariable())
return false; // only local variables are inline-assemlby lvalues
for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i)
_assembly.append(Instruction::POP); // remove value just to verify the stack height
}
else
return false;
}
return true;
});
return false;
}
bool TypeChecker::visit(IfStatement const& _ifStatement)
{
expectType(_ifStatement.condition(), BoolType());
_ifStatement.trueStatement().accept(*this);
if (_ifStatement.falseStatement())
_ifStatement.falseStatement()->accept(*this);
return false;
}
bool TypeChecker::visit(WhileStatement const& _whileStatement)
{
expectType(_whileStatement.condition(), BoolType());
_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(), BoolType());
if (_forStatement.loopExpression())
_forStatement.loopExpression()->accept(*this);
_forStatement.body().accept(*this);
return false;
}
void TypeChecker::endVisit(Return const& _return)
{
if (!_return.expression())
return;
ParameterList const* params = _return.annotation().functionReturnParameters;
if (!params)
{
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(type(*_return.expression()).get()))
{
if (tupleType->components().size() != params->parameters().size())
typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes)))
typeError(
_return.expression()->location(),
"Return argument type " +
type(*_return.expression())->toString() +
" is not implicitly convertible to expected type " +
TupleType(returnTypes).toString(false) +
"."
);
}
else if (params->parameters().size() != 1)
typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
else
{
TypePointer const& expected = type(*params->parameters().front());
if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected))
typeError(
_return.expression()->location(),
"Return argument type " +
type(*_return.expression())->toString() +
" is not implicitly convertible to expected type (type of first return variable) " +
expected->toString() +
"."
);
}
}
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())
fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
VariableDeclaration const& varDecl = *_statement.declarations().front();
if (!varDecl.annotation().type)
fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
if (auto ref = dynamic_cast(type(varDecl).get()))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
warning(
varDecl.location(),
"Uninitialized storage pointer. Did you mean ' memory " + varDecl.name() + "'?"
);
}
}
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(type(*_statement.initialValue()).get()))
valueTypes = tupleType->components();
else
valueTypes = TypePointers{type(*_statement.initialValue())};
// Determine which component is assigned to which variable.
// If numbers do not match, fill up if variables begin or end empty (not both).
vector& assignments = _statement.annotation().assignments;
assignments.resize(valueTypes.size(), nullptr);
vector> const& variables = _statement.declarations();
if (variables.empty())
{
if (!valueTypes.empty())
fatalTypeError(
_statement.location(),
"Too many components (" +
toString(valueTypes.size()) +
") in value for variable assignment (0) needed"
);
}
else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back())
fatalTypeError(
_statement.location(),
"Wildcard both at beginning and end of variable declaration list is only allowed "
"if the number of components is equal."
);
size_t minNumValues = variables.size();
if (!variables.empty() && (!variables.back() || !variables.front()))
--minNumValues;
if (valueTypes.size() < minNumValues)
fatalTypeError(
_statement.location(),
"Not enough components (" +
toString(valueTypes.size()) +
") in value to assign all variables (" +
toString(minNumValues) + ")."
);
if (valueTypes.size() > variables.size() && variables.front() && variables.back())
fatalTypeError(
_statement.location(),
"Too many components (" +
toString(valueTypes.size()) +
") in value for variable assignment (" +
toString(minNumValues) +
" needed)."
);
bool fillRight = !variables.empty() && (!variables.back() || variables.front());
for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i)
if (fillRight)
assignments[i] = variables[i].get();
else
assignments[assignments.size() - i - 1] = variables[variables.size() - i - 1].get();
for (size_t i = 0; i < assignments.size(); ++i)
{
if (!assignments[i])
continue;
VariableDeclaration const& var = *assignments[i];
solAssert(!var.value(), "Value has to be tied to statement.");
TypePointer const& valueComponentType = valueTypes[i];
solAssert(!!valueComponentType, "");
if (!var.annotation().type)
{
// Infer type from value.
solAssert(!var.typeName(), "");
var.annotation().type = valueComponentType->mobileType();
if (!var.annotation().type)
{
if (valueComponentType->category() == Type::Category::RationalNumber)
fatalTypeError(
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
" (absolute value too large or divison by zero)."
);
else
solAssert(false, "");
}
var.accept(*this);
}
else
{
var.accept(*this);
if (!valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type))
{
if (
valueComponentType->category() == Type::Category::RationalNumber &&
dynamic_cast(*valueComponentType).isFractional() &&
valueComponentType->mobileType()
)
typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
" is not implicitly convertible to expected type " +
var.annotation().type->toString() +
". Try converting to type " +
valueComponentType->mobileType()->toString() +
" or use an explicit conversion."
);
else
typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
" is not implicitly convertible to expected type " +
var.annotation().type->toString() +
"."
);
}
}
}
return false;
}
void TypeChecker::endVisit(ExpressionStatement const& _statement)
{
if (type(_statement.expression())->category() == Type::Category::RationalNumber)
if (!dynamic_cast(*type(_statement.expression())).mobileType())
typeError(_statement.expression().location(), "Invalid rational number.");
if (auto call = dynamic_cast(&_statement.expression()))
{
if (auto callType = dynamic_cast(type(call->expression()).get()))
{
using Location = FunctionType::Location;
Location location = callType->location();
if (
location == Location::Bare ||
location == Location::BareCallCode ||
location == Location::BareDelegateCall ||
location == Location::Send
)
warning(_statement.location(), "Return value of low-level calls not used.");
}
}
}
bool TypeChecker::visit(Conditional const& _conditional)
{
expectType(_conditional.condition(), BoolType());
_conditional.trueExpression().accept(*this);
_conditional.falseExpression().accept(*this);
TypePointer trueType = type(_conditional.trueExpression())->mobileType();
TypePointer falseType = type(_conditional.falseExpression())->mobileType();
if (!trueType)
fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type.");
if (!falseType)
fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type.");
TypePointer commonType = Type::commonType(trueType, falseType);
if (!commonType)
{
typeError(
_conditional.location(),
"True expression's type " +
trueType->toString() +
" doesn't match false expression's type " +
falseType->toString() +
"."
);
// even we can't find a common type, we have to set a type here,
// otherwise the upper statement will not be able to check the type.
commonType = trueType;
}
_conditional.annotation().type = commonType;
if (_conditional.annotation().lValueRequested)
typeError(
_conditional.location(),
"Conditional expression as left value is not supported yet."
);
return false;
}
bool TypeChecker::visit(Assignment const& _assignment)
{
requireLValue(_assignment.leftHandSide());
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
if (TupleType const* tupleType = dynamic_cast(t.get()))
{
// Sequenced assignments of tuples is not valid, make the result a "void" type.
_assignment.annotation().type = make_shared();
expectType(_assignment.rightHandSide(), *tupleType);
}
else if (t->category() == Type::Category::Mapping)
{
typeError(_assignment.location(), "Mappings cannot be assigned to.");
_assignment.rightHandSide().accept(*this);
}
else if (_assignment.assignmentOperator() == Token::Assign)
expectType(_assignment.rightHandSide(), *t);
else
{
// compound assignment
_assignment.rightHandSide().accept(*this);
TypePointer resultType = t->binaryOperatorResult(
Token::AssignmentToBinaryOp(_assignment.assignmentOperator()),
type(_assignment.rightHandSide())
);
if (!resultType || *resultType != *t)
typeError(
_assignment.location(),
"Operator " +
string(Token::toString(_assignment.assignmentOperator())) +
" not compatible with types " +
t->toString() +
" and " +
type(_assignment.rightHandSide())->toString()
);
}
return false;
}
bool TypeChecker::visit(TupleExpression const& _tuple)
{
vector> const& components = _tuple.components();
TypePointers types;
if (_tuple.annotation().lValueRequested)
{
if (_tuple.isInlineArray())
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 = make_shared(types);
// If some of the components are not LValues, the error is reported above.
_tuple.annotation().isLValue = true;
}
else
{
TypePointer inlineArrayType;
for (size_t i = 0; i < components.size(); ++i)
{
// Outside of an lvalue-context, the only situation where a component can be empty is (x,).
if (!components[i] && !(i == 1 && components.size() == 2))
fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
else if (components[i])
{
components[i]->accept(*this);
types.push_back(type(*components[i]));
if (_tuple.isInlineArray())
solAssert(!!types[i], "Inline array cannot have empty components");
if (_tuple.isInlineArray())
{
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
fatalTypeError(components[i]->location(), "Invalid mobile type.");
if (i == 0)
inlineArrayType = types[i];
else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
}
}
else
types.push_back(TypePointer());
}
if (_tuple.isInlineArray())
{
if (!inlineArrayType)
fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
_tuple.annotation().type = make_shared(DataLocation::Memory, inlineArrayType, types.size());
}
else
{
if (components.size() == 1)
_tuple.annotation().type = type(*components[0]);
else
{
if (components.size() == 2 && !components[1])
types.pop_back();
_tuple.annotation().type = make_shared(types);
}
}
}
return false;
}
bool TypeChecker::visit(UnaryOperation const& _operation)
{
// Inc, Dec, Add, Sub, Not, BitNot, Delete
Token::Value op = _operation.getOperator();
if (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete)
requireLValue(_operation.subExpression());
else
_operation.subExpression().accept(*this);
TypePointer const& subExprType = type(_operation.subExpression());
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
if (!t)
{
typeError(
_operation.location(),
"Unary operator " +
string(Token::toString(op)) +
" cannot be applied to type " +
subExprType->toString()
);
t = subExprType;
}
_operation.annotation().type = t;
return false;
}
void TypeChecker::endVisit(BinaryOperation const& _operation)
{
TypePointer const& leftType = type(_operation.leftExpression());
TypePointer const& rightType = type(_operation.rightExpression());
TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
if (!commonType)
{
typeError(
_operation.location(),
"Operator " +
string(Token::toString(_operation.getOperator())) +
" not compatible with types " +
leftType->toString() +
" and " +
rightType->toString()
);
commonType = leftType;
}
_operation.annotation().commonType = commonType;
_operation.annotation().type =
Token::isCompareOp(_operation.getOperator()) ?
make_shared() :
commonType;
}
bool TypeChecker::visit(FunctionCall const& _functionCall)
{
bool isPositionalCall = _functionCall.names().empty();
vector> arguments = _functionCall.arguments();
vector> const& argumentNames = _functionCall.names();
// We need to check arguments' type first as they will be needed for overload resolution.
shared_ptr argumentTypes;
if (isPositionalCall)
argumentTypes = make_shared();
for (ASTPointer const& argument: arguments)
{
argument->accept(*this);
// only store them for positional calls
if (isPositionalCall)
argumentTypes->push_back(type(*argument));
}
if (isPositionalCall)
_functionCall.expression().annotation().argumentTypes = move(argumentTypes);
_functionCall.expression().accept(*this);
TypePointer expressionType = type(_functionCall.expression());
if (auto const* typeType = dynamic_cast(expressionType.get()))
{
_functionCall.annotation().isStructConstructorCall = (typeType->actualType()->category() == Type::Category::Struct);
_functionCall.annotation().isTypeConversion = !_functionCall.annotation().isStructConstructorCall;
}
else
_functionCall.annotation().isStructConstructorCall = _functionCall.annotation().isTypeConversion = false;
if (_functionCall.annotation().isTypeConversion)
{
TypeType const& t = dynamic_cast(*expressionType);
TypePointer resultType = t.actualType();
if (arguments.size() != 1)
typeError(_functionCall.location(), "Exactly one argument expected for explicit type conversion.");
else if (!isPositionalCall)
typeError(_functionCall.location(), "Type conversion cannot allow named arguments.");
else
{
TypePointer const& argType = type(*arguments.front());
if (auto argRefType = dynamic_cast(argType.get()))
// do not change the data location when converting
// (data location cannot yet be specified for type conversions)
resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType);
if (!argType->isExplicitlyConvertibleTo(*resultType))
typeError(_functionCall.location(), "Explicit type conversion not allowed.");
}
_functionCall.annotation().type = resultType;
return false;
}
// Actual function call or struct constructor call.
FunctionTypePointer functionType;
/// For error message: Struct members that were removed during conversion to memory.
set membersRemovedForStructConstructor;
if (_functionCall.annotation().isStructConstructorCall)
{
TypeType const& t = dynamic_cast(*expressionType);
auto const& structType = dynamic_cast(*t.actualType());
functionType = structType.constructorType();
membersRemovedForStructConstructor = structType.membersMissingInMemory();
}
else
functionType = dynamic_pointer_cast(expressionType);
if (!functionType)
{
typeError(_functionCall.location(), "Type is not callable");
_functionCall.annotation().type = make_shared();
return false;
}
else if (functionType->returnParameterTypes().size() == 1)
_functionCall.annotation().type = functionType->returnParameterTypes().front();
else
_functionCall.annotation().type = make_shared(functionType->returnParameterTypes());
TypePointers parameterTypes = functionType->parameterTypes();
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
{
string msg =
"Wrong argument count for function call: " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
if (_functionCall.annotation().isStructConstructorCall && !membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
typeError(_functionCall.location(), msg);
}
else if (isPositionalCall)
{
// call by positional arguments
for (size_t i = 0; i < arguments.size(); ++i)
{
auto const& argType = type(*arguments[i]);
if (functionType->takesArbitraryParameters())
{
if (auto t = dynamic_cast(argType.get()))
if (!t->mobileType())
typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
}
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
typeError(
arguments[i]->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*arguments[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested."
);
}
}
else
{
// call by named arguments
auto const& parameterNames = functionType->parameterNames();
if (functionType->takesArbitraryParameters())
typeError(
_functionCall.location(),
"Named arguments cannnot be used for functions that take arbitrary parameters."
);
else if (parameterNames.size() > argumentNames.size())
typeError(_functionCall.location(), "Some argument names are missing.");
else if (parameterNames.size() < argumentNames.size())
typeError(_functionCall.location(), "Too many arguments.");
else
{
// check duplicate 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;
typeError(arguments[i]->location(), "Duplicate named argument.");
}
// check actual types
if (!duplication)
for (size_t i = 0; i < argumentNames.size(); i++)
{
bool found = false;
for (size_t j = 0; j < parameterNames.size(); j++)
if (parameterNames[j] == *argumentNames[i])
{
found = true;
// check type convertible
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[j]))
typeError(
arguments[i]->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*arguments[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested."
);
break;
}
if (!found)
typeError(
_functionCall.location(),
"Named argument does not match function declaration."
);
}
}
}
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(&_newExpression.typeName()))
{
auto contract = dynamic_cast(&dereference(*contractName));
if (!contract)
fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (!contract->annotation().isFullyImplemented)
typeError(_newExpression.location(), "Trying to create an instance of 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))
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())
fatalTypeError(
_newExpression.typeName().location(),
"Type cannot live outside storage."
);
if (!type->isDynamicallySized())
typeError(
_newExpression.typeName().location(),
"Length has to be placed in parentheses after the array type for new expression."
);
type = ReferenceType::copyForLocationIfReference(DataLocation::Memory, type);
_newExpression.annotation().type = make_shared(
TypePointers{make_shared(256)},
TypePointers{type},
strings(),
strings(),
FunctionType::Location::ObjectCreation
);
}
else
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& argumentTypes = _memberAccess.annotation().argumentTypes;
MemberList::MemberMap possibleMembers = exprType->members(m_scope).membersByName(memberName);
if (possibleMembers.size() > 1 && argumentTypes)
{
// do overload resolution
for (auto it = possibleMembers.begin(); it != possibleMembers.end();)
if (
it->type->category() == Type::Category::Function &&
!dynamic_cast(*it->type).canTakeArguments(*argumentTypes, exprType)
)
it = possibleMembers.erase(it);
else
++it;
}
if (possibleMembers.size() == 0)
{
auto storageType = ReferenceType::copyForLocationIfReference(
DataLocation::Storage,
exprType
);
if (!storageType->members(m_scope).membersByName(memberName).empty())
fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" is not available in " +
exprType->toString() +
" outside of storage."
);
fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" not found or not visible "
"after argument-dependent lookup in " + exprType->toString() +
(memberName == "value" ? " - did you forget the \"payable\" modifier?" : "")
);
}
else if (possibleMembers.size() > 1)
fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" not unique "
"after argument-dependent lookup in " + exprType->toString() +
(memberName == "value" ? " - did you forget the \"payable\" modifier?" : "")
);
auto& annotation = _memberAccess.annotation();
annotation.referencedDeclaration = possibleMembers.front().declaration;
annotation.type = possibleMembers.front().type;
if (auto funType = dynamic_cast(annotation.type.get()))
if (funType->bound() && !exprType->isImplicitlyConvertibleTo(*funType->selfType()))
typeError(
_memberAccess.location(),
"Function \"" + memberName + "\" cannot be called on an object of type " +
exprType->toString() + " (expected " + funType->selfType()->toString() + ")"
);
if (exprType->category() == Type::Category::Struct)
annotation.isLValue = true;
else if (exprType->category() == Type::Category::Array)
{
auto const& arrayType(dynamic_cast(*exprType));
annotation.isLValue = (
memberName == "length" &&
arrayType.location() == DataLocation::Storage &&
arrayType.isDynamicallySized()
);
}
else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast(exprType.get()))
{
if (ContractType const* contractType = dynamic_cast(typeType->actualType().get()))
annotation.isLValue = annotation.referencedDeclaration->isLValue();
}
return false;
}
bool TypeChecker::visit(IndexAccess const& _access)
{
_access.baseExpression().accept(*this);
TypePointer baseType = type(_access.baseExpression());
TypePointer resultType;
bool isLValue = false;
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
case Type::Category::Array:
{
ArrayType const& actualType = dynamic_cast(*baseType);
if (!index)
typeError(_access.location(), "Index expression cannot be omitted.");
else if (actualType.isString())
{
typeError(_access.location(), "Index access for string is not possible.");
index->accept(*this);
}
else
{
expectType(*index, IntegerType(256));
if (auto numberType = dynamic_cast(type(*index).get()))
{
if (!numberType->isFractional()) // error is reported above
if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr))
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(*baseType);
if (!index)
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(*baseType);
if (!index)
resultType = make_shared(make_shared(DataLocation::Memory, typeType.actualType()));
else
{
expectType(*index, IntegerType(256));
if (auto length = dynamic_cast(type(*index).get()))
resultType = make_shared(make_shared(
DataLocation::Memory,
typeType.actualType(),
length->literalValue(nullptr)
));
else
fatalTypeError(index->location(), "Integer constant expected.");
}
break;
}
case Type::Category::FixedBytes:
{
FixedBytesType const& bytesType = dynamic_cast(*baseType);
if (!index)
typeError(_access.location(), "Index expression cannot be omitted.");
else
{
expectType(*index, IntegerType(256));
if (auto integerType = dynamic_cast(type(*index).get()))
if (bytesType.numBytes() <= integerType->literalValue(nullptr))
typeError(_access.location(), "Out of bounds array access.");
}
resultType = make_shared(1);
isLValue = false; // @todo this heavily depends on how it is embedded
break;
}
default:
fatalTypeError(
_access.baseExpression().location(),
"Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")"
);
}
_access.annotation().type = move(resultType);
_access.annotation().isLValue = isLValue;
return false;
}
bool TypeChecker::visit(Identifier const& _identifier)
{
IdentifierAnnotation& annotation = _identifier.annotation();
if (!annotation.referencedDeclaration)
{
if (!annotation.argumentTypes)
fatalTypeError(_identifier.location(), "Unable to determine overloaded type.");
if (annotation.overloadedDeclarations.empty())
fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
else if (annotation.overloadedDeclarations.size() == 1)
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
else
{
vector candidates;
for (Declaration const* declaration: annotation.overloadedDeclarations)
{
TypePointer function = declaration->type();
solAssert(!!function, "Requested type not present.");
auto const* functionType = dynamic_cast(function.get());
if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration);
}
if (candidates.empty())
fatalTypeError(_identifier.location(), "No matching declaration found after argument-dependent lookup.");
else if (candidates.size() == 1)
annotation.referencedDeclaration = candidates.front();
else
fatalTypeError(_identifier.location(), "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)
fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
return false;
}
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = make_shared(Type::fromElementaryTypeName(_expr.typeName()));
}
void TypeChecker::endVisit(Literal const& _literal)
{
_literal.annotation().type = Type::forLiteral(_literal);
if (!_literal.annotation().type)
fatalTypeError(_literal.location(), "Invalid literal value.");
}
bool TypeChecker::contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set const& _seenContracts
) const
{
// Naive depth-first search that remembers nodes already seen.
if (_seenContracts.count(&_contract))
return true;
set 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;
}
void TypeChecker::expectType(Expression const& _expression, Type const& _expectedType)
{
_expression.accept(*this);
if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType))
{
if (
type(_expression)->category() == Type::Category::RationalNumber &&
dynamic_pointer_cast(type(_expression))->isFractional() &&
type(_expression)->mobileType()
)
typeError(
_expression.location(),
"Type " +
type(_expression)->toString() +
" is not implicitly convertible to expected type " +
_expectedType.toString() +
". Try converting to type " +
type(_expression)->mobileType()->toString() +
" or use an explicit conversion."
);
else
typeError(
_expression.location(),
"Type " +
type(_expression)->toString() +
" is not implicitly convertible to expected type " +
_expectedType.toString() +
"."
);
}
}
void TypeChecker::requireLValue(Expression const& _expression)
{
_expression.annotation().lValueRequested = true;
_expression.accept(*this);
if (!_expression.annotation().isLValue)
typeError(_expression.location(), "Expression has to be an lvalue.");
}
void TypeChecker::typeError(SourceLocation const& _location, string const& _description)
{
auto err = make_shared(Error::Type::TypeError);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_comment(_description);
m_errors.push_back(err);
}
void TypeChecker::warning(SourceLocation const& _location, string const& _description)
{
auto err = make_shared(Error::Type::Warning);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_comment(_description);
m_errors.push_back(err);
}
void TypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
{
typeError(_location, _description);
BOOST_THROW_EXCEPTION(FatalError());
}