Skip to content

Commit

Permalink
Merge pull request #14566 from ethereum/new-analysis-defining-builtins
Browse files Browse the repository at this point in the history
Defining built-ins in experimental analysis
  • Loading branch information
cameel authored Sep 28, 2023
2 parents 271d55c + 0819e31 commit bdd2f02
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 128 deletions.
19 changes: 5 additions & 14 deletions liblangutil/Token.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,13 @@ namespace solidity::langutil
T(Leave, "leave", 0) \
\
T(NonExperimentalEnd, nullptr, 0) /* used as non-experimental enum end marker */ \
/* Experimental Solidity specific keywords. */ \
/* Experimental Solidity specific keywords. */ \
K(Class, "class", 0) \
K(Instantiation, "instantiation", 0) \
K(Word, "word", 0) \
K(Integer, "integer", 0) \
K(Integer, "Integer", 0) \
K(Itself, "itself", 0) \
K(Void, "void", 0) \
K(Pair, "pair", 0) \
K(Fun, "fun", 0) \
K(Unit, "unit", 0) \
K(StaticAssert, "static_assert", 0) \
K(Builtin, "__builtin", 0) \
T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \
\
/* Illegal token - not able to scan. */ \
Expand All @@ -304,12 +300,7 @@ namespace TokenTraits
constexpr size_t count() { return static_cast<size_t>(Token::NUM_TOKENS); }

// Predicates
constexpr bool isElementaryTypeName(Token tok)
{
return (Token::Int <= tok && tok < Token::TypesEnd) ||
tok == Token::Word || tok == Token::Void || tok == Token::Integer ||
tok == Token::Pair || tok == Token::Unit || tok == Token::Fun;
}
constexpr bool isElementaryTypeName(Token tok) { return Token::Int <= tok && tok < Token::TypesEnd; }
constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; }
constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; }
constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd ||
Expand Down Expand Up @@ -350,7 +341,7 @@ namespace TokenTraits
{
return tok == Token::Assembly || tok == Token::Contract || tok == Token::External || tok == Token::Fallback ||
tok == Token::Pragma || tok == Token::Import || tok == Token::As || tok == Token::Function || tok == Token::Let ||
tok == Token::Return || tok == Token::Type || tok == Token::Bool || tok == Token::If || tok == Token::Else ||
tok == Token::Return || tok == Token::Type || tok == Token::If || tok == Token::Else ||
tok == Token::Do || tok == Token::While || tok == Token::For || tok == Token::Continue || tok == Token::Break ||
(tok > Token::NonExperimentalEnd && tok < Token::ExperimentalEnd);
}
Expand Down
29 changes: 29 additions & 0 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -2596,6 +2596,35 @@ class TypeClassName: public ASTNode
std::variant<Token, ASTPointer<IdentifierPath>> m_name;
};

class Builtin: public Expression
{
public:
Builtin(
int64_t _id,
SourceLocation _location,
ASTPointer<ASTString> _nameParameter,
SourceLocation _nameParameterLocation
):
Expression(_id, std::move(_location)),
m_nameParameter(std::move(_nameParameter)),
m_nameParameterLocation(std::move(_nameParameterLocation))
{
solAssert(m_nameParameter);
}

void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;

bool experimentalSolidityOnly() const override { return true; }

ASTString const& nameParameter() const { return *m_nameParameter; }
SourceLocation const& nameParameterLocation() const { return m_nameParameterLocation; }

private:
ASTPointer<ASTString> m_nameParameter;
SourceLocation m_nameParameterLocation;
};

/// @}

}
1 change: 1 addition & 0 deletions libsolidity/ast/ASTForward.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class TypeClassDefinition;
class TypeClassInstantiation;
class TypeClassName;
class TypeDefinition;
class Builtin;
/// @}

class VariableScope;
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/ast/ASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class ASTVisitor
virtual bool visit(TypeClassInstantiation& _node) { return visitNode(_node); }
virtual bool visit(TypeDefinition& _node) { return visitNode(_node); }
virtual bool visit(TypeClassName& _node) { return visitNode(_node); }
virtual bool visit(Builtin& _node) { return visitNode(_node); }
/// @}

virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); }
Expand Down Expand Up @@ -178,6 +179,7 @@ class ASTVisitor
virtual void endVisit(TypeClassInstantiation& _node) { endVisitNode(_node); }
virtual void endVisit(TypeDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(TypeClassName& _node) { endVisitNode(_node); }
virtual void endVisit(Builtin& _node) { endVisitNode(_node); }
/// @}

protected:
Expand Down Expand Up @@ -263,6 +265,7 @@ class ASTConstVisitor
virtual bool visit(TypeClassInstantiation const& _node) { return visitNode(_node); }
virtual bool visit(TypeDefinition const& _node) { return visitNode(_node); }
virtual bool visit(TypeClassName const& _node) { return visitNode(_node); }
virtual bool visit(Builtin const& _node) { return visitNode(_node); }
/// @}

virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); }
Expand Down Expand Up @@ -326,6 +329,7 @@ class ASTConstVisitor
virtual void endVisit(TypeClassInstantiation const& _node) { endVisitNode(_node); }
virtual void endVisit(TypeDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(TypeClassName const& _node) { endVisitNode(_node); }
virtual void endVisit(Builtin const& _node) { endVisitNode(_node); }
/// @}

protected:
Expand Down
12 changes: 12 additions & 0 deletions libsolidity/ast/AST_accept.h
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,18 @@ void TypeClassName::accept(ASTConstVisitor& _visitor) const
}
_visitor.endVisit(*this);
}

void Builtin::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}

void Builtin::accept(ASTConstVisitor& _visitor) const
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}
/// @}

}
90 changes: 24 additions & 66 deletions libsolidity/experimental/analysis/TypeInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,6 @@ TypeInference::TypeInference(Analysis& _analysis):
defineBinaryCompareOperator(BuiltinClass::LessOrEqual, Token::LessThanOrEqual, "leq");
defineBinaryCompareOperator(BuiltinClass::Greater, Token::GreaterThan, "gt");
defineBinaryCompareOperator(BuiltinClass::GreaterOrEqual, Token::GreaterThanOrEqual, "geq");

{
auto [members, newlyInserted] = annotation().members.emplace(m_typeSystem.constructor(PrimitiveType::Bool), std::map<std::string, TypeMember>{});
solAssert(newlyInserted);
members->second.emplace("abs", TypeMember{helper.functionType(m_wordType, m_boolType)});
members->second.emplace("rep", TypeMember{helper.functionType(m_boolType, m_wordType)});
}
{
Type first = m_typeSystem.freshTypeVariable({});
Type second = m_typeSystem.freshTypeVariable({});
Type pairType = m_typeSystem.type(PrimitiveType::Pair, {first, second});
auto [members, newlyInserted] = annotation().members.emplace(m_typeSystem.constructor(PrimitiveType::Pair), std::map<std::string, TypeMember>{});
solAssert(newlyInserted);
members->second.emplace("first", TypeMember{helper.functionType(pairType, first)});
members->second.emplace("second", TypeMember{helper.functionType(pairType, second)});
}
}

bool TypeInference::analyze(SourceUnit const& _sourceUnit)
Expand Down Expand Up @@ -307,48 +291,6 @@ bool TypeInference::visit(InlineAssembly const& _inlineAssembly)
return false;
}

bool TypeInference::visit(ElementaryTypeNameExpression const& _expression)
{
auto& expressionAnnotation = annotation(_expression);
solAssert(!expressionAnnotation.type);

switch (m_expressionContext)
{
case ExpressionContext::Term:
case ExpressionContext::Type:
if (auto constructor = m_analysis.annotation<TypeRegistration>(_expression).typeConstructor)
{
std::vector<Type> arguments;
std::generate_n(std::back_inserter(arguments), m_typeSystem.constructorInfo(*constructor).arguments(), [&]() {
return m_typeSystem.freshTypeVariable({});
});
// TODO: get rid of the distinction (assign a function on unit for the empty case)? Ambiguity?
if (arguments.empty() || m_expressionContext == ExpressionContext::Term)
expressionAnnotation.type = m_typeSystem.type(*constructor, arguments);
else
{
TypeSystemHelpers helper{m_typeSystem};
expressionAnnotation.type =
helper.typeFunctionType(
helper.tupleType(arguments),
m_typeSystem.type(*constructor, arguments)
);
}
}
else
{
m_errorReporter.typeError(4107_error, _expression.location(), "No type constructor registered for elementary type name.");
expressionAnnotation.type = m_typeSystem.freshTypeVariable({});
}
break;
case ExpressionContext::Sort:
m_errorReporter.typeError(2024_error, _expression.location(), "Elementary type name expression not supported in sort context.");
expressionAnnotation.type = m_typeSystem.freshTypeVariable({});
break;
}
return false;
}

bool TypeInference::visit(BinaryOperation const& _binaryOperation)
{
auto& operationAnnotation = annotation(_binaryOperation);
Expand Down Expand Up @@ -825,6 +767,8 @@ void TypeInference::endVisit(MemberAccess const& _memberAccess)

bool TypeInference::visit(TypeDefinition const& _typeDefinition)
{
bool isBuiltIn = dynamic_cast<Builtin const*>(_typeDefinition.typeExpression());

TypeSystemHelpers helper{m_typeSystem};
auto& typeDefinitionAnnotation = annotation(_typeDefinition);
if (typeDefinitionAnnotation.type)
Expand All @@ -833,14 +777,6 @@ bool TypeInference::visit(TypeDefinition const& _typeDefinition)
if (_typeDefinition.arguments())
_typeDefinition.arguments()->accept(*this);

std::optional<Type> underlyingType;
if (_typeDefinition.typeExpression())
{
ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type};
_typeDefinition.typeExpression()->accept(*this);
underlyingType = annotation(*_typeDefinition.typeExpression()).type;
}

std::vector<Type> arguments;
if (_typeDefinition.arguments())
for (size_t i = 0; i < _typeDefinition.arguments()->parameters().size(); ++i)
Expand All @@ -852,6 +788,19 @@ bool TypeInference::visit(TypeDefinition const& _typeDefinition)
else
typeDefinitionAnnotation.type = helper.typeFunctionType(helper.tupleType(arguments), definedType);

std::optional<Type> underlyingType;

if (isBuiltIn)
// TODO: This special case should eventually become user-definable.
underlyingType = m_wordType;
else if (_typeDefinition.typeExpression())
{
ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type};
_typeDefinition.typeExpression()->accept(*this);

underlyingType = annotation(*_typeDefinition.typeExpression()).type;
}

TypeConstructor constructor = typeConstructor(&_typeDefinition);
auto [members, newlyInserted] = annotation().members.emplace(constructor, std::map<std::string, TypeMember>{});
solAssert(newlyInserted, fmt::format("Members of type '{}' are already defined.", m_typeSystem.constructorInfo(constructor).name));
Expand All @@ -860,6 +809,15 @@ bool TypeInference::visit(TypeDefinition const& _typeDefinition)
members->second.emplace("abs", TypeMember{helper.functionType(*underlyingType, definedType)});
members->second.emplace("rep", TypeMember{helper.functionType(definedType, *underlyingType)});
}

if (helper.isPrimitiveType(definedType, PrimitiveType::Pair))
{
solAssert(isBuiltIn);
solAssert(arguments.size() == 2);
members->second.emplace("first", TypeMember{helper.functionType(definedType, arguments[0])});
members->second.emplace("second", TypeMember{helper.functionType(definedType, arguments[1])});
}

return false;
}

Expand Down
1 change: 0 additions & 1 deletion libsolidity/experimental/analysis/TypeInference.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class TypeInference: public ASTConstVisitor

bool visit(MemberAccess const& _memberAccess) override;
void endVisit(MemberAccess const& _memberAccess) override;
bool visit(ElementaryTypeNameExpression const& _expression) override;

bool visit(TypeClassDefinition const& _typeClassDefinition) override;
bool visit(TypeClassInstantiation const& _typeClassInstantiation) override;
Expand Down
90 changes: 57 additions & 33 deletions libsolidity/experimental/analysis/TypeRegistration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,30 @@ bool TypeRegistration::visit(TypeClassDefinition const& _typeClassDefinition)
return true;
}

bool TypeRegistration::visit(ElementaryTypeName const& _typeName)
bool TypeRegistration::visit(Builtin const& _builtin)
{
if (annotation(_typeName).typeConstructor)
if (annotation(_builtin).typeConstructor)
return false;
annotation(_typeName).typeConstructor = [&]() -> std::optional<TypeConstructor> {
switch (_typeName.typeName().token())
{
case Token::Void:
return m_typeSystem.constructor(PrimitiveType::Void);
case Token::Fun:
return m_typeSystem.constructor(PrimitiveType::Function);
case Token::Unit:
return m_typeSystem.constructor(PrimitiveType::Unit);
case Token::Pair:
return m_typeSystem.constructor(PrimitiveType::Pair);
case Token::Word:
return m_typeSystem.constructor(PrimitiveType::Word);
case Token::Integer:
return m_typeSystem.constructor(PrimitiveType::Integer);
case Token::Bool:
return m_typeSystem.constructor(PrimitiveType::Bool);
default:
m_errorReporter.fatalTypeError(7758_error, _typeName.location(), "Expected primitive type.");
return std::nullopt;
}
}();

auto primitiveType = [&](std::string _name) -> std::optional<PrimitiveType> {
if (_name == "void") return PrimitiveType::Void;
if (_name == "fun") return PrimitiveType::Function;
if (_name == "unit") return PrimitiveType::Unit;
if (_name == "pair") return PrimitiveType::Pair;
if (_name == "word") return PrimitiveType::Word;
if (_name == "integer") return PrimitiveType::Integer;
if (_name == "bool") return PrimitiveType::Bool;
return std::nullopt;
}(_builtin.nameParameter());

if (!primitiveType.has_value())
m_errorReporter.fatalTypeError(
7758_error,
_builtin.location(),
"Expected the name of a built-in primitive type."
);

annotation(_builtin).typeConstructor = m_typeSystem.constructor(primitiveType.value());
return true;
}

Expand Down Expand Up @@ -165,15 +163,41 @@ bool TypeRegistration::visit(TypeClassInstantiation const& _typeClassInstantiati

bool TypeRegistration::visit(TypeDefinition const& _typeDefinition)
{
if (annotation(_typeDefinition).typeConstructor)
return false;
annotation(_typeDefinition).typeConstructor = m_typeSystem.declareTypeConstructor(
_typeDefinition.name(),
"t_" + *_typeDefinition.annotation().canonicalName + "_" + util::toString(_typeDefinition.id()),
_typeDefinition.arguments() ? _typeDefinition.arguments()->parameters().size() : 0,
&_typeDefinition
);
return true;
return !annotation(_typeDefinition).typeConstructor.has_value();
}

void TypeRegistration::endVisit(TypeDefinition const& _typeDefinition)
{
if (annotation(_typeDefinition).typeConstructor.has_value())
return;

if (auto const* builtin = dynamic_cast<Builtin const*>(_typeDefinition.typeExpression()))
{
auto [previousDefinitionIt, inserted] = annotation().builtinTypeDefinitions.try_emplace(
builtin->nameParameter(),
&_typeDefinition
);

if (inserted)
annotation(_typeDefinition).typeConstructor = annotation(*builtin).typeConstructor;
else
{
auto const& [builtinName, previousDefinition] = *previousDefinitionIt;
m_errorReporter.typeError(
9609_error,
_typeDefinition.location(),
SecondarySourceLocation{}.append("Previous definition:", previousDefinition->location()),
"Duplicate builtin type definition."
);
}
}
else
annotation(_typeDefinition).typeConstructor = m_typeSystem.declareTypeConstructor(
_typeDefinition.name(),
"t_" + *_typeDefinition.annotation().canonicalName + "_" + util::toString(_typeDefinition.id()),
_typeDefinition.arguments() ? _typeDefinition.arguments()->parameters().size() : 0,
&_typeDefinition
);
}

TypeRegistration::Annotation& TypeRegistration::annotation(ASTNode const& _node)
Expand Down
Loading

0 comments on commit bdd2f02

Please sign in to comment.