Merge pull request #11806 from ethereum/user-defined-types

User defined value types
This commit is contained in:
Harikrishnan Mulackal
2021-09-09 10:28:35 +02:00
committed by GitHub
70 changed files with 1874 additions and 1 deletions
@@ -140,6 +140,30 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
return false;
}
void DeclarationTypeChecker::endVisit(UserDefinedValueTypeDefinition const& _userDefined)
{
TypeName const* typeName = _userDefined.underlyingType();
solAssert(typeName, "");
if (!dynamic_cast<ElementaryTypeName const*>(typeName))
m_errorReporter.fatalTypeError(
8657_error,
typeName->location(),
"The underlying type for a user defined value type has to be an elementary value type."
);
Type const* type = typeName->annotation().type;
solAssert(type, "");
solAssert(!dynamic_cast<UserDefinedValueType const*>(type), "");
if (!type->isValueType())
m_errorReporter.typeError(
8129_error,
_userDefined.location(),
"The underlying type of the user defined value type \"" +
_userDefined.name() +
"\" is not a value type."
);
}
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{
if (_typeName.annotation().type)
@@ -158,6 +182,8 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract);
else if (auto userDefinedValueType = dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::userDefinedValueType(*userDefinedValueType);
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
@@ -60,6 +60,7 @@ private:
void endVisit(VariableDeclaration const& _variable) override;
bool visit(EnumDefinition const& _enum) override;
bool visit(StructDefinition const& _struct) override;
void endVisit(UserDefinedValueTypeDefinition const& _userDefined) override;
bool visit(UsingForDirective const& _usingForDirective) override;
bool visit(InheritanceSpecifier const& _inheritanceSpecifier) override;
+7
View File
@@ -2481,6 +2481,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes = functionType->returnParameterTypes();
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
typeCheckFunctionGeneralChecks(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
break;
}
default:
{
typeCheckFunctionCall(_functionCall, functionType);
+6
View File
@@ -335,6 +335,12 @@ TypeNameAnnotation& TypeName::annotation() const
return initAnnotation<TypeNameAnnotation>();
}
Type const* UserDefinedValueTypeDefinition::type() const
{
solAssert(m_underlyingType->annotation().type, "");
return TypeProvider::typeType(TypeProvider::userDefinedValueType(*this));
}
Type const* StructDefinition::type() const
{
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
+31
View File
@@ -726,6 +726,37 @@ public:
Type const* type() const override;
};
/**
* User defined value types, i.e., custom types, for example, `type MyInt is int`. Allows creating a
* zero cost abstraction over value type with stricter type requirements.
*/
class UserDefinedValueTypeDefinition: public Declaration
{
public:
UserDefinedValueTypeDefinition(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> _name,
SourceLocation _nameLocation,
ASTPointer<TypeName> _underlyingType
):
Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default),
m_underlyingType(std::move(_underlyingType))
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Type const* type() const override;
TypeName const* underlyingType() const { return m_underlyingType.get(); }
private:
/// The name of the underlying type
ASTPointer<TypeName> m_underlyingType;
};
/**
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively
+1
View File
@@ -51,6 +51,7 @@ class UsingForDirective;
class StructDefinition;
class EnumDefinition;
class EnumValue;
class UserDefinedValueTypeDefinition;
class ParameterList;
class FunctionDefinition;
class VariableDeclaration;
+14
View File
@@ -354,6 +354,20 @@ bool ASTJsonConverter::visit(EnumValue const& _node)
return false;
}
bool ASTJsonConverter::visit(UserDefinedValueTypeDefinition const& _node)
{
solAssert(_node.underlyingType(), "");
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
make_pair("underlyingType", toJson(*_node.underlyingType()))
};
setJsonNode(_node, "UserDefinedValueTypeDefinition", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(ParameterList const& _node)
{
setJsonNode(_node, "ParameterList", {
+1
View File
@@ -81,6 +81,7 @@ public:
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(UserDefinedValueTypeDefinition const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override;
+12
View File
@@ -133,6 +133,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createEnumDefinition(_json);
if (nodeType == "EnumValue")
return createEnumValue(_json);
if (nodeType == "UserDefinedValueTypeDefinition")
return createUserDefinedValueTypeDefinition(_json);
if (nodeType == "ParameterList")
return createParameterList(_json);
if (nodeType == "OverrideSpecifier")
@@ -387,6 +389,16 @@ ASTPointer<EnumValue> ASTJsonImporter::createEnumValue(Json::Value const& _node)
);
}
ASTPointer<UserDefinedValueTypeDefinition> ASTJsonImporter::createUserDefinedValueTypeDefinition(Json::Value const& _node)
{
return createASTNode<UserDefinedValueTypeDefinition>(
_node,
memberAsASTString(_node, "name"),
createNameSourceLocation(_node),
convertJsonToASTNode<TypeName>(member(_node, "underlyingType"))
);
}
ASTPointer<ParameterList> ASTJsonImporter::createParameterList(Json::Value const& _node)
{
std::vector<ASTPointer<VariableDeclaration>> parameters;
+1
View File
@@ -81,6 +81,7 @@ private:
ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node);
ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node);
ASTPointer<EnumValue> createEnumValue(Json::Value const& _node);
ASTPointer<UserDefinedValueTypeDefinition> createUserDefinedValueTypeDefinition(Json::Value const& _node);
ASTPointer<ParameterList> createParameterList(Json::Value const& _node);
ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node);
ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node);
+4
View File
@@ -61,6 +61,7 @@ public:
virtual bool visit(IdentifierPath& _node) { return visitNode(_node); }
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
@@ -116,6 +117,7 @@ public:
virtual void endVisit(IdentifierPath& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
@@ -194,6 +196,7 @@ public:
virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition const& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective const& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
@@ -248,6 +251,7 @@ public:
virtual void endVisit(IdentifierPath const& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective const& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }
+20
View File
@@ -164,6 +164,26 @@ void EnumValue::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UsingForDirective::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
+5
View File
@@ -578,3 +578,8 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu
{
return createAndGet<MappingType>(_keyType, _valueType);
}
UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
{
return createAndGet<UserDefinedValueType>(_definition);
}
+2
View File
@@ -201,6 +201,8 @@ public:
static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
static UserDefinedValueType const* userDefinedValueType(UserDefinedValueTypeDefinition const& _definition);
private:
/// Global TypeProvider instance.
static TypeProvider& instance()
+60
View File
@@ -2533,6 +2533,36 @@ unsigned EnumType::memberValue(ASTString const& _member) const
solAssert(false, "Requested unknown enum value " + _member);
}
Type const& UserDefinedValueType::underlyingType() const
{
Type const* type = m_definition.underlyingType()->annotation().type;
solAssert(type, "");
return *type;
}
string UserDefinedValueType::richIdentifier() const
{
return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id());
}
bool UserDefinedValueType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
UserDefinedValueType const& other = dynamic_cast<UserDefinedValueType const&>(_other);
return other.definition() == definition();
}
string UserDefinedValueType::toString(bool /* _short */) const
{
return "user defined type " + definition().name();
}
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
{
return underlyingType().stackItems();
}
BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@@ -2884,6 +2914,8 @@ string FunctionType::richIdentifier() const
case Kind::GasLeft: id += "gasleft"; break;
case Kind::Event: id += "event"; break;
case Kind::Error: id += "error"; break;
case Kind::Wrap: id += "wrap"; break;
case Kind::Unwrap: id += "unwrap"; break;
case Kind::SetGas: id += "setgas"; break;
case Kind::SetValue: id += "setvalue"; break;
case Kind::BlockHash: id += "blockhash"; break;
@@ -3754,6 +3786,34 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
for (ASTPointer<EnumValue> const& enumValue: enumDef.members())
members.emplace_back(enumValue.get(), enumType);
}
else if (m_actualType->category() == Category::UserDefinedValueType)
{
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(*m_actualType);
members.emplace_back(
"wrap",
TypeProvider::function(
TypePointers{&userDefined.underlyingType()},
TypePointers{&userDefined},
strings{string{}},
strings{string{}},
FunctionType::Kind::Wrap,
false, /*_arbitraryParameters */
StateMutability::Pure
)
);
members.emplace_back(
"unwrap",
TypeProvider::function(
TypePointers{&userDefined},
TypePointers{&userDefined.underlyingType()},
strings{string{}},
strings{string{}},
FunctionType::Kind::Unwrap,
false, /* _arbitraryParameters */
StateMutability::Pure
)
);
}
else if (
auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType);
arrayType && arrayType->isByteArray()
+50 -1
View File
@@ -174,7 +174,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
};
@@ -1082,6 +1082,53 @@ private:
EnumDefinition const& m_enum;
};
/**
* The type of a UserDefinedValueType.
*/
class UserDefinedValueType: public Type
{
public:
explicit UserDefinedValueType(UserDefinedValueTypeDefinition const& _definition):
m_definition(_definition)
{}
Category category() const override { return Category::UserDefinedValueType; }
Type const& underlyingType() const;
UserDefinedValueTypeDefinition const& definition() const { return m_definition; }
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override { return &underlyingType(); }
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
unsigned calldataEncodedSize(bool _padded) const override { return underlyingType().calldataEncodedSize(_padded); }
bool leftAligned() const override { return underlyingType().leftAligned(); }
bool canBeStored() const override { return underlyingType().canBeStored(); }
u256 storageSize() const override { return underlyingType().storageSize(); }
bool isValueType() const override
{
solAssert(underlyingType().isValueType(), "");
return true;
}
bool nameable() const override
{
solAssert(underlyingType().nameable(), "");
return true;
}
std::string toString(bool _short) const override;
std::string canonicalName() const override { solAssert(false, ""); }
std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
private:
UserDefinedValueTypeDefinition const& m_definition;
};
/**
* Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders).
@@ -1150,6 +1197,8 @@ public:
RIPEMD160, ///< CALL to special contract for ripemd160
Event, ///< syntactic sugar for LOG*
Error, ///< creating an error instance in revert or require
Wrap, ///< customType.wrap(...) for user defined value types
Unwrap, ///< customType.unwrap(...) for user defined value types
SetGas, ///< modify the default gas value for the function call
SetValue, ///< modify the default value transfer for the function call
BlockHash, ///< BLOCKHASH
+27
View File
@@ -772,6 +772,33 @@ void CompilerUtils::convertType(
Type::Category stackTypeCategory = _typeOnStack.category();
Type::Category targetTypeCategory = _targetType.category();
if (stackTypeCategory == Type::Category::UserDefinedValueType)
{
solAssert(_cleanupNeeded, "");
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_typeOnStack);
solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType(), "");
return convertType(
userDefined.underlyingType(),
_targetType,
_cleanupNeeded,
_chopSignBits,
_asPartOfArgumentDecoding
);
}
if (targetTypeCategory == Type::Category::UserDefinedValueType)
{
solAssert(_cleanupNeeded, "");
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_targetType);
solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()), "");
return convertType(
_typeOnStack,
userDefined.underlyingType(),
_cleanupNeeded,
_chopSignBits,
_asPartOfArgumentDecoding
);
}
if (auto contrType = dynamic_cast<ContractType const*>(&_typeOnStack))
solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\"");
@@ -957,6 +957,35 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
);
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
solAssert(arguments.size() == 1, "");
Type const* argumentType = arguments.at(0)->annotation().type;
Type const* functionCallType = _functionCall.annotation().type;
solAssert(argumentType, "");
solAssert(functionCallType, "");
FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap)
{
solAssert(
argumentType->isImplicitlyConvertibleTo(
dynamic_cast<UserDefinedValueType const&>(*functionCallType).underlyingType()
),
""
);
solAssert(argumentType->isImplicitlyConvertibleTo(*function.parameterTypes()[0]), "");
}
else
solAssert(
dynamic_cast<UserDefinedValueType const&>(*argumentType) ==
dynamic_cast<UserDefinedValueType const&>(*function.parameterTypes()[0]),
""
);
acceptAndConvert(*arguments[0], *function.parameterTypes()[0]);
break;
}
case FunctionType::Kind::BlockHash:
{
acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true);
@@ -2157,6 +2186,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
{
// no-op
}
else if (dynamic_cast<StructDefinition const*>(declaration))
{
// no-op
+14
View File
@@ -3168,6 +3168,16 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _to == dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), "");
return conversionFunction(dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), _to);
}
if (_to.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _from.isImplicitlyConvertibleTo(dynamic_cast<UserDefinedValueType const&>(_to).underlyingType()), "");
return conversionFunction(_from, dynamic_cast<UserDefinedValueType const&>(_to).underlyingType());
}
if (_from.category() == Type::Category::Function)
{
solAssert(_to.category() == Type::Category::Function, "");
@@ -3696,6 +3706,9 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
string YulUtilFunctions::cleanupFunction(Type const& _type)
{
if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type))
return cleanupFunction(userDefinedValueType->underlyingType());
string functionName = string("cleanup_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
@@ -3816,6 +3829,7 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail
case Type::Category::Mapping:
case Type::Category::FixedBytes:
case Type::Category::Contract:
case Type::Category::UserDefinedValueType:
{
templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))");
break;
@@ -1049,6 +1049,24 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
);
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
solAssert(arguments.size() == 1, "");
FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap)
solAssert(
type(*arguments.at(0)).isImplicitlyConvertibleTo(
dynamic_cast<UserDefinedValueType const&>(type(_functionCall)).underlyingType()
),
""
);
else
solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType, "");
define(_functionCall, *arguments.at(0));
break;
}
case FunctionType::Kind::Assert:
case FunctionType::Kind::Require:
{
@@ -2001,6 +2019,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
}
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType))
define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n";
else if (dynamic_cast<UserDefinedValueType const*>(&actualType))
solAssert(member == "wrap" || member == "unwrap", "");
else if (auto const* arrayType = dynamic_cast<ArrayType const*>(&actualType))
solAssert(arrayType->isByteArray() && member == "concat", "");
else
@@ -2312,6 +2332,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
{
// no-op
}
else
{
solAssert(false, "Identifier type not expected in expression context.");
+21
View File
@@ -113,6 +113,9 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
case Token::Enum:
nodes.push_back(parseEnumDefinition());
break;
case Token::Type:
nodes.push_back(parseUserDefinedValueTypeDefinition());
break;
case Token::Function:
nodes.push_back(parseFunctionDefinition(true));
break;
@@ -364,6 +367,8 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition());
else if (currentTokenValue == Token::Type)
subNodes.push_back(parseUserDefinedValueTypeDefinition());
else if (
// Workaround because `error` is not a keyword.
currentTokenValue == Token::Identifier &&
@@ -1010,6 +1015,22 @@ ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName()
return nodeFactory.createNode<UserDefinedTypeName>(identifierPath);
}
ASTPointer<UserDefinedValueTypeDefinition> Parser::parseUserDefinedValueTypeDefinition()
{
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Type);
auto&& [name, nameLocation] = expectIdentifierWithLocation();
expectToken(Token::Is);
ASTPointer<TypeName> typeName = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UserDefinedValueTypeDefinition>(
name,
move(nameLocation),
typeName
);
}
ASTPointer<IdentifierPath> Parser::parseIdentifierPath()
{
RecursionGuard recursionGuard(*this);
+1
View File
@@ -95,6 +95,7 @@ private:
ASTPointer<ASTNode> parseFunctionDefinition(bool _freeFunction = false);
ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition();
ASTPointer<UserDefinedValueTypeDefinition> parseUserDefinedValueTypeDefinition();
ASTPointer<EnumValue> parseEnumValue();
ASTPointer<VariableDeclaration> parseVariableDeclaration(
VarDeclParserOptions const& _options = {},