| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // The implementation for the ConvertingTreeVisitor that re-prints a raw::File |
| // back into text format per some set of syntax rules. |
| #include "fidl/new_syntax_converter.h" |
| |
| namespace fidl::conv { |
| |
| // Until FTP-033 is fully implemented, it is possible for "strict" types to not |
| // have an actual "strict" keyword preceding them (ie, "strict union U {...}" |
| // and "union U {...}" are represented identically in the raw AST). This |
| // helper function works around that problem by determining whether or not the |
| // actual "strict" keyword was used in the declaration text. |
| std::optional<types::Strictness> optional_strictness(types::Strictness strictness, bool specified) { |
| if (!specified) { |
| return std::nullopt; |
| } |
| return strictness; |
| } |
| |
| // For types that only accept the strictness modifier (currently "bits" and |
| // "enum"), we don't store the presence of the modifier keyword as a bool. |
| // Instead, we just match the first token to its sub-kind to deduce whether or |
| // not the modifier keyword is used. |
| std::optional<types::Strictness> optional_strictness(Token& decl_start_token) { |
| switch (decl_start_token.subkind()) { |
| case Token::Subkind::kStrict: |
| return types::Strictness::kStrict; |
| case Token::Subkind::kFlexible: |
| return types::Strictness::kFlexible; |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| // Returns the "builtin" definition underpinning a type. If named declaration |
| // is actually an alias, this method will recurse until all aliases are |
| // dereferenced and an actual, FIDL-native type can be deduced. |
| std::optional<UnderlyingType> resolve_as_user_defined_type(const flat::Name& name, |
| bool is_behind_alias) { |
| const flat::Library* lib = name.library(); |
| const flat::Decl* decl_ptr = lib->LookupDeclByName(name); |
| if (decl_ptr == nullptr) { |
| return std::nullopt; |
| } |
| |
| const flat::Decl::Kind& kind = decl_ptr->kind; |
| if (kind != flat::Decl::Kind::kTypeAlias) { |
| if (kind == flat::Decl::Kind::kResource) { |
| // Special case: the only "resource_definition" in existence at the |
| // moment is the one that defines "handle," so if we get to this point, we |
| // should just assume the underlying type is a handle. |
| return UnderlyingType(flat::Type::Kind::kHandle, is_behind_alias); |
| } |
| return UnderlyingType(decl_ptr->kind, is_behind_alias); |
| } |
| |
| auto type_alias_ptr = static_cast<const flat::TypeAlias*>(decl_ptr); |
| auto underlying_type = |
| resolve_as_user_defined_type(type_alias_ptr->partial_type_ctor->name, true); |
| if (underlying_type != std::nullopt) { |
| return underlying_type; |
| } |
| return UnderlyingType(type_alias_ptr->partial_type_ctor->type->kind, true); |
| } |
| |
| // Matches a string keyword to the "builtin" representing the FIDL-native type |
| // it represents. |
| std::optional<UnderlyingType> resolve_as_user_defined_type(const std::string& keyword) { |
| const flat::Typespace root = flat::Typespace::RootTypes(nullptr); |
| const flat::Name instrinsic = flat::Name::CreateIntrinsic(keyword); |
| const flat::TypeTemplate* t = root.LookupTemplate(instrinsic); |
| if (t == nullptr) { |
| return std::nullopt; |
| } |
| |
| if (keyword == "array") { |
| return UnderlyingType(flat::Type::Kind::kArray, false); |
| } else if (keyword == "vector") { |
| return UnderlyingType(flat::Type::Kind::kVector, false); |
| } else if (keyword == "bytes") { |
| return UnderlyingType(flat::Type::Kind::kVector, false); |
| } else if (keyword == "string") { |
| return UnderlyingType(flat::Type::Kind::kString, false); |
| } else if (keyword == "handle") { |
| return UnderlyingType(flat::Type::Kind::kHandle, false); |
| } else if (keyword == "request") { |
| return UnderlyingType(flat::Type::Kind::kRequestHandle, false); |
| } else { |
| return UnderlyingType(flat::Type::Kind::kPrimitive, false); |
| } |
| } |
| |
| // Given a non-compound identifier, and a reference to the library in which |
| // that identifier is defined, we should be able to resolve the underlying |
| // built-in type underpinning that identifier. |
| std::optional<UnderlyingType> resolve_identifier(const std::unique_ptr<raw::Identifier>& identifier, |
| const flat::Library* lib) { |
| std::string type_decl = identifier->copy_to_str(); |
| |
| // Break up the type declaration - discard any "wrapped" types. |
| size_t bracket_pos = type_decl.find_first_of('<'); |
| if (bracket_pos != std::string::npos) { |
| type_decl = type_decl.substr(0, bracket_pos); |
| }; |
| |
| // We'll need to make a flat::Name from the type_decl string, which can then |
| // be used to search the library for the name's definition recursively until |
| // its underlying type can be deduced. |
| auto underlying_type = |
| resolve_as_user_defined_type(flat::Name::CreateSourced(lib, identifier->span()), false); |
| if (underlying_type) { |
| return underlying_type; |
| } |
| return resolve_as_user_defined_type(type_decl); |
| } |
| |
| // Lookup the definition of a type's "key" identifier (ie, "vector" in the |
| // identifier "vector<array<uint8>:>" or "Foo" in "some.lib.Foo") in a given |
| // library. |
| std::optional<UnderlyingType> resolve_type( |
| const std::unique_ptr<raw::TypeConstructorOld>& type_ctor, const flat::Library* lib) { |
| std::unique_ptr<raw::CompoundIdentifier>& id = type_ctor->identifier; |
| std::string type_decl = id->copy_to_str(); |
| |
| // If there is at least one period in the declaration identifier, there is a |
| // possibility that this is a reference to an imported library. To verify |
| // this, we'll construct the library name (ex, "some.library.Foo" becomes |
| // just "some.library") and see if we can find it in the final library's |
| // dependent libraries. |
| size_t last_dot_pos = type_decl.find_last_of('.'); |
| if (last_dot_pos != std::string::npos) { |
| flat::Library* dep_lib = nullptr; |
| std::vector<std::string_view> lib_name; |
| for (size_t i = 0; i < id->components.size() - 1; i++) { |
| lib_name.emplace_back(id->components[i]->span().data()); |
| } |
| |
| const flat::Libraries* libs = lib->GetLibraries(); |
| if (libs->Lookup(lib_name, &dep_lib)) { |
| return resolve_identifier(id->components.back(), dep_lib); |
| } |
| }; |
| |
| // Looks like this was not a reference to a definition in an imported |
| // library after all. Go ahead and look for it in our current library. |
| return resolve_identifier(id->components.back(), lib); |
| } |
| |
| std::optional<UnderlyingType> ConvertingTreeVisitor::resolve( |
| const std::unique_ptr<raw::TypeConstructorOld>& type_ctor) { |
| return resolve_type(type_ctor, library_); |
| } |
| |
| void ConvertingTreeVisitor::OnBitsDeclaration( |
| const std::unique_ptr<raw::BitsDeclaration>& element) { |
| Token& end = element->identifier->end_; |
| if (element->maybe_type_ctor != nullptr) { |
| end = element->maybe_type_ctor->end_; |
| } |
| |
| auto ref = |
| element->maybe_type_ctor == nullptr |
| ? std::nullopt |
| : std::make_optional<std::reference_wrapper<std::unique_ptr<raw::TypeConstructorOld>>>( |
| element->maybe_type_ctor); |
| std::unique_ptr<Conversion> conv = std::make_unique<BitsDeclarationConversion>( |
| element->identifier, ref, optional_strictness(*element->decl_start_token)); |
| Converting converting(this, std::move(conv), *element->decl_start_token, end); |
| TreeVisitor::OnBitsDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnConstDeclaration( |
| const std::unique_ptr<raw::ConstDeclaration>& element) { |
| const auto& type_ctor = std::get<std::unique_ptr<raw::TypeConstructorOld>>(element->type_ctor); |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<NameAndTypeConversion>(element->identifier, type_ctor); |
| Converting converting(this, std::move(conv), type_ctor->start_, element->identifier->end_); |
| TreeVisitor::OnConstDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnEnumDeclaration( |
| const std::unique_ptr<raw::EnumDeclaration>& element) { |
| Token& end = element->identifier->end_; |
| if (element->maybe_type_ctor != nullptr) { |
| end = element->maybe_type_ctor->end_; |
| } |
| |
| auto ref = |
| element->maybe_type_ctor == nullptr |
| ? std::nullopt |
| : std::make_optional<std::reference_wrapper<std::unique_ptr<raw::TypeConstructorOld>>>( |
| element->maybe_type_ctor); |
| std::unique_ptr<Conversion> conv = std::make_unique<EnumDeclarationConversion>( |
| element->identifier, ref, optional_strictness(*element->decl_start_token)); |
| Converting converting(this, std::move(conv), *element->decl_start_token, end); |
| TreeVisitor::OnEnumDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnFile(std::unique_ptr<fidl::raw::File> const& element) { |
| last_conversion_end_ = element->start_.previous_end().data().data(); |
| comments_ = std::move(element->comment_tokens_list); |
| DeclarationOrderTreeVisitor::OnFile(element); |
| converted_output_ += last_conversion_end_; |
| } |
| |
| void ConvertingTreeVisitor::OnParameter(const std::unique_ptr<raw::Parameter>& element) { |
| const auto& type_ctor = std::get<std::unique_ptr<raw::TypeConstructorOld>>(element->type_ctor); |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<NameAndTypeConversion>(element->identifier, type_ctor); |
| Converting converting(this, std::move(conv), type_ctor->start_, element->identifier->end_); |
| TreeVisitor::OnParameter(element); |
| } |
| |
| void ConvertingTreeVisitor::OnStructDeclaration( |
| const std::unique_ptr<raw::StructDeclaration>& element) { |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<StructDeclarationConversion>(element->identifier, element->resourceness); |
| Converting converting(this, std::move(conv), *element->decl_start_token, |
| element->identifier->end_); |
| TreeVisitor::OnStructDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnResourceProperty( |
| const std::unique_ptr<raw::ResourceProperty>& element) { |
| const auto& type_ctor = std::get<std::unique_ptr<raw::TypeConstructorOld>>(element->type_ctor); |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<NameAndTypeConversion>(element->identifier, type_ctor); |
| Converting converting(this, std::move(conv), type_ctor->start_, element->identifier->end_); |
| TreeVisitor::OnResourceProperty(element); |
| } |
| |
| void ConvertingTreeVisitor::OnStructMember(const std::unique_ptr<raw::StructMember>& element) { |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<NameAndTypeConversion>(element->identifier, element->type_ctor); |
| Converting converting(this, std::move(conv), element->type_ctor->start_, |
| element->identifier->end_); |
| TreeVisitor::OnStructMember(element); |
| } |
| |
| void ConvertingTreeVisitor::OnTableDeclaration( |
| const std::unique_ptr<raw::TableDeclaration>& element) { |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<TableDeclarationConversion>(element->identifier, element->resourceness); |
| Converting converting(this, std::move(conv), *element->decl_start_token, |
| element->identifier->end_); |
| TreeVisitor::OnTableDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnTableMember(const std::unique_ptr<raw::TableMember>& element) { |
| if (element->maybe_used != nullptr) { |
| std::unique_ptr<Conversion> conv = std::make_unique<NameAndTypeConversion>( |
| element->maybe_used->identifier, element->maybe_used->type_ctor); |
| Converting converting(this, std::move(conv), element->maybe_used->type_ctor->start_, |
| element->maybe_used->identifier->end_); |
| TreeVisitor::OnTableMember(element); |
| } else { |
| TreeVisitor::OnTableMember(element); |
| } |
| } |
| |
| void ConvertingTreeVisitor::OnTypeConstructorOld( |
| const std::unique_ptr<raw::TypeConstructorOld>& element) { |
| std::optional<UnderlyingType> underlying_type = resolve(element); |
| |
| // We should never get a null Builtin - if we do, there is a mistake in the |
| // converter code. Failing this assert means we are looking at an |
| // identifier that is neither explicitly defined in the source, nor |
| // intrinsic to the language. If that's the case, where did it come from? |
| assert(underlying_type.has_value() && "must resolve underlying builtin value for type"); |
| |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<TypeConversion>(element, underlying_type.value()); |
| Converting converting(this, std::move(conv), element->start_, element->end_); |
| TreeVisitor::OnTypeConstructorOld(element); |
| } |
| |
| void ConvertingTreeVisitor::OnUnionDeclaration( |
| const std::unique_ptr<raw::UnionDeclaration>& element) { |
| std::unique_ptr<Conversion> conv = std::make_unique<UnionDeclarationConversion>( |
| element->identifier, optional_strictness(element->strictness, element->strictness_specified), |
| element->resourceness); |
| Converting converting(this, std::move(conv), *element->decl_start_token, |
| element->identifier->end_); |
| TreeVisitor::OnUnionDeclaration(element); |
| } |
| |
| void ConvertingTreeVisitor::OnUnionMember(const std::unique_ptr<raw::UnionMember>& element) { |
| if (element->maybe_used != nullptr) { |
| std::unique_ptr<Conversion> conv = std::make_unique<NameAndTypeConversion>( |
| element->maybe_used->identifier, element->maybe_used->type_ctor); |
| Converting converting(this, std::move(conv), element->maybe_used->type_ctor->start_, |
| element->maybe_used->identifier->end_); |
| TreeVisitor::OnUnionMember(element); |
| } else { |
| TreeVisitor::OnUnionMember(element); |
| } |
| } |
| |
| void ConvertingTreeVisitor::OnUsing(const std::unique_ptr<raw::Using>& element) { |
| if (element->maybe_type_ctor != nullptr) { |
| std::unique_ptr<Conversion> conv = |
| std::make_unique<UsingKeywordConversion>(element->using_path, element->maybe_type_ctor); |
| Converting converting(this, std::move(conv), *element->decl_start_token, |
| element->maybe_type_ctor->end_); |
| } |
| TreeVisitor::OnUsing(element); |
| } |
| |
| Converting::Converting(ConvertingTreeVisitor* ctv, std::unique_ptr<Conversion> conversion, |
| const Token& start, const Token& end) |
| : ctv_(ctv) { |
| const char* copy_from = ctv_->last_conversion_end_; |
| const char* copy_until = start.data().data(); |
| const char* conversion_end = end.data().data() + end.data().length(); |
| |
| if (conversion_end > ctv_->last_conversion_end_) { |
| // We should only enter this block if we are in a nested conversion. |
| ctv_->last_conversion_end_ = conversion_end; |
| } |
| if (copy_from < copy_until) { |
| auto cr = std::make_unique<CopyRange>(copy_from, copy_until); |
| conversion->AddPrefix(std::move(cr)); |
| } |
| |
| // Any stray comments contained inside the span being converted should be |
| // added to the prefix as well. |
| while (ctv->last_comment_ < ctv->comments_.size()) { |
| std::string_view comment = ctv->comments_[ctv->last_comment_]->span().data(); |
| |
| // Make sure not to consume comments past the end of the current conversion |
| // span. |
| if (comment.data() > ctv_->last_conversion_end_) { |
| break; |
| } |
| |
| if (comment.data() > start.data().data()) { |
| const char* from = comment.data(); |
| const char* until = from + comment.length() + 1; |
| auto cr = std::make_unique<CopyRange>(from, until); |
| conversion->AddPrefix(std::move(cr)); |
| } |
| ctv->last_comment_++; |
| } |
| |
| ctv_->open_conversions_.push(std::move(conversion)); |
| } |
| |
| Converting::~Converting() { |
| std::unique_ptr<Conversion> conv = std::move(ctv_->open_conversions_.top()); |
| ctv_->open_conversions_.pop(); |
| std::string text = conv->Write(ctv_->to_syntax_); |
| if (!ctv_->open_conversions_.empty()) { |
| ctv_->open_conversions_.top()->AddChildText(text); |
| } else { |
| ctv_->converted_output_ += text; |
| } |
| } |
| |
| } // namespace fidl::conv |