| // Copyright 2022 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. |
| |
| #include "tools/fidl/fidlc/src/resolve_step.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <string_view> |
| #include <utility> |
| |
| #include "tools/fidl/fidlc/src/diagnostics.h" |
| #include "tools/fidl/fidlc/src/flat_ast.h" |
| |
| namespace fidlc { |
| |
| void ResolveStep::RunImpl() { |
| // In a single pass: |
| // (1) parse all references into keys/contextuals; |
| // (2) insert reference edges into the graph. |
| library()->TraverseElements([&](Element* element) { |
| VisitElement(element, Context(Context::Mode::kParseAndInsert, element)); |
| }); |
| |
| // Add all elements of this library to the graph, with membership edges. |
| for (auto& entry : library()->declarations.all) { |
| Decl* decl = entry.second; |
| // Note: It's important to insert decl here so that (1) we properly |
| // initialize its points in the next loop, and (2) we can always recursively |
| // look up a neighbor in the graph, even if it has out-degree zero. |
| graph_.try_emplace(decl); |
| decl->ForEachMember([this, &decl](Element* member) { graph_[member].neighbors.insert(decl); }); |
| } |
| |
| // Initialize point sets for each element in the graph. |
| for (auto& [element, info] : graph_) { |
| // There shouldn't be any library elements in the graph because they are |
| // special (they don't get split, so their availabilities stop at |
| // kInherited). We don't add membership edges to them, and we specifically |
| // avoid adding reference edges to them in ResolveStep::ParseReference. |
| ZX_ASSERT(element->kind != Element::Kind::kLibrary); |
| // Each element starts with between 2 and 5 points. All have (1) `added` and |
| // (2) `removed`. Some have (3) `deprecated`. Some are added back for legacy |
| // support, so they have (4) LEGACY and (5) +inf. Elements from other |
| // libraries (that exist due to reference edges) only ever have 2 points |
| // because those libraries are already compiled, hence post-decomposition. |
| info.points = element->availability.points(); |
| } |
| |
| // Run the temporal decomposition algorithm. |
| std::vector<const Element*> worklist; |
| worklist.reserve(graph_.size()); |
| for (auto& [element, info] : graph_) { |
| worklist.push_back(element); |
| } |
| while (!worklist.empty()) { |
| const Element* element = worklist.back(); |
| worklist.pop_back(); |
| auto& [element_points, neighbors] = graph_.at(element); |
| for (auto& neighbor : neighbors) { |
| auto& neighbor_points = graph_.at(neighbor).points; |
| auto min = *neighbor_points.begin(); |
| auto max = *neighbor_points.rbegin(); |
| bool pushed_neighbor = false; |
| for (auto p : element_points) { |
| if (p > min && p < max) { |
| auto [iter, inserted] = neighbor_points.insert(p); |
| if (inserted && !pushed_neighbor) { |
| worklist.push_back(neighbor); |
| pushed_neighbor = true; |
| } |
| } |
| } |
| } |
| } |
| |
| // Split declarations based on the final point sets. |
| Library::Declarations decomposed_declarations; |
| for (auto [name, decl] : library()->declarations.all) { |
| auto decl_present = decl->availability.set(); |
| auto& points = graph_.at(decl).points; |
| ZX_ASSERT_MSG(points.size() >= 2, "every decl must have at least 2 points"); |
| // Note: Even if there are only two points, we still "split" the decl into |
| // one piece. There is no need to make it a special case. |
| auto prev = *points.begin(); |
| for (auto it = std::next(points.begin()); it != points.end(); ++it) { |
| auto range = VersionRange(prev, *it); |
| if (auto overlap = VersionSet::Intersect(VersionSet(range), decl_present)) { |
| ZX_ASSERT_MSG(overlap == VersionSet(range), |
| "decomposed range must wholly inside or outside the availability"); |
| decomposed_declarations.Insert(decl->Split(range)); |
| } else { |
| auto [a, b] = range.pair(); |
| auto [x, maybe_y] = decl_present.ranges(); |
| auto removed = x.pair().second; |
| ZX_ASSERT_MSG(a >= removed && b <= Version::Legacy(), |
| "skipped range must lie within [removed, LEGACY)"); |
| } |
| prev = *it; |
| } |
| } |
| library()->declarations = std::move(decomposed_declarations); |
| |
| // Resolve all references and validate them. |
| library()->TraverseElements([&](Element* element) { |
| VisitElement(element, Context(Context::Mode::kResolveAndValidate, element)); |
| }); |
| } |
| |
| void ResolveStep::VisitElement(Element* element, Context context) { |
| for (auto& attribute : element->attributes->attributes) { |
| for (auto& arg : attribute->args) { |
| VisitConstant(arg->value.get(), context); |
| } |
| } |
| switch (element->kind) { |
| case Element::Kind::kAlias: { |
| auto alias_decl = static_cast<Alias*>(element); |
| VisitTypeConstructor(alias_decl->partial_type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kNewType: { |
| auto new_type = static_cast<NewType*>(element); |
| VisitTypeConstructor(new_type->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kConst: { |
| auto const_decl = static_cast<Const*>(element); |
| VisitTypeConstructor(const_decl->type_ctor.get(), context); |
| VisitConstant(const_decl->value.get(), context); |
| break; |
| } |
| case Element::Kind::kBits: { |
| auto bits_decl = static_cast<Bits*>(element); |
| VisitTypeConstructor(bits_decl->subtype_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kBitsMember: { |
| auto bits_member = static_cast<Bits::Member*>(element); |
| VisitConstant(bits_member->value.get(), context); |
| break; |
| } |
| case Element::Kind::kEnum: { |
| auto enum_decl = static_cast<Enum*>(element); |
| VisitTypeConstructor(enum_decl->subtype_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kEnumMember: { |
| auto enum_member = static_cast<Enum::Member*>(element); |
| VisitConstant(enum_member->value.get(), context); |
| break; |
| } |
| case Element::Kind::kStructMember: { |
| auto struct_member = static_cast<Struct::Member*>(element); |
| VisitTypeConstructor(struct_member->type_ctor.get(), context); |
| if (auto& constant = struct_member->maybe_default_value) { |
| VisitConstant(constant.get(), context); |
| } |
| break; |
| } |
| case Element::Kind::kTableMember: { |
| auto table_member = static_cast<Table::Member*>(element); |
| VisitTypeConstructor(table_member->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kUnionMember: { |
| auto union_member = static_cast<Union::Member*>(element); |
| VisitTypeConstructor(union_member->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kOverlayMember: { |
| auto overlay_member = static_cast<Overlay::Member*>(element); |
| VisitTypeConstructor(overlay_member->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kProtocolCompose: { |
| auto composed_protocol = static_cast<Protocol::ComposedProtocol*>(element); |
| VisitReference(composed_protocol->reference, context); |
| break; |
| } |
| case Element::Kind::kProtocolMethod: { |
| auto method = static_cast<Protocol::Method*>(element); |
| if (auto& type_ctor = method->maybe_request) { |
| VisitTypeConstructor(type_ctor.get(), context); |
| } |
| if (auto& type_ctor = method->maybe_response) { |
| VisitTypeConstructor(type_ctor.get(), context); |
| } |
| break; |
| } |
| case Element::Kind::kServiceMember: { |
| auto service_member = static_cast<Service::Member*>(element); |
| VisitTypeConstructor(service_member->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kResource: { |
| auto resource_decl = static_cast<Resource*>(element); |
| VisitTypeConstructor(resource_decl->subtype_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kResourceProperty: { |
| auto resource_property = static_cast<Resource::Property*>(element); |
| VisitTypeConstructor(resource_property->type_ctor.get(), context); |
| break; |
| } |
| case Element::Kind::kBuiltin: |
| case Element::Kind::kLibrary: |
| case Element::Kind::kProtocol: |
| case Element::Kind::kService: |
| case Element::Kind::kStruct: |
| case Element::Kind::kTable: |
| case Element::Kind::kUnion: |
| case Element::Kind::kOverlay: |
| break; |
| } |
| } |
| |
| void ResolveStep::VisitTypeConstructor(TypeConstructor* type_ctor, Context context) { |
| VisitReference(type_ctor->layout, context); |
| for (auto& param : type_ctor->parameters->items) { |
| switch (param->kind) { |
| case LayoutParameter::kLiteral: |
| break; |
| case LayoutParameter::kType: { |
| auto type_param = static_cast<TypeLayoutParameter*>(param.get()); |
| VisitTypeConstructor(type_param->type_ctor.get(), context); |
| break; |
| } |
| case LayoutParameter::kIdentifier: { |
| auto identifier_param = static_cast<IdentifierLayoutParameter*>(param.get()); |
| VisitReference(identifier_param->reference, context); |
| // After resolving an IdentifierLayoutParameter, we can determine |
| // whether it's a type constructor or a constant. |
| if (identifier_param->reference.state() == Reference::State::kResolved) { |
| identifier_param->Disambiguate(); |
| } |
| break; |
| } |
| } |
| } |
| auto constraint_context = ConstraintContext(type_ctor, context); |
| for (auto& constraint : type_ctor->constraints->items) { |
| VisitConstant(constraint.get(), constraint_context); |
| } |
| } |
| |
| void ResolveStep::VisitConstant(Constant* constant, Context context) { |
| switch (constant->kind) { |
| case Constant::Kind::kLiteral: |
| break; |
| case Constant::Kind::kIdentifier: { |
| auto identifier_constant = static_cast<IdentifierConstant*>(constant); |
| VisitReference(identifier_constant->reference, context); |
| break; |
| } |
| case Constant::Kind::kBinaryOperator: { |
| auto binop_constant = static_cast<BinaryOperatorConstant*>(constant); |
| VisitConstant(binop_constant->left_operand.get(), context); |
| VisitConstant(binop_constant->right_operand.get(), context); |
| break; |
| } |
| } |
| } |
| |
| ResolveStep::Context ResolveStep::ConstraintContext(const TypeConstructor* type_ctor, |
| Context context) { |
| switch (context.mode) { |
| case Context::Mode::kParseAndInsert: { |
| // Assume all constraints might be contextual. |
| Context augmented(Context::Mode::kParseAndInsert, context.enclosing); |
| augmented.allow_contextual = true; |
| return augmented; |
| } |
| case Context::Mode::kResolveAndValidate: |
| // Handled below. |
| break; |
| } |
| if (type_ctor->layout.state() != Reference::State::kResolved) { |
| return context; |
| } |
| auto target = type_ctor->layout.resolved().element(); |
| if (target->kind != Element::Kind::kResource) { |
| return context; |
| } |
| auto subtype_property = static_cast<Resource*>(target)->LookupProperty("subtype"); |
| if (!subtype_property) { |
| return context; |
| } |
| auto& subtype_layout = subtype_property->type_ctor->layout; |
| // If the resource_definition is in the same library, we might not have |
| // resolved it yet depending on the element traversal order. |
| ResolveReference(subtype_layout, Context(Context::Mode::kResolveAndValidate, subtype_property)); |
| if (subtype_layout.state() == Reference::State::kFailed) { |
| return context; |
| } |
| auto subtype_target = subtype_layout.resolved().element(); |
| if (subtype_target->kind != Element::Kind::kEnum) { |
| return context; |
| } |
| Context augmented(Context::Mode::kResolveAndValidate, context.enclosing); |
| augmented.maybe_resource_subtype = static_cast<Enum*>(subtype_target); |
| return augmented; |
| } |
| |
| // Helper for looking up names as libraries, decls, or members. The Try* methods |
| // do not report an error, while the Must* methods do. |
| class ResolveStep::Lookup final { |
| public: |
| Lookup(ResolveStep* step, const Reference& ref) : step_(step), ref_(ref) {} |
| |
| const Library* TryLibrary(std::string_view name) { |
| auto root_library = step_->all_libraries()->root_library(); |
| if (name == root_library->name) { |
| return root_library; |
| } |
| auto filename = ref_.span().source_file().filename(); |
| return step_->library()->dependencies.LookupAndMarkUsed(filename, name); |
| } |
| |
| std::optional<Reference::Key> TryDecl(const Library* library, std::string_view name) { |
| auto [begin, end] = library->declarations.all.equal_range(name); |
| if (begin == end) { |
| return std::nullopt; |
| } |
| // TryDecl is only used from within ParseSourcedReference, which should not |
| // resolve Internal declarations names; only synthetic references can |
| // resolve internal names. |
| // Internal declarations should only exist in the root library, and should |
| // never have conflicting names, so any match should have only one element. |
| // We therefore return nullopt if any of the declarations found is an |
| // internal one. |
| for (auto it = begin; it != end; ++it) { |
| if (it->second->kind == Decl::Kind::kBuiltin && |
| static_cast<Builtin*>(it->second)->IsInternal()) { |
| return std::nullopt; |
| } |
| } |
| return Reference::Key(library, name); |
| } |
| |
| std::optional<Reference::Key> MustDecl(const Library* library, std::string_view name) { |
| if (auto key = TryDecl(library, name)) { |
| return key; |
| } |
| reporter()->Fail(ErrNameNotFound, ref_.span(), name, library); |
| return std::nullopt; |
| } |
| |
| Element* TryMember(Decl* parent, std::string_view name) { |
| switch (parent->kind) { |
| case Decl::Kind::kBits: |
| for (auto& member : static_cast<Bits*>(parent)->members) { |
| if (member.name.data() == name) { |
| return &member; |
| } |
| } |
| return nullptr; |
| case Decl::Kind::kEnum: |
| for (auto& member : static_cast<Enum*>(parent)->members) { |
| if (member.name.data() == name) { |
| return &member; |
| } |
| } |
| return nullptr; |
| default: |
| return nullptr; |
| } |
| } |
| |
| Element* MustMember(Decl* parent, std::string_view name) { |
| switch (parent->kind) { |
| case Decl::Kind::kBits: |
| case Decl::Kind::kEnum: |
| if (auto member = TryMember(parent, name)) { |
| return member; |
| } |
| break; |
| default: |
| reporter()->Fail(ErrCannotReferToMember, ref_.span(), parent); |
| return nullptr; |
| } |
| reporter()->Fail(ErrMemberNotFound, ref_.span(), parent, name); |
| return nullptr; |
| } |
| |
| private: |
| Reporter* reporter() { return step_->reporter(); } |
| |
| ResolveStep* step_; |
| const Reference& ref_; |
| }; |
| |
| void ResolveStep::VisitReference(Reference& ref, Context context) { |
| switch (context.mode) { |
| case Context::Mode::kParseAndInsert: |
| ParseReference(ref, context); |
| InsertReferenceEdges(ref, context); |
| break; |
| case Context::Mode::kResolveAndValidate: { |
| ResolveReference(ref, context); |
| ValidateReference(ref, context); |
| break; |
| } |
| } |
| } |
| |
| // Returns true if ref (assumed to be resolved) refers to HEAD. |
| static bool IsResolvedToHead(const Reference& ref) { |
| return ref.resolved().element()->kind == Element::Kind::kBuiltin && |
| static_cast<Builtin*>(ref.resolved().element())->id == Builtin::Identity::kHead; |
| } |
| |
| void ResolveStep::ParseReference(Reference& ref, Context context) { |
| auto initial_state = ref.state(); |
| auto checkpoint = reporter()->Checkpoint(); |
| switch (initial_state) { |
| case Reference::State::kRawSynthetic: |
| ParseSyntheticReference(ref, context); |
| break; |
| case Reference::State::kRawSourced: |
| ParseSourcedReference(ref, context); |
| break; |
| case Reference::State::kResolved: |
| // This can only happen for the early compilation of HEAD in |
| // AttributeArgSchema::TryResolveAsHead. |
| ZX_ASSERT(IsResolvedToHead(ref)); |
| return; |
| default: |
| ZX_PANIC("unexpected reference state"); |
| } |
| if (ref.state() == initial_state) { |
| ZX_ASSERT_MSG(checkpoint.NumNewErrors() > 0, "should have reported an error"); |
| ref.MarkFailed(); |
| return; |
| } |
| // A library element can reference things via attribute arguments. Other than |
| // HEAD, which causes an early return above, we don't allow this: how would we |
| // decide what value to use if the const it's referencing has different values |
| // at different versions? |
| if (context.enclosing->kind == Element::Kind::kLibrary) { |
| reporter()->Fail(ErrReferenceInLibraryAttribute, ref.span()); |
| ref.MarkFailed(); |
| return; |
| } |
| } |
| |
| void ResolveStep::ParseSyntheticReference(Reference& ref, Context context) { |
| // Note that we can't use target.name() here because it returns a Name by |
| // value, which would go out of scope. |
| auto& name = ref.raw_synthetic().target.element()->AsDecl()->name; |
| ref.SetKey(Reference::Key(name.library(), name.decl_name())); |
| } |
| |
| void ResolveStep::ParseSourcedReference(Reference& ref, Context context) { |
| // This implements the identifier resolution algorithm from |
| // https://fuchsia.dev/fuchsia-src/reference/fidl/language/language#resolution-algorithm |
| const auto& components = ref.raw_sourced().components; |
| Lookup lookup(this, ref); |
| switch (components.size()) { |
| case 1: { |
| if (auto key = lookup.TryDecl(library(), components[0])) { |
| ref.SetKey(key.value()); |
| } else if (auto key = lookup.TryDecl(all_libraries()->root_library(), components[0])) { |
| ref.SetKey(key.value()); |
| } else if (context.allow_contextual) { |
| ref.MarkContextual(); |
| } else { |
| reporter()->Fail(ErrNameNotFound, ref.span(), components[0], library()); |
| } |
| break; |
| } |
| case 2: { |
| if (auto key = lookup.TryDecl(library(), components[0])) { |
| ref.SetKey(key.value().Member(components[1])); |
| } else if (auto dep_library = lookup.TryLibrary(components[0])) { |
| if (auto key = lookup.MustDecl(dep_library, components[1])) { |
| ref.SetKey(key.value()); |
| } |
| } else { |
| reporter()->Fail(ErrNameNotFound, ref.span(), components[0], library()); |
| } |
| break; |
| } |
| default: { |
| std::string long_library_name; |
| size_t prev_size; |
| for (auto it = components.begin(); it != components.end() - 1; ++it) { |
| prev_size = long_library_name.size(); |
| if (it != components.begin()) |
| long_library_name.push_back('.'); |
| long_library_name.append(*it); |
| } |
| auto short_library_name = std::string_view(long_library_name.data(), prev_size); |
| if (auto dep_library = lookup.TryLibrary(long_library_name)) { |
| if (auto key = lookup.MustDecl(dep_library, components.back())) { |
| ref.SetKey(key.value()); |
| } |
| } else if (auto dep_library = lookup.TryLibrary(short_library_name)) { |
| if (auto key = lookup.MustDecl(dep_library, components[components.size() - 2])) { |
| ref.SetKey(key.value().Member(components.back())); |
| } |
| } else { |
| reporter()->Fail(ErrUnknownDependentLibrary, ref.span(), long_library_name, |
| short_library_name); |
| } |
| break; |
| } |
| } |
| } |
| |
| void ResolveStep::InsertReferenceEdges(const Reference& ref, Context context) { |
| // Don't insert edges for a contextual reference, if parsing failed, or if the |
| // reference is already resolved (see comment in ResolveStep::ParseReference |
| // for details about when this happens). |
| if (ref.state() == Reference::State::kContextual || ref.state() == Reference::State::kFailed || |
| ref.state() == Reference::State::kResolved) { |
| return; |
| } |
| auto key = ref.key(); |
| // Only insert edges if the target is in the same platform. |
| if (key.library->platform.value() != library()->platform.value()) { |
| return; |
| } |
| // Note: key.library may is not necessarily library(), thus |
| // key.library->declarations could be pre-decomposition or post-decomposition. |
| // Although no branching is needed here, this is important to keep in mind. |
| auto [begin, end] = key.library->declarations.all.equal_range(key.decl_name); |
| for (auto it = begin; it != end; ++it) { |
| Element* target = it->second; |
| Element* enclosing = context.enclosing; |
| // Don't insert a self-loop. |
| if (target == enclosing) { |
| continue; |
| } |
| // Only insert an edge if we have a chance of resolving to this target |
| // post-decomposition (as opposed to one of the other same-named targets). |
| if (VersionSet::Intersect(target->availability.set(), enclosing->availability.set())) { |
| graph_[target].neighbors.insert(enclosing); |
| } |
| } |
| } |
| |
| void ResolveStep::ResolveReference(Reference& ref, Context context) { |
| auto initial_state = ref.state(); |
| auto checkpoint = reporter()->Checkpoint(); |
| switch (initial_state) { |
| case Reference::State::kFailed: |
| case Reference::State::kResolved: |
| // Nothing to do, either failed parsing or already attempted resolving. |
| return; |
| case Reference::State::kContextual: |
| ResolveContextualReference(ref, context); |
| break; |
| case Reference::State::kKey: |
| ResolveKeyReference(ref, context); |
| break; |
| default: |
| ZX_PANIC("unexpected reference state"); |
| } |
| if (ref.state() == initial_state) { |
| ZX_ASSERT_MSG(checkpoint.NumNewErrors() > 0, "should have reported an error"); |
| ref.MarkFailed(); |
| } |
| } |
| |
| void ResolveStep::ResolveContextualReference(Reference& ref, Context context) { |
| auto name = ref.contextual().name; |
| auto subtype_enum = context.maybe_resource_subtype; |
| if (!subtype_enum) { |
| reporter()->Fail(ErrNameNotFound, ref.span(), name, library()); |
| return; |
| } |
| Lookup lookup(this, ref); |
| auto member = lookup.TryMember(subtype_enum, name); |
| if (!member) { |
| reporter()->Fail(ErrNameNotFound, ref.span(), name, library()); |
| return; |
| } |
| ref.ResolveTo(Reference::Target(member, subtype_enum)); |
| } |
| |
| void ResolveStep::ResolveKeyReference(Reference& ref, Context context) { |
| auto decl = LookupDeclByKey(ref, context); |
| if (!decl) { |
| return; |
| } |
| if (!ref.key().member_name) { |
| ref.ResolveTo(Reference::Target(decl)); |
| return; |
| } |
| Lookup lookup(this, ref); |
| auto member = lookup.MustMember(decl, ref.key().member_name.value()); |
| if (!member) { |
| return; |
| } |
| ref.ResolveTo(Reference::Target(member, decl)); |
| } |
| |
| Decl* ResolveStep::LookupDeclByKey(const Reference& ref, Context context) { |
| auto key = ref.key(); |
| auto [begin, end] = key.library->declarations.all.equal_range(key.decl_name); |
| ZX_ASSERT_MSG(begin != end, "key must exist"); |
| auto platform = key.library->platform.value(); |
| // Case #1: source and target libraries are versioned in the same platform. |
| if (library()->platform == platform) { |
| for (auto it = begin; it != end; ++it) { |
| auto decl = it->second; |
| auto us = context.enclosing->availability.range(); |
| auto them = decl->availability.range(); |
| if (auto overlap = VersionRange::Intersect(us, them)) { |
| ZX_ASSERT_MSG(overlap.value() == us, "referencee must outlive referencer"); |
| return decl; |
| } |
| } |
| // TODO(https://fxbug.dev/42146818): Provide a nicer error message in the case where a |
| // decl with that name does exist, but in a different version range. |
| reporter()->Fail(ErrNameNotFound, ref.span(), key.decl_name, key.library); |
| return nullptr; |
| } |
| // Case #2: source and target libraries are versioned in different platforms. |
| auto version = version_selection()->Lookup(platform); |
| for (auto it = begin; it != end; ++it) { |
| auto decl = it->second; |
| if (decl->availability.range().Contains(version)) { |
| return decl; |
| } |
| } |
| // TODO(https://fxbug.dev/42146818): Provide a nicer error message in the case where |
| // a decl with that name does exist, but in a different version range. |
| reporter()->Fail(ErrNameNotFound, ref.span(), key.decl_name, key.library); |
| return nullptr; |
| } |
| |
| void ResolveStep::ValidateReference(const Reference& ref, Context context) { |
| if (ref.state() == Reference::State::kFailed) { |
| return; |
| } |
| if (!ref.IsSynthetic() && ref.resolved().name().as_anonymous()) { |
| reporter()->Fail(ErrAnonymousNameReference, ref.span(), ref.resolved().name()); |
| } |
| if (context.enclosing->kind == Element::Kind::kLibrary) { |
| // A library element can reference things via attribute arguments. The only |
| // such reference we allow is to HEAD (see ResolveStep::ParseReference). |
| // Return early since the library's availability never advances to |
| // kNarrowed, which is assumed below. |
| ZX_ASSERT(IsResolvedToHead(ref)); |
| return; |
| } |
| |
| auto source = context.enclosing; |
| auto target = ref.resolved().element(); |
| auto source_deprecated = source->availability.is_deprecated(); |
| auto target_deprecated = target->availability.is_deprecated(); |
| |
| // TODO(https://fxbug.dev/42052719): The check below is a stopgap solution to allow |
| // @deprecated elements to reference @available(deprecated=...) elements. We |
| // should solve this in a more principled way by layering the latter on the |
| // former. For example, that would also ensure that the following works: |
| // |
| // @deprecated |
| // type Foo = struct { member DeprecatedType; }; |
| // @available(deprecated=1) |
| // alias DeprecatedType = bool; |
| // |
| // Whereas with the current stopgap you'd have to also add @deprecated on the |
| // member itself. |
| if (source->attributes->Get("deprecated")) { |
| source_deprecated = true; |
| } |
| |
| if (source_deprecated || !target_deprecated) { |
| return; |
| } |
| |
| auto& source_platform = library()->platform.value(); |
| auto& target_platform = ref.resolved().library()->platform.value(); |
| |
| if (source_platform == target_platform) { |
| reporter()->Fail(ErrInvalidReferenceToDeprecated, ref.span(), target, |
| source->availability.range(), source_platform, source); |
| } else { |
| reporter()->Fail(ErrInvalidReferenceToDeprecatedOtherPlatform, ref.span(), target, |
| target->availability.range(), target_platform, source, |
| source->availability.range(), source_platform); |
| } |
| } |
| |
| } // namespace fidlc |