diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -88,6 +88,7 @@ RK_ObjCInterface, RK_ObjCProtocol, RK_MacroDefinition, + RK_Typedef, }; private: @@ -396,6 +397,30 @@ virtual void anchor(); }; +/// This holds information associated with typedefs. +/// +/// Note: Typedefs for anonymous enums and structs typically don't get emitted +/// by the serializers but still get a TypedefRecord. Instead we use the +/// typedef name as a name for the underlying anonymous struct or enum. +struct TypedefRecord : APIRecord { + SymbolReference UnderlyingType; + + TypedefRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference UnderlyingType) + : APIRecord(RK_Typedef, Name, USR, Loc, Availability, LinkageInfo(), + Comment, Declaration, SubHeading), + UnderlyingType(UnderlyingType) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_Typedef; + } + +private: + virtual void anchor(); +}; + /// APISet holds the set of API records collected from given inputs. class APISet { public: @@ -564,6 +589,19 @@ DeclarationFragments Declaration, DeclarationFragments SubHeading); + /// Create a typedef record into the API set. + /// + /// Note: the caller is responsible for keeping the StringRef \p Name and + /// \p USR alive. APISet::copyString provides a way to copy strings into + /// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method + /// to generate the USR for \c D and keep it alive in APISet. + TypedefRecord *addTypedef(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + SymbolReference UnderlyingType); + /// A mapping type to store a set of APIRecord%s with the declaration name as /// the key. template &getMacros() const { return Macros; } + const RecordMap &getTypedefs() const { return Typedefs; } /// Generate and store the USR of declaration \p D. /// @@ -626,6 +665,7 @@ RecordMap ObjCInterfaces; RecordMap ObjCProtocols; RecordMap Macros; + RecordMap Typedefs; }; } // namespace extractapi diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -230,6 +230,10 @@ static DeclarationFragments getFragmentsForMacro(StringRef Name, const MacroDirective *MD); + /// Build DeclarationFragments for a typedef \p TypedefNameDecl. + static DeclarationFragments + getFragmentsForTypedef(const TypedefNameDecl *Decl); + /// Build sub-heading fragments for a NamedDecl. static DeclarationFragments getSubHeading(const NamedDecl *); diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -146,6 +146,9 @@ /// Serialize a macro defintion record. void serializeMacroDefinitionRecord(const MacroDefinitionRecord &Record); + /// Serialize a typedef record. + void serializeTypedefRecord(const TypedefRecord &Record); + /// Push a component to the current path components stack. /// /// \param Component The component to push onto the path components stack. diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -170,6 +170,17 @@ return addTopLevelRecord(Macros, Name, USR, Loc, Declaration, SubHeading); } +TypedefRecord *APISet::addTypedef(StringRef Name, StringRef USR, + PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + SymbolReference UnderlyingType) { + return addTopLevelRecord(Typedefs, Name, USR, Loc, Availability, Comment, + Declaration, SubHeading, UnderlyingType); +} + StringRef APISet::recordUSR(const Decl *D) { SmallString<128> USR; index::generateUSRForDecl(D, USR); @@ -211,3 +222,4 @@ void ObjCInterfaceRecord::anchor() {} void ObjCProtocolRecord::anchor() {} void MacroDefinitionRecord::anchor() {} +void TypedefRecord::anchor() {} diff --git a/clang/lib/ExtractAPI/CMakeLists.txt b/clang/lib/ExtractAPI/CMakeLists.txt --- a/clang/lib/ExtractAPI/CMakeLists.txt +++ b/clang/lib/ExtractAPI/CMakeLists.txt @@ -8,6 +8,7 @@ DeclarationFragments.cpp Serialization/SerializerBase.cpp Serialization/SymbolGraphSerializer.cpp + TypedefUnderlyingTypeResolver.cpp LINK_LIBS clangAST diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/ExtractAPI/DeclarationFragments.h" +#include "TypedefUnderlyingTypeResolver.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/StringSwitch.h" @@ -250,6 +251,31 @@ return Fragments.append(Base.getAsString(), DeclarationFragments::FragmentKind::Keyword); + // If the type is a typedefed type, get the underlying TypedefNameDecl for a + // direct reference to the typedef instead of the wrapped type. + if (const TypedefType *TypedefTy = dyn_cast(T)) { + const TypedefNameDecl *Decl = TypedefTy->getDecl(); + std::string USR = + TypedefUnderlyingTypeResolver(Context).getUSRForType(QualType(T, 0)); + return Fragments.append(Decl->getName(), + DeclarationFragments::FragmentKind::TypeIdentifier, + USR); + } + + // If the base type is a TagType (struct/interface/union/class/enum), let's + // get the underlying Decl for better names and USRs. + if (const TagType *TagTy = dyn_cast(Base)) { + const TagDecl *Decl = TagTy->getDecl(); + // Anonymous decl, skip this fragment. + if (Decl->getName().empty()) + return Fragments; + SmallString<128> TagUSR; + clang::index::generateUSRForDecl(Decl, TagUSR); + return Fragments.append(Decl->getName(), + DeclarationFragments::FragmentKind::TypeIdentifier, + TagUSR); + } + // If the base type is an ObjCInterfaceType, use the underlying // ObjCInterfaceDecl for the true USR. if (const auto *ObjCIT = dyn_cast(Base)) { @@ -426,8 +452,8 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForEnum(const EnumDecl *EnumDecl) { - // TODO: After we support typedef records, if there's a typedef for this enum - // just use the declaration fragments of the typedef decl. + if (const auto *TypedefNameDecl = EnumDecl->getTypedefNameForAnonDecl()) + return getFragmentsForTypedef(TypedefNameDecl); DeclarationFragments Fragments, After; Fragments.append("enum", DeclarationFragments::FragmentKind::Keyword); @@ -457,8 +483,8 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForStruct(const RecordDecl *Record) { - // TODO: After we support typedef records, if there's a typedef for this - // struct just use the declaration fragments of the typedef decl. + if (const auto *TypedefNameDecl = Record->getTypedefNameForAnonDecl()) + return getFragmentsForTypedef(TypedefNameDecl); DeclarationFragments Fragments; Fragments.append("struct", DeclarationFragments::FragmentKind::Keyword); @@ -680,6 +706,20 @@ return Fragments; } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForTypedef( + const TypedefNameDecl *Decl) { + DeclarationFragments Fragments, After; + Fragments.append("typedef", DeclarationFragments::FragmentKind::Keyword) + .appendSpace() + .append(getFragmentsForType(Decl->getUnderlyingType(), + Decl->getASTContext(), After)) + .append(std::move(After)) + .appendSpace() + .append(Decl->getName(), DeclarationFragments::FragmentKind::Identifier); + + return Fragments; +} + template FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionT *Function) { diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -12,6 +12,7 @@ /// //===----------------------------------------------------------------------===// +#include "TypedefUnderlyingTypeResolver.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" @@ -41,6 +42,13 @@ namespace { +StringRef getTypedefName(const TagDecl *Decl) { + if (const auto *TypedefDecl = Decl->getTypedefNameForAnonDecl()) + return TypedefDecl->getName(); + + return {}; +} + /// The RecursiveASTVisitor to traverse symbol declarations and collect API /// information. class ExtractAPIVisitor : public RecursiveASTVisitor { @@ -161,6 +169,8 @@ // Collect symbol information. StringRef Name = Decl->getName(); + if (Name.empty()) + Name = getTypedefName(Decl); StringRef USR = API.recordUSR(Decl); PresumedLoc Loc = Context.getSourceManager().getPresumedLoc(Decl->getLocation()); @@ -196,6 +206,8 @@ // Collect symbol information. StringRef Name = Decl->getName(); + if (Name.empty()) + Name = getTypedefName(Decl); StringRef USR = API.recordUSR(Decl); PresumedLoc Loc = Context.getSourceManager().getPresumedLoc(Decl->getLocation()); @@ -296,6 +308,36 @@ return true; } + bool VisitTypedefNameDecl(const TypedefNameDecl *Decl) { + // Skip ObjC Type Parameter for now. + if (isa(Decl)) + return true; + + if (!Decl->isDefinedOutsideFunctionOrMethod()) + return true; + + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Decl->getLocation()); + StringRef Name = Decl->getName(); + AvailabilityInfo Availability = getAvailability(Decl); + StringRef USR = API.recordUSR(Decl); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + QualType Type = Decl->getUnderlyingType(); + SymbolReference SymRef = + TypedefUnderlyingTypeResolver(Context).getSymbolReferenceForType(Type, + API); + + API.addTypedef(Name, USR, Loc, Availability, Comment, + DeclarationFragmentsBuilder::getFragmentsForTypedef(Decl), + DeclarationFragmentsBuilder::getSubHeading(Decl), SymRef); + + return true; + } + private: /// Get availability information of the declaration \p D. AvailabilityInfo getAvailability(const Decl *D) const { diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -408,6 +408,11 @@ case APIRecord::RK_MacroDefinition: Kind["identifier"] = AddLangPrefix("macro"); Kind["displayName"] = "Macro"; + break; + case APIRecord::RK_Typedef: + Kind["identifier"] = AddLangPrefix("typealias"); + Kind["displayName"] = "Type Alias"; + break; } return Kind; @@ -618,6 +623,27 @@ Symbols.emplace_back(std::move(*Macro)); } +void SymbolGraphSerializer::serializeTypedefRecord( + const TypedefRecord &Record) { + // Typedefs of anonymous types have their entries unified with the underlying + // type. + bool ShouldDrop = Record.UnderlyingType.Name.empty(); + // enums declared with `NS_OPTION` have a named enum and a named typedef, with + // the same name + ShouldDrop |= (Record.UnderlyingType.Name == Record.Name); + if (ShouldDrop) + return; + + auto TypedefPathComponentGuard = makePathComponentGuard(Record.Name); + auto Typedef = serializeAPIRecord(Record); + if (!Typedef) + return; + + (*Typedef)["type"] = Record.UnderlyingType.USR; + + Symbols.emplace_back(std::move(*Typedef)); +} + SymbolGraphSerializer::PathComponentGuard SymbolGraphSerializer::makePathComponentGuard(StringRef Component) { return PathComponentGuard(PathComponents, Component); @@ -651,6 +677,9 @@ for (const auto &Macro : API.getMacros()) serializeMacroDefinitionRecord(*Macro.second); + for (const auto &Typedef : API.getTypedefs()) + serializeTypedefRecord(*Typedef.second); + Root["symbols"] = std::move(Symbols); Root["relationships"] = std::move(Relationships); diff --git a/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.h b/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.h new file mode 100644 --- /dev/null +++ b/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.h @@ -0,0 +1,46 @@ +//===- ExtractAPI/TypedefUnderlyingTypeResolver.h ---------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the UnderlyingTypeResolver which is a helper type for +/// resolving the undelrying type for a given QualType and exposing that +/// information in various forms. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNDERLYING_TYPE_RESOLVER_H +#define LLVM_CLANG_UNDERLYING_TYPE_RESOLVER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ExtractAPI/API.h" + +#include + +namespace clang { +namespace extractapi { + +struct TypedefUnderlyingTypeResolver { + + /// Get a SymbolReference for the given type. + SymbolReference getSymbolReferenceForType(QualType Type, APISet &API) const; + + /// Get a USR for the given type. + std::string getUSRForType(QualType Type) const; + + explicit TypedefUnderlyingTypeResolver(ASTContext &Context) + : Context(Context) {} + +private: + ASTContext &Context; +}; + +} // namespace extractapi +} // namespace clang + +#endif // LLVM_CLANG_UNDERLYING_TYPE_RESOLVER_H diff --git a/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.cpp b/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/ExtractAPI/TypedefUnderlyingTypeResolver.cpp @@ -0,0 +1,79 @@ +//===- ExtractAPI/TypedefUnderlyingTypeResolver.cpp -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements UnderlyingTypeResolver. +/// +//===----------------------------------------------------------------------===// + +#include "TypedefUnderlyingTypeResolver.h" +#include "clang/Index/USRGeneration.h" + +using namespace clang; +using namespace extractapi; + +namespace { + +const NamedDecl *getUnderlyingTypeDecl(QualType Type) { + const NamedDecl *TypeDecl = nullptr; + + const TypedefType *TypedefTy = Type->getAs(); + if (TypedefTy) + TypeDecl = TypedefTy->getDecl(); + if (const TagType *TagTy = Type->getAs()) { + TypeDecl = TagTy->getDecl(); + } else if (const ObjCInterfaceType *ObjCITy = + Type->getAs()) { + TypeDecl = ObjCITy->getDecl(); + } + + if (TypeDecl && TypedefTy) { + // if this is a typedef to another typedef, use the typedef's decl for the + // USR - this will actually be in the output, unlike a typedef to an + // anonymous decl + const TypedefNameDecl *TypedefDecl = TypedefTy->getDecl(); + if (TypedefDecl->getUnderlyingType()->isTypedefNameType()) + TypeDecl = TypedefDecl; + } + + return TypeDecl; +} + +} // namespace + +SymbolReference +TypedefUnderlyingTypeResolver::getSymbolReferenceForType(QualType Type, + APISet &API) const { + std::string TypeName = Type.getAsString(); + SmallString<128> TypeUSR; + const NamedDecl *TypeDecl = getUnderlyingTypeDecl(Type); + const TypedefType *TypedefTy = Type->getAs(); + + if (TypeDecl) { + if (!TypedefTy) + TypeName = TypeDecl->getName().str(); + + clang::index::generateUSRForDecl(TypeDecl, TypeUSR); + } else { + clang::index::generateUSRForType(Type, Context, TypeUSR); + } + + return {API.copyString(TypeName), API.copyString(TypeUSR)}; +} + +std::string TypedefUnderlyingTypeResolver::getUSRForType(QualType Type) const { + SmallString<128> TypeUSR; + const NamedDecl *TypeDecl = getUnderlyingTypeDecl(Type); + + if (TypeDecl) + clang::index::generateUSRForDecl(TypeDecl, TypeUSR); + else + clang::index::generateUSRForType(Type, Context, TypeUSR); + + return std::string(TypeUSR); +} diff --git a/clang/test/ExtractAPI/typedef.c b/clang/test/ExtractAPI/typedef.c new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/typedef.c @@ -0,0 +1,101 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ +// RUN: %t/reference.output.json +// RUN: %clang -extract-api --product-name=Typedef -target arm64-apple-macosx \ +// RUN: -x objective-c-header %t/input.h -o %t/output.json | FileCheck -allow-empty %s + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/output.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- input.h +typedef int MyInt; + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "Typedef", + "platform": { + "architecture": "arm64", + "operatingSystem": { + "minimumVersion": { + "major": 11, + "minor": 0, + "patch": 0 + }, + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyInt" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyInt" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 13, + "line": 1 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyInt" + } + ], + "title": "MyInt" + }, + "pathComponents": [ + "MyInt" + ], + "type": "c:I" + } + ] +} diff --git a/clang/test/ExtractAPI/typedef_anonymous_record.c b/clang/test/ExtractAPI/typedef_anonymous_record.c new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/typedef_anonymous_record.c @@ -0,0 +1,203 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ +// RUN: %t/reference.output.json +// RUN: %clang -extract-api --product-name=TypedefChain -target arm64-apple-macosx \ +// RUN: -x objective-c-header %t/input.h -o %t/output.json | FileCheck -allow-empty %s + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/output.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- input.h +typedef struct { } MyStruct; +typedef MyStruct MyStructStruct; +typedef MyStructStruct MyStructStructStruct; + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "TypedefChain", + "platform": { + "architecture": "arm64", + "operatingSystem": { + "minimumVersion": { + "major": 11, + "minor": 0, + "patch": 0 + }, + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "keyword", + "spelling": "struct" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyStruct" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:@SA@MyStruct" + }, + "kind": { + "displayName": "Structure", + "identifier": "objective-c.struct" + }, + "location": { + "position": { + "character": 9, + "line": 1 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "title": "MyStruct" + }, + "pathComponents": [ + "MyStruct" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:@SA@MyStruct", + "spelling": "MyStruct" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyStructStruct" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyStructStruct" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 18, + "line": 2 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyStructStruct" + } + ], + "title": "MyStructStruct" + }, + "pathComponents": [ + "MyStructStruct" + ], + "type": "c:@SA@MyStruct" + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:input.h@T@MyStructStruct", + "spelling": "MyStructStruct" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyStructStructStruct" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyStructStructStruct" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 24, + "line": 3 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyStructStructStruct" + } + ], + "title": "MyStructStructStruct" + }, + "pathComponents": [ + "MyStructStructStruct" + ], + "type": "c:input.h@T@MyStructStruct" + } + ] +} diff --git a/clang/test/ExtractAPI/typedef_chain.c b/clang/test/ExtractAPI/typedef_chain.c new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/typedef_chain.c @@ -0,0 +1,211 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ +// RUN: %t/reference.output.json +// RUN: %clang -extract-api --product-name=TypedefChain -target arm64-apple-macosx \ +// RUN: -x objective-c-header %t/input.h -o %t/output.json | FileCheck -allow-empty %s + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/output.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- input.h +typedef int MyInt; +typedef MyInt MyIntInt; +typedef MyIntInt MyIntIntInt; + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "TypedefChain", + "platform": { + "architecture": "arm64", + "operatingSystem": { + "minimumVersion": { + "major": 11, + "minor": 0, + "patch": 0 + }, + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyInt" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyInt" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 13, + "line": 1 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyInt" + } + ], + "title": "MyInt" + }, + "pathComponents": [ + "MyInt" + ], + "type": "c:I" + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:input.h@T@MyInt", + "spelling": "MyInt" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyIntInt" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyIntInt" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 15, + "line": 2 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyIntInt" + } + ], + "title": "MyIntInt" + }, + "pathComponents": [ + "MyIntInt" + ], + "type": "c:input.h@T@MyInt" + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "typedef" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:input.h@T@MyIntInt", + "spelling": "MyIntInt" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyIntIntInt" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:input.h@T@MyIntIntInt" + }, + "kind": { + "displayName": "Type Alias", + "identifier": "objective-c.typealias" + }, + "location": { + "position": { + "character": 18, + "line": 3 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyIntIntInt" + } + ], + "title": "MyIntIntInt" + }, + "pathComponents": [ + "MyIntIntInt" + ], + "type": "c:input.h@T@MyIntInt" + } + ] +}