Parsing nested types.
The grammar supports nested types now. preprocess/dumpapi dumps nested
types.
interface IFoo {
enum Type { FOO, BAR }
parcelable Result { ... }
Result foo(Type type);
}
Backend support is not supported yet.
Bug: 182508839
Test: aidl_unittests
Change-Id: Ic7d1bc8aec788b65de85caad2a813a52fb2cfc5e
diff --git a/aidl.cpp b/aidl.cpp
index e90fae1..882aef6 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -688,6 +688,12 @@
for (const auto& defined_type : typenames.MainDocument().DefinedTypes()) {
AIDL_FATAL_IF(defined_type == nullptr, input_file);
+ // TODO(b/182508839) add backend support for nested types
+ if (!defined_type->GetNestedTypes().empty()) {
+ AIDL_ERROR(defined_type) << "Nested types are not supported yet.";
+ return false;
+ }
+
string output_file_name = options.OutputFile();
// if needed, generate the output file name from the base folder
if (output_file_name.empty() && !options.OutputDir().empty()) {
diff --git a/aidl_dumpapi.cpp b/aidl_dumpapi.cpp
index ce5a1f7..7d3f19d 100644
--- a/aidl_dumpapi.cpp
+++ b/aidl_dumpapi.cpp
@@ -56,6 +56,9 @@
for (const auto& constdecl : dt.GetConstantDeclarations()) {
constdecl->DispatchVisit(*this);
}
+ for (const auto& nested : dt.GetNestedTypes()) {
+ nested->DispatchVisit(*this);
+ }
}
// Dumps comment only if its has meaningful tags.
diff --git a/aidl_language.cpp b/aidl_language.cpp
index a0cad71..b1c4a12 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -882,7 +882,7 @@
}
AidlMember::AidlMember(const AidlLocation& location, const Comments& comments)
- : AidlCommentable(location, comments) {}
+ : AidlAnnotatable(location, comments) {}
AidlConstantDeclaration::AidlConstantDeclaration(const AidlLocation& location,
AidlTypeSpecifier* type, const std::string& name,
@@ -962,7 +962,7 @@
AidlDefinedType::AidlDefinedType(const AidlLocation& location, const std::string& name,
const Comments& comments, const std::string& package,
std::vector<std::unique_ptr<AidlMember>>* members)
- : AidlAnnotatable(location, comments), AidlScope(this), name_(name), package_(package) {
+ : AidlMember(location, comments), AidlScope(this), name_(name), package_(package) {
// adjust name/package when name is fully qualified (for preprocessed files)
if (package_.empty() && name_.find('.') != std::string::npos) {
// Note that this logic is absolutely wrong. Given a parcelable
@@ -984,6 +984,9 @@
variables_.emplace_back(variable);
} else if (auto method = AidlCast<AidlMethod>(*m); method) {
methods_.emplace_back(method);
+ } else if (auto type = AidlCast<AidlDefinedType>(*m); type) {
+ type->SetEnclosingScope(this);
+ types_.emplace_back(type);
} else {
AIDL_FATAL(*m) << "Unknown member type.";
}
@@ -1007,12 +1010,39 @@
if (package_.empty()) {
return GetName();
}
+ if (auto parent = GetParentType(); parent) {
+ return parent->GetCanonicalName() + "." + GetName();
+ }
return GetPackage() + "." + GetName();
}
bool AidlDefinedType::CheckValidWithMembers(const AidlTypenames& typenames) const {
bool success = true;
+ for (const auto& t : GetNestedTypes()) {
+ success = success && t->CheckValid(typenames);
+ }
+
+ std::set<std::string> nested_type_names;
+ for (const auto& t : GetNestedTypes()) {
+ bool duplicated = !nested_type_names.emplace(t->GetName()).second;
+ if (duplicated) {
+ AIDL_ERROR(t) << "Redefinition of '" << t->GetName() << "'.";
+ success = false;
+ }
+ // nested type can't have a parent name
+ if (t->GetName() == GetName()) {
+ AIDL_ERROR(t) << "Nested type '" << GetName() << "' has the same name as its parent.";
+ success = false;
+ }
+ // For now we don't allow "interface" to be nested
+ if (AidlCast<AidlInterface>(*t)) {
+ AIDL_ERROR(t) << "'" << t->GetName()
+ << "' is nested. Interfaces should be at the root scope.";
+ return false;
+ }
+ }
+
for (const auto& v : GetFields()) {
const bool field_valid = v->CheckValid(typenames);
success = success && field_valid;
@@ -1066,23 +1096,45 @@
return success;
}
+const AidlDefinedType* AidlDefinedType::GetParentType() const {
+ AIDL_FATAL_IF(GetEnclosingScope() == nullptr, this) << "Scope is not set.";
+ return AidlCast<AidlDefinedType>(GetEnclosingScope()->GetNode());
+}
+
+// Resolve `name` in the current scope. If not found, delegate to the parent
std::string AidlDefinedType::ResolveName(const std::string& name) const {
- // TODO(b/182508839): resolve with nested types when we support nested types
- //
- // For example, in the following (the syntax is TBD), t1's Type is x.Foo.Bar.Type
- // while t2's Type is y.Type.
- //
+ // For example, in the following, t1's type Baz means x.Foo.Bar.Baz
+ // while t2's type is y.Baz.
// package x;
- // import y.Type;
+ // import y.Baz;
// parcelable Foo {
// parcelable Bar {
- // enum Type { Type1, Type2 }
- // Type t1;
+ // enum Baz { ... }
+ // Baz t1; // -> should be x.Foo.Bar.Baz
// }
- // Type t2;
+ // Baz t2; // -> should be y.Baz
+ // Bar.Baz t3; // -> should be x.Foo.Bar.Baz
// }
AIDL_FATAL_IF(!GetEnclosingScope(), this)
<< "Type should have an enclosing scope.(e.g. AidlDocument)";
+ if (AidlTypenames::IsBuiltinTypename(name)) {
+ return name;
+ }
+
+ const auto first_dot = name.find_first_of('.');
+ // For "Outer.Inner", we look up "Outer" in the import list.
+ const std::string class_name =
+ (first_dot == std::string::npos) ? name : name.substr(0, first_dot);
+ // Keep ".Inner", to make a fully-qualified name
+ const std::string nested_type = (first_dot == std::string::npos) ? "" : name.substr(first_dot);
+
+ // check if it is a nested type
+ for (const auto& type : GetNestedTypes()) {
+ if (type->GetName() == class_name) {
+ return type->GetCanonicalName() + nested_type;
+ }
+ }
+
return GetEnclosingScope()->ResolveName(name);
}
diff --git a/aidl_language.h b/aidl_language.h
index 59bd1cd..2f81522 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -458,7 +458,7 @@
// Returns the universal value unaltered.
std::string AidlConstantValueDecorator(const AidlTypeSpecifier& type, const std::string& raw_value);
-class AidlMember : public AidlCommentable {
+class AidlMember : public AidlAnnotatable {
public:
AidlMember(const AidlLocation& location, const Comments& comments);
virtual ~AidlMember() = default;
@@ -894,7 +894,7 @@
// AidlDefinedType represents either an interface, parcelable, or enum that is
// defined in the source file.
-class AidlDefinedType : public AidlAnnotatable, public AidlScope {
+class AidlDefinedType : public AidlMember, public AidlScope {
public:
AidlDefinedType(const AidlLocation& location, const std::string& name, const Comments& comments,
const std::string& package, std::vector<std::unique_ptr<AidlMember>>* members);
@@ -963,7 +963,8 @@
return const_cast<AidlParcelable*>(
const_cast<const AidlDefinedType*>(this)->AsUnstructuredParcelable());
}
-
+ const AidlDefinedType* GetParentType() const;
+ const std::vector<std::unique_ptr<AidlDefinedType>>& GetNestedTypes() const { return types_; }
const std::vector<std::unique_ptr<AidlVariableDeclaration>>& GetFields() const {
return variables_;
}
@@ -992,6 +993,7 @@
std::vector<std::unique_ptr<AidlVariableDeclaration>> variables_;
std::vector<std::unique_ptr<AidlConstantDeclaration>> constants_;
std::vector<std::unique_ptr<AidlMethod>> methods_;
+ std::vector<std::unique_ptr<AidlDefinedType>> types_;
std::vector<const AidlMember*> members_; // keep members in order of appearance.
};
diff --git a/aidl_language_y.yy b/aidl_language_y.yy
index b9e1d0c..e37fc3f 100644
--- a/aidl_language_y.yy
+++ b/aidl_language_y.yy
@@ -347,6 +347,10 @@
$1->emplace_back($2);
$$ = $1;
}
+ | parcelable_members decl {
+ $1->emplace_back($2);
+ $$ = $1;
+ }
| parcelable_members error ';' {
ps->AddError();
$$ = $1;
@@ -398,6 +402,8 @@
{ $1->push_back(std::unique_ptr<AidlMember>($2)); $$ = $1; }
| interface_members constant_decl
{ $1->push_back(std::unique_ptr<AidlMember>($2)); $$ = $1; }
+ | interface_members decl
+ { $1->push_back(std::unique_ptr<AidlMember>($2)); $$ = $1; }
| interface_members error ';' {
ps->AddError();
$$ = $1;
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index 13d7bcf..d1ccfcb 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -127,36 +127,50 @@
// Add types in two steps to avoid adding a type while the doc is rejected.
// 1. filter types to add
// 2. add types
- for (const auto& type : doc->DefinedTypes()) {
- if (IsBuiltinTypename(type->GetName())) {
- // ParcelFileDescriptor is treated as a built-in type, but it's also in the framework.aidl.
- // So aidl should ignore built-in types in framework.aidl to prevent duplication.
- // (b/130899491)
- if (is_preprocessed) {
- continue;
- }
- // HasValidNameComponents handles name conflicts with built-in types
- }
- if (auto prev_definition = defined_types_.find(type->GetCanonicalName());
- prev_definition != defined_types_.end()) {
- // Skip duplicate type in preprocessed document
- if (is_preprocessed) {
- continue;
+ std::function<bool(const std::vector<std::unique_ptr<AidlDefinedType>>&)> collect_types_to_add;
+ collect_types_to_add = [&](auto& types) {
+ for (const auto& type : types) {
+ if (IsBuiltinTypename(type->GetName())) {
+ // ParcelFileDescriptor is treated as a built-in type, but it's also in the framework.aidl.
+ // So aidl should ignore built-in types in framework.aidl to prevent duplication.
+ // (b/130899491)
+ if (is_preprocessed) {
+ continue;
+ }
+ // HasValidNameComponents handles name conflicts with built-in types
}
- // Overwrite duplicate type which is already added via preprocessed with a new one
- if (!prev_definition->second->GetDocument().IsPreprocessed()) {
- AIDL_ERROR(type) << "redefinition: " << type->GetCanonicalName() << " is defined "
- << prev_definition->second->GetLocation();
+
+ if (auto prev_definition = defined_types_.find(type->GetCanonicalName());
+ prev_definition != defined_types_.end()) {
+ // Skip duplicate type in preprocessed document
+ if (is_preprocessed) {
+ continue;
+ }
+ // Overwrite duplicate type which is already added via preprocessed with a new one
+ if (!prev_definition->second->GetDocument().IsPreprocessed()) {
+ AIDL_ERROR(type) << "redefinition: " << type->GetCanonicalName() << " is defined "
+ << prev_definition->second->GetLocation();
+ return false;
+ }
+ }
+
+ if (!HasValidNameComponents(*type)) {
+ return false;
+ }
+
+ types_to_add.push_back(type.get());
+
+ // recursively check nested types
+ if (!collect_types_to_add(type->GetNestedTypes())) {
return false;
}
}
+ return true;
+ };
- if (!HasValidNameComponents(*type)) {
- return false;
- }
-
- types_to_add.push_back(type.get());
+ if (!collect_types_to_add(doc->DefinedTypes())) {
+ return false;
}
for (const auto& type : types_to_add) {
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 25437f9..b650e97 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -25,6 +25,7 @@
#include <memory>
#include <set>
#include <string>
+#include <variant>
#include <vector>
#include "aidl_checkapi.h"
@@ -45,6 +46,7 @@
using std::set;
using std::string;
using std::unique_ptr;
+using std::variant;
using std::vector;
using testing::HasSubstr;
using testing::TestParamInfo;
@@ -1414,7 +1416,7 @@
EXPECT_EQ("-1", cpp_constants[0]->ValueString(cpp::ConstantValueDecorator));
}
-TEST_P(AidlTest, UnderstandsNestedParcelables) {
+TEST_P(AidlTest, UnderstandsNestedUnstructuredParcelables) {
io_delegate_.SetFileContents(
"p/Outer.aidl",
"package p; parcelable Outer.Inner cpp_header \"baz/header\";");
@@ -1432,7 +1434,7 @@
EXPECT_EQ("::p::Outer::Inner", cpp::CppNameOf(nested_type, typenames_));
}
-TEST_P(AidlTest, UnderstandsNestedParcelablesWithoutImports) {
+TEST_P(AidlTest, UnderstandsNestedUnstructuredParcelablesWithoutImports) {
io_delegate_.SetFileContents("p/Outer.aidl",
"package p; parcelable Outer.Inner cpp_header \"baz/header\";");
import_paths_.emplace("");
@@ -1448,6 +1450,221 @@
EXPECT_EQ("::p::Outer::Inner", cpp::CppNameOf(nested_type, typenames_));
}
+TEST_F(AidlTest, UnderstandsNestedTypes) {
+ io_delegate_.SetFileContents("p/IOuter.aidl",
+ "package p;\n"
+ "interface IOuter {\n"
+ " parcelable Inner {}\n"
+ "}");
+ import_paths_.emplace("");
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "import p.IOuter;\n"
+ "interface IFoo {\n"
+ " IOuter.Inner get();\n"
+ "}";
+ CaptureStderr();
+ EXPECT_NE(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_EQ(GetCapturedStderr(), "");
+
+ EXPECT_TRUE(typenames_.ResolveTypename("p.IOuter.Inner").is_resolved);
+ // C++ uses "::" instead of "." to refer to a inner class.
+ AidlTypeSpecifier nested_type(AIDL_LOCATION_HERE, "p.IOuter.Inner", false, nullptr, {});
+ EXPECT_EQ("::p::IOuter::Inner", cpp::CppNameOf(nested_type, typenames_));
+}
+
+TEST_F(AidlTest, UnderstandsNestedTypesViaFullyQualifiedName) {
+ io_delegate_.SetFileContents("p/IOuter.aidl",
+ "package p;\n"
+ "interface IOuter {\n"
+ " parcelable Inner {}\n"
+ "}");
+ import_paths_.emplace("");
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "interface IFoo {\n"
+ " p.IOuter.Inner get();\n"
+ "}";
+ CaptureStderr();
+ EXPECT_NE(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_EQ(GetCapturedStderr(), "");
+
+ EXPECT_TRUE(typenames_.ResolveTypename("p.IOuter.Inner").is_resolved);
+}
+
+TEST_F(AidlTest, UnderstandsNestedTypesViaFullyQualifiedImport) {
+ io_delegate_.SetFileContents("p/IOuter.aidl",
+ "package p;\n"
+ "interface IOuter {\n"
+ " parcelable Inner {}\n"
+ "}");
+ import_paths_.emplace("");
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "import p.IOuter.Inner;"
+ "interface IFoo {\n"
+ " Inner get();\n"
+ "}";
+ CaptureStderr();
+ EXPECT_NE(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_EQ(GetCapturedStderr(), "");
+
+ EXPECT_TRUE(typenames_.ResolveTypename("p.IOuter.Inner").is_resolved);
+}
+
+TEST_F(AidlTest, UnderstandsNestedTypesInTheSameScope) {
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "interface IFoo {\n"
+ " parcelable Result {}\n"
+ " Result get();\n"
+ "}";
+ CaptureStderr();
+ EXPECT_NE(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_EQ(GetCapturedStderr(), "");
+
+ EXPECT_TRUE(typenames_.ResolveTypename("p.IFoo.Result").is_resolved);
+}
+
+// Finding the type of nested named member.
+struct TypeFinder : AidlVisitor {
+ string name;
+ const AidlTypeSpecifier* type = nullptr;
+ TypeFinder(std::string name) : name(name) {}
+ void Visit(const AidlVariableDeclaration& v) override {
+ if (v.GetName() == name) {
+ type = &v.GetType();
+ }
+ }
+ void Visit(const AidlMethod& m) override {
+ if (m.GetName() == name) {
+ type = &m.GetType();
+ }
+ }
+ static string Get(const AidlDefinedType& type, const string& name) {
+ TypeFinder v(name);
+ VisitTopDown(v, type);
+ return v.type ? v.type->Signature() : "(null)";
+ };
+};
+
+TEST_F(AidlTest, UnderstandsNestedTypesViaQualifiedInTheSameScope) {
+ io_delegate_.SetFileContents("q/IBar.aidl",
+ "package q;\n"
+ "interface IBar {\n"
+ " parcelable Baz {}\n"
+ "}");
+ import_paths_.emplace("");
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "import q.IBar;\n"
+ "interface IFoo {\n"
+ " parcelable Nested {\n"
+ " Baz t1;\n"
+ " }\n"
+ " parcelable Baz { }\n"
+ " IBar.Baz t2();\n"
+ " Baz t3();\n"
+ "}";
+ CaptureStderr();
+ auto foo = Parse(input_path, input, typenames_, Options::Language::CPP);
+ EXPECT_EQ(GetCapturedStderr(), "");
+ ASSERT_NE(nullptr, foo);
+
+ EXPECT_EQ(TypeFinder::Get(*foo, "t1"), "p.IFoo.Baz");
+ EXPECT_EQ(TypeFinder::Get(*foo, "t2"), "q.IBar.Baz");
+ EXPECT_EQ(TypeFinder::Get(*foo, "t3"), "p.IFoo.Baz");
+}
+
+TEST_F(AidlTest, RejectsNestedTypesWithParentsName) {
+ const string input_path = "p/Foo.aidl";
+ const string input =
+ "package p;\n"
+ "parcelable Foo {\n"
+ " parcelable Foo {}\n"
+ "}";
+ CaptureStderr();
+ EXPECT_EQ(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_THAT(GetCapturedStderr(), HasSubstr("Nested type 'Foo' has the same name as its parent."));
+}
+
+TEST_F(AidlTest, RejectsInterfaceAsNestedTypes) {
+ const string input_path = "p/IFoo.aidl";
+ const string input =
+ "package p;\n"
+ "interface IFoo {\n"
+ " interface ICallback { void done(); }\n"
+ " void doTask(ICallback cb);\n"
+ "}";
+ CaptureStderr();
+ EXPECT_EQ(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_THAT(GetCapturedStderr(), HasSubstr("Interfaces should be at the root scope"));
+}
+
+TEST_F(AidlTest, RejectsNestedTypesWithDuplicateNames) {
+ const string input_path = "p/Foo.aidl";
+ const string input =
+ "package p;\n"
+ "interface Foo {\n"
+ " parcelable Bar {}\n"
+ " parcelable Bar {}\n"
+ "}";
+ CaptureStderr();
+ EXPECT_EQ(nullptr, Parse(input_path, input, typenames_, Options::Language::CPP));
+ EXPECT_THAT(GetCapturedStderr(), HasSubstr("Redefinition of 'Bar'"));
+}
+
+TEST_F(AidlTest, TypeResolutionWithMultipleLevelsOfNesting) {
+ struct Failure {
+ string err;
+ };
+ struct TestCase {
+ string type;
+ variant<string, Failure> expected; // success<0> or failure<1>
+ };
+ vector<TestCase> cases = {
+ {"foo.A", "foo.A"},
+ {"foo.A.B", "foo.A.B"},
+ {"@nullable(heap=true) A", "foo.A.B.A"},
+ // In the scope of foo.A.B.A, B is resolved to A.B.A.B first.
+ {"B.A", Failure{"Failed to resolve 'B.A'"}},
+ {"B", "foo.A.B.A.B"},
+ {"A.B", "foo.A.B.A.B"},
+ };
+ const string input_path = "foo/A.aidl";
+ for (auto& [type, expected] : cases) {
+ AidlTypenames typenames;
+ // clang-format off
+ const string input =
+ "package foo;\n"
+ "parcelable A {\n"
+ " parcelable B {\n"
+ " parcelable A {\n"
+ " parcelable B {\n"
+ " }\n"
+ " " + type + " m;\n"
+ " }\n"
+ " }\n"
+ "}";
+ // clang-format on
+ CaptureStderr();
+ auto foo = Parse(input_path, input, typenames, Options::Language::CPP);
+ if (auto failure = std::get_if<Failure>(&expected); failure) {
+ ASSERT_EQ(nullptr, foo);
+ EXPECT_THAT(GetCapturedStderr(), HasSubstr(failure->err));
+ } else {
+ EXPECT_EQ(GetCapturedStderr(), "");
+ ASSERT_NE(nullptr, foo);
+ EXPECT_EQ(TypeFinder::Get(*foo, "m"), std::get<string>(expected));
+ }
+ }
+}
+
TEST_F(AidlTest, CppNameOf_GenericType) {
const string input_path = "p/Wrapper.aidl";
const string input = "package p; parcelable Wrapper<T> {}";
diff --git a/preprocess.cpp b/preprocess.cpp
index 2594ba6..b332f2e 100644
--- a/preprocess.cpp
+++ b/preprocess.cpp
@@ -27,7 +27,7 @@
namespace {
// PreprocessVisitor emits
-// - type including comments(hide/deprecated) and annotations
+// - types including comments(hide/deprecated) and annotations
// - constant delcarations for interface/parcelable/unions
// - enumerators for enums
struct PreprocessVisitor : AidlVisitor {
@@ -37,7 +37,12 @@
void DumpType(const AidlDefinedType& dt, const string& type) {
DumpComments(dt);
DumpAnnotations(dt);
- out << type << " " << dt.GetCanonicalName();
+ // Top-level definition emits canonical name while nested type emits "name" only.
+ if (dt.GetParentType()) {
+ out << type << " " << dt.GetName();
+ } else {
+ out << type << " " << dt.GetCanonicalName();
+ }
if (auto generic_type = dt.AsParameterizable(); generic_type && generic_type->IsGeneric()) {
out << "<" << Join(generic_type->GetTypeParameters(), ", ") << ">";
}
@@ -48,6 +53,9 @@
for (const auto& constdecl : dt.GetConstantDeclarations()) {
constdecl->DispatchVisit(*this);
}
+ for (const auto& nested : dt.GetNestedTypes()) {
+ nested->DispatchVisit(*this);
+ }
out.Dedent();
out << "}\n";
}