data: Parsing Typedefs with ParseTypeUsage
With this change typedefs have two TokenLocations, one for the TypeName and one for the UnderlyingType. When the typedef is used with a variable the typedef is referenced now and not the underlying type.
This commit is contained in:
@@ -90,3 +90,5 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
typedef A* D;
|
typedef A* D;
|
||||||
|
|
||||||
|
D globalD;
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ void Storage::logLocations() const
|
|||||||
|
|
||||||
|
|
||||||
void Storage::onTypedefParsed(
|
void Storage::onTypedefParsed(
|
||||||
const ParseLocation& location, const std::string& fullName, const DataType& underlyingType, AccessType access
|
const ParseLocation& location, const std::string& fullName, const ParseTypeUsage& underlyingType, AccessType access
|
||||||
){
|
){
|
||||||
log("typedef", fullName + " -> " + underlyingType.getFullTypeName(), location);
|
log("typedef", fullName + " -> " + underlyingType.dataType.getFullTypeName(), location);
|
||||||
|
|
||||||
Node* node = m_graph.createNodeHierarchy(Node::NODE_TYPEDEF, fullName);
|
Node* node = m_graph.createNodeHierarchy(Node::NODE_TYPEDEF, fullName);
|
||||||
addAccess(node, access);
|
addAccess(node, access);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public:
|
|||||||
|
|
||||||
// ParserClient implementation
|
// ParserClient implementation
|
||||||
virtual void onTypedefParsed(
|
virtual void onTypedefParsed(
|
||||||
const ParseLocation& location, const std::string& fullName, const DataType& underlyingType,
|
const ParseLocation& location, const std::string& fullName, const ParseTypeUsage& underlyingType,
|
||||||
AccessType access
|
AccessType access
|
||||||
);
|
);
|
||||||
virtual void onClassParsed(
|
virtual void onClassParsed(
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public:
|
|||||||
virtual ~ParserClient();
|
virtual ~ParserClient();
|
||||||
|
|
||||||
virtual void onTypedefParsed(
|
virtual void onTypedefParsed(
|
||||||
const ParseLocation& location, const std::string& fullName, const DataType& underlyingType,
|
const ParseLocation& location, const std::string& fullName, const ParseTypeUsage& underlyingType,
|
||||||
AccessType access) = 0;
|
AccessType access) = 0;
|
||||||
virtual void onClassParsed(
|
virtual void onClassParsed(
|
||||||
const ParseLocation& location, const std::string& fullName, AccessType access,
|
const ParseLocation& location, const std::string& fullName, AccessType access,
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ bool ASTVisitor::VisitStmt(const clang::Stmt* statement)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ASTVisitor::VisitTypedefDecl(const clang::TypedefDecl* declaration)
|
bool ASTVisitor::VisitTypedefDecl(clang::TypedefDecl* declaration)
|
||||||
{
|
{
|
||||||
if (hasValidLocation(declaration))
|
if (hasValidLocation(declaration))
|
||||||
{
|
{
|
||||||
m_client->onTypedefParsed(
|
m_client->onTypedefParsed(
|
||||||
getParseLocation(declaration->getSourceRange()),
|
getParseLocationForNamedDecl(declaration),
|
||||||
declaration->getQualifiedNameAsString(),
|
declaration->getQualifiedNameAsString(),
|
||||||
utility::qualTypeToDataType(declaration->getUnderlyingType()),
|
getParseTypeUsage(declaration->getTypeSourceInfo()->getTypeLoc(), declaration->getUnderlyingType()),
|
||||||
convertAccessType(declaration->getAccess())
|
convertAccessType(declaration->getAccess())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -382,23 +382,21 @@ ParseVariable ASTVisitor::getParseVariable(clang::DeclaratorDecl* declaration) c
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ParseVariable(
|
return ParseVariable(
|
||||||
getParseTypeUsage(declaration),
|
getParseTypeUsage(declaration->getTypeSourceInfo()->getTypeLoc(), declaration->getType()),
|
||||||
declaration->getQualifiedNameAsString(),
|
declaration->getQualifiedNameAsString(),
|
||||||
isStatic
|
isStatic
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseTypeUsage ASTVisitor::getParseTypeUsage(clang::DeclaratorDecl* declaration) const
|
ParseTypeUsage ASTVisitor::getParseTypeUsage(clang::TypeLoc typeLoc, const clang::QualType& type) const
|
||||||
{
|
{
|
||||||
clang::TypeLoc loc = declaration->getTypeSourceInfo()->getTypeLoc();
|
while (typeLoc.getNextTypeLoc())
|
||||||
|
|
||||||
while (loc.getNextTypeLoc())
|
|
||||||
{
|
{
|
||||||
loc = loc.getNextTypeLoc();
|
typeLoc = typeLoc.getNextTypeLoc();
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseLocation parseLocation = getParseLocation(loc.getSourceRange());
|
ParseLocation parseLocation = getParseLocation(typeLoc.getSourceRange());
|
||||||
DataType dataType = utility::qualTypeToDataType(declaration->getType());
|
DataType dataType = utility::qualTypeToDataType(type);
|
||||||
|
|
||||||
parseLocation.endColumnNumber += dataType.getRawTypeName().size() - 1;
|
parseLocation.endColumnNumber += dataType.getRawTypeName().size() - 1;
|
||||||
|
|
||||||
@@ -410,19 +408,8 @@ ParseTypeUsage ASTVisitor::getParseTypeUsageOfReturnType(clang::FunctionDecl* de
|
|||||||
// TODO: use FunctionDecl::getReturnTypeSourceRange() in newer clang version
|
// TODO: use FunctionDecl::getReturnTypeSourceRange() in newer clang version
|
||||||
const clang::TypeSourceInfo *TSI = declaration->getTypeSourceInfo();
|
const clang::TypeSourceInfo *TSI = declaration->getTypeSourceInfo();
|
||||||
const clang::FunctionTypeLoc FTL = TSI->getTypeLoc().IgnoreParens().getAs<clang::FunctionTypeLoc>();
|
const clang::FunctionTypeLoc FTL = TSI->getTypeLoc().IgnoreParens().getAs<clang::FunctionTypeLoc>();
|
||||||
clang::TypeLoc loc = FTL.getReturnLoc();
|
|
||||||
|
|
||||||
while (loc.getNextTypeLoc())
|
return getParseTypeUsage(FTL.getReturnLoc(), declaration->getReturnType());
|
||||||
{
|
|
||||||
loc = loc.getNextTypeLoc();
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseLocation parseLocation = getParseLocation(loc.getSourceRange());
|
|
||||||
DataType dataType = utility::qualTypeToDataType(declaration->getReturnType());
|
|
||||||
|
|
||||||
parseLocation.endColumnNumber += dataType.getRawTypeName().size() - 1;
|
|
||||||
|
|
||||||
return ParseTypeUsage(parseLocation, dataType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ParseTypeUsage> ASTVisitor::getParameters(clang::FunctionDecl* declaration) const
|
std::vector<ParseTypeUsage> ASTVisitor::getParameters(clang::FunctionDecl* declaration) const
|
||||||
@@ -431,7 +418,8 @@ std::vector<ParseTypeUsage> ASTVisitor::getParameters(clang::FunctionDecl* decla
|
|||||||
|
|
||||||
for (unsigned i = 0; i < declaration->getNumParams(); i++)
|
for (unsigned i = 0; i < declaration->getNumParams(); i++)
|
||||||
{
|
{
|
||||||
parameters.push_back(getParseTypeUsage(declaration->getParamDecl(i)));
|
clang::ParmVarDecl* paramDecl = declaration->getParamDecl(i);
|
||||||
|
parameters.push_back(getParseTypeUsage(paramDecl->getTypeSourceInfo()->getTypeLoc(), paramDecl->getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public:
|
|||||||
// RecursiveASTVisitor implementation
|
// RecursiveASTVisitor implementation
|
||||||
virtual bool VisitStmt(const clang::Stmt* statement); // avoid visiting
|
virtual bool VisitStmt(const clang::Stmt* statement); // avoid visiting
|
||||||
|
|
||||||
virtual bool VisitTypedefDecl(const clang::TypedefDecl* declaration); // typedefs
|
virtual bool VisitTypedefDecl(clang::TypedefDecl* declaration); // typedefs
|
||||||
virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl* declaration); // structs, classes and inheritance
|
virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl* declaration); // structs, classes and inheritance
|
||||||
virtual bool VisitVarDecl(clang::VarDecl* declaration); // global variables and static fields
|
virtual bool VisitVarDecl(clang::VarDecl* declaration); // global variables and static fields
|
||||||
virtual bool VisitFieldDecl(clang::FieldDecl* declaration); // fields
|
virtual bool VisitFieldDecl(clang::FieldDecl* declaration); // fields
|
||||||
@@ -51,7 +51,7 @@ private:
|
|||||||
ParseLocation getParseLocationOfFunctionBody(clang::FunctionDecl* decl) const;
|
ParseLocation getParseLocationOfFunctionBody(clang::FunctionDecl* decl) const;
|
||||||
ParseLocation getParseLocationOfRecordBody(clang::CXXRecordDecl* decl) const;
|
ParseLocation getParseLocationOfRecordBody(clang::CXXRecordDecl* decl) const;
|
||||||
ParseVariable getParseVariable(clang::DeclaratorDecl* declaration) const;
|
ParseVariable getParseVariable(clang::DeclaratorDecl* declaration) const;
|
||||||
ParseTypeUsage getParseTypeUsage(clang::DeclaratorDecl* declaration) const;
|
ParseTypeUsage getParseTypeUsage(clang::TypeLoc typeLoc, const clang::QualType& type) const;
|
||||||
ParseTypeUsage getParseTypeUsageOfReturnType(clang::FunctionDecl* declaration) const;
|
ParseTypeUsage getParseTypeUsageOfReturnType(clang::FunctionDecl* declaration) const;
|
||||||
std::vector<ParseTypeUsage> getParameters(clang::FunctionDecl* declaration) const;
|
std::vector<ParseTypeUsage> getParameters(clang::FunctionDecl* declaration) const;
|
||||||
DataType qualTypeToDataType(clang::QualType qualType);
|
DataType qualTypeToDataType(clang::QualType qualType);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "data/type/modifier/DataTypeModifierReference.h"
|
#include "data/type/modifier/DataTypeModifierReference.h"
|
||||||
#include "data/type/DataTypeModifierStack.h"
|
#include "data/type/DataTypeModifierStack.h"
|
||||||
#include "data/type/DataTypeQualifierList.h"
|
#include "data/type/DataTypeQualifierList.h"
|
||||||
|
#include "utility/utilityString.h"
|
||||||
|
|
||||||
namespace utility
|
namespace utility
|
||||||
{
|
{
|
||||||
@@ -18,11 +19,18 @@ namespace utility
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
const clang::Type* type = qualType.getTypePtr();
|
const clang::Type* type = qualType.getTypePtr();
|
||||||
if (type->isPointerType())
|
if (type->getAs<clang::TypedefType>())
|
||||||
|
{
|
||||||
|
typeName = utility::substrAfter(qualType.getAsString(), ' ');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (type->isPointerType())
|
||||||
{
|
{
|
||||||
std::shared_ptr<DataTypeModifier> modifier = std::make_shared<DataTypeModifierPointer>();
|
std::shared_ptr<DataTypeModifier> modifier = std::make_shared<DataTypeModifierPointer>();
|
||||||
if (qualType.isConstQualified())
|
if (qualType.isConstQualified())
|
||||||
|
{
|
||||||
modifier->addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
modifier->addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
||||||
|
}
|
||||||
modifierStack.push(modifier);
|
modifierStack.push(modifier);
|
||||||
|
|
||||||
qualType = type->getPointeeType();
|
qualType = type->getPointeeType();
|
||||||
@@ -31,7 +39,9 @@ namespace utility
|
|||||||
{
|
{
|
||||||
std::shared_ptr<DataTypeModifier> modifier = std::make_shared<DataTypeModifierArray>();
|
std::shared_ptr<DataTypeModifier> modifier = std::make_shared<DataTypeModifierArray>();
|
||||||
if (qualType.isConstQualified())
|
if (qualType.isConstQualified())
|
||||||
|
{
|
||||||
modifier->addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
modifier->addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
||||||
|
}
|
||||||
modifierStack.push(modifier);
|
modifierStack.push(modifier);
|
||||||
|
|
||||||
qualType = clang::dyn_cast<clang::ArrayType>(type)->getElementType();
|
qualType = clang::dyn_cast<clang::ArrayType>(type)->getElementType();
|
||||||
@@ -46,21 +56,10 @@ namespace utility
|
|||||||
}
|
}
|
||||||
else if (type->isStructureOrClassType() || type->isEnumeralType())
|
else if (type->isStructureOrClassType() || type->isEnumeralType())
|
||||||
{
|
{
|
||||||
typeName = qualType.getAsString();
|
|
||||||
|
|
||||||
// we are working on the string here to not lose the namespace information stored in the name.
|
// we are working on the string here to not lose the namespace information stored in the name.
|
||||||
size_t nameStartPosition = typeName.find(' ');
|
typeName = utility::substrAfter(qualType.getAsString(), ' ');
|
||||||
if (nameStartPosition != typeName.npos)
|
|
||||||
{
|
|
||||||
nameStartPosition += 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nameStartPosition = 0;
|
|
||||||
}
|
|
||||||
typeName = typeName.substr(nameStartPosition, typeName.size());
|
|
||||||
|
|
||||||
//m_typeName = qualType.getBaseTypeIdentifier()->getName(); // this one does not keep namespace information.
|
// typeName = qualType.getBaseTypeIdentifier()->getName(); // this one does not keep namespace information.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -71,7 +70,9 @@ namespace utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (qualType.isConstQualified())
|
if (qualType.isConstQualified())
|
||||||
|
{
|
||||||
qualifierList.addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
qualifierList.addQualifier(DataTypeQualifierList::QUALIFIER_CONST);
|
||||||
|
}
|
||||||
|
|
||||||
return DataType(typeName, qualifierList, modifierStack);
|
return DataType(typeName, qualifierList, modifierStack);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
namespace utility
|
namespace utility
|
||||||
{
|
{
|
||||||
|
std::string substrAfter(const std::string& str, char delimiter)
|
||||||
|
{
|
||||||
|
size_t pos = str.find(delimiter);
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
return str.substr(pos + 1, str.size());
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
bool isPrefix(const std::string& prefix, const std::string& text)
|
bool isPrefix(const std::string& prefix, const std::string& text)
|
||||||
{
|
{
|
||||||
typedef std::pair<std::string::const_iterator, std::string::const_iterator> ResType;
|
typedef std::pair<std::string::const_iterator, std::string::const_iterator> ResType;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace utility
|
|||||||
template<typename ContainerType = std::vector<std::string>>
|
template<typename ContainerType = std::vector<std::string>>
|
||||||
ContainerType split(const std::string& str, const std::string& delimiter);
|
ContainerType split(const std::string& str, const std::string& delimiter);
|
||||||
|
|
||||||
|
std::string substrAfter(const std::string& str, char delimiter);
|
||||||
|
|
||||||
bool isPrefix(const std::string& prefix, const std::string& text);
|
bool isPrefix(const std::string& prefix, const std::string& text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> uint <1:1 1:22>");
|
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> uint <1:22 1:25>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_cxx_parser_finds_typedef_in_named_namespace()
|
void test_cxx_parser_finds_typedef_in_named_namespace()
|
||||||
@@ -376,7 +376,7 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> test::uint <3:2 3:23>");
|
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> test::uint <3:23 3:26>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_cxx_parser_finds_typedef_in_anonymous_namespace()
|
void test_cxx_parser_finds_typedef_in_anonymous_namespace()
|
||||||
@@ -389,7 +389,7 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> (anonymous namespace)::uint <3:2 3:23>");
|
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> (anonymous namespace)::uint <3:23 3:26>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_cxx_parser_finds_typedef_that_uses_type_defined_in_named_namespace()
|
void test_cxx_parser_finds_typedef_that_uses_type_defined_in_named_namespace()
|
||||||
@@ -403,7 +403,35 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
TS_ASSERT_EQUALS(client->typedefs[0], "test::TestStruct -> globalTestStruct <5:1 5:26>");
|
TS_ASSERT_EQUALS(client->typedefs[0], "test::TestStruct -> globalTestStruct <5:26 5:41>");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_cxx_parser_finds_global_variable_with_typedef_type()
|
||||||
|
{
|
||||||
|
std::shared_ptr<TestParserClient> client = parseCode(
|
||||||
|
"typedef unsigned int uint;\n"
|
||||||
|
"uint number;\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
|
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int -> uint <1:22 1:25>");
|
||||||
|
|
||||||
|
TS_ASSERT_EQUALS(client->globalVariables.size(), 1);
|
||||||
|
TS_ASSERT_EQUALS(client->globalVariables[0], "uint number <2:6 2:11>");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_cxx_parser_finds_global_variable_with_qualified_typedef_type()
|
||||||
|
{
|
||||||
|
std::shared_ptr<TestParserClient> client = parseCode(
|
||||||
|
"typedef unsigned int* uint;\n"
|
||||||
|
"const uint* number;\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
TS_ASSERT_EQUALS(client->typedefs.size(), 1);
|
||||||
|
TS_ASSERT_EQUALS(client->typedefs[0], "unsigned int * -> uint <1:23 1:26>");
|
||||||
|
|
||||||
|
TS_ASSERT_EQUALS(client->globalVariables.size(), 1);
|
||||||
|
TS_ASSERT_EQUALS(client->globalVariables[0], "uint const * number <2:13 2:18>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_cxx_parser_finds_public_inheritance()
|
void test_cxx_parser_finds_public_inheritance()
|
||||||
@@ -797,11 +825,11 @@ private:
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void onTypedefParsed(
|
virtual void onTypedefParsed(
|
||||||
const ParseLocation& location, const std::string& fullName, const DataType& underlyingType,
|
const ParseLocation& location, const std::string& fullName, const ParseTypeUsage& underlyingType,
|
||||||
AccessType access
|
AccessType access
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
std::string str = addAccessPrefix(underlyingType.getFullTypeName() + " -> " + fullName, access);
|
std::string str = addAccessPrefix(underlyingType.dataType.getFullTypeName() + " -> " + fullName, access);
|
||||||
typedefs.push_back(addLocationSuffix(str, location));
|
typedefs.push_back(addLocationSuffix(str, location));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,31 @@ public:
|
|||||||
TS_ASSERT_EQUALS(result[3], "C");
|
TS_ASSERT_EQUALS(result[3], "C");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_substr_after_with_single_delimiter_occurence()
|
||||||
|
{
|
||||||
|
TS_ASSERT_EQUALS(utility::substrAfter("foo bar", ' '), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_substr_after_with_multiple_delimiter_occurences()
|
||||||
|
{
|
||||||
|
TS_ASSERT_EQUALS(utility::substrAfter("foo bar foo", ' '), "bar foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_substr_after_with_no_delimiter_occurence()
|
||||||
|
{
|
||||||
|
TS_ASSERT_EQUALS(utility::substrAfter("foobar", ' '), "foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_substr_after_with_delimiter_at_start()
|
||||||
|
{
|
||||||
|
TS_ASSERT_EQUALS(utility::substrAfter(" foobar", ' '), "foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_substr_after_with_delimiter_at_end()
|
||||||
|
{
|
||||||
|
TS_ASSERT_EQUALS(utility::substrAfter("foobar ", ' '), "");
|
||||||
|
}
|
||||||
|
|
||||||
void test_empty_string_is_detected_as_prefix_of_any_other_string()
|
void test_empty_string_is_detected_as_prefix_of_any_other_string()
|
||||||
{
|
{
|
||||||
const std::string foo = "foo";
|
const std::string foo = "foo";
|
||||||
|
|||||||
Reference in New Issue
Block a user