blob: 9cb038e0b7a2f82065a66bce401eaec2095e0a66 [file] [log] [blame]
// Copyright 2018 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 "fidl/flat_ast.h"
#include <assert.h>
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include "fidl/attributes.h"
#include "fidl/diagnostic_types.h"
#include "fidl/diagnostics.h"
#include "fidl/experimental_flags.h"
#include "fidl/flat/types.h"
#include "fidl/lexer.h"
#include "fidl/names.h"
#include "fidl/ordinals.h"
#include "fidl/parser.h"
#include "fidl/raw_ast.h"
#include "fidl/types.h"
#include "fidl/utils.h"
namespace fidl {
namespace flat {
using namespace diagnostics;
namespace {
class ScopeInsertResult {
public:
explicit ScopeInsertResult(std::unique_ptr<SourceSpan> previous_occurrence)
: previous_occurrence_(std::move(previous_occurrence)) {}
static ScopeInsertResult Ok() { return ScopeInsertResult(nullptr); }
static ScopeInsertResult FailureAt(SourceSpan previous) {
return ScopeInsertResult(std::make_unique<SourceSpan>(previous));
}
bool ok() const { return previous_occurrence_ == nullptr; }
const SourceSpan& previous_occurrence() const {
assert(!ok());
return *previous_occurrence_;
}
private:
std::unique_ptr<SourceSpan> previous_occurrence_;
};
template <typename T>
class Scope {
public:
ScopeInsertResult Insert(const T& t, SourceSpan span) {
auto iter = scope_.find(t);
if (iter != scope_.end()) {
return ScopeInsertResult::FailureAt(iter->second);
} else {
scope_.emplace(t, span);
return ScopeInsertResult::Ok();
}
}
typename std::map<T, SourceSpan>::const_iterator begin() const { return scope_.begin(); }
typename std::map<T, SourceSpan>::const_iterator end() const { return scope_.end(); }
private:
std::map<T, SourceSpan> scope_;
};
using Ordinal64Scope = Scope<uint64_t>;
std::optional<std::pair<uint64_t, SourceSpan>> FindFirstNonDenseOrdinal(
const Ordinal64Scope& scope) {
uint64_t last_ordinal_seen = 0;
for (const auto& ordinal_and_loc : scope) {
uint64_t next_expected_ordinal = last_ordinal_seen + 1;
if (ordinal_and_loc.first != next_expected_ordinal) {
return std::optional{std::make_pair(next_expected_ordinal, ordinal_and_loc.second)};
}
last_ordinal_seen = ordinal_and_loc.first;
}
return std::nullopt;
}
struct MethodScope {
Ordinal64Scope ordinals;
Scope<std::string> canonical_names;
Scope<const Protocol*> protocols;
};
// A helper class to derive the resourceness of synthesized decls based on their
// members. If the given std::optional<types::Resourceness> is already set
// (meaning the decl is user-defined, not synthesized), this does nothing.
//
// Types added via AddType must already be compiled. In other words, there must
// not be cycles among the synthesized decls.
class DeriveResourceness {
public:
explicit DeriveResourceness(std::optional<types::Resourceness>* target)
: target_(target), derive_(!target->has_value()), result_(types::Resourceness::kValue) {}
~DeriveResourceness() {
if (derive_) {
*target_ = result_;
}
}
void AddType(const Type* type) {
if (derive_ && result_ == types::Resourceness::kValue &&
type->Resourceness() == types::Resourceness::kResource) {
result_ = types::Resourceness::kResource;
}
}
private:
std::optional<types::Resourceness>* const target_;
const bool derive_;
types::Resourceness result_;
};
// A helper class to track when a Decl is compiling and compiled.
class Compiling {
public:
explicit Compiling(Decl* decl) : decl_(decl) { decl_->compiling = true; }
~Compiling() {
decl_->compiling = false;
decl_->compiled = true;
}
private:
Decl* decl_;
};
template <typename T>
std::unique_ptr<Diagnostic> ValidateUnknownConstraints(const Decl& decl,
types::Strictness decl_strictness,
const std::vector<const T*>* members) {
if (!members)
return nullptr;
const bool is_transitional = decl.HasAttribute("Transitional");
const bool is_strict = [&] {
switch (decl_strictness) {
case types::Strictness::kStrict:
return true;
case types::Strictness::kFlexible:
return false;
}
}();
bool found_member = false;
for (const auto* member : *members) {
const bool has_unknown = member->attributes && member->attributes->HasAttribute("Unknown");
if (!has_unknown)
continue;
if (is_strict && !is_transitional) {
return Reporter::MakeError(ErrUnknownAttributeOnInvalidType, member->name);
}
if (found_member) {
return Reporter::MakeError(ErrUnknownAttributeOnMultipleMembers, member->name);
}
found_member = true;
}
return nullptr;
}
} // namespace
TypeConstructorPtr GetTypeCtorAsPtr(const TypeConstructor& type_ctor) {
return std::visit(fidl::utils::matchers{
[](const std::unique_ptr<TypeConstructorOld>& e) -> TypeConstructorPtr {
return e.get();
},
[](const std::unique_ptr<TypeConstructorNew>& e) -> TypeConstructorPtr {
return e.get();
},
},
type_ctor);
}
uint32_t PrimitiveType::SubtypeSize(types::PrimitiveSubtype subtype) {
switch (subtype) {
case types::PrimitiveSubtype::kBool:
case types::PrimitiveSubtype::kInt8:
case types::PrimitiveSubtype::kUint8:
return 1u;
case types::PrimitiveSubtype::kInt16:
case types::PrimitiveSubtype::kUint16:
return 2u;
case types::PrimitiveSubtype::kFloat32:
case types::PrimitiveSubtype::kInt32:
case types::PrimitiveSubtype::kUint32:
return 4u;
case types::PrimitiveSubtype::kFloat64:
case types::PrimitiveSubtype::kInt64:
case types::PrimitiveSubtype::kUint64:
return 8u;
}
}
bool Decl::HasAttribute(std::string_view name) const {
if (!attributes)
return false;
return attributes->HasAttribute(std::string(name));
}
std::string_view Decl::GetAttribute(std::string_view name) const {
if (!attributes)
return std::string_view();
for (const auto& attribute : attributes->attributes) {
if (attribute.name == name) {
if (attribute.value != "") {
const auto& value = attribute.value;
return std::string_view(value.data(), value.size());
}
// Don't search for another attribute with the same name.
break;
}
}
return std::string_view();
}
std::string Decl::GetName() const { return std::string(name.decl_name()); }
const std::set<std::pair<std::string, std::string_view>> allowed_simple_unions{{
{"fuchsia.io", "NodeInfo"},
}};
bool IsSimple(const Type* type, Reporter* reporter) {
auto depth = fidl::OldWireFormatDepth(type);
switch (type->kind) {
case Type::Kind::kVector: {
auto vector_type = static_cast<const VectorType*>(type);
if (*vector_type->element_count == Size::Max())
return false;
switch (vector_type->element_type->kind) {
case Type::Kind::kHandle:
case Type::Kind::kRequestHandle:
case Type::Kind::kPrimitive:
return true;
case Type::Kind::kArray:
case Type::Kind::kVector:
case Type::Kind::kString:
case Type::Kind::kIdentifier:
return false;
}
}
case Type::Kind::kString: {
auto string_type = static_cast<const StringType*>(type);
return *string_type->max_size < Size::Max();
}
case Type::Kind::kArray:
case Type::Kind::kHandle:
case Type::Kind::kRequestHandle:
case Type::Kind::kPrimitive:
return depth == 0u;
case Type::Kind::kIdentifier: {
auto identifier_type = static_cast<const IdentifierType*>(type);
if (identifier_type->type_decl->kind == Decl::Kind::kUnion) {
auto union_name = std::make_pair<const std::string&, const std::string_view&>(
LibraryName(identifier_type->name.library(), "."), identifier_type->name.decl_name());
if (allowed_simple_unions.find(union_name) == allowed_simple_unions.end()) {
// Any unions not in the allow-list are treated as non-simple.
reporter->Report(ErrUnionCannotBeSimple, identifier_type->name.span(),
identifier_type->name);
return false;
}
}
switch (identifier_type->nullability) {
case types::Nullability::kNullable:
// If the identifier is nullable, then we can handle a depth of 1
// because the secondary object is directly accessible.
return depth <= 1u;
case types::Nullability::kNonnullable:
return depth == 0u;
}
}
}
}
FieldShape Struct::Member::fieldshape(WireFormat wire_format) const {
return FieldShape(*this, wire_format);
}
FieldShape Table::Member::Used::fieldshape(WireFormat wire_format) const {
return FieldShape(*this, wire_format);
}
FieldShape Union::Member::Used::fieldshape(WireFormat wire_format) const {
return FieldShape(*this, wire_format);
}
std::vector<std::reference_wrapper<const Union::Member>> Union::MembersSortedByXUnionOrdinal()
const {
std::vector<std::reference_wrapper<const Member>> sorted_members(members.cbegin(),
members.cend());
std::sort(sorted_members.begin(), sorted_members.end(),
[](const auto& member1, const auto& member2) {
return member1.get().ordinal->value < member2.get().ordinal->value;
});
return sorted_members;
}
bool Typespace::Create(const LibraryMediator& lib, const flat::Name& name,
const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor,
const std::optional<Name>& handle_subtype_identifier,
const std::unique_ptr<Constant>& handle_rights,
const std::unique_ptr<Constant>& maybe_size, types::Nullability nullability,
const Type** out_type, LayoutInvocation* out_params) {
std::unique_ptr<Type> type;
if (!CreateNotOwned(lib, name, maybe_arg_type_ctor, handle_subtype_identifier, handle_rights,
maybe_size, nullability, &type, out_params))
return false;
types_.push_back(std::move(type));
*out_type = types_.back().get();
return true;
}
bool Typespace::Create(const LibraryMediator& lib, const flat::Name& name,
const std::unique_ptr<LayoutParameterList>& parameters,
const std::unique_ptr<TypeConstraints>& constraints, const Type** out_type,
LayoutInvocation* out_params) {
std::unique_ptr<Type> type;
if (!CreateNotOwned(lib, name, parameters, constraints, &type, out_params))
return false;
types_.push_back(std::move(type));
*out_type = types_.back().get();
return true;
}
bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name,
const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor,
const std::optional<Name>& handle_subtype_identifier,
const std::unique_ptr<Constant>& handle_rights,
const std::unique_ptr<Constant>& maybe_size,
types::Nullability nullability, std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) {
// TODO(pascallouis): lookup whether we've already created the type, and
// return it rather than create a new one. Lookup must be by name,
// arg_type, size, and nullability.
auto type_template = LookupTemplate(name);
if (type_template == nullptr) {
reporter_->Report(ErrUnknownType, name.span(), name);
return false;
}
return type_template->Create(lib,
{.name = name,
.maybe_arg_type_ctor = maybe_arg_type_ctor,
.handle_subtype_identifier = handle_subtype_identifier,
.handle_rights = handle_rights,
.maybe_size = maybe_size,
.nullability = nullability},
out_type, out_params);
}
bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name,
const std::unique_ptr<LayoutParameterList>& parameters,
const std::unique_ptr<TypeConstraints>& constraints,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) {
// TODO(pascallouis): lookup whether we've already created the type, and
// return it rather than create a new one. Lookup must be by name,
// arg_type, size, and nullability.
auto type_template = LookupTemplate(name);
if (type_template == nullptr) {
reporter_->Report(ErrUnknownType, name.span(), name);
return false;
}
return type_template->Create(lib,
{.name = name, .parameters = parameters, .constraints = constraints},
out_type, out_params);
}
void Typespace::AddTemplate(std::unique_ptr<TypeTemplate> type_template) {
templates_.emplace(type_template->name(), std::move(type_template));
}
const TypeTemplate* Typespace::LookupTemplate(const flat::Name& name) const {
auto global_name = Name::Key(nullptr, name.decl_name());
if (auto iter = templates_.find(global_name); iter != templates_.end()) {
return iter->second.get();
}
if (auto iter = templates_.find(name); iter != templates_.end()) {
return iter->second.get();
}
return nullptr;
}
template <typename... Args>
bool TypeTemplate::Fail(const ErrorDef<const TypeTemplate*, Args...>& err,
const std::optional<SourceSpan>& span, const Args&... args) const {
reporter_->Report(err, span, this, args...);
return false;
}
template <typename... Args>
bool TypeTemplate::Fail(const ErrorDef<Args...>& err, const Args&... args) const {
reporter_->Report(err, args...);
return false;
}
bool TypeTemplate::ResolveOldSyntaxArgs(const LibraryMediator& lib,
const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<CreateInvocation>* out_args,
LayoutInvocation* out_params) const {
const Type* maybe_arg_type = nullptr;
if (unresolved_args.maybe_arg_type_ctor != nullptr) {
if (!lib.ResolveType(unresolved_args.maybe_arg_type_ctor.get()))
return false;
maybe_arg_type = unresolved_args.maybe_arg_type_ctor->type;
out_params->element_type_resolved = maybe_arg_type;
out_params->element_type_raw = unresolved_args.maybe_arg_type_ctor.get();
}
const Size* size = nullptr;
if (unresolved_args.maybe_size != nullptr) {
if (!lib.ResolveSizeBound(unresolved_args.maybe_size.get(), &size)) {
reporter_->Report(ErrCouldNotParseSizeBound, unresolved_args.maybe_size->span);
return false;
}
out_params->size_resolved = size;
out_params->size_raw = unresolved_args.maybe_size.get();
}
Resource* handle_resource_decl = nullptr;
if (unresolved_args.handle_subtype_identifier || unresolved_args.handle_rights) {
if (!GetResource(lib, unresolved_args.name, &handle_resource_decl))
return false;
// TODO(fxbug.dev/74909): Once bare handles are disallowed we won't need to
// throw this error "at use time" anymore and can move it back into
// HandleTypeTemplate::GetResource. At that point, we can also add back some
// more helpful data to the error (like the offending type constructor's name
// and span) which we don't have access to here.
if (!handle_resource_decl)
return Fail(ErrHandleSubtypeNotResource);
}
std::optional<uint32_t> obj_type = std::nullopt;
std::optional<types::HandleSubtype> handle_subtype = std::nullopt;
if (unresolved_args.handle_subtype_identifier) {
const Name& name = *unresolved_args.handle_subtype_identifier;
// the new path uses Constants, the old path uses Names; convert the Name
// to a Constant here to share code paths.
auto into_constant = static_cast<std::unique_ptr<Constant>>(
std::make_unique<IdentifierConstant>(name, *name.span()));
assert(handle_resource_decl);
uint32_t raw_obj_type;
if (!lib.ResolveAsHandleSubtype(handle_resource_decl, into_constant, &raw_obj_type))
return Fail(ErrCouldNotResolveHandleSubtype, name);
obj_type = raw_obj_type;
handle_subtype = types::HandleSubtype(raw_obj_type);
out_params->subtype_resolved = raw_obj_type;
}
const HandleRights* rights = nullptr;
if (unresolved_args.handle_rights) {
if (!lib.ResolveAsHandleRights(handle_resource_decl, unresolved_args.handle_rights.get(),
&rights))
return Fail(ErrCouldNotResolveHandleRights);
out_params->rights_resolved = rights;
out_params->rights_raw = unresolved_args.handle_rights.get();
}
// No work needed for nullability - in the old syntax there's nothing to resolve
// because ? always indicates nullable.
out_params->nullability = unresolved_args.nullability;
*out_args =
std::make_unique<CreateInvocation>(unresolved_args.name, maybe_arg_type, obj_type,
handle_subtype, rights, size, unresolved_args.nullability);
return true;
}
bool TypeTemplate::GetResource(const LibraryMediator& lib, const Name& name,
Resource** out_resource) const {
assert(false &&
"Only the HandleTypeTemplate should ever need to do this, because of hardcoding in the "
"parser");
__builtin_unreachable();
}
class PrimitiveTypeTemplate : public TypeTemplate {
public:
PrimitiveTypeTemplate(Typespace* typespace, Reporter* reporter, const std::string& name,
types::PrimitiveSubtype subtype)
: TypeTemplate(Name::CreateIntrinsic(name), typespace, reporter), subtype_(subtype) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 0) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
num_params);
}
PrimitiveType type(name_, subtype_);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type != nullptr)
return Fail(ErrCannotBeParameterized, args->name.span());
PrimitiveType type(name_, subtype_);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
private:
const types::PrimitiveSubtype subtype_;
};
bool PrimitiveType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints, const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
size_t num_constraints = constraints.items.size();
// assume that a lone constraint was an attempt at specifying `optional` and provide a more
// specific error
// TOOD(fxbug.dev/75112): actually try to compile the optional constraint
if (num_constraints == 1)
return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
if (num_constraints > 1)
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints);
*out_type = std::make_unique<PrimitiveType>(name, subtype);
return true;
}
bool PrimitiveType::ApplySomeLayoutParametersAndConstraints(
const LibraryMediator& lib, const CreateInvocation& create_invocation,
const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (create_invocation.size != nullptr)
return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
if (create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
*out_type = std::make_unique<PrimitiveType>(name, subtype);
return true;
}
class ArrayTypeTemplate final : public TypeTemplate {
public:
ArrayTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("array"), typespace, reporter) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = unresolved_args.parameters->items.size();
size_t expected_params = 2;
if (num_params != expected_params) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span,
expected_params, num_params);
}
const Type* element_type = nullptr;
if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type))
return false;
out_params->element_type_resolved = element_type;
out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor();
const Size* size = nullptr;
if (!lib.ResolveParamAsSize(this, unresolved_args.parameters->items[1], &size))
return false;
out_params->size_resolved = size;
out_params->size_raw = unresolved_args.parameters->items[1]->AsConstant();
ArrayType type(name_, element_type, size);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type == nullptr)
return Fail(ErrMustBeParameterized, args->name.span());
if (args->size == nullptr)
return Fail(ErrMustHaveSize, args->name.span());
if (args->size->value == 0)
return Fail(ErrMustHaveNonZeroSize, args->name.span());
ArrayType type(name_, args->arg_type, args->size);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
};
bool ArrayType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints, const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
size_t num_constraints = constraints.items.size();
// assume that a lone constraint was an attempt at specifying `optional` and provide a more
// specific error
// TOOD(fxbug.dev/75112): actually try to compile the optional constraint
if (num_constraints == 1)
return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
if (num_constraints > 1)
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints);
*out_type = std::make_unique<ArrayType>(name, element_type, element_count);
return true;
}
bool ArrayType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
const CreateInvocation& create_invocation,
const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (create_invocation.size && create_invocation.size != element_count)
return lib.Fail(ErrCannotParameterizeAlias, create_invocation.name.span(), layout);
if (create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
*out_type = std::make_unique<ArrayType>(name, element_type, element_count);
return true;
}
class BytesTypeTemplate final : public TypeTemplate {
public:
BytesTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter),
uint8_type_(kUint8Type) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 0) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
num_params);
}
VectorType type(name_, &uint8_type_);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type != nullptr)
return Fail(ErrCannotBeParameterized, args->name.span());
VectorType type(name_, &uint8_type_);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
private:
// TODO(fxbug.dev/7724): Remove when canonicalizing types.
const Name kUint8TypeName = Name::CreateIntrinsic("uint8");
const PrimitiveType kUint8Type = PrimitiveType(kUint8TypeName, types::PrimitiveSubtype::kUint8);
const PrimitiveType uint8_type_;
};
bool VectorBaseType::ResolveSizeAndNullability(const LibraryMediator& lib,
const TypeConstraints& constraints,
const TypeTemplate* layout,
LayoutInvocation* out_params) {
size_t num_constraints = constraints.items.size();
if (num_constraints == 1) {
LibraryMediator::ResolvedConstraint resolved;
if (!lib.ResolveConstraintAs(
constraints.items[0],
{LibraryMediator::ConstraintKind::kSize, LibraryMediator::ConstraintKind::kNullability},
nullptr /* resource_decl */, &resolved))
return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
switch (resolved.kind) {
case LibraryMediator::ConstraintKind::kSize:
out_params->size_resolved = resolved.value.size;
out_params->size_raw = constraints.items[0].get();
break;
case LibraryMediator::ConstraintKind::kNullability:
out_params->nullability = types::Nullability::kNullable;
break;
default:
assert(false && "Compiler bug: resolved to wrong constraint kind");
}
} else if (num_constraints == 2) {
// first constraint must be size, followed by optional
if (!lib.ResolveSizeBound(constraints.items[0].get(), &out_params->size_resolved))
return lib.Fail(ErrCouldNotParseSizeBound, std::nullopt);
out_params->size_raw = constraints.items[0].get();
if (!lib.ResolveAsOptional(constraints.items[1].get())) {
return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout);
}
out_params->nullability = types::Nullability::kNullable;
} else if (num_constraints >= 3) {
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(2), num_constraints);
}
return true;
}
const Size VectorBaseType::kMaxSize = Size::Max();
class VectorTypeTemplate final : public TypeTemplate {
public:
VectorTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 1) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(1),
num_params);
}
const Type* element_type = nullptr;
if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type))
return false;
out_params->element_type_resolved = element_type;
out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor();
VectorType type(name_, element_type);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type == nullptr)
return Fail(ErrMustBeParameterized, args->name.span());
VectorType type(name_, args->arg_type);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
};
bool VectorType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints, const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (!ResolveSizeAndNullability(lib, constraints, layout, out_params))
return false;
bool is_already_nullable = nullability == types::Nullability::kNullable;
bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable;
if (is_already_nullable && is_nullability_applied)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
auto merged_nullability = is_already_nullable || is_nullability_applied
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
if (element_count != &kMaxSize && out_params->size_resolved)
return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
auto merged_size = out_params->size_resolved ? out_params->size_resolved : element_count;
*out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability);
return true;
}
bool VectorType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
const CreateInvocation& create_invocation,
const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
bool is_already_nullable = nullability == types::Nullability::kNullable;
bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable;
if (is_already_nullable && is_nullability_applied)
return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout);
auto merged_nullability = is_already_nullable || is_nullability_applied
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
// TODO(fxbug.dev/74193): take the smaller bound
if (element_count != &kMaxSize && create_invocation.size) {
return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
}
auto merged_size = create_invocation.size ? create_invocation.size : element_count;
*out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability);
return true;
}
class StringTypeTemplate final : public TypeTemplate {
public:
StringTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("string"), typespace, reporter) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 0) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
num_params);
}
StringType type(name_);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type != nullptr)
return Fail(ErrCannotBeParameterized, args->name.span());
StringType type(name_);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
};
bool StringType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints, const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (!ResolveSizeAndNullability(lib, constraints, layout, out_params))
return false;
bool is_already_nullable = nullability == types::Nullability::kNullable;
bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable;
if (is_already_nullable && is_nullability_applied)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
auto merged_nullability = is_already_nullable || is_nullability_applied
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
if (max_size != &kMaxSize && out_params->size_resolved)
return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
auto merged_size = out_params->size_resolved ? out_params->size_resolved : max_size;
*out_type = std::make_unique<StringType>(name, merged_size, merged_nullability);
return true;
}
bool StringType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
const CreateInvocation& create_invocation,
const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
bool is_already_nullable = nullability == types::Nullability::kNullable;
bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable;
if (is_already_nullable && is_nullability_applied)
return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout);
auto merged_nullability = is_already_nullable || is_nullability_applied
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
// Note that we don't have a way of knowing whether a size was actually specified,
// since unspecified sizes are always replaced with a MAX default. Assume that
// MAX means unspecified (this means that we would allow bounding twice if the
// user uses MAX both times).
// TODO(fxbug.dev/74193): take the smaller bound
if (*max_size != kMaxSize && create_invocation.size)
return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
auto merged_size = create_invocation.size ? create_invocation.size : max_size;
*out_type = std::make_unique<StringType>(name, merged_size, merged_nullability);
return true;
}
class HandleTypeTemplate final : public TypeTemplate {
public:
HandleTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("handle"), typespace, reporter) {}
// Currently we take a name as parameter, but the parser restricts this name to be
// something that ends in "handle".
// In a more general implementation, we would add such an entry at "Consume" time of
// the resource in question, allowing us to set a pointer to the Resource declaration
// on the HandleTypeTemplate itself. We can't currently do this because we don't have
// access to the definition of "handle" when we insert it into the root typespace, so we
// need to resort to looking it up and doing validation at runtime.
bool GetResource(const LibraryMediator& lib, const Name& name,
Resource** out_resource) const override {
Decl* handle_decl = lib.LookupDeclByName(name);
if (!handle_decl || handle_decl->kind != Decl::Kind::kResource) {
// TODO(fxbug.dev/74909): We can't error yet, because this may be a bare
// `handle` with no constraints applied. Leave out_resource as a nullptr
// for the caller to handle and break out early.
return true;
}
auto* resource = static_cast<Resource*>(handle_decl);
if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
GetName(resource->subtype_ctor).full_name() != "uint32") {
reporter_->Report(ErrResourceMustBeUint32Derived, resource->name);
return false;
}
*out_resource = resource;
return true;
}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
size_t num_params = !args.parameters->items.empty();
if (num_params)
return Fail(ErrWrongNumberOfLayoutParameters, args.parameters->span, size_t(0), num_params);
Resource* handle_resource_decl = nullptr;
if (!GetResource(lib, args.name, &handle_resource_decl))
return false;
HandleType type(name_, handle_resource_decl);
return type.ApplyConstraints(lib, *args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> resolved;
if (!ResolveOldSyntaxArgs(lib, args, &resolved, out_params))
return false;
assert(resolved->arg_type == nullptr);
if (resolved->size != nullptr)
return Fail(ErrCannotHaveSize, resolved->name.span());
// Note that in the old syntax, we'll already have looked up the Resource*
// (if necessary) since the old syntax resolves arguments ahead of time (see
// call to ResolveOldSyntaxArgs above). However, we still need to obtain the
// Resource* and pass it to the HandleType, since it may be used to resolve
// more constraints later (e.g. if there's an alias to this handle that also
// specifies more constraints) in the new syntax.
Resource* handle_resource_decl = nullptr;
if (!GetResource(lib, args.name, &handle_resource_decl))
return false;
HandleType type(name_, handle_resource_decl);
return type.ApplySomeLayoutParametersAndConstraints(lib, *resolved, this, out_type, out_params);
}
private:
const static HandleRights kSameRights;
};
const HandleRights HandleType::kSameRights = HandleRights(kHandleSameRights);
bool HandleType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints, const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
// We need to store this separately from out_params, because out_params doesn't
// store the raw Constant that gets resolved to a nullability constraint.
std::optional<SourceSpan> applied_nullability_span;
size_t num_constraints = constraints.items.size();
// There's a bit of a chicken and egg problem here: we only want to error when the
// resource_decl is undefined while trying to resolve a subtype or handle rights
// constant, but we don't know if a constraint is a subtype or handle rights or
// something else (like optional) without the resource_decl.
// In practice, such an edge case is almost guaranteed not to happen, so just error
// if there is no resource_decl and there are any constraints at all.
if (num_constraints > 0 && !resource_decl) {
// TODO(fxbug.dev/74909): Once bare handles are disallowed we won't need to
// throw this error "at use time" anymore and can move it back into
// HandleTypeTemplate::GetResource. At that point, we can also add back some
// more helpful data to the error (like the offending type constructor's name
// and span) which we don't have access to here.
return lib.Fail(ErrHandleSubtypeNotResource, std::nullopt);
}
if (num_constraints == 0) {
// no constraints: set to default subtype below
} else if (num_constraints == 1) {
// lone constraint can be either subtype or optional
auto constraint_span = constraints.items[0]->span;
LibraryMediator::ResolvedConstraint resolved;
if (!lib.ResolveConstraintAs(constraints.items[0],
{LibraryMediator::ConstraintKind::kHandleSubtype,
LibraryMediator::ConstraintKind::kNullability},
resource_decl, &resolved))
return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
switch (resolved.kind) {
case LibraryMediator::ConstraintKind::kHandleSubtype:
out_params->subtype_resolved = resolved.value.handle_subtype;
out_params->subtype_raw = constraints.items[0].get();
break;
case LibraryMediator::ConstraintKind::kNullability:
out_params->nullability = types::Nullability::kNullable;
applied_nullability_span = constraint_span;
break;
default:
assert(false && "Compiler bug: resolved to wrong constraint kind");
}
} else if (num_constraints == 2) {
// the first constraint must be subtype
auto constraint_span = constraints.items[0]->span;
uint32_t obj_type = 0;
if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type))
return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
out_params->subtype_resolved = obj_type;
out_params->subtype_raw = constraints.items[0].get();
// the second constraint can either be rights or optional
constraint_span = constraints.items[1]->span;
LibraryMediator::ResolvedConstraint resolved;
if (!lib.ResolveConstraintAs(constraints.items[1],
{LibraryMediator::ConstraintKind::kHandleRights,
LibraryMediator::ConstraintKind::kNullability},
resource_decl, &resolved))
return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
switch (resolved.kind) {
case LibraryMediator::ConstraintKind::kHandleRights:
out_params->rights_resolved = resolved.value.handle_rights;
out_params->rights_raw = constraints.items[1].get();
break;
case LibraryMediator::ConstraintKind::kNullability:
out_params->nullability = types::Nullability::kNullable;
applied_nullability_span = constraint_span;
break;
default:
assert(false && "Compiler bug: resolved to wrong constraint kind");
}
} else if (num_constraints == 3) {
// no degrees of freedom: must be subtype, followed by rights, then optional
uint32_t obj_type = 0;
if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type))
return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
out_params->subtype_resolved = obj_type;
out_params->subtype_raw = constraints.items[0].get();
const HandleRights* rights = nullptr;
if (!lib.ResolveAsHandleRights(resource_decl, constraints.items[1].get(), &rights))
return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout);
out_params->rights_resolved = rights;
out_params->rights_raw = constraints.items[1].get();
if (!lib.ResolveAsOptional(constraints.items[2].get()))
return lib.Fail(ErrUnexpectedConstraint, constraints.items[2]->span, layout);
out_params->nullability = types::Nullability::kNullable;
applied_nullability_span = constraints.items[2]->span;
} else {
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(3), num_constraints);
}
bool has_obj_type = subtype != types::HandleSubtype::kHandle;
if (has_obj_type && out_params->subtype_resolved)
return lib.Fail(ErrCannotConstrainTwice, out_params->subtype_raw->span, layout);
// TODO(fxbug.dev/64629): We need to allow setting a default obj_type in
// resource_definition declarations rather than hard-coding.
uint32_t merged_obj_type = obj_type;
if (out_params->subtype_resolved) {
merged_obj_type = out_params->subtype_resolved.value();
}
bool has_nullability = nullability == types::Nullability::kNullable;
if (has_nullability && out_params->nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotIndicateNullabilityTwice, applied_nullability_span, layout);
auto merged_nullability =
has_nullability || out_params->nullability == types::Nullability::kNullable
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
bool has_rights = rights != &kSameRights;
if (has_rights && out_params->rights_resolved)
return lib.Fail(ErrCannotConstrainTwice, out_params->rights_raw->span, layout);
auto merged_rights = rights;
if (out_params->rights_resolved) {
merged_rights = out_params->rights_resolved;
}
*out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type,
types::HandleSubtype(merged_obj_type), merged_rights,
merged_nullability);
return true;
}
bool HandleType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
const CreateInvocation& create_invocation,
const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (create_invocation.size)
return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
bool has_obj_type = subtype != types::HandleSubtype::kHandle;
if (has_obj_type && create_invocation.obj_type)
return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout);
uint32_t merged_obj_type = obj_type;
if (create_invocation.obj_type.has_value())
merged_obj_type = create_invocation.obj_type.value();
bool has_nullability = nullability == types::Nullability::kNullable;
if (has_nullability && create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
auto merged_nullability =
has_nullability || create_invocation.nullability == types::Nullability::kNullable
? types::Nullability::kNullable
: types::Nullability::kNonnullable;
bool has_rights = rights != &kSameRights;
if (has_rights && create_invocation.handle_rights)
return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout);
auto merged_rights = rights;
if (create_invocation.handle_rights)
merged_rights = create_invocation.handle_rights;
*out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type,
types::HandleSubtype(merged_obj_type), merged_rights,
merged_nullability);
return true;
}
class RequestTypeTemplate final : public TypeTemplate {
public:
RequestTypeTemplate(Typespace* typespace, Reporter* reporter)
: TypeTemplate(Name::CreateIntrinsic("request"), typespace, reporter) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
assert(false && "Compiler bug: this type template should only be used in the old syntax");
return false;
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (args->arg_type == nullptr)
return Fail(ErrMustBeParameterized, args->name.span());
if (args->arg_type->kind != Type::Kind::kIdentifier)
return Fail(ErrMustBeAProtocol, args->name.span());
auto protocol_type = static_cast<const IdentifierType*>(args->arg_type);
if (protocol_type->type_decl->kind != Decl::Kind::kProtocol)
return Fail(ErrMustBeAProtocol, args->name.span());
if (args->size != nullptr)
return Fail(ErrCannotHaveSize, args->name.span());
RequestHandleType type(name_, protocol_type);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
};
bool RequestHandleType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints,
const TypeTemplate* layout,
std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
assert(false && "Compiler bug: this type should only be used in the old syntax");
return false;
}
bool RequestHandleType::ApplySomeLayoutParametersAndConstraints(
const LibraryMediator& lib, const CreateInvocation& create_invocation,
const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
if (create_invocation.size)
return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
if (nullability == types::Nullability::kNullable &&
create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
auto merged_nullability = nullability;
if (create_invocation.nullability == types::Nullability::kNullable)
merged_nullability = create_invocation.nullability;
*out_type = std::make_unique<RequestHandleType>(name, protocol_type, merged_nullability);
return true;
}
class TypeDeclTypeTemplate final : public TypeTemplate {
public:
TypeDeclTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, Library* library,
TypeDecl* type_decl)
: TypeTemplate(std::move(name), typespace, reporter),
library_(library),
type_decl_(type_decl) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kProtocol) {
if (type_decl_->compiling) {
type_decl_->recursive = true;
} else {
if (!library_->CompileDecl(type_decl_)) {
return false;
}
}
}
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 0) {
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
num_params);
}
IdentifierType type(name_, type_decl_);
return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
assert(!args->handle_subtype);
if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kProtocol) {
if (type_decl_->compiling) {
type_decl_->recursive = true;
} else {
if (!library_->CompileDecl(type_decl_)) {
return false;
}
}
}
if (args->arg_type != nullptr)
return Fail(ErrCannotBeParameterized, args->name.span());
IdentifierType type(name_, type_decl_);
return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
}
private:
Library* library_;
TypeDecl* type_decl_;
};
bool IdentifierType::ApplyConstraints(const flat::LibraryMediator& lib,
const TypeConstraints& constraints,
const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
size_t num_constraints = constraints.items.size();
switch (type_decl->kind) {
// These types have no allowed constraints
case Decl::Kind::kBits:
case Decl::Kind::kEnum:
case Decl::Kind::kTable:
// assume that a lone constraint was an attempt at specifying `optional` and provide a more
// specific error
// TOOD(fxbug.dev/75112): actually try to compile the optional constraint
if (num_constraints == 1)
return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
if (num_constraints > 1) {
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0),
num_constraints);
}
break;
// These types have one allowed constraint (`optional`). For type aliases,
// we need to allow the possibility that the concrete type does allow `optional`,
// if it doesn't the Type itself will catch the error.
case Decl::Kind::kTypeAlias:
case Decl::Kind::kStruct:
case Decl::Kind::kUnion:
if (num_constraints > 1) {
return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(1),
num_constraints);
}
break;
case Decl::Kind::kConst:
case Decl::Kind::kResource:
// Cannot have const: entries for constants do not exist in the typespace, so
// they're caught earlier.
// Cannot have resource: resource types should have resolved to the HandleTypeTemplate
assert(false && "Compiler bug: unexpected identifier type decl kind");
break;
// TODO(fxbug.dev/75837):
// These can't be used as types. This will be caught later, in VerifyTypeCategory.
case Decl::Kind::kService:
case Decl::Kind::kProtocol:
break;
}
types::Nullability applied_nullability = types::Nullability::kNonnullable;
if (num_constraints == 1) {
// must be optional
if (!lib.ResolveAsOptional(constraints.items[0].get()))
return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
applied_nullability = types::Nullability::kNullable;
}
if (nullability == types::Nullability::kNullable &&
applied_nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
auto merged_nullability = nullability;
if (applied_nullability == types::Nullability::kNullable)
merged_nullability = applied_nullability;
out_params->nullability = applied_nullability;
*out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability);
return true;
}
bool IdentifierType::ApplySomeLayoutParametersAndConstraints(
const LibraryMediator& lib, const CreateInvocation& create_invocation,
const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
LayoutInvocation* out_params) const {
switch (type_decl->kind) {
// These types can't be nullable
case Decl::Kind::kBits:
case Decl::Kind::kEnum:
case Decl::Kind::kTable:
if (create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
break;
// These types have one allowed constraint (`optional`). For type aliases,
// we need to allow the possibility that the concrete type does allow `optional`,
// if it doesn't the Type itself will catch the error.
case Decl::Kind::kProtocol:
case Decl::Kind::kTypeAlias:
case Decl::Kind::kStruct:
case Decl::Kind::kUnion:
if (nullability == types::Nullability::kNullable &&
create_invocation.nullability == types::Nullability::kNullable)
return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
break;
// These should never be encountered
case Decl::Kind::kConst:
case Decl::Kind::kResource: {
// Cannot have const: entries for constants do not exist in the typespace
// Cannot have resource: resource types should have resolved to the HandleTypeTemplate
assert(false);
break;
}
// TODO(fxbug.dev/75837):
// Services are not allowed to be used as types. This is caught later, during
// VerifyTypeCategory.
case Decl::Kind::kService:
break;
}
auto merged_nullability = nullability;
if (create_invocation.nullability == types::Nullability::kNullable)
merged_nullability = create_invocation.nullability;
*out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability);
return true;
}
class TypeAliasTypeTemplate final : public TypeTemplate {
public:
TypeAliasTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, TypeAlias* decl)
: TypeTemplate(std::move(name), typespace, reporter), decl_(decl) {}
bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
if (!decl_->compiled) {
if (decl_->compiling) {
return Fail(ErrIncludeCycle);
}
if (!lib.CompileDecl(decl_)) {
return false;
}
}
size_t num_params = unresolved_args.parameters->items.size();
if (num_params != 0)
return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
num_params);
// Compilation failed while trying to resolve something farther up the chain;
// exit early
if (!GetType(decl_->partial_type_ctor))
return false;
const auto& aliased_type = GetType(decl_->partial_type_ctor);
out_params->from_type_alias = decl_;
return aliased_type->ApplyConstraints(lib, *unresolved_args.constraints, this, out_type,
out_params);
}
bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
std::unique_ptr<CreateInvocation> args;
if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
return false;
// Note that because fidlc only populates these handle fields if it sees
// "handle" in the type constructor, aliases of handles will never correctly
// capture any handle constraints. It is not a TODO to fix this since this
// issue does not exist in the new syntax.
assert(!args->handle_subtype);
assert(!args->handle_rights);
if (!decl_->compiled) {
if (decl_->compiling) {
return Fail(ErrIncludeCycle);
}
if (!lib.CompileDecl(decl_)) {
return false;
}
}
if (unresolved_args.maybe_arg_type_ctor != nullptr)
return Fail(ErrCannotParameterizeAlias, args->name.span());
// Compilation failed while trying to resolve something farther up the chain;
// exit early
if (!GetType(decl_->partial_type_ctor))
return false;
const auto& aliased_type = GetType(decl_->partial_type_ctor);
out_params->from_type_alias = decl_;
return aliased_type->ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type,
out_params);
}
private:
TypeAlias* decl_;
};
Typespace Typespace::RootTypes(Reporter* reporter) {
Typespace root_typespace(reporter);
auto add_template = [&](std::unique_ptr<TypeTemplate> type_template) {
const Name& name = type_template->name();
root_typespace.templates_.emplace(name, std::move(type_template));
};
auto add_primitive = [&](const std::string& name, types::PrimitiveSubtype subtype) {
add_template(std::make_unique<PrimitiveTypeTemplate>(&root_typespace, reporter, name, subtype));
};
add_primitive("bool", types::PrimitiveSubtype::kBool);
add_primitive("int8", types::PrimitiveSubtype::kInt8);
add_primitive("int16", types::PrimitiveSubtype::kInt16);
add_primitive("int32", types::PrimitiveSubtype::kInt32);
add_primitive("int64", types::PrimitiveSubtype::kInt64);
add_primitive("uint8", types::PrimitiveSubtype::kUint8);
add_primitive("uint16", types::PrimitiveSubtype::kUint16);
add_primitive("uint32", types::PrimitiveSubtype::kUint32);
add_primitive("uint64", types::PrimitiveSubtype::kUint64);
add_primitive("float32", types::PrimitiveSubtype::kFloat32);
add_primitive("float64", types::PrimitiveSubtype::kFloat64);
// TODO(fxbug.dev/7807): Remove when there is generalized support.
const static auto kByteName = Name::CreateIntrinsic("byte");
const static auto kBytesName = Name::CreateIntrinsic("bytes");
root_typespace.templates_.emplace(
kByteName, std::make_unique<PrimitiveTypeTemplate>(&root_typespace, reporter, "uint8",
types::PrimitiveSubtype::kUint8));
root_typespace.templates_.emplace(kBytesName,
std::make_unique<BytesTypeTemplate>(&root_typespace, reporter));
add_template(std::make_unique<ArrayTypeTemplate>(&root_typespace, reporter));
add_template(std::make_unique<VectorTypeTemplate>(&root_typespace, reporter));
add_template(std::make_unique<StringTypeTemplate>(&root_typespace, reporter));
add_template(std::make_unique<HandleTypeTemplate>(&root_typespace, reporter));
add_template(std::make_unique<RequestTypeTemplate>(&root_typespace, reporter));
return root_typespace;
}
AttributeSchema::AttributeSchema(const std::set<Placement>& allowed_placements,
const std::set<std::string> allowed_values, Constraint constraint)
: allowed_placements_(allowed_placements),
allowed_values_(allowed_values),
constraint_(std::move(constraint)) {}
AttributeSchema AttributeSchema::Deprecated() {
return AttributeSchema({Placement::kDeprecated}, {});
}
void AttributeSchema::ValidatePlacement(Reporter* reporter, const raw::Attribute& attribute,
Placement placement) const {
if (allowed_placements_.size() == 0)
return;
if (allowed_placements_.size() == 1 && *allowed_placements_.cbegin() == Placement::kDeprecated) {
reporter->Report(ErrDeprecatedAttribute, attribute.span(), attribute);
return;
}
auto iter = allowed_placements_.find(placement);
if (iter != allowed_placements_.end())
return;
reporter->Report(ErrInvalidAttributePlacement, attribute.span(), attribute);
}
void AttributeSchema::ValidateValue(Reporter* reporter, const raw::Attribute& attribute) const {
if (allowed_values_.size() == 0)
return;
auto iter = allowed_values_.find(attribute.value);
if (iter != allowed_values_.end())
return;
reporter->Report(ErrInvalidAttributeValue, attribute.span(), attribute, attribute.value,
allowed_values_);
}
void AttributeSchema::ValidateConstraint(Reporter* reporter, const raw::Attribute& attribute,
const Decl* decl) const {
auto check = reporter->Checkpoint();
auto passed = constraint_(reporter, attribute, decl);
if (passed) {
assert(check.NoNewErrors() && "cannot add errors and pass");
} else if (check.NoNewErrors()) {
// TODO(pascallouis): It would be nicer to use the span of
// the declaration, however we do not keep it around today.
reporter->Report(ErrAttributeConstraintNotSatisfied, attribute.span(), attribute,
attribute.value);
}
}
bool SimpleLayoutConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) {
assert(decl->kind == Decl::Kind::kStruct);
auto struct_decl = static_cast<const Struct*>(decl);
bool ok = true;
for (const auto& member : struct_decl->members) {
if (!IsSimple(GetType(member.type_ctor), reporter)) {
reporter->Report(ErrMemberMustBeSimple, member.name, member.name.data());
ok = false;
}
}
return ok;
}
bool ParseBound(Reporter* reporter, const SourceSpan& span, const std::string& input,
uint32_t* out_value) {
auto result = utils::ParseNumeric(input, out_value, 10);
switch (result) {
case utils::ParseNumericResult::kOutOfBounds:
reporter->Report(ErrBoundIsTooBig, span);
return false;
case utils::ParseNumericResult::kMalformed: {
reporter->Report(ErrUnableToParseBound, span, input);
return false;
}
case utils::ParseNumericResult::kSuccess:
return true;
}
}
bool Library::VerifyInlineSize(const Struct* struct_decl) {
if (struct_decl->typeshape(WireFormat::kV1NoEe).InlineSize() >= 65536) {
return Library::Fail(ErrInlineSizeExceeds64k, *struct_decl);
}
return true;
}
bool MaxBytesConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) {
uint32_t bound;
if (!ParseBound(reporter, attribute.span(), attribute.value, &bound))
return false;
uint32_t max_bytes = std::numeric_limits<uint32_t>::max();
switch (decl->kind) {
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<const Struct*>(decl);
max_bytes = struct_decl->typeshape(WireFormat::kV1NoEe).InlineSize() +
struct_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine();
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<const Table*>(decl);
max_bytes = table_decl->typeshape(WireFormat::kV1NoEe).InlineSize() +
table_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine();
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<const Union*>(decl);
max_bytes = union_decl->typeshape(WireFormat::kV1NoEe).InlineSize() +
union_decl->typeshape(WireFormat::kV1NoEe).MaxOutOfLine();
break;
}
default:
assert(false && "unexpected kind");
return false;
}
if (max_bytes > bound) {
reporter->Report(ErrTooManyBytes, attribute.span(), bound, max_bytes);
return false;
}
return true;
}
bool MaxHandlesConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) {
uint32_t bound;
if (!ParseBound(reporter, attribute.span(), attribute.value, &bound))
return false;
uint32_t max_handles = std::numeric_limits<uint32_t>::max();
switch (decl->kind) {
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<const Struct*>(decl);
max_handles = struct_decl->typeshape(WireFormat::kV1NoEe).MaxHandles();
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<const Table*>(decl);
max_handles = table_decl->typeshape(WireFormat::kV1NoEe).MaxHandles();
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<const Union*>(decl);
max_handles = union_decl->typeshape(WireFormat::kV1NoEe).MaxHandles();
break;
}
default:
assert(false && "unexpected kind");
return false;
}
if (max_handles > bound) {
reporter->Report(ErrTooManyHandles, attribute.span(), bound, max_handles);
return false;
}
return true;
}
bool ResultShapeConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) {
assert(decl->kind == Decl::Kind::kUnion);
auto union_decl = static_cast<const Union*>(decl);
assert(union_decl->members.size() == 2);
auto& error_member = union_decl->members.at(1);
assert(error_member.maybe_used && "must have an error member");
auto error_type = GetType(error_member.maybe_used->type_ctor);
const PrimitiveType* error_primitive = nullptr;
if (error_type->kind == Type::Kind::kPrimitive) {
error_primitive = static_cast<const PrimitiveType*>(error_type);
} else if (error_type->kind == Type::Kind::kIdentifier) {
auto identifier_type = static_cast<const IdentifierType*>(error_type);
if (identifier_type->type_decl->kind == Decl::Kind::kEnum) {
auto error_enum = static_cast<const Enum*>(identifier_type->type_decl);
assert(GetType(error_enum->subtype_ctor)->kind == Type::Kind::kPrimitive);
error_primitive = static_cast<const PrimitiveType*>(GetType(error_enum->subtype_ctor));
}
}
if (!error_primitive || (error_primitive->subtype != types::PrimitiveSubtype::kInt32 &&
error_primitive->subtype != types::PrimitiveSubtype::kUint32)) {
reporter->Report(ErrInvalidErrorType, decl->name.span());
return false;
}
return true;
}
static std::string Trim(std::string s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !utils::IsWhitespace(static_cast<char>(ch));
}));
s.erase(std::find_if(s.rbegin(), s.rend(),
[](int ch) { return !utils::IsWhitespace(static_cast<char>(ch)); })
.base(),
s.end());
return s;
}
bool TransportConstraint(Reporter* reporter, const raw::Attribute& attribute, const Decl* decl) {
// Parse comma separated transports
const std::string& value = attribute.value;
std::string::size_type prev_pos = 0;
std::string::size_type pos;
std::vector<std::string> transports;
while ((pos = value.find(',', prev_pos)) != std::string::npos) {
transports.emplace_back(Trim(value.substr(prev_pos, pos - prev_pos)));
prev_pos = pos + 1;
}
transports.emplace_back(Trim(value.substr(prev_pos)));
// Validate that they're ok
// function-local static pointer to non-trivially-destructible type
// is allowed by styleguide
static const auto kValidTransports = new std::set<std::string>{
"Banjo",
"Channel",
"Syscall",
};
for (auto transport : transports) {
if (kValidTransports->count(transport) == 0) {
reporter->Report(ErrInvalidTransportType, decl->name.span(), transport, *kValidTransports);
return false;
}
}
return true;
}
Resource::Property* Resource::LookupProperty(std::string_view name) {
for (Property& property : properties) {
if (property.name.data() == name.data()) {
return &property;
}
}
return nullptr;
}
Libraries::Libraries() {
// clang-format off
AddAttributeSchema("Discoverable", AttributeSchema({
AttributeSchema::Placement::kProtocolDecl,
}, {
"",
}));
AddAttributeSchema("Doc", AttributeSchema({
/* any placement */
}, {
/* any value */
}));
AddAttributeSchema("Layout", AttributeSchema::Deprecated()),
AddAttributeSchema("ForDeprecatedCBindings", AttributeSchema({
AttributeSchema::Placement::kProtocolDecl,
AttributeSchema::Placement::kStructDecl,
}, {
"",
},
SimpleLayoutConstraint));
AddAttributeSchema("MaxBytes", AttributeSchema({
AttributeSchema::Placement::kProtocolDecl,
AttributeSchema::Placement::kMethod,
AttributeSchema::Placement::kStructDecl,
AttributeSchema::Placement::kTableDecl,
AttributeSchema::Placement::kUnionDecl,
}, {
/* any value */
},
MaxBytesConstraint));
AddAttributeSchema("MaxHandles", AttributeSchema({
AttributeSchema::Placement::kProtocolDecl,
AttributeSchema::Placement::kMethod,
AttributeSchema::Placement::kStructDecl,
AttributeSchema::Placement::kTableDecl,
AttributeSchema::Placement::kUnionDecl,
}, {
/* any value */
},
MaxHandlesConstraint));
AddAttributeSchema("Result", AttributeSchema({
AttributeSchema::Placement::kUnionDecl,
}, {
"",
},
ResultShapeConstraint));
AddAttributeSchema("Selector", AttributeSchema({
AttributeSchema::Placement::kMethod,
}, {
/* any value */
}));
AddAttributeSchema("Transitional", AttributeSchema({
AttributeSchema::Placement::kMethod,
AttributeSchema::Placement::kBitsDecl,
AttributeSchema::Placement::kEnumDecl,
AttributeSchema::Placement::kUnionDecl,
}, {
/* any value */
}));
AddAttributeSchema("Transport", AttributeSchema({
AttributeSchema::Placement::kProtocolDecl,
}, {
/* any value */
}, TransportConstraint));
AddAttributeSchema("Unknown", AttributeSchema({
AttributeSchema::Placement::kEnumMember,
AttributeSchema::Placement::kUnionMember,
}, {
""
}));
// clang-format on
}
bool Libraries::Insert(std::unique_ptr<Library> library) {
std::vector<std::string_view> library_name = library->name();
auto iter = all_libraries_.emplace(library_name, std::move(library));
return iter.second;
}
bool Libraries::Lookup(const std::vector<std::string_view>& library_name,
Library** out_library) const {
auto iter = all_libraries_.find(library_name);
if (iter == all_libraries_.end()) {
return false;
}
*out_library = iter->second.get();
return true;
}
std::set<std::vector<std::string_view>> Libraries::Unused(const Library* target_library) const {
std::set<std::vector<std::string_view>> unused;
for (auto& name_library : all_libraries_)
unused.insert(name_library.first);
unused.erase(target_library->name());
std::set<const Library*> worklist = {target_library};
while (worklist.size() != 0) {
auto it = worklist.begin();
auto next = *it;
worklist.erase(it);
for (const auto dependency : next->dependencies()) {
unused.erase(dependency->name());
worklist.insert(dependency);
}
}
return unused;
}
size_t EditDistance(const std::string& sequence1, const std::string& sequence2) {
size_t s1_length = sequence1.length();
size_t s2_length = sequence2.length();
size_t row1[s1_length + 1];
size_t row2[s1_length + 1];
size_t* last_row = row1;
size_t* this_row = row2;
for (size_t i = 0; i <= s1_length; i++)
last_row[i] = i;
for (size_t j = 0; j < s2_length; j++) {
this_row[0] = j + 1;
auto s2c = sequence2[j];
for (size_t i = 1; i <= s1_length; i++) {
auto s1c = sequence1[i - 1];
this_row[i] = std::min(std::min(last_row[i] + 1, this_row[i - 1] + 1),
last_row[i - 1] + (s1c == s2c ? 0 : 1));
}
std::swap(last_row, this_row);
}
return last_row[s1_length];
}
const AttributeSchema* Libraries::RetrieveAttributeSchema(Reporter* reporter,
const raw::Attribute& attribute) const {
const auto& attribute_name = fidl::utils::to_upper_camel_case(attribute.name);
auto iter = attribute_schemas_.find(attribute_name);
if (iter != attribute_schemas_.end()) {
const auto& schema = iter->second;
return &schema;
}
// Skip typo check?
if (reporter == nullptr)
return nullptr;
// Match against all known attributes.
for (const auto& name_and_schema : attribute_schemas_) {
auto edit_distance = EditDistance(name_and_schema.first, attribute_name);
if (0 < edit_distance && edit_distance < 2) {
reporter->Report(WarnAttributeTypo, attribute.span(), attribute_name, name_and_schema.first);
return nullptr;
}
}
return nullptr;
}
bool Dependencies::Register(const SourceSpan& span, std::string_view filename, Library* dep_library,
const std::unique_ptr<raw::Identifier>& maybe_alias) {
refs_.push_back(std::make_unique<LibraryRef>(span, dep_library));
auto ref = refs_.back().get();
auto library_name = dep_library->name();
if (!InsertByName(filename, library_name, ref)) {
return false;
}
if (maybe_alias) {
std::vector<std::string_view> alias_name = {maybe_alias->span().data()};
if (!InsertByName(filename, alias_name, ref)) {
return false;
}
}
dependencies_aggregate_.insert(dep_library);
return true;
}
bool Dependencies::InsertByName(std::string_view filename,
const std::vector<std::string_view>& name, LibraryRef* ref) {
auto iter = dependencies_.find(std::string(filename));
if (iter == dependencies_.end()) {
dependencies_.emplace(filename, std::make_unique<ByName>());
}
iter = dependencies_.find(std::string(filename));
assert(iter != dependencies_.end());
auto insert = iter->second->emplace(name, ref);
return insert.second;
}
bool Dependencies::Contains(std::string_view filename, const std::vector<std::string_view>& name) {
auto iter1 = dependencies_.find(std::string(filename));
if (iter1 == dependencies_.end()) {
return false;
}
auto iter2 = iter1->second->find(name);
return iter2 != iter1->second->end();
}
bool Dependencies::Lookup(std::string_view filename, const std::vector<std::string_view>& name,
Dependencies::LookupMode mode, Library** out_library) const {
auto iter1 = dependencies_.find(std::string(filename));
if (iter1 == dependencies_.end()) {
return false;
}
auto iter2 = iter1->second->find(name);
if (iter2 == iter1->second->end()) {
return false;
}
auto ref = iter2->second;
if (mode == Dependencies::LookupMode::kUse) {
ref->used_ = true;
}
*out_library = ref->library_;
return true;
}
bool Dependencies::VerifyAllDependenciesWereUsed(const Library& for_library, Reporter* reporter) {
auto checkpoint = reporter->Checkpoint();
for (auto by_name_iter = dependencies_.begin(); by_name_iter != dependencies_.end();
by_name_iter++) {
const auto& by_name = *by_name_iter->second;
for (const auto& name_to_ref : by_name) {
const auto& ref = name_to_ref.second;
if (ref->used_)
continue;
reporter->Report(ErrUnusedImport, ref->span_, for_library.name(), ref->library_->name(),
ref->library_->name());
}
}
return checkpoint.NoNewErrors();
}
// Consuming the AST is primarily concerned with walking the tree and
// flattening the representation. The AST's declaration nodes are
// converted into the Library's foo_declaration structures. This means pulling
// a struct declaration inside a protocol out to the top level and
// so on.
std::string LibraryName(const Library* library, std::string_view separator) {
if (library != nullptr) {
return StringJoin(library->name(), separator);
}
return std::string();
}
bool Library::Fail(std::unique_ptr<Diagnostic> err) {
assert(err && "should not report nullptr error");
reporter_->Report(std::move(err));
return false;
}
template <typename... Args>
bool Library::Fail(const ErrorDef<Args...>& err, const Args&... args) {
reporter_->Report(err, args...);
return false;
}
template <typename... Args>
bool Library::Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span,
const Args&... args) {
reporter_->Report(err, span, args...);
return false;
}
void Library::ValidateAttributesPlacement(AttributeSchema::Placement placement,
const raw::AttributeList* attributes) {
if (attributes == nullptr)
return;
for (const auto& attribute : attributes->attributes) {
auto schema = all_libraries_->RetrieveAttributeSchema(reporter_, attribute);
if (schema != nullptr) {
schema->ValidatePlacement(reporter_, attribute, placement);
schema->ValidateValue(reporter_, attribute);
}
}
}
void Library::ValidateAttributesConstraints(const Decl* decl,
const raw::AttributeList* attributes) {
if (attributes == nullptr)
return;
for (const auto& attribute : attributes->attributes) {
auto schema = all_libraries_->RetrieveAttributeSchema(nullptr, attribute);
if (schema != nullptr)
schema->ValidateConstraint(reporter_, attribute, decl);
}
}
bool Library::LookupDependency(std::string_view filename, const std::vector<std::string_view>& name,
Library** out_library) const {
return dependencies_.Lookup(filename, name, Dependencies::LookupMode::kSilent, out_library);
}
SourceSpan Library::GeneratedSimpleName(const std::string& name) {
return generated_source_file_.AddLine(name);
}
std::string Library::NextAnonymousName() {
// TODO(fxbug.dev/7920): Improve anonymous name generation. We want to be
// specific about how these names are generated once they appear in the
// JSON IR, and are exposed to the backends.
std::ostringstream data;
data << "SomeLongAnonymousPrefix";
data << anon_counter_++;
return data.str();
}
std::optional<Name> Library::CompileCompoundIdentifier(
const raw::CompoundIdentifier* compound_identifier) {
const auto& components = compound_identifier->components;
assert(components.size() >= 1);
SourceSpan decl_name = components.back()->span();
// First try resolving the identifier in the library.
if (components.size() == 1) {
return Name::CreateSourced(this, decl_name);
}
std::vector<std::string_view> library_name;
for (auto iter = components.begin(); iter != components.end() - 1; ++iter) {
library_name.push_back((*iter)->span().data());
}
auto filename = compound_identifier->span().source_file().filename();
Library* dep_library = nullptr;
if (dependencies_.Lookup(filename, library_name, Dependencies::LookupMode::kUse, &dep_library)) {
return Name::CreateSourced(dep_library, decl_name);
}
// If the identifier is not found in the library it might refer to a
// declaration with a member (e.g. library.EnumX.val or BitsY.val).
SourceSpan member_name = decl_name;
SourceSpan member_decl_name = components.rbegin()[1]->span();
if (components.size() == 2) {
return Name::CreateSourced(this, member_decl_name, std::string(member_name.data()));
}
std::vector<std::string_view> member_library_name(library_name);
member_library_name.pop_back();
Library* member_dep_library = nullptr;
if (dependencies_.Lookup(filename, member_library_name, Dependencies::LookupMode::kUse,
&member_dep_library)) {
return Name::CreateSourced(member_dep_library, member_decl_name,
std::string(member_name.data()));
}
Fail(ErrUnknownDependentLibrary, components[0]->span(), library_name, member_library_name);
return std::nullopt;
}
namespace {
template <typename T>
void StoreDecl(Decl* decl_ptr, std::vector<std::unique_ptr<T>>* declarations) {
std::unique_ptr<T> t_decl;
t_decl.reset(static_cast<T*>(decl_ptr));
declarations->push_back(std::move(t_decl));
}
} // namespace
bool Library::RegisterDecl(std::unique_ptr<Decl> decl) {
assert(decl);
auto decl_ptr = decl.release();
auto kind = decl_ptr->kind;
switch (kind) {
case Decl::Kind::kBits:
StoreDecl(decl_ptr, &bits_declarations_);
break;
case Decl::Kind::kConst:
StoreDecl(decl_ptr, &const_declarations_);
break;
case Decl::Kind::kEnum:
StoreDecl(decl_ptr, &enum_declarations_);
break;
case Decl::Kind::kProtocol:
StoreDecl(decl_ptr, &protocol_declarations_);
break;
case Decl::Kind::kResource:
StoreDecl(decl_ptr, &resource_declarations_);
break;
case Decl::Kind::kService:
StoreDecl(decl_ptr, &service_declarations_);
break;
case Decl::Kind::kStruct:
StoreDecl(decl_ptr, &struct_declarations_);
break;
case Decl::Kind::kTable:
StoreDecl(decl_ptr, &table_declarations_);
break;
case Decl::Kind::kTypeAlias:
StoreDecl(decl_ptr, &type_alias_declarations_);
break;
case Decl::Kind::kUnion:
StoreDecl(decl_ptr, &union_declarations_);
break;
} // switch
const Name& name = decl_ptr->name;
{
const auto it = declarations_.emplace(name, decl_ptr);
if (!it.second) {
const auto previous_name = it.first->second->name;
assert(previous_name.span() && "declarations_ has a name with no span");
return Fail(ErrNameCollision, name.span(), name, *previous_name.span());
}
}
const auto canonical_decl_name = utils::canonicalize(name.decl_name());
{
const auto it = declarations_by_canonical_name_.emplace(canonical_decl_name, decl_ptr);
if (!it.second) {
const auto previous_name = it.first->second->name;
assert(previous_name.span() && "declarations_by_canonical_name_ has a name with no span");
return Fail(ErrNameCollisionCanonical, name.span(), name, previous_name,
*previous_name.span(), canonical_decl_name);
}
}
if (name.span()) {
if (dependencies_.Contains(name.span()->source_file().filename(), {name.span()->data()})) {
return Fail(ErrDeclNameConflictsWithLibraryImport, name, name);
}
if (dependencies_.Contains(name.span()->source_file().filename(), {canonical_decl_name})) {
return Fail(ErrDeclNameConflictsWithLibraryImportCanonical, name, name, canonical_decl_name);
}
}
switch (kind) {
case Decl::Kind::kBits:
case Decl::Kind::kEnum:
case Decl::Kind::kService:
case Decl::Kind::kStruct:
case Decl::Kind::kTable:
case Decl::Kind::kUnion:
case Decl::Kind::kProtocol: {
auto type_decl = static_cast<TypeDecl*>(decl_ptr);
auto type_template =
std::make_unique<TypeDeclTypeTemplate>(name, typespace_, reporter_, this, type_decl);
typespace_->AddTemplate(std::move(type_template));
break;
}
case Decl::Kind::kTypeAlias: {
auto type_alias_decl = static_cast<TypeAlias*>(decl_ptr);
auto type_alias_template =
std::make_unique<TypeAliasTypeTemplate>(name, typespace_, reporter_, type_alias_decl);
typespace_->AddTemplate(std::move(type_alias_template));
break;
}
case Decl::Kind::kConst:
case Decl::Kind::kResource:
break;
} // switch
return true;
}
ConsumeStep Library::StartConsumeStep(fidl::utils::Syntax syntax) {
return ConsumeStep(this, syntax);
}
CompileStep Library::StartCompileStep() { return CompileStep(this); }
VerifyResourcenessStep Library::StartVerifyResourcenessStep() {
return VerifyResourcenessStep(this);
}
VerifyAttributesStep Library::StartVerifyAttributesStep() { return VerifyAttributesStep(this); }
bool Library::ConsumeConstant(std::unique_ptr<raw::Constant> raw_constant,
std::unique_ptr<Constant>* out_constant) {
switch (raw_constant->kind) {
case raw::Constant::Kind::kIdentifier: {
auto identifier = static_cast<raw::IdentifierConstant*>(raw_constant.get());
auto name = CompileCompoundIdentifier(identifier->identifier.get());
if (!name)
return false;
*out_constant =
std::make_unique<IdentifierConstant>(std::move(name.value()), identifier->span());
break;
}
case raw::Constant::Kind::kLiteral: {
auto literal = static_cast<raw::LiteralConstant*>(raw_constant.get());
std::unique_ptr<LiteralConstant> out;
ConsumeLiteralConstant(literal, &out);
*out_constant = std::unique_ptr<Constant>(out.release());
break;
}
case raw::Constant::Kind::kBinaryOperator: {
auto binary_operator_constant = static_cast<raw::BinaryOperatorConstant*>(raw_constant.get());
BinaryOperatorConstant::Operator op;
switch (binary_operator_constant->op) {
case raw::BinaryOperatorConstant::Operator::kOr:
op = BinaryOperatorConstant::Operator::kOr;
break;
}
std::unique_ptr<Constant> left_operand;
if (!ConsumeConstant(std::move(binary_operator_constant->left_operand), &left_operand)) {
return false;
}
std::unique_ptr<Constant> right_operand;
if (!ConsumeConstant(std::move(binary_operator_constant->right_operand), &right_operand)) {
return false;
}
*out_constant = std::make_unique<BinaryOperatorConstant>(
std::move(left_operand), std::move(right_operand), op, binary_operator_constant->span());
break;
}
}
return true;
}
void Library::ConsumeLiteralConstant(raw::LiteralConstant* raw_constant,
std::unique_ptr<LiteralConstant>* out_constant) {
*out_constant = std::make_unique<LiteralConstant>(std::move(raw_constant->literal));
}
bool Library::ConsumeTypeConstructorOld(std::unique_ptr<raw::TypeConstructorOld> raw_type_ctor,
std::unique_ptr<TypeConstructorOld>* out_type_ctor) {
auto name = CompileCompoundIdentifier(raw_type_ctor->identifier.get());
if (!name)
return false;
std::unique_ptr<TypeConstructorOld> maybe_arg_type_ctor;
if (raw_type_ctor->maybe_arg_type_ctor != nullptr) {
if (!ConsumeTypeConstructorOld(std::move(raw_type_ctor->maybe_arg_type_ctor),
&maybe_arg_type_ctor))
return false;
}
std::unique_ptr<Constant> maybe_size;
if (raw_type_ctor->maybe_size != nullptr) {
if (!ConsumeConstant(std::move(raw_type_ctor->maybe_size), &maybe_size))
return false;
}
std::unique_ptr<Constant> handle_rights;
if (raw_type_ctor->handle_rights != nullptr) {
if (!ConsumeConstant(std::move(raw_type_ctor->handle_rights), &handle_rights))
return false;
}
std::optional<Name> handle_subtype_identifier;
if (raw_type_ctor->handle_subtype_identifier) {
handle_subtype_identifier =
Name::CreateSourced(this, raw_type_ctor->handle_subtype_identifier->span());
}
*out_type_ctor = std::make_unique<TypeConstructorOld>(
std::move(name.value()), std::move(maybe_arg_type_ctor), std::move(handle_subtype_identifier),
std::move(handle_rights), std::move(maybe_size), raw_type_ctor->nullability);
return true;
}
void Library::ConsumeUsing(std::unique_ptr<raw::Using> using_directive) {
if (using_directive->attributes && using_directive->attributes->attributes.size() != 0) {
Fail(ErrAttributesNotAllowedOnLibraryImport, using_directive->span(),
*(using_directive->attributes));
return;
}
std::vector<std::string_view> library_name;
for (const auto& component : using_directive->using_path->components) {
library_name.push_back(component->span().data());
}
Library* dep_library = nullptr;
if (!all_libraries_->Lookup(library_name, &dep_library)) {
Fail(ErrUnknownLibrary, using_directive->using_path->components[0]->span(), library_name);
return;
}
auto filename = using_directive->span().source_file().filename();
if (!dependencies_.Register(using_directive->span(), filename, dep_library,
using_directive->maybe_alias)) {
Fail(ErrDuplicateLibraryImport, library_name);
return;
}
// Import declarations, and type aliases of dependent library.
const auto& declarations = dep_library->declarations_;
declarations_.insert(declarations.begin(), declarations.end());
}
bool Library::ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration) {
assert(alias_declaration->alias && IsTypeConstructorDefined(alias_declaration->type_ctor));
auto alias_name = Name::CreateSourced(this, alias_declaration->alias->span());
TypeConstructor type_ctor_;
if (!ConsumeTypeConstructor(std::move(alias_declaration->type_ctor), alias_name, &type_ctor_))
return false;
return RegisterDecl(std::make_unique<TypeAlias>(std::move(alias_declaration->attributes),
std::move(alias_name), std::move(type_ctor_)));
}
void Library::ConsumeBitsDeclaration(std::unique_ptr<raw::BitsDeclaration> bits_declaration) {
std::vector<Bits::Member> members;
for (auto& member : bits_declaration->members) {
auto span = member->identifier->span();
std::unique_ptr<Constant> value;
if (!ConsumeConstant(std::move(member->value), &value))
return;
members.emplace_back(span, std::move(value), std::move(member->attributes));
// TODO(pascallouis): right now, members are not registered. Look into
// registering them, potentially under the bits name qualifier such as
// <name_of_bits>.<name_of_member>.
}
std::unique_ptr<TypeConstructorOld> type_ctor;
if (bits_declaration->maybe_type_ctor) {
if (!ConsumeTypeConstructorOld(std::move(bits_declaration->maybe_type_ctor), &type_ctor))
return;
} else {
type_ctor = TypeConstructorOld::CreateSizeType();
}
RegisterDecl(std::make_unique<Bits>(
std::move(bits_declaration->attributes),
Name::CreateSourced(this, bits_declaration->identifier->span()), std::move(type_ctor),
std::move(members), bits_declaration->strictness));
}
void Library::ConsumeConstDeclaration(std::unique_ptr<raw::ConstDeclaration> const_declaration) {
auto attributes = std::move(const_declaration->attributes);
auto span = const_declaration->identifier->span();
auto name = Name::CreateSourced(this, span);
TypeConstructor type_ctor;
// TODO(fxbug.dev/73285): shouldn't need to care about context here
if (!ConsumeTypeConstructor(std::move(const_declaration->type_ctor), name, &type_ctor))
return;
std::unique_ptr<Constant> constant;
if (!ConsumeConstant(std::move(const_declaration->constant), &constant))
return;
RegisterDecl(std::make_unique<Const>(std::move(attributes), std::move(name), std::move(type_ctor),
std::move(constant)));
}
void Library::ConsumeEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration) {
std::vector<Enum::Member> members;
for (auto& member : enum_declaration->members) {
auto span = member->identifier->span();
std::unique_ptr<Constant> value;
if (!ConsumeConstant(std::move(member->value), &value))
return;
members.emplace_back(span, std::move(value), std::move(member->attributes));
// TODO(pascallouis): right now, members are not registered. Look into
// registering them, potentially under the enum name qualifier such as
// <name_of_enum>.<name_of_member>.
}
std::unique_ptr<TypeConstructorOld> type_ctor;
if (enum_declaration->maybe_type_ctor) {
if (!ConsumeTypeConstructorOld(std::move(enum_declaration->maybe_type_ctor), &type_ctor))
return;
} else {
type_ctor = TypeConstructorOld::CreateSizeType();
}
RegisterDecl(std::make_unique<Enum>(
std::move(enum_declaration->attributes),
Name::CreateSourced(this, enum_declaration->identifier->span()), std::move(type_ctor),
std::move(members), enum_declaration->strictness));
}
bool Library::CreateMethodResult(const Name& protocol_name, SourceSpan response_span,
raw::ProtocolMethod* method, Struct* in_response,
Struct** out_response) {
// Compile the error type.
std::unique_ptr<TypeConstructorOld> error_type_ctor;
if (!ConsumeTypeConstructorOld(std::move(method->maybe_error_ctor), &error_type_ctor))
return false;
// Make the Result union containing the response struct and the
// error type.
SourceSpan method_name_span = method->identifier->span();
// TODO(fxbug.dev/8027): Join spans of response and error constructor for `result_name`.
auto result_name = Name::CreateDerived(
this, response_span,
StringJoin({protocol_name.decl_name(), method_name_span.data(), "Result"}, "_"));
raw::SourceElement sourceElement = raw::SourceElement(fidl::Token(), fidl::Token());
Union::Member response_member{
std::make_unique<raw::Ordinal64>(sourceElement,
1), // success case explicitly has ordinal 1
IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable),
GeneratedSimpleName("response"), nullptr};
Union::Member error_member{
std::make_unique<raw::Ordinal64>(sourceElement, 2), // error case explicitly has ordinal 2
std::move(error_type_ctor), GeneratedSimpleName("err"), nullptr};
std::vector<Union::Member> result_members;
result_members.push_back(std::move(response_member));
result_members.push_back(std::move(error_member));
std::vector<raw::Attribute> result_attributes;
result_attributes.emplace_back(*method, raw::Attribute::Provenance::kDefault, "Result", "");
auto result_attributelist =
std::make_unique<raw::AttributeList>(*method, std::move(result_attributes));
auto union_decl = std::make_unique<Union>(std::move(result_attributelist), std::move(result_name),
std::move(result_members), types::Strictness::kStrict,
std::nullopt /* resourceness */);
auto result_decl = union_decl.get();
if (!RegisterDecl(std::move(union_decl)))
return false;
// Make a new response struct for the method containing just the
// result union.
std::vector<Struct::Member> response_members;
response_members.push_back(
Struct::Member(IdentifierTypeForDecl(result_decl, types::Nullability::kNonnullable),
GeneratedSimpleName("result"), nullptr, nullptr));
auto struct_decl = std::make_unique<Struct>(
nullptr /* attributes */, Name::CreateDerived(this, response_span, NextAnonymousName()),
std::move(response_members), std::nullopt /* resourceness */,
true /* is_request_or_response */);
auto struct_decl_ptr = struct_decl.get();
if (!RegisterDecl(std::move(struct_decl)))
return false;
*out_response = struct_decl_ptr;
return true;
}
void Library::ConsumeProtocolDeclaration(
std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration) {
auto attributes = std::move(protocol_declaration->attributes);
auto name = Name::CreateSourced(this, protocol_declaration->identifier->span());
std::set<Name> composed_protocols;
for (auto& composed_protocol : protocol_declaration->composed_protocols) {
auto& protocol_name = composed_protocol->protocol_name;
auto composed_protocol_name = CompileCompoundIdentifier(protocol_name.get());
if (!composed_protocol_name)
return;
if (!composed_protocols.insert(std::move(composed_protocol_name.value())).second) {
Fail(ErrProtocolComposedMultipleTimes, composed_protocol_name->span());
return;
}
}
std::vector<Protocol::Method> methods;
for (auto& method : protocol_declaration->methods) {
auto selector =
fidl::ordinals::GetSelector(method->attributes.get(), method->identifier->span());
if (!utils::IsValidIdentifierComponent(selector) &&
!utils::IsValidFullyQualifiedMethodIdentifier(selector)) {
Fail(ErrInvalidSelectorValue, method->identifier->span());
}
auto generated_ordinal64 = std::make_unique<raw::Ordinal64>(
method_hasher_(library_name_, name.decl_name(), selector, *method->identifier));
auto attributes = std::move(method->attributes);
SourceSpan method_name = method->identifier->span();
Struct* maybe_request = nullptr;
if (method->maybe_request != nullptr) {
auto request_span = method->maybe_request->span();
auto request_name = Name::CreateDerived(this, request_span, NextAnonymousName());
if (!ConsumeParameterList(std::move(request_name), std::move(method->maybe_request), true,
&maybe_request))
return;
}
Struct* maybe_response = nullptr;
if (method->maybe_response != nullptr) {
const bool has_error = (method->maybe_error_ctor != nullptr);
SourceSpan response_span = method->maybe_response->span();
Name response_name = Name::CreateDerived(
this, response_span,
has_error ? StringJoin({name.decl_name(), method_name.data(), "Response"}, "_")
: NextAnonymousName());
if (!ConsumeParameterList(std::move(response_name), std::move(method->maybe_response),
!has_error, &maybe_response))
return;
if (has_error) {
if (!CreateMethodResult(name, response_span, method.get(), maybe_response, &maybe_response))
return;
}
}
assert(maybe_request != nullptr || maybe_response != nullptr);
methods.emplace_back(std::move(attributes), std::move(generated_ordinal64),
std::move(method_name), std::move(maybe_request),
std::move(maybe_response));
}
RegisterDecl(std::make_unique<Protocol>(std::move(attributes), std::move(name),
std::move(composed_protocols), std::move(methods)));
}
bool Library::ConsumeResourceDeclaration(
std::unique_ptr<raw::ResourceDeclaration> resource_declaration) {
auto name = Name::CreateSourced(this, resource_declaration->identifier->span());
std::vector<Resource::Property> properties;
for (auto& property : resource_declaration->properties) {
TypeConstructor type_ctor;
auto anonymous_resource_name = Name::CreateDerived(
this, property->span(),
std::string(name.decl_name()) + std::string(property->identifier->span().data()));
if (!ConsumeTypeConstructor(std::move(property->type_ctor), anonymous_resource_name,
&type_ctor))
return false;
auto attributes = std::move(property->attributes);
properties.emplace_back(std::move(type_ctor), property->identifier->span(),
std::move(attributes));
}
TypeConstructor type_ctor;
if (raw::IsTypeConstructorDefined(resource_declaration->maybe_type_ctor)) {
// TODO(fxbug.dev/73285): shouldn't need to care about the naming context here.
if (!ConsumeTypeConstructor(std::move(resource_declaration->maybe_type_ctor), name, &type_ctor))
return false;
} else {
type_ctor = TypeConstructorOld::CreateSizeType();
}
return RegisterDecl(std::make_unique<Resource>(std::move(resource_declaration->attributes),
std::move(name), std::move(type_ctor),
std::move(properties)));
}
std::unique_ptr<TypeConstructorOld> Library::IdentifierTypeForDecl(const Decl* decl,
types::Nullability nullability) {
return std::make_unique<TypeConstructorOld>(decl->name, nullptr /* maybe_arg_type */,
std::optional<Name>() /* handle_subtype_identifier */,
nullptr /* handle_rights */, nullptr /* maybe_size */,
nullability);
}
bool Library::ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list,
bool is_request_or_response, Struct** out_struct_decl) {
std::vector<Struct::Member> members;
for (auto& parameter : parameter_list->parameter_list) {
TypeConstructor type_ctor;
// TODO(fxbug.dev/73285): finalize layout naming
auto param_name = Name::CreateDerived(
this, parameter->span(),
std::string(name.decl_name()) +
utils::to_upper_camel_case(std::string(parameter->identifier->span().data())));
if (!ConsumeTypeConstructor(std::move(parameter->type_ctor), param_name, &type_ctor))
return false;
ValidateAttributesPlacement(AttributeSchema::Placement::kStructMember,
parameter->attributes.get());
members.emplace_back(std::move(type_ctor), parameter->identifier->span(),
nullptr /* maybe_default_value */, std::move(parameter->attributes));
}
if (!RegisterDecl(std::make_unique<Struct>(nullptr /* attributes */, std::move(name),
std::move(members), std::nullopt /* resourceness */,
is_request_or_response)))
return false;
*out_struct_decl = struct_declarations_.back().get();
return true;
}
void Library::ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl) {
auto attributes = std::move(service_decl->attributes);
auto name = Name::CreateSourced(this, service_decl->identifier->span());
std::vector<Service::Member> members;
for (auto& member : service_decl->members) {
TypeConstructor type_ctor;
// TODO(fxbug.dev/73285): shouldn't need to care about naming context here.
if (!ConsumeTypeConstructor(std::move(member->type_ctor), name, &type_ctor))
return;
members.emplace_back(std::move(type_ctor), member->identifier->span(),
std::move(member->attributes));
}
RegisterDecl(
std::make_unique<Service>(std::move(attributes), std::move(name), std::move(members)));
}
void Library::ConsumeStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration) {
auto attributes = std::move(struct_declaration->attributes);
auto name = Name::CreateSourced(this, struct_declaration->identifier->span());
std::vector<Struct::Member> members;
for (auto& member : struct_declaration->members) {
std::unique_ptr<TypeConstructorOld> type_ctor;
if (!ConsumeTypeConstructorOld(std::move(member->type_ctor), &type_ctor))
return;
std::unique_ptr<Constant> maybe_default_value;
if (member->maybe_default_value != nullptr) {
if (!ConsumeConstant(std::move(member->maybe_default_value), &maybe_default_value))
return;
}
auto attributes = std::move(member->attributes);
members.emplace_back(std::move(type_ctor), member->identifier->span(),
std::move(maybe_default_value), std::move(attributes));
}
RegisterDecl(std::make_unique<Struct>(std::move(attributes), std::move(name), std::move(members),
struct_declaration->resourceness));
}
void Library::ConsumeTableDeclaration(std::unique_ptr<raw::TableDeclaration> table_declaration) {
auto attributes = std::move(table_declaration->attributes);
auto name = Name::CreateSourced(this, table_declaration->identifier->span());
std::vector<Table::Member> members;
for (auto& member : table_declaration->members) {
auto ordinal_literal = std::move(member->ordinal);
if (member->maybe_used) {
std::unique_ptr<TypeConstructorOld> type_ctor;
if (!ConsumeTypeConstructorOld(std::move(member->maybe_used->type_ctor), &type_ctor))
return;
std::unique_ptr<Constant> maybe_default_value;
if (member->maybe_used->maybe_default_value) {
// TODO(fxbug.dev/7932): Support defaults on tables.
const auto default_value = member->maybe_used->maybe_default_value.get();
reporter_->Report(ErrDefaultsOnTablesNotSupported, default_value->span());
}
if (type_ctor->nullability != types::Nullability::kNonnullable) {
Fail(ErrNullableTableMember, member->span());
return;
}
auto attributes = std::move(member->maybe_used->attributes);
members.emplace_back(std::move(ordinal_literal), std::move(type_ctor),
member->maybe_used->identifier->span(), std::move(maybe_default_value),
std::move(attributes));
} else {
members.emplace_back(std::move(ordinal_literal), member->span());
}
}
RegisterDecl(std::make_unique<Table>(std::move(attributes), std::move(name), std::move(members),
table_declaration->strictness,
table_declaration->resourceness));
}
void Library::ConsumeUnionDeclaration(std::unique_ptr<raw::UnionDeclaration> union_declaration) {
auto name = Name::CreateSourced(this, union_declaration->identifier->span());
assert(!union_declaration->members.empty() && "unions must have at least one member");
auto union_name =
std::pair<std::string, std::string_view>(LibraryName(this, "."), name.decl_name());
std::vector<Union::Member> members;
for (auto& member : union_declaration->members) {
auto explicit_ordinal = std::move(member->ordinal);
if (member->maybe_used) {
std::unique_ptr<TypeConstructorOld> type_ctor;
if (!ConsumeTypeConstructorOld(std::move(member->maybe_used->type_ctor), &type_ctor))
return;
if (member->maybe_used->maybe_default_value) {
const auto default_value = member->maybe_used->maybe_default_value.get();
reporter_->Report(ErrDefaultsOnUnionsNotSupported, default_value->span());
}
if (type_ctor->nullability != types::Nullability::kNonnullable) {
Fail(ErrNullableUnionMember, member->span());
return;
}
members.emplace_back(std::move(explicit_ordinal), std::move(type_ctor),
member->maybe_used->identifier->span(),
std::move(member->maybe_used->attributes));
} else {
members.emplace_back(std::move(explicit_ordinal), member->span());
}
}
RegisterDecl(std::make_unique<Union>(std::move(union_declaration->attributes), std::move(name),
std::move(members), union_declaration->strictness,
union_declaration->resourceness));
}
// TODO(fxbug.dev/71536): these conversion methods may need to be refactored
// once the new flat AST lands, and such coercion is no longer needed.
template <typename T, typename M>
bool Library::ConsumeValueLayout(std::unique_ptr<raw::Layout> layout, const Name& context) {
std::vector<M> members;
size_t index = 0;
for (auto& mem : layout->members) {
auto member = static_cast<raw::ValueLayoutMember*>(mem.get());
auto span = member->identifier->span();
std::unique_ptr<Constant> value;
if (!ConsumeConstant(std::move(member->value), &value))
return false;
members.emplace_back(span, std::move(value), /*attributes=*/nullptr);
index++;
}
std::unique_ptr<TypeConstructorNew> subtype_ctor;
if (layout->subtype_ctor != nullptr) {
if (!ConsumeTypeConstructorNew(std::move(layout->subtype_ctor), context, &subtype_ctor))
return false;
} else {
subtype_ctor = TypeConstructorNew::CreateSizeType();
}
RegisterDecl(std::make_unique<T>(
/*attributes=*/nullptr, context, std::move(subtype_ctor), std::move(members),
layout->strictness.value_or(types::Strictness::kFlexible)));
return true;
}
template <typename T, typename M>
bool Library::ConsumeOrdinaledLayout(std::unique_ptr<raw::Layout> layout, const Name& context) {
std::vector<M> members;
for (auto& mem : layout->members) {
auto member = static_cast<raw::OrdinaledLayoutMember*>(mem.get());
if (member->reserved) {
members.emplace_back(std::move(member->ordinal), member->span());
continue;
}
auto name_of_anonymous_layout = Name::CreateDerived(
this, member->span(),
std::string(context.decl_name()) +
utils::to_upper_camel_case(std::string(member->identifier->span().data())));
std::unique_ptr<TypeConstructorNew> type_ctor;
if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout,
&type_ctor))
return false;
members.emplace_back(std::move(member->ordinal), std::move(type_ctor),
member->identifier->span(), /*attributes=*/nullptr);
}
RegisterDecl(std::make_unique<T>(
/*attributes=*/nullptr, context, std::move(members),
layout->strictness.value_or(types::Strictness::kFlexible), layout->resourceness));
return true;
}
bool Library::ConsumeStructLayout(std::unique_ptr<raw::Layout> layout, const Name& context) {
std::vector<Struct::Member> members;
for (auto& mem : layout->members) {
auto member = static_cast<raw::StructLayoutMember*>(mem.get());
auto name_of_anonymous_layout = Name::CreateDerived(
this, member->span(),
std::string(context.decl_name()) +
utils::to_upper_camel_case(std::string(member->identifier->span().data())));
std::unique_ptr<TypeConstructorNew> type_ctor;
if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout,
&type_ctor))
return false;
std::unique_ptr<Constant> default_value;
if (member->default_value != nullptr) {
ConsumeConstant(std::move(member->default_value), &default_value);
}
members.emplace_back(std::move(type_ctor), member->identifier->span(), std::move(default_value),
/*attributes=*/nullptr);
}
RegisterDecl(std::make_unique<Struct>(/*attributes=*/nullptr, context, std::move(members),
layout->resourceness));
return true;
}
bool Library::ConsumeLayout(std::unique_ptr<raw::Layout> layout, const Name& context) {
switch (layout->kind) {
case raw::Layout::Kind::kBits: {
return ConsumeValueLayout<Bits, Bits::Member>(std::move(layout), context);
}
case raw::Layout::Kind::kEnum: {
return ConsumeValueLayout<Enum, Enum::Member>(std::move(layout), context);
}
case raw::Layout::Kind::kStruct: {
return ConsumeStructLayout(std::move(layout), context);
}
case raw::Layout::Kind::kTable: {
return ConsumeOrdinaledLayout<Table, Table::Member>(std::move(layout), context);
}
case raw::Layout::Kind::kUnion: {
return ConsumeOrdinaledLayout<Union, Union::Member>(std::move(layout), context);
}
}
assert(false && "layouts must be of type bits, enum, struct, table, or union");
return true;
}
bool Library::ConsumeTypeConstructorNew(std::unique_ptr<raw::TypeConstructorNew> raw_type_ctor,
const Name& context,
std::unique_ptr<TypeConstructorNew>* out_type_ctor) {
if (raw_type_ctor->layout_ref->kind == raw::LayoutReference::Kind::kInline) {
// TODO(fxbug.dev/74683): If we don't want users to be able to refer to anonymous
// layouts by figuring out their generated name, we'll need to update this section
const auto& params = raw_type_ctor->parameters;
const auto& constraints = raw_type_ctor->constraints;
if (params && !params->items.empty()) {
return Fail(ErrLayoutCannotBeParameterized, params->span(), context);
}
if (constraints && !constraints->items.empty()) {
return Fail(ErrCannotConstrainInLayoutDecl, constraints->span());
}
auto inline_ref = static_cast<raw::InlineLayoutReference*>(raw_type_ctor->layout_ref.get());
if (!ConsumeLayout(std::move(inline_ref->layout), context))
return false;
std::vector<std::unique_ptr<LayoutParameter>> no_params;
std::vector<std::unique_ptr<Constant>> no_constraints;
if (out_type_ctor)
*out_type_ctor = std::make_unique<TypeConstructorNew>(
context, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt),
std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt));
return true;
}
auto named_ref = static_cast<raw::NamedLayoutReference*>(raw_type_ctor->layout_ref.get());
SourceSpan name_span = named_ref->identifier->span();
auto name = CompileCompoundIdentifier(named_ref->identifier.get());
if (!name)
return false;
std::vector<std::unique_ptr<LayoutParameter>> params;
std::optional<SourceSpan> params_span;
if (raw_type_ctor->parameters) {
params_span = raw_type_ctor->parameters->span();
for (auto& p : raw_type_ctor->parameters->items) {
auto param = std::move(p);
auto span = param->span();
switch (param->kind) {
case raw::LayoutParameter::Kind::kLiteral: {
auto literal_param = static_cast<raw::LiteralLayoutParameter*>(param.get());
std::unique_ptr<LiteralConstant> constant;
ConsumeLiteralConstant(literal_param->literal.get(), &constant);
std::unique_ptr<LayoutParameter> consumed =
std::make_unique<LiteralLayoutParameter>(std::move(constant), span);
params.push_back(std::move(consumed));
break;
}
case raw::LayoutParameter::Kind::kType: {
auto type_param = static_cast<raw::TypeLayoutParameter*>(param.get());
std::unique_ptr<TypeConstructorNew> type_ctor;
auto nested_name = Name::CreateDerived(this, name_span, context.full_name());
if (!ConsumeTypeConstructorNew(std::move(type_param->type_ctor), nested_name, &type_ctor))
return false;
std::unique_ptr<LayoutParameter> consumed =
std::make_unique<TypeLayoutParameter>(std::move(type_ctor), span);
params.push_back(std::move(consumed));
break;
}
case raw::LayoutParameter::Kind::kIdentifier: {
auto id_param = static_cast<raw::IdentifierLayoutParameter*>(param.get());
auto name = CompileCompoundIdentifier(id_param->identifier.get());
if (!name)
return false;
std::unique_ptr<LayoutParameter> consumed =
std::make_unique<IdentifierLayoutParameter>(std::move(name.value()), span);
params.push_back(std::move(consumed));
break;
}
}
}
}
std::vector<std::unique_ptr<Constant>> constraints;
std::optional<SourceSpan> constraints_span;
if (raw_type_ctor->constraints) {
constraints_span = raw_type_ctor->constraints->span();
for (auto& c : raw_type_ctor->constraints->items) {
std::unique_ptr<Constant> constraint;
if (!ConsumeConstant(std::move(c), &constraint))
return false;
constraints.push_back(std::move(constraint));
}
}
assert(out_type_ctor && "out type ctors should always be provided for a named type ctor");
*out_type_ctor = std::make_unique<TypeConstructorNew>(
std::move(name.value()),
std::make_unique<LayoutParameterList>(std::move(params), params_span),
std::make_unique<TypeConstraints>(std::move(constraints), constraints_span));
return true;
}
bool Library::ConsumeTypeConstructor(raw::TypeConstructor raw_type_ctor, const Name& context,
TypeConstructor* out_type) {
return std::visit(fidl::utils::matchers{
[&, this](std::unique_ptr<raw::TypeConstructorOld> e) -> bool {
std::unique_ptr<TypeConstructorOld> out;
bool result = ConsumeTypeConstructorOld(std::move(e), &out);
*out_type = std::move(out);
return result;
},
[&, this](std::unique_ptr<raw::TypeConstructorNew> e) -> bool {
std::unique_ptr<TypeConstructorNew> out;
bool result = ConsumeTypeConstructorNew(std::move(e), context, &out);
*out_type = std::move(out);
return result;
},
},
std::move(raw_type_ctor));
}
void Library::ConsumeTypeDecl(std::unique_ptr<raw::TypeDecl> type_decl) {
auto name = Name::CreateSourced(this, type_decl->identifier->span());
auto& layout_ref = type_decl->type_ctor->layout_ref;
// TODO(fxbug.dev/7807)
if (layout_ref->kind == raw::LayoutReference::Kind::kNamed) {
auto named_ref = static_cast<raw::NamedLayoutReference*>(layout_ref.get());
Fail(ErrNewTypesNotAllowed, name, named_ref->span().data());
return;
}
ConsumeTypeConstructorNew(std::move(type_decl->type_ctor), name, nullptr);
}
bool Library::ConsumeFile(std::unique_ptr<raw::File> file) {
if (file->attributes) {
ValidateAttributesPlacement(AttributeSchema::Placement::kLibrary, file->attributes.get());
if (!attributes_) {
attributes_ = std::move(file->attributes);
} else {
AttributesBuilder attributes_builder(reporter_, std::move(attributes_->attributes));
for (auto& attribute : file->attributes->attributes) {
if (!attributes_builder.Insert(std::move(attribute)))
return false;
}
attributes_ = std::make_unique<raw::AttributeList>(
raw::SourceElement(file->attributes->start_, file->attributes->end_),
attributes_builder.Done());
}
}
// All fidl files in a library should agree on the library name.
std::vector<std::string_view> new_name;
for (const auto& part : file->library_name->components) {
new_name.push_back(part->span().data());
}
if (!library_name_.empty()) {
if (new_name != library_name_) {
return Fail(ErrFilesDisagreeOnLibraryName, file->library_name->components[0]->span());
}
} else {
library_name_ = new_name;
}
auto step = StartConsumeStep(file->syntax);
auto using_list = std::move(file->using_list);
for (auto& using_directive : using_list) {
step.ForUsing(std::move(using_directive));
}
auto alias_list = std::move(file->alias_list);
for (auto& alias_declaration : alias_list) {
step.ForAliasDeclaration(std::move(alias_declaration));
}
auto bits_declaration_list = std::move(file->bits_declaration_list);
for (auto& bits_declaration : bits_declaration_list) {
step.ForBitsDeclaration(std::move(bits_declaration));
}
auto const_declaration_list = std::move(file->const_declaration_list);
for (auto& const_declaration : const_declaration_list) {
step.ForConstDeclaration(std::move(const_declaration));
}
auto enum_declaration_list = std::move(file->enum_declaration_list);
for (auto& enum_declaration : enum_declaration_list) {
step.ForEnumDeclaration(std::move(enum_declaration));
}
auto protocol_declaration_list = std::move(file->protocol_declaration_list);
for (auto& protocol_declaration : protocol_declaration_list) {
step.ForProtocolDeclaration(std::move(protocol_declaration));
}
auto resource_declaration_list = std::move(file->resource_declaration_list);
for (auto& resource_declaration : resource_declaration_list) {
step.ForResourceDeclaration(std::move(resource_declaration));
}
auto service_declaration_list = std::move(file->service_declaration_list);
for (auto& service_declaration : service_declaration_list) {
step.ForServiceDeclaration(std::move(service_declaration));
}
auto struct_declaration_list = std::move(file->struct_declaration_list);
for (auto& struct_declaration : struct_declaration_list) {
step.ForStructDeclaration(std::move(struct_declaration));
}
auto table_declaration_list = std::move(file->table_declaration_list);
for (auto& table_declaration : table_declaration_list) {
step.ForTableDeclaration(std::move(table_declaration));
}
auto union_declaration_list = std::move(file->union_declaration_list);
for (auto& union_declaration : union_declaration_list) {
step.ForUnionDeclaration(std::move(union_declaration));
}
auto type_decls = std::move(file->type_decls);
for (auto& type_decl : type_decls) {
step.ForTypeDecl(std::move(type_decl));
}
return step.Done();
}
bool Library::ResolveOrOperatorConstant(Constant* constant, const Type* type,
const ConstantValue& left_operand,
const ConstantValue& right_operand) {
assert(left_operand.kind == right_operand.kind &&
"left and right operands of or operator must be of the same kind");
type = TypeResolve(type);
if (type == nullptr)
return false;
if (type->kind != Type::Kind::kPrimitive) {
return Fail(ErrOrOperatorOnNonPrimitiveValue);
}
std::unique_ptr<ConstantValue> left_operand_u64;
std::unique_ptr<ConstantValue> right_operand_u64;
if (!left_operand.Convert(ConstantValue::Kind::kUint64, &left_operand_u64))
return false;
if (!right_operand.Convert(ConstantValue::Kind::kUint64, &right_operand_u64))
return false;
NumericConstantValue<uint64_t> result =
*static_cast<NumericConstantValue<uint64_t>*>(left_operand_u64.get()) |
*static_cast<NumericConstantValue<uint64_t>*>(right_operand_u64.get());
std::unique_ptr<ConstantValue> converted_result;
if (!result.Convert(ConstantValuePrimitiveKind(static_cast<const PrimitiveType*>(type)->subtype),
&converted_result))
return false;
constant->ResolveTo(std::move(converted_result));
return true;
}
bool Library::ResolveConstant(Constant* constant, const Type* type) {
assert(constant != nullptr);
// Prevent re-entry.
if (constant->compiled)
return constant->IsResolved();
constant->compiled = true;
switch (constant->kind) {
case Constant::Kind::kIdentifier: {
auto identifier_constant = static_cast<IdentifierConstant*>(constant);
return ResolveIdentifierConstant(identifier_constant, type);
}
case Constant::Kind::kLiteral: {
auto literal_constant = static_cast<LiteralConstant*>(constant);
return ResolveLiteralConstant(literal_constant, type);
}
case Constant::Kind::kSynthesized: {
assert(false && "Compiler bug: synthesized constant does not have a resolved value!");
break;
}
case Constant::Kind::kBinaryOperator: {
auto binary_operator_constant = static_cast<BinaryOperatorConstant*>(constant);
if (!ResolveConstant(binary_operator_constant->left_operand.get(), type)) {
return false;
}
if (!ResolveConstant(binary_operator_constant->right_operand.get(), type)) {
return false;
}
switch (binary_operator_constant->op) {
case BinaryOperatorConstant::Operator::kOr: {
return ResolveOrOperatorConstant(constant, type,
binary_operator_constant->left_operand->Value(),
binary_operator_constant->right_operand->Value());
}
}
assert(false && "Compiler bug: unhandled binary operator");
break;
}
}
__builtin_unreachable();
}
ConstantValue::Kind Library::ConstantValuePrimitiveKind(
const types::PrimitiveSubtype primitive_subtype) {
switch (primitive_subtype) {
case types::PrimitiveSubtype::kBool:
return ConstantValue::Kind::kBool;
case types::PrimitiveSubtype::kInt8:
return ConstantValue::Kind::kInt8;
case types::PrimitiveSubtype::kInt16:
return ConstantValue::Kind::kInt16;
case types::PrimitiveSubtype::kInt32:
return ConstantValue::Kind::kInt32;
case types::PrimitiveSubtype::kInt64:
return ConstantValue::Kind::kInt64;
case types::PrimitiveSubtype::kUint8:
return ConstantValue::Kind::kUint8;
case types::PrimitiveSubtype::kUint16:
return ConstantValue::Kind::kUint16;
case types::PrimitiveSubtype::kUint32:
return ConstantValue::Kind::kUint32;
case types::PrimitiveSubtype::kUint64:
return ConstantValue::Kind::kUint64;
case types::PrimitiveSubtype::kFloat32:
return ConstantValue::Kind::kFloat32;
case types::PrimitiveSubtype::kFloat64:
return ConstantValue::Kind::kFloat64;
}
assert(false && "Compiler bug: unhandled primitive subtype");
}
bool Library::ResolveIdentifierConstant(IdentifierConstant* identifier_constant, const Type* type) {
assert(TypeCanBeConst(type) &&
"Compiler bug: resolving identifier constant to non-const-able type!");
auto decl = LookupDeclByName(identifier_constant->name.memberless_key());
if (!decl)
return false;
if (!CompileDecl(decl)) {
return false;
}
const Type* const_type = nullptr;
const ConstantValue* const_val = nullptr;
switch (decl->kind) {
case Decl::Kind::kConst: {
auto const_decl = static_cast<Const*>(decl);
const_type = GetType(const_decl->type_ctor);
const_val = &const_decl->value->Value();
break;
}
case Decl::Kind::kEnum: {
// If there is no member name, fallthrough to default.
if (auto member_name = identifier_constant->name.member_name(); member_name) {
auto enum_decl = static_cast<Enum*>(decl);
const_type = GetType(enum_decl->subtype_ctor);
for (auto& member : enum_decl->members) {
if (member.name.data() == member_name) {
const_val = &member.value->Value();
}
}
if (!const_val) {
return Fail(ErrUnknownEnumMember, identifier_constant->name.span(),
std::string_view(*member_name));
}
break;
}
[[fallthrough]];
}
case Decl::Kind::kBits: {
// If there is no member name, fallthrough to default.
if (auto member_name = identifier_constant->name.member_name(); member_name) {
auto bits_decl = static_cast<Bits*>(decl);
const_type = GetType(bits_decl->subtype_ctor);
for (auto& member : bits_decl->members) {
if (member.name.data() == member_name) {
const_val = &member.value->Value();
}
}
if (!const_val) {
return Fail(ErrUnknownBitsMember, identifier_constant->name.span(),
std::string_view(*member_name));
}
break;
}
[[fallthrough]];
}
default: {
return Fail(ErrExpectedValueButGotType, identifier_constant->name.span(),
identifier_constant->name);
}
}
assert(const_val && "Compiler bug: did not set const_val");
assert(const_type && "Compiler bug: did not set const_type");
std::unique_ptr<ConstantValue> resolved_val;
switch (type->kind) {
case Type::Kind::kString: {
if (!TypeIsConvertibleTo(const_type, type))
goto fail_cannot_convert;
if (!const_val->Convert(ConstantValue::Kind::kString, &resolved_val))
goto fail_cannot_convert;
break;
}
case Type::Kind::kPrimitive: {
auto primitive_type = static_cast<const PrimitiveType*>(type);
if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val))
goto fail_cannot_convert;
break;
}
case Type::Kind::kIdentifier: {
auto identifier_type = static_cast<const IdentifierType*>(type);
const PrimitiveType* primitive_type;
switch (identifier_type->type_decl->kind) {
case Decl::Kind::kEnum: {
auto enum_decl = static_cast<const Enum*>(identifier_type->type_decl);
assert(GetType(enum_decl->subtype_ctor)->kind == Type::Kind::kPrimitive);
primitive_type = static_cast<const PrimitiveType*>(GetType(enum_decl->subtype_ctor));
break;
}
case Decl::Kind::kBits: {
auto bits_decl = static_cast<const Bits*>(identifier_type->type_decl);
assert(GetType(bits_decl->subtype_ctor)->kind == Type::Kind::kPrimitive);
primitive_type = static_cast<const PrimitiveType*>(GetType(bits_decl->subtype_ctor));
break;
}
default: {
assert(false && "Compiler bug: identifier not of const-able type.");
}
}
auto fail_with_mismatched_type = [this, identifier_type](const Name& type_name) {
return Fail(ErrMismatchedNameTypeAssignment, identifier_type->type_decl->name, type_name);
};
switch (decl->kind) {
case Decl::Kind::kConst: {
if (const_type->name != identifier_type->type_decl->name)
return fail_with_mismatched_type(const_type->name);
break;
}
case Decl::Kind::kBits:
case Decl::Kind::kEnum: {
if (decl->name != identifier_type->type_decl->name)
return fail_with_mismatched_type(decl->name);
break;
}
default: {
assert(false && "Compiler bug: identifier not of const-able type.");
}
}
if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val))
goto fail_cannot_convert;
break;
}
default: {
assert(false && "Compiler bug: identifier not of const-able type.");
}
}
identifier_constant->ResolveTo(std::move(resolved_val));
return true;
fail_cannot_convert:
return Fail(ErrCannotConvertConstantToType, identifier_constant, const_type, type);
}
bool Library::ResolveLiteralConstant(LiteralConstant* literal_constant, const Type* type) {
switch (literal_constant->literal->kind) {
case raw::Literal::Kind::kString: {
if (type->kind != Type::Kind::kString)
goto return_fail;
auto string_type = static_cast<const StringType*>(type);
auto string_literal = static_cast<raw::StringLiteral*>(literal_constant->literal.get());
auto string_data = string_literal->span().data();
// TODO(pascallouis): because data() contains the raw content,
// with the two " to identify strings, we need to take this
// into account. We should expose the actual size of string
// literals properly, and take into account escaping.
uint64_t string_size = string_data.size() - 2;
if (string_type->max_size->value < string_size) {
return Fail(ErrStringConstantExceedsSizeBound, literal_constant->literal->span(),
literal_constant, string_size, type);
}
literal_constant->ResolveTo(
std::make_unique<StringConstantValue>(string_literal->span().data()));
return true;
}
case raw::Literal::Kind::kTrue: {
if (type->kind != Type::Kind::kPrimitive)
goto return_fail;
if (static_cast<const PrimitiveType*>(type)->subtype != types::PrimitiveSubtype::kBool)
goto return_fail;
literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(true));
return true;
}
case raw::Literal::Kind::kFalse: {
if (type->kind != Type::Kind::kPrimitive)
goto return_fail;
if (static_cast<const PrimitiveType*>(type)->subtype != types::PrimitiveSubtype::kBool)
goto return_fail;
literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(false));
return true;
}
case raw::Literal::Kind::kNumeric: {
if (type->kind != Type::Kind::kPrimitive)
goto return_fail;
// These must be initialized out of line to allow for goto statement
const raw::NumericLiteral* numeric_literal;
const PrimitiveType* primitive_type;
numeric_literal = static_cast<const raw::NumericLiteral*>(literal_constant->literal.get());
primitive_type = static_cast<const PrimitiveType*>(type);
switch (primitive_type->subtype) {
case types::PrimitiveSubtype::kInt8: {
int8_t value;
if (!ParseNumericLiteral<int8_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int8_t>>(value));
return true;
}
case types::PrimitiveSubtype::kInt16: {
int16_t value;
if (!ParseNumericLiteral<int16_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int16_t>>(value));
return true;
}
case types::PrimitiveSubtype::kInt32: {
int32_t value;
if (!ParseNumericLiteral<int32_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int32_t>>(value));
return true;
}
case types::PrimitiveSubtype::kInt64: {
int64_t value;
if (!ParseNumericLiteral<int64_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<int64_t>>(value));
return true;
}
case types::PrimitiveSubtype::kUint8: {
uint8_t value;
if (!ParseNumericLiteral<uint8_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint8_t>>(value));
return true;
}
case types::PrimitiveSubtype::kUint16: {
uint16_t value;
if (!ParseNumericLiteral<uint16_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint16_t>>(value));
return true;
}
case types::PrimitiveSubtype::kUint32: {
uint32_t value;
if (!ParseNumericLiteral<uint32_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint32_t>>(value));
return true;
}
case types::PrimitiveSubtype::kUint64: {
uint64_t value;
if (!ParseNumericLiteral<uint64_t>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<uint64_t>>(value));
return true;
}
case types::PrimitiveSubtype::kFloat32: {
float value;
if (!ParseNumericLiteral<float>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<float>>(value));
return true;
}
case types::PrimitiveSubtype::kFloat64: {
double value;
if (!ParseNumericLiteral<double>(numeric_literal, &value))
goto return_fail;
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<double>>(value));
return true;
}
default:
goto return_fail;
}
return_fail:
return Fail(ErrConstantCannotBeInterpretedAsType, literal_constant->literal->span(),
literal_constant, type);
}
}
}
bool Library::ResolveAsOptional(Constant* constant) const {
assert(constant);
if (constant->kind != Constant::Kind::kIdentifier)
return false;
// This refers to the `optional` constraint only if it is "optional" AND
// it is not shadowed by a previous definition.
// Note that as we improve scoping rules, we would need to allow `fidl.optional`
// to be the FQN for the `optional` constant.
auto identifier_constant = static_cast<IdentifierConstant*>(constant);
auto decl = LookupDeclByName(identifier_constant->name.memberless_key());
if (decl)
return false;
return identifier_constant->name.decl_name() == "optional";
}
const Type* Library::TypeResolve(const Type* type) {
if (type->kind != Type::Kind::kIdentifier) {
return type;
}
auto identifier_type = static_cast<const IdentifierType*>(type);
Decl* decl = LookupDeclByName(identifier_type->name);
if (!decl) {
Fail(ErrCouldNotResolveIdentifierToType);
return nullptr;
}
if (!CompileDecl(decl))
return nullptr;
switch (decl->kind) {
case Decl::Kind::kBits:
return GetType(static_cast<const Bits*>(decl)->subtype_ctor);
case Decl::Kind::kEnum:
return GetType(static_cast<const Enum*>(decl)->subtype_ctor);
default:
return type;
}
}
bool Library::TypeCanBeConst(const Type* type) {
switch (type->kind) {
case flat::Type::Kind::kString:
return type->nullability != types::Nullability::kNullable;
case flat::Type::Kind::kPrimitive:
return true;
case flat::Type::Kind::kIdentifier: {
auto identifier_type = static_cast<const IdentifierType*>(type);
switch (identifier_type->type_decl->kind) {
case Decl::Kind::kEnum:
case Decl::Kind::kBits:
return true;
default:
return false;
}
}
default:
return false;
} // switch
}
bool Library::TypeIsConvertibleTo(const Type* from_type, const Type* to_type) {
switch (to_type->kind) {
case flat::Type::Kind::kString: {
if (from_type->kind != flat::Type::Kind::kString)
return false;
auto from_string_type = static_cast<const flat::StringType*>(from_type);
auto to_string_type = static_cast<const flat::StringType*>(to_type);
if (to_string_type->nullability == types::Nullability::kNonnullable &&
from_string_type->nullability != types::Nullability::kNonnullable)
return false;
if (to_string_type->max_size->value < from_string_type->max_size->value)
return false;
return true;
}
case flat::Type::Kind::kPrimitive: {
if (from_type->kind != flat::Type::Kind::kPrimitive) {
return false;
}
auto from_primitive_type = static_cast<const flat::PrimitiveType*>(from_type);
auto to_primitive_type = static_cast<const flat::PrimitiveType*>(to_type);
switch (to_primitive_type->subtype) {
case types::PrimitiveSubtype::kBool:
return from_primitive_type->subtype == types::PrimitiveSubtype::kBool;
default:
// TODO(pascallouis): be more precise about convertibility, e.g. it
// should not be allowed to convert a float to an int.
return from_primitive_type->subtype != types::PrimitiveSubtype::kBool;
}
}
default:
return false;
} // switch
}
// Library resolution is concerned with resolving identifiers to their
// declarations, and with computing type sizes and alignments.
Decl* Library::LookupDeclByName(Name::Key name) const {
auto iter = declarations_.find(name);
if (iter == declarations_.end()) {
return nullptr;
}
return iter->second;
}
template <typename NumericType>
bool Library::ParseNumericLiteral(const raw::NumericLiteral* literal,
NumericType* out_value) const {
assert(literal != nullptr);
assert(out_value != nullptr);
auto data = literal->span().data();
std::string string_data(data.data(), data.data() + data.size());
auto result = utils::ParseNumeric(string_data, out_value);
return result == utils::ParseNumericResult::kSuccess;
}
bool Library::AddConstantDependencies(const Constant* constant, std::set<const Decl*>* out_edges) {
switch (constant->kind) {
case Constant::Kind::kIdentifier: {
auto identifier = static_cast<const flat::IdentifierConstant*>(constant);
auto decl = LookupDeclByName(identifier->name.memberless_key());
if (decl == nullptr) {
return Fail(ErrFailedConstantLookup, identifier->name, identifier->name);
}
out_edges->insert(decl);
break;
}
case Constant::Kind::kLiteral:
case Constant::Kind::kSynthesized: {
// Literal and synthesized constants have no dependencies on other declarations.
break;
}
case Constant::Kind::kBinaryOperator: {
auto op = static_cast<const flat::BinaryOperatorConstant*>(constant);
return AddConstantDependencies(op->left_operand.get(), out_edges) &&
AddConstantDependencies(op->right_operand.get(), out_edges);
}
}
return true;
}
// Calculating declaration dependencies is largely serving the C/C++ family of languages bindings.
// For instance, the declaration of a struct member type must be defined before the containing
// struct if that member is stored inline.
// Given the FIDL declarations:
//
// struct D2 { D1 d; }
// struct D1 { int32 x; }
//
// We must first declare D1, followed by D2 when emitting C code.
//
// Below, an edge from D1 to D2 means that we must see the declaration of of D1 before
// the declaration of D2, i.e. the calculated set of |out_edges| represents all the declarations
// that |decl| depends on.
//
// Notes:
// - Nullable structs do not require dependency edges since they are boxed via a
// pointer indirection, and their content placed out-of-line.
bool Library::DeclDependencies(const Decl* decl, std::set<const Decl*>* out_edges) {
std::set<const Decl*> edges;
auto maybe_add_decl = [&edges](TypeConstructorPtr type_ctor) {
TypeConstructorPtr current = type_ctor;
for (;;) {
const auto& invocation = GetLayoutInvocation(current);
if (invocation.from_type_alias) {
assert(!invocation.element_type_resolved &&
"Compiler bug: partial aliases should be disallowed");
edges.insert(invocation.from_type_alias);
return;
}
const Type* type = GetType(current);
if (type->nullability == types::Nullability::kNullable)
return;
switch (type->kind) {
case Type::Kind::kHandle: {
auto handle_type = static_cast<const HandleType*>(type);
// TODO(fxbug.dev/74909): the only case where resource_decl is nullptr
// is when the type constructor is referring to a bare, unconstrained
// `handle`, in which case there is no resource definition to depend
// on. Once this is disallowed, we can remove this check.
if (handle_type->resource_decl) {
auto decl = static_cast<const Decl*>(handle_type->resource_decl);
edges.insert(decl);
}
return;
}
case Type::Kind::kPrimitive:
case Type::Kind::kString:
case Type::Kind::kRequestHandle:
return;
case Type::Kind::kArray:
case Type::Kind::kVector: {
if (IsTypeConstructorDefined(invocation.element_type_raw)) {
current = invocation.element_type_raw;
break;
}
// The type_ctor won't have an arg_type_ctor if the type is Bytes.
// In that case, just return since there are no edges
return;
}
case Type::Kind::kIdentifier: {
// should have been caught above and returned early.
assert(type->nullability != types::Nullability::kNullable);
auto identifier_type = static_cast<const IdentifierType*>(type);
auto decl = static_cast<const Decl*>(identifier_type->type_decl);
if (decl->kind != Decl::Kind::kProtocol) {
edges.insert(decl);
}
return;
}
}
}
};
switch (decl->kind) {
case Decl::Kind::kBits: {
auto bits_decl = static_cast<const Bits*>(decl);
maybe_add_decl(GetTypeCtorAsPtr(bits_decl->subtype_ctor));
for (const auto& member : bits_decl->members) {
if (!AddConstantDependencies(member.value.get(), &edges)) {
return false;
}
}
break;
}
case Decl::Kind::kConst: {
auto const_decl = static_cast<const Const*>(decl);
maybe_add_decl(GetTypeCtorAsPtr(const_decl->type_ctor));
if (!AddConstantDependencies(const_decl->value.get(), &edges)) {
return false;
}
break;
}
case Decl::Kind::kEnum: {
auto enum_decl = static_cast<const Enum*>(decl);
maybe_add_decl(GetTypeCtorAsPtr(enum_decl->subtype_ctor));
for (const auto& member : enum_decl->members) {
if (!AddConstantDependencies(member.value.get(), &edges)) {
return false;
}
}
break;
}
case Decl::Kind::kProtocol: {
auto protocol_decl = static_cast<const Protocol*>(decl);
for (const auto& composed_protocol : protocol_decl->composed_protocols) {
if (auto type_decl = LookupDeclByName(composed_protocol); type_decl) {
edges.insert(type_decl);
}
}
for (const auto& method : protocol_decl->methods) {
if (method.maybe_request != nullptr) {
edges.insert(method.maybe_request);
}
if (method.maybe_response != nullptr) {
edges.insert(method.maybe_response);
}
}
break;
}
case Decl::Kind::kResource: {
auto resource_decl = static_cast<const Resource*>(decl);
maybe_add_decl(GetTypeCtorAsPtr(resource_decl->subtype_ctor));
break;
}
case Decl::Kind::kService: {
auto service_decl = static_cast<const Service*>(decl);
for (const auto& member : service_decl->members) {
maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor));
}
break;
}
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<const Struct*>(decl);
for (const auto& member : struct_decl->members) {
maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor));
if (member.maybe_default_value) {
if (!AddConstantDependencies(member.maybe_default_value.get(), &edges)) {
return false;
}
}
}
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<const Table*>(decl);
for (const auto& member : table_decl->members) {
if (!member.maybe_used)
continue;
maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor));
if (member.maybe_used->maybe_default_value) {
if (!AddConstantDependencies(member.maybe_used->maybe_default_value.get(), &edges)) {
return false;
}
}
}
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<const Union*>(decl);
for (const auto& member : union_decl->members) {
if (!member.maybe_used)
continue;
maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor));
}
break;
}
case Decl::Kind::kTypeAlias: {
auto type_alias_decl = static_cast<const TypeAlias*>(decl);
maybe_add_decl(GetTypeCtorAsPtr(type_alias_decl->partial_type_ctor));
}
} // switch
*out_edges = std::move(edges);
return true;
}
namespace {
// Declaration comparator.
//
// (1) To compare two Decl's in the same library, it suffices to compare the
// unqualified names of the Decl's. (This is faster.)
//
// (2) To compare two Decl's across libraries, we rely on the fully qualified
// names of the Decl's. (This is slower.)
struct CmpDeclInLibrary {
bool operator()(const Decl* a, const Decl* b) const {
assert(a->name != b->name || a == b);
const Library* a_library = a->name.library();
const Library* b_library = b->name.library();
if (a_library != b_library) {
return NameFlatName(a->name) < NameFlatName(b->name);
} else {
return a->name.decl_name() < b->name.decl_name();
}
}
};
} // namespace
bool Library::SortDeclarations() {
// |degree| is the number of undeclared dependencies for each decl.
std::map<const Decl*, uint32_t, CmpDeclInLibrary> degrees;
// |inverse_dependencies| records the decls that depend on each decl.
std::map<const Decl*, std::vector<const Decl*>, CmpDeclInLibrary> inverse_dependencies;
for (const auto& name_and_decl : declarations_) {
const Decl* decl = name_and_decl.second;
std::set<const Decl*> deps;
if (!DeclDependencies(decl, &deps))
return false;
degrees[decl] = static_cast<uint32_t>(deps.size());
for (const Decl* dep : deps) {
inverse_dependencies[dep].push_back(decl);
}
}
// Start with all decls that have no incoming edges.
std::vector<const Decl*> decls_without_deps;
for (const auto& decl_and_degree : degrees) {
if (decl_and_degree.second == 0u) {
decls_without_deps.push_back(decl_and_degree.first);
}
}
while (!decls_without_deps.empty()) {
// Pull one out of the queue.
auto decl = decls_without_deps.back();
decls_without_deps.pop_back();
assert(degrees[decl] == 0u);
declaration_order_.push_back(decl);
// Decrement the incoming degree of all the other decls it
// points to.
auto& inverse_deps = inverse_dependencies[decl];
for (const Decl* inverse_dep : inverse_deps) {
uint32_t& degree = degrees[inverse_dep];
assert(degree != 0u);
degree -= 1;
if (degree == 0u)
decls_without_deps.push_back(inverse_dep);
}
}
if (declaration_order_.size() != degrees.size()) {
// We didn't visit all the edges! There was a cycle.
return Fail(ErrIncludeCycle);
}
return true;
}
bool Library::CompileDecl(Decl* decl) {
if (decl->compiled)
return true;
Compiling guard(decl);
switch (decl->kind) {
case Decl::Kind::kBits: {
auto bits_decl = static_cast<Bits*>(decl);
if (!CompileBits(bits_decl))
return false;
break;
}
case Decl::Kind::kConst: {
auto const_decl = static_cast<Const*>(decl);
if (!CompileConst(const_decl))
return false;
break;
}
case Decl::Kind::kEnum: {
auto enum_decl = static_cast<Enum*>(decl);
if (!CompileEnum(enum_decl))
return false;
break;
}
case Decl::Kind::kProtocol: {
auto protocol_decl = static_cast<Protocol*>(decl);
if (!CompileProtocol(protocol_decl))
return false;
break;
}
case Decl::Kind::kResource: {
auto resource_decl = static_cast<Resource*>(decl);
if (!CompileResource(resource_decl))
return false;
break;
}
case Decl::Kind::kService: {
auto service_decl = static_cast<Service*>(decl);
if (!CompileService(service_decl))
return false;
break;
}
case Decl::Kind::kStruct: {
auto struct_decl = static_cast<Struct*>(decl);
if (!CompileStruct(struct_decl))
return false;
break;
}
case Decl::Kind::kTable: {
auto table_decl = static_cast<Table*>(decl);
if (!CompileTable(table_decl))
return false;
break;
}
case Decl::Kind::kUnion: {
auto union_decl = static_cast<Union*>(decl);
if (!CompileUnion(union_decl))
return false;
break;
}
case Decl::Kind::kTypeAlias: {
auto type_alias_decl = static_cast<TypeAlias*>(decl);
if (!CompileTypeAlias(type_alias_decl))
return false;
break;
}
} // switch
return true;
}
void Library::VerifyDeclAttributes(const Decl* decl) {
assert(decl->compiled && "verification must happen after compilation of decls");
auto placement_ok = reporter_->Checkpoint();
switch (decl->kind) {
case Decl::Kind::kBits: {
auto bits_declaration = static_cast<const Bits*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kBitsDecl,
bits_declaration->attributes.get());
for (const auto& member : bits_declaration->members) {
ValidateAttributesPlacement(AttributeSchema::Placement::kBitsMember,
member.attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraints.
ValidateAttributesConstraints(bits_declaration, bits_declaration->attributes.get());
}
break;
}
case Decl::Kind::kConst: {
auto const_decl = static_cast<const Const*>(decl);
// Attributes: for const declarations, we only check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kConstDecl,
const_decl->attributes.get());
break;
}
case Decl::Kind::kEnum: {
auto enum_declaration = static_cast<const Enum*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kEnumDecl,
enum_declaration->attributes.get());
for (const auto& member : enum_declaration->members) {
ValidateAttributesPlacement(AttributeSchema::Placement::kEnumMember,
member.attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraints.
ValidateAttributesConstraints(enum_declaration, enum_declaration->attributes.get());
}
break;
}
case Decl::Kind::kProtocol: {
auto protocol_declaration = static_cast<const Protocol*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kProtocolDecl,
protocol_declaration->attributes.get());
for (const auto& method_with_info : protocol_declaration->all_methods) {
ValidateAttributesPlacement(AttributeSchema::Placement::kMethod,
method_with_info.method->attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraints.
for (const auto method_with_info : protocol_declaration->all_methods) {
const auto& method = *method_with_info.method;
if (method.maybe_request) {
ValidateAttributesConstraints(method.maybe_request,
protocol_declaration->attributes.get());
ValidateAttributesConstraints(method.maybe_request, method.attributes.get());
}
if (method.maybe_response) {
ValidateAttributesConstraints(method.maybe_response,
protocol_declaration->attributes.get());
ValidateAttributesConstraints(method.maybe_response, method.attributes.get());
}
}
}
break;
}
case Decl::Kind::kResource: {
auto resource_declaration = static_cast<const Resource*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kResourceDecl,
resource_declaration->attributes.get());
for (const auto& property : resource_declaration->properties) {
ValidateAttributesPlacement(AttributeSchema::Placement::kResourceProperty,
property.attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraints.
ValidateAttributesConstraints(resource_declaration, resource_declaration->attributes.get());
}
break;
}
case Decl::Kind::kService: {
auto service_decl = static_cast<const Service*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kServiceDecl,
service_decl->attributes.get());
for (const auto& member : service_decl->members) {
ValidateAttributesPlacement(AttributeSchema::Placement::kServiceMember,
member.attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraint.
ValidateAttributesConstraints(service_decl, service_decl->attributes.get());
}
break;
}
case Decl::Kind::kStruct: {
auto struct_declaration = static_cast<const Struct*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kStructDecl,
struct_declaration->attributes.get());
for (const auto& member : struct_declaration->members) {
ValidateAttributesPlacement(AttributeSchema::Placement::kStructMember,
member.attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraint.
ValidateAttributesConstraints(struct_declaration, struct_declaration->attributes.get());
}
break;
}
case Decl::Kind::kTable: {
auto table_declaration = static_cast<const Table*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kTableDecl,
table_declaration->attributes.get());
for (const auto& member : table_declaration->members) {
if (!member.maybe_used)
continue;
ValidateAttributesPlacement(AttributeSchema::Placement::kTableMember,
member.maybe_used->attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraint.
ValidateAttributesConstraints(table_declaration, table_declaration->attributes.get());
}
break;
}
case Decl::Kind::kUnion: {
auto union_declaration = static_cast<const Union*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kUnionDecl,
union_declaration->attributes.get());
for (const auto& member : union_declaration->members) {
if (!member.maybe_used)
continue;
ValidateAttributesPlacement(AttributeSchema::Placement::kUnionMember,
member.maybe_used->attributes.get());
}
if (placement_ok.NoNewErrors()) {
// Attributes: check constraint.
ValidateAttributesConstraints(union_declaration, union_declaration->attributes.get());
}
break;
}
case Decl::Kind::kTypeAlias: {
auto type_alias_declaration = static_cast<const TypeAlias*>(decl);
// Attributes: check placement.
ValidateAttributesPlacement(AttributeSchema::Placement::kTypeAliasDecl,
type_alias_declaration->attributes.get());
if (placement_ok.NoNewErrors()) {
// Attributes: check constraints.
ValidateAttributesConstraints(type_alias_declaration,
type_alias_declaration->attributes.get());
}
break;
}
} // switch
}
void VerifyResourcenessStep::ForDecl(const Decl* decl) {
assert(decl->compiled && "verification must happen after compilation of decls");
switch (decl->kind) {
case Decl::Kind::kStruct: {
const auto* struct_decl = static_cast<const Struct*>(decl);
if (struct_decl->resourceness == types::Resourceness::kValue) {
for (const auto& member : struct_decl->members) {
if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) {
library_->reporter_->Report(ErrTypeMustBeResource, struct_decl->name.span(),
struct_decl->name, member.name.data(),
std::string_view("struct"), struct_decl->name);
}
}
}
break;
}
case Decl::Kind::kTable: {
const auto* table_decl = static_cast<const Table*>(decl);
if (table_decl->resourceness == types::Resourceness::kValue) {
for (const auto& member : table_decl->members) {
if (member.maybe_used) {
const auto& used = *member.maybe_used;
if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) {
library_->reporter_->Report(ErrTypeMustBeResource, table_decl->name.span(),
table_decl->name, used.name.data(),
std::string_view("table"), table_decl->name);
}
}
}
}
break;
}
case Decl::Kind::kUnion: {
const auto* union_decl = static_cast<const Union*>(decl);
if (union_decl->resourceness == types::Resourceness::kValue) {
for (const auto& member : union_decl->members) {
if (member.maybe_used) {
const auto& used = *member.maybe_used;
if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) {
library_->reporter_->Report(ErrTypeMustBeResource, union_decl->name.span(),
union_decl->name, used.name.data(),
std::string_view("union"), union_decl->name);
}
}
}
}
break;
}
default:
break;
}
}
types::Resourceness Type::Resourceness() const {
switch (this->kind) {
case Type::Kind::kPrimitive:
case Type::Kind::kString:
return types::Resourceness::kValue;
case Type::Kind::kHandle:
case Type::Kind::kRequestHandle:
return types::Resourceness::kResource;
case Type::Kind::kArray:
return static_cast<const ArrayType*>(this)->element_type->Resourceness();
case Type::Kind::kVector:
return static_cast<const VectorType*>(this)->element_type->Resourceness();
case Type::Kind::kIdentifier:
break;
}
auto decl = static_cast<const IdentifierType*>(this)->type_decl;
switch (decl->kind) {
case Decl::Kind::kBits:
case Decl::Kind::kEnum:
return types::Resourceness::kValue;
case Decl::Kind::kProtocol:
return types::Resourceness::kResource;
case Decl::Kind::kStruct:
assert(decl->compiled && "Compiler bug: accessing resourceness of not-yet-compiled struct");
return static_cast<const Struct*>(decl)->resourceness.value();
case Decl::Kind::kTable:
return static_cast<const Table*>(decl)->resourceness;
case Decl::Kind::kUnion:
assert(decl->compiled && "Compiler bug: accessing resourceness of not-yet-compiled union");
return static_cast<const Union*>(decl)->resourceness.value();
case Decl::Kind::kConst:
case Decl::Kind::kResource:
case Decl::Kind::kService:
case Decl::Kind::kTypeAlias:
assert(false && "Compiler bug: unexpected kind");
}
__builtin_unreachable();
}
types::Resourceness VerifyResourcenessStep::EffectiveResourceness(const Type* type) {
switch (type->kind) {
case Type::Kind::kPrimitive:
case Type::Kind::kString:
return types::Resourceness::kValue;
case Type::Kind::kHandle:
case Type::Kind::kRequestHandle:
return types::Resourceness::kResource;
case Type::Kind::kArray:
return EffectiveResourceness(static_cast<const ArrayType*>(type)->element_type);
case Type::Kind::kVector:
return EffectiveResourceness(static_cast<const VectorType*>(type)->element_type);
case Type::Kind::kIdentifier:
break;
}
auto decl = static_cast<const IdentifierType*>(type)->type_decl;
switch (decl->kind) {
case Decl::Kind::kBits:
case Decl::Kind::kEnum:
return types::Resourceness::kValue;
case Decl::Kind::kProtocol:
return types::Resourceness::kResource;
case Decl::Kind::kStruct:
if (static_cast<const Struct*>(decl)->resourceness.value() ==
types::Resourceness::kResource) {
return types::Resourceness::kResource;
}
break;
case Decl::Kind::kTable:
if (static_cast<const Table*>(decl)->resourceness == types::Resourceness::kResource) {
return types::Resourceness::kResource;
}
break;
case Decl::Kind::kUnion:
if (static_cast<const Union*>(decl)->resourceness.value() == types::Resourceness::kResource) {
return types::Resourceness::kResource;
}
break;
case Decl::Kind::kService:
return types::Resourceness::kValue;
case Decl::Kind::kConst:
case Decl::Kind::kResource:
case Decl::Kind::kTypeAlias:
assert(false && "Compiler bug: unexpected kind");
}
const auto [it, inserted] = effective_resourceness_.try_emplace(decl, std::nullopt);
if (!inserted) {
const auto& maybe_value = it->second;
// If we already computed effective resourceness, return it. If we started
// computing it but did not complete (nullopt), we're in a cycle, so return
// kValue as the default assumption.
return maybe_value.value_or(types::Resourceness::kValue);
}
switch (decl->kind) {
case Decl::Kind::kStruct:
for (const auto& member : static_cast<const Struct*>(decl)->members) {
if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) {
effective_resourceness_[decl] = types::Resourceness::kResource;
return types::Resourceness::kResource;
}
}
break;
case Decl::Kind::kTable:
for (const auto& member : static_cast<const Table*>(decl)->members) {
const auto& used = member.maybe_used;
if (used &&
EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) {
effective_resourceness_[decl] = types::Resourceness::kResource;
return types::Resourceness::kResource;
}
}
break;
case Decl::Kind::kUnion:
for (const auto& member : static_cast<const Union*>(decl)->members) {
const auto& used = member.maybe_used;
if (used &&
EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) {
effective_resourceness_[decl] = types::Resourceness::kResource;
return types::Resourceness::kResource;
}
}
break;
default:
assert(false && "Compiler bug: unexpected kind");
}
effective_resourceness_[decl] = types::Resourceness::kValue;
return types::Resourceness::kValue;
}
bool Library::CompileBits(Bits* bits_declaration) {
if (!CompileTypeConstructor(&bits_declaration->subtype_ctor))
return false;
if (GetType(bits_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration,
GetType(bits_declaration->subtype_ctor));
}
// Validate constants.
auto primitive_type = static_cast<const PrimitiveType*>(GetType(bits_declaration->subtype_ctor));
switch (primitive_type->subtype) {
case types::PrimitiveSubtype::kUint8: {
uint8_t mask;
if (!ValidateBitsMembersAndCalcMask<uint8_t>(bits_declaration, &mask))
return false;
bits_declaration->mask = mask;
break;
}
case types::PrimitiveSubtype::kUint16: {
uint16_t mask;
if (!ValidateBitsMembersAndCalcMask<uint16_t>(bits_declaration, &mask))
return false;
bits_declaration->mask = mask;
break;
}
case types::PrimitiveSubtype::kUint32: {
uint32_t mask;
if (!ValidateBitsMembersAndCalcMask<uint32_t>(bits_declaration, &mask))
return false;
bits_declaration->mask = mask;
break;
}
case types::PrimitiveSubtype::kUint64: {
uint64_t mask;
if (!ValidateBitsMembersAndCalcMask<uint64_t>(bits_declaration, &mask))
return false;
bits_declaration->mask = mask;
break;
}
case types::PrimitiveSubtype::kBool:
case types::PrimitiveSubtype::kInt8:
case types::PrimitiveSubtype::kInt16:
case types::PrimitiveSubtype::kInt32:
case types::PrimitiveSubtype::kInt64:
case types::PrimitiveSubtype::kFloat32:
case types::PrimitiveSubtype::kFloat64:
return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration,
GetType(bits_declaration->subtype_ctor));
}
{
// In the line below, `nullptr` needs an explicit cast to the pointer type due to
// C++ template mechanics.
auto err = ValidateUnknownConstraints<const Bits::Member>(
*bits_declaration, bits_declaration->strictness, nullptr);
if (err) {
return Fail(std::move(err));
}
}
return true;
}
bool Library::CompileConst(Const* const_declaration) {
if (!CompileTypeConstructor(&const_declaration->type_ctor))
return false;
const auto* const_type = GetType(const_declaration->type_ctor);
if (!TypeCanBeConst(const_type)) {
return Fail(ErrInvalidConstantType, *const_declaration, const_type);
}
if (!ResolveConstant(const_declaration->value.get(), const_type))
return Fail(ErrCannotResolveConstantValue, *const_declaration);
return true;
}
bool Library::CompileEnum(Enum* enum_declaration) {
if (!CompileTypeConstructor(&enum_declaration->subtype_ctor))
return false;
if (GetType(enum_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration,
GetType(enum_declaration->subtype_ctor));
}
// Validate constants.
auto primitive_type = static_cast<const PrimitiveType*>(GetType(enum_declaration->subtype_ctor));
enum_declaration->type = primitive_type;
switch (primitive_type->subtype) {
case types::PrimitiveSubtype::kInt8: {
int8_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<int8_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_signed = unknown_value;
break;
}
case types::PrimitiveSubtype::kInt16: {
int16_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<int16_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_signed = unknown_value;
break;
}
case types::PrimitiveSubtype::kInt32: {
int32_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<int32_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_signed = unknown_value;
break;
}
case types::PrimitiveSubtype::kInt64: {
int64_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<int64_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_signed = unknown_value;
break;
}
case types::PrimitiveSubtype::kUint8: {
uint8_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<uint8_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_unsigned = unknown_value;
break;
}
case types::PrimitiveSubtype::kUint16: {
uint16_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<uint16_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_unsigned = unknown_value;
break;
}
case types::PrimitiveSubtype::kUint32: {
uint32_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<uint32_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_unsigned = unknown_value;
break;
}
case types::PrimitiveSubtype::kUint64: {
uint64_t unknown_value;
if (!ValidateEnumMembersAndCalcUnknownValue<uint64_t>(enum_declaration, &unknown_value))
return false;
enum_declaration->unknown_value_unsigned = unknown_value;
break;
}
case types::PrimitiveSubtype::kBool:
case types::PrimitiveSubtype::kFloat32:
case types::PrimitiveSubtype::kFloat64:
return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration,
GetType(enum_declaration->subtype_ctor));
}
return true;
}
bool HasSimpleLayout(const Decl* decl) { return decl->HasAttribute("ForDeprecatedCBindings"); }
bool Library::CompileResource(Resource* resource_declaration) {
Scope<std::string_view> scope;
if (!CompileTypeConstructor(&resource_declaration->subtype_ctor))
return false;
if (GetType(resource_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
return Fail(ErrEnumTypeMustBeIntegralPrimitive, *resource_declaration,
GetType(resource_declaration->subtype_ctor));
}
for (auto& property : resource_declaration->properties) {
auto name_result = scope.Insert(property.name.data(), property.name);
if (!name_result.ok())
return Fail(ErrDuplicateResourcePropertyName, property.name,
name_result.previous_occurrence());
if (!CompileTypeConstructor(&property.type_ctor))
return false;
}
return true;
}
bool Library::CompileProtocol(Protocol* protocol_declaration) {
MethodScope method_scope;
auto CheckScopes = [this, &protocol_declaration, &method_scope](const Protocol* protocol,
auto Visitor) -> bool {
for (const auto& name : protocol->composed_protocols) {
auto decl = LookupDeclByName(name);
// TODO(fxbug.dev/7926): Special handling here should not be required, we
// should first rely on creating the types representing composed
// protocols.
if (!decl) {
return Fail(ErrUnknownType, name, name);
}
if (decl->kind != Decl::Kind::kProtocol)
return Fail(ErrComposingNonProtocol, name);
auto composed_protocol = static_cast<const Protocol*>(decl);
auto span = composed_protocol->name.span();
assert(span);
if (method_scope.protocols.Insert(composed_protocol, span.value()).ok()) {
if (!Visitor(composed_protocol, Visitor))
return false;
} else {
// Otherwise we have already seen this protocol in
// the inheritance graph.
}
}
for (const auto& method : protocol->methods) {
const auto original_name = method.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = method_scope.canonical_names.Insert(canonical_name, method.name);
if (!name_result.ok()) {
if (original_name == name_result.previous_occurrence().data()) {
return Fail(ErrDuplicateMethodName, method.name, original_name,
name_result.previous_occurrence());
}
const auto previous_span = name_result.previous_occurrence();
return Fail(ErrDuplicateMethodNameCanonical, method.name, original_name,
previous_span.data(), previous_span, canonical_name);
}
if (method.generated_ordinal64->value == 0)
return Fail(ErrGeneratedZeroValueOrdinal, method.generated_ordinal64->span());
auto ordinal_result =
method_scope.ordinals.Insert(method.generated_ordinal64->value, method.name);
if (!ordinal_result.ok()) {
std::string replacement_method(
fidl::ordinals::GetSelector(method.attributes.get(), method.name));
replacement_method.push_back('_');
return Fail(ErrDuplicateMethodOrdinal, method.generated_ordinal64->span(),
ordinal_result.previous_occurrence(), replacement_method);
}
// Add a pointer to this method to the protocol_declarations list.
bool is_composed = protocol_declaration != protocol;
protocol_declaration->all_methods.emplace_back(&method, is_composed);
}
return true;
};
if (!CheckScopes(protocol_declaration, CheckScopes))
return false;
for (auto& method : protocol_declaration->methods) {
if (method.maybe_request) {
if (!CompileDecl(method.maybe_request))
return false;
}
if (method.maybe_response) {
if (!CompileDecl(method.maybe_response))
return false;
}
}
return true;
}
bool Library::CompileService(Service* service_decl) {
Scope<std::string> scope;
for (auto& member : service_decl->members) {
const auto original_name = member.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = scope.Insert(canonical_name, member.name);
if (!name_result.ok()) {
const auto previous_span = name_result.previous_occurrence();
if (original_name == name_result.previous_occurrence().data()) {
return Fail(ErrDuplicateServiceMemberName, member.name, original_name, previous_span);
}
return Fail(ErrDuplicateServiceMemberNameCanonical, member.name, original_name,
previous_span.data(), previous_span, canonical_name);
}
if (!CompileTypeConstructor(&member.type_ctor))
return false;
// There's a mismatch between the "default" allowed categories and what is actually allowed
// in this context: in the new syntax, nothing changes. In the old syntax, we are more
// restrictive in this context, requiring kProtocolOnly rather than kTypeOrProtocol (which is
// the default for TypeConstructorOld).
bool ok =
std::visit(fidl::utils::matchers{
[this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool {
return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(),
AllowedCategories::kProtocolOnly);
},
[](const std::unique_ptr<TypeConstructorNew>& t) -> bool { return true; }},
member.type_ctor);
if (!ok)
return false;
if (GetType(member.type_ctor)->nullability != types::Nullability::kNonnullable)
return Fail(ErrNullableServiceMember, member.name);
}
return true;
}
bool Library::CompileStruct(Struct* struct_declaration) {
Scope<std::string> scope;
DeriveResourceness derive_resourceness(&struct_declaration->resourceness);
for (auto& member : struct_declaration->members) {
const auto original_name = member.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = scope.Insert(canonical_name, member.name);
if (!name_result.ok()) {
const auto previous_span = name_result.previous_occurrence();
if (original_name == previous_span.data()) {
return Fail(struct_declaration->is_request_or_response ? ErrDuplicateMethodParameterName
: ErrDuplicateStructMemberName,
member.name, original_name, previous_span);
}
return Fail(struct_declaration->is_request_or_response
? ErrDuplicateMethodParameterNameCanonical
: ErrDuplicateStructMemberNameCanonical,
member.name, original_name, previous_span.data(), previous_span, canonical_name);
}
if (!CompileTypeConstructor(&member.type_ctor))
return false;
assert(!(struct_declaration->is_request_or_response && member.maybe_default_value) &&
"method parameters cannot have default values");
if (member.maybe_default_value) {
const auto* default_value_type = GetType(member.type_ctor);
if (!TypeCanBeConst(default_value_type)) {
return Fail(ErrInvalidStructMemberType, *struct_declaration, NameIdentifier(member.name),
default_value_type);
}
if (!ResolveConstant(member.maybe_default_value.get(), default_value_type)) {
return false;
}
}
derive_resourceness.AddType(GetType(member.type_ctor));
}
return true;
}
bool Library::CompileTable(Table* table_declaration) {
Scope<std::string> name_scope;
Ordinal64Scope ordinal_scope;
for (auto& member : table_declaration->members) {
const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
if (!ordinal_result.ok()) {
return Fail(ErrDuplicateTableFieldOrdinal, member.ordinal->span(),
ordinal_result.previous_occurrence());
}
if (member.maybe_used) {
auto& member_used = *member.maybe_used;
const auto original_name = member_used.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = name_scope.Insert(canonical_name, member_used.name);
if (!name_result.ok()) {
const auto previous_span = name_result.previous_occurrence();
if (original_name == name_result.previous_occurrence().data()) {
return Fail(ErrDuplicateTableFieldName, member_used.name, original_name, previous_span);
}
return Fail(ErrDuplicateTableFieldNameCanonical, member_used.name, original_name,
previous_span.data(), previous_span, canonical_name);
}
if (!CompileTypeConstructor(&member_used.type_ctor)) {
return false;
}
if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) {
return Fail(ErrNullableTableMember, member_used.name);
}
}
}
if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) {
auto [ordinal, span] = *ordinal_and_loc;
return Fail(ErrNonDenseOrdinal, span, ordinal);
}
return true;
}
bool Library::CompileUnion(Union* union_declaration) {
Scope<std::string> scope;
Ordinal64Scope ordinal_scope;
DeriveResourceness derive_resourceness(&union_declaration->resourceness);
for (const auto& member : union_declaration->members) {
const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
if (!ordinal_result.ok()) {
return Fail(ErrDuplicateUnionMemberOrdinal, member.ordinal->span(),
ordinal_result.previous_occurrence());
}
if (member.maybe_used) {
const auto& member_used = *member.maybe_used;
const auto original_name = member_used.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = scope.Insert(canonical_name, member_used.name);
if (!name_result.ok()) {
const auto previous_span = name_result.previous_occurrence();
if (original_name == name_result.previous_occurrence().data()) {
return Fail(ErrDuplicateUnionMemberName, member_used.name, original_name, previous_span);
}
return Fail(ErrDuplicateUnionMemberNameCanonical, member_used.name, original_name,
previous_span.data(), previous_span, canonical_name);
}
if (!CompileTypeConstructor(const_cast<TypeConstructor*>(&member_used.type_ctor))) {
return false;
}
if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) {
return Fail(ErrNullableUnionMember, member_used.name);
}
derive_resourceness.AddType(GetType(member_used.type_ctor));
}
}
if (auto ordinal_and_loc = FindFirstNonDenseOrdinal(ordinal_scope)) {
auto [ordinal, span] = *ordinal_and_loc;
return Fail(ErrNonDenseOrdinal, span, ordinal);
}
{
std::vector<const Union::Member::Used*> used_members;
for (const auto& member : union_declaration->members) {
if (member.maybe_used)
used_members.push_back(member.maybe_used.get());
}
auto err = ValidateUnknownConstraints(*union_declaration, union_declaration->strictness,
&used_members);
if (err) {
return Fail(std::move(err));
}
}
return true;
}
bool Library::CompileTypeAlias(TypeAlias* type_alias) {
if (GetName(type_alias->partial_type_ctor) == type_alias->name)
// fidlc's current semantics for cases like `alias foo = foo;` is to
// include the LHS in the scope while compiling the RHS. Note that because
// of an interaction with a fidlc scoping bug that prevents shadowing builtins,
// this means that `alias Recursive = Recursive;` will fail with an includes
// cycle error, but e.g. `alias uint32 = uint32;` won't because the user
// defined `uint32` fails to shadow the builtin which means that we successfully
// resolve the RHS. To avoid inconsistent semantics, we need to manually
// catch this case and fail.
return Fail(ErrIncludeCycle);
return CompileTypeConstructor(&type_alias->partial_type_ctor);
}
bool Library::Compile() {
// We process declarations in topologically sorted order. For
// example, we process a struct member's type before the entire
// struct.
auto compile_step = StartCompileStep();
for (auto& name_and_decl : declarations_) {
Decl* decl = name_and_decl.second;
compile_step.ForDecl(decl);
}
if (!compile_step.Done())
return false;
if (!SortDeclarations()) {
return false;
}
auto verify_resourceness_step = StartVerifyResourcenessStep();
for (const Decl* decl : declaration_order_) {
verify_resourceness_step.ForDecl(decl);
}
if (!verify_resourceness_step.Done())
return false;
auto verify_attributes_step = StartVerifyAttributesStep();
for (const Decl* decl : declaration_order_) {
verify_attributes_step.ForDecl(decl);
}
if (!verify_attributes_step.Done())
return false;
for (const Decl* decl : declaration_order_) {
if (decl->kind == Decl::Kind::kStruct) {
auto struct_decl = static_cast<const Struct*>(decl);
if (!VerifyInlineSize(struct_decl)) {
return false;
}
}
}
if (!dependencies_.VerifyAllDependenciesWereUsed(*this, reporter_))
return false;
return reporter_->errors().size() == 0;
}
bool Library::CompileTypeConstructor(TypeConstructor* type_ctor) {
return std::visit(fidl::utils::matchers{
[&, this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool {
return CompileTypeConstructorOld(type_ctor.get());
},
[&, this](const std::unique_ptr<TypeConstructorNew>& type_ctor) -> bool {
return CompileTypeConstructorNew(type_ctor.get());
},
},
*type_ctor);
}
bool Library::CompileTypeConstructorOld(TypeConstructorOld* type_ctor) {
if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->maybe_arg_type_ctor,
type_ctor->handle_subtype_identifier, type_ctor->handle_rights,
type_ctor->maybe_size, type_ctor->nullability, &type_ctor->type,
&type_ctor->resolved_params))
return false;
// postcondition: compilation sets the Type of the TypeConstructor
assert(type_ctor->type && "type constructors' type not resolved after compilation");
return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(),
AllowedCategories::kTypeOrProtocol);
}
bool Library::CompileTypeConstructorNew(TypeConstructorNew* type_ctor) {
if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->parameters,
type_ctor->constraints, &type_ctor->type, &type_ctor->resolved_params))
return false;
// // postcondition: compilation sets the Type of the TypeConstructor
assert(type_ctor->type && "type constructors' type not resolved after compilation");
return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(), AllowedCategories::kTypeOnly);
return false;
}
bool Library::VerifyTypeCategory(const Type* type, std::optional<SourceSpan> span,
AllowedCategories category) {
assert(type && "CompileTypeConstructor did not set Type");
if (type->kind != Type::Kind::kIdentifier) {
// we assume that all non-identifier types (i.e. builtins) are actually
// types (and not e.g. protocols or services).
return category == AllowedCategories::kProtocolOnly ? Fail(ErrCannotUseType, span) : true;
}
auto identifier_type = static_cast<const IdentifierType*>(type);
switch (identifier_type->type_decl->kind) {
// services are never allowed in any context
case Decl::Kind::kService:
return Fail(ErrCannotUseService, span);
break;
case Decl::Kind::kProtocol:
if (category == AllowedCategories::kTypeOnly)
return Fail(ErrCannotUseProtocol, span);
break;
default:
if (category == AllowedCategories::kProtocolOnly)
return Fail(ErrCannotUseType, span);
break;
}
return true;
}
bool Library::ResolveHandleRightsConstant(Resource* resource, Constant* constant,
const HandleRights** out_rights) {
if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
GetName(resource->subtype_ctor).full_name() != "uint32") {
return Fail(ErrResourceMustBeUint32Derived, resource->name);
}
auto rights_property = resource->LookupProperty("rights");
if (!rights_property) {
return Fail(ErrResourceMissingRightsProperty, resource->name);
}
Decl* rights_decl = LookupDeclByName(GetName(rights_property->type_ctor));
if (!rights_decl || rights_decl->kind != Decl::Kind::kBits) {
return Fail(ErrResourceRightsPropertyMustReferToBits, resource->name);
}
if (!GetType(rights_property->type_ctor)) {
if (!CompileTypeConstructor(&rights_property->type_ctor))
return false;
}
const Type* rights_type = GetType(rights_property->type_ctor);
if (!ResolveConstant(constant, rights_type))
return false;
if (out_rights)
*out_rights = static_cast<const HandleRights*>(&constant->Value());
return true;
}
bool Library::ResolveHandleSubtypeIdentifier(Resource* resource,
const std::unique_ptr<Constant>& constant,
uint32_t* out_obj_type) {
// We only support an extremely limited form of resource suitable for
// handles here, where it must be:
// - derived from uint32
// - have a single properties element
// - the single property element must be a reference to an enum
// - the single property must be named "subtype".
if (constant->kind != Constant::Kind::kIdentifier) {
return Fail(ErrHandleSubtypeMustReferToResourceSubtype, constant->span);
}
auto identifier_constant = static_cast<IdentifierConstant*>(constant.get());
const Name& handle_subtype_identifier = identifier_constant->name;
if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
GetName(resource->subtype_ctor).full_name() != "uint32") {
return Fail(ErrResourceMustBeUint32Derived, resource->name);
}
auto subtype_property = resource->LookupProperty("subtype");
if (!subtype_property) {
return Fail(ErrResourceMissingSubtypeProperty, resource->name);
}
Decl* subtype_decl = LookupDeclByName(GetName(subtype_property->type_ctor));
if (!subtype_decl || subtype_decl->kind != Decl::Kind::kEnum) {
return Fail(ErrResourceSubtypePropertyMustReferToEnum, resource->name);
}
if (!GetType(subtype_property->type_ctor)) {
if (!CompileTypeConstructor(&subtype_property->type_ctor))
return false;
}
const Type* subtype_type = GetType(subtype_property->type_ctor);
auto* subtype_enum = static_cast<Enum*>(subtype_decl);
for (const auto& member : subtype_enum->members) {
if (member.name.data() == handle_subtype_identifier.span()->data()) {
if (!ResolveConstant(member.value.get(), subtype_type)) {
return false;
}
const flat::ConstantValue& value = member.value->Value();
auto obj_type = static_cast<uint32_t>(
reinterpret_cast<const flat::NumericConstantValue<uint32_t>&>(value));
*out_obj_type = obj_type;
return true;
}
}
return false;
}
bool Library::ResolveSizeBound(Constant* size_constant, const Size** out_size) {
if (!ResolveConstant(size_constant, &kSizeType)) {
if (size_constant->kind == Constant::Kind::kIdentifier) {
auto name = static_cast<IdentifierConstant*>(size_constant)->name;
if (name.library() == this && name.decl_name() == "MAX" && !name.member_name()) {
size_constant->ResolveTo(std::make_unique<Size>(Size::Max()));
}
}
}
if (!size_constant->IsResolved()) {
return false;
}
if (out_size) {
*out_size = static_cast<const Size*>(&size_constant->Value());
}
return true;
}
template <typename DeclType, typename MemberType>
bool Library::ValidateMembers(DeclType* decl, MemberValidator<MemberType> validator) {
assert(decl != nullptr);
constexpr const char* decl_type = std::is_same_v<DeclType, Enum> ? "enum" : "bits";
Scope<std::string> name_scope;
Scope<MemberType> value_scope;
bool success = true;
for (const auto& member : decl->members) {
assert(member.value != nullptr && "Compiler bug: member value is null!");
if (!ResolveConstant(member.value.get(), GetType(decl->subtype_ctor))) {
return Fail(ErrCouldNotResolveMember, member.name, std::string(decl_type));
}
// Check that the member identifier hasn't been used yet
const auto original_name = member.name.data();
const auto canonical_name = utils::canonicalize(original_name);
const auto name_result = name_scope.Insert(canonical_name, member.name);
if (!name_result.ok()) {
const auto previous_span = name_result.previous_occurrence();
// We can log the error and then continue validating for other issues in the decl
if (original_name == name_result.previous_occurrence().data()) {
success = Fail(ErrDuplicateMemberName, member.name, std::string_view(decl_type),
original_name, previous_span);
} else {
success = Fail(ErrDuplicateMemberNameCanonical, member.name, std::string_view(decl_type),
original_name, previous_span.data(), previous_span, canonical_name);
}
}
MemberType value =
static_cast<const NumericConstantValue<MemberType>&>(member.value->Value()).value;
const auto value_result = value_scope.Insert(value, member.name);
if (!value_result.ok()) {
const auto previous_span = value_result.previous_occurrence();
// We can log the error and then continue validating other members for other bugs
success = Fail(ErrDuplicateMemberValue, member.name, std::string_view(decl_type),
original_name, previous_span.data(), previous_span);
}
auto err = validator(value, member.attributes.get());
if (err) {
err->span = member.name;
success = Fail(std::move(err));
}
}
return success;
}
template <typename T>
static bool IsPowerOfTwo(T t) {
if (t == 0) {
return false;
}
if ((t & (t - 1)) != 0) {
return false;
}
return true;
}
template <typename MemberType>
bool Library::ValidateBitsMembersAndCalcMask(Bits* bits_decl, MemberType* out_mask) {
static_assert(std::is_unsigned<MemberType>::value && !std::is_same<MemberType, bool>::value,
"Bits members must be an unsigned integral type!");
// Each bits member must be a power of two.
MemberType mask = 0u;
auto validator = [&mask](MemberType member,
const raw::AttributeList*) -> std::unique_ptr<Diagnostic> {
if (!IsPowerOfTwo(member)) {
return Reporter::MakeError(ErrBitsMemberMustBePowerOfTwo);
}
mask |= member;
return nullptr;
};
if (!ValidateMembers<Bits, MemberType>(bits_decl, validator)) {
return false;
}
*out_mask = mask;
return true;
}
template <typename MemberType>
bool Library::ValidateEnumMembersAndCalcUnknownValue(Enum* enum_decl,
MemberType* out_unknown_value) {
static_assert(std::is_integral<MemberType>::value && !std::is_same<MemberType, bool>::value,
"Enum members must be an integral type!");
auto unknown_value = std::numeric_limits<MemberType>::max();
for (const auto& member : enum_decl->members) {
if (!ResolveConstant(member.value.get(), GetType(enum_decl->subtype_ctor))) {
return Fail(ErrCouldNotResolveMember, member.name, std::string("enum"));
}
auto attributes = member.attributes.get();
if (attributes && attributes->HasAttribute("Unknown")) {
unknown_value =
static_cast<const NumericConstantValue<MemberType>&>(member.value->Value()).value;
}
}
*out_unknown_value = unknown_value;
auto validator = [enum_decl, unknown_value](
MemberType member,
const raw::AttributeList* attributes) -> std::unique_ptr<Diagnostic> {
switch (enum_decl->strictness) {
case types::Strictness::kFlexible:
break;
case types::Strictness::kStrict:
// Strict enums cannot have [Unknown] attributes on members, but that will be validated by
// ValidateUnknownConstraints() (called later in this method).
return nullptr;
}
if (member != unknown_value)
return nullptr;
if (attributes && attributes->HasAttribute("Unknown"))
return nullptr;
return Reporter::MakeError(ErrFlexibleEnumMemberWithMaxValue, std::to_string(unknown_value),
std::to_string(unknown_value), std::to_string(unknown_value),
std::to_string(unknown_value));
};
if (!ValidateMembers<Enum, MemberType>(enum_decl, validator))
return false;
{
std::vector<const Enum::Member*> members;
members.reserve(enum_decl->members.size());
for (const auto& member : enum_decl->members) {
members.push_back(&member);
}
auto err = ValidateUnknownConstraints(*enum_decl, enum_decl->strictness, &members);
if (err) {
return Fail(std::move(err));
}
}
return true;
}
bool Library::HasAttribute(std::string_view name) const {
if (!attributes_)
return false;
return attributes_->HasAttribute(std::string(name));
}
const std::set<Library*>& Library::dependencies() const { return dependencies_.dependencies(); }
std::unique_ptr<TypeConstructorOld> TypeConstructorOld::CreateSizeType() {
return std::make_unique<TypeConstructorOld>(
Name::CreateIntrinsic("uint32"), nullptr /* maybe_arg_type */,
std::optional<Name>() /* handle_subtype_identifier */, nullptr /* handle_rights */,
nullptr /* maybe_size */, types::Nullability::kNonnullable);
}
std::unique_ptr<TypeConstructorNew> TypeConstructorNew::CreateSizeType() {
std::vector<std::unique_ptr<LayoutParameter>> no_params;
std::vector<std::unique_ptr<Constant>> no_constraints;
return std::make_unique<TypeConstructorNew>(
Name::CreateIntrinsic("uint32"),
std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt /* span */),
std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt /* span */));
}
bool LibraryMediator::ResolveParamAsType(const flat::TypeTemplate* layout,
const std::unique_ptr<LayoutParameter>& param,
const Type** out_type) const {
auto type_ctor = param->AsTypeCtor();
auto check = library_->reporter_->Checkpoint();
if (!type_ctor || !ResolveType(type_ctor)) {
// if there were no errors reported but we couldn't resolve to a type, it must
// mean that the parameter referred to a non-type, so report a new error here.
if (check.NoNewErrors()) {
return library_->Fail(ErrExpectedType, param->span);
}
// otherwise, there was an error during the type resolution process, so we
// should just report that rather than add an extra error here
return false;
}
*out_type = type_ctor->type;
return true;
}
bool LibraryMediator::ResolveParamAsSize(const flat::TypeTemplate* layout,
const std::unique_ptr<LayoutParameter>& param,
const Size** out_size) const {
// We could use param->AsConstant() here, leading to code similar to ResolveParamAsType.
// However, unlike ErrExpectedType, ErrExpectedValueButGotType requires a name to be
// reported, which would require doing a switch on the parameter kind anyway to find
// its Name. So we just handle all the cases ourselves from the start.
switch (param->kind) {
case LayoutParameter::Kind::kLiteral: {
auto literal_param = static_cast<LiteralLayoutParameter*>(param.get());
if (!ResolveSizeBound(literal_param->literal.get(), out_size))
return library_->Fail(ErrCouldNotParseSizeBound);
break;
}
case LayoutParameter::kType: {
auto type_param = static_cast<TypeLayoutParameter*>(param.get());
return library_->Fail(ErrExpectedValueButGotType, type_param->type_ctor->name);
}
case LayoutParameter::Kind::kIdentifier: {
auto ambig_param = static_cast<IdentifierLayoutParameter*>(param.get());
auto as_constant = ambig_param->AsConstant();
if (!ResolveSizeBound(as_constant, out_size))
return library_->Fail(ErrExpectedValueButGotType, ambig_param->name);
break;
}
}
assert(*out_size);
if ((*out_size)->value == 0)
return library_->Fail(ErrMustHaveNonZeroSize, param->span, layout);
return true;
}
bool LibraryMediator::ResolveConstraintAs(const std::unique_ptr<Constant>& constraint,
const std::vector<ConstraintKind>& interpretations,
Resource* resource, ResolvedConstraint* out) const {
for (const auto& constraint_kind : interpretations) {
out->kind = constraint_kind;
switch (constraint_kind) {
case ConstraintKind::kHandleSubtype: {
assert(resource &&
"Compiler bug: must pass resource if trying to resolve to handle subtype");
if (ResolveAsHandleSubtype(resource, constraint, &out->value.handle_subtype))
return true;
break;
}
case ConstraintKind::kHandleRights: {
assert(resource &&
"Compiler bug: must pass resource if trying to resolve to handle rights");
if (ResolveAsHandleRights(resource, constraint.get(), &(out->value.handle_rights)))
return true;
break;
}
case ConstraintKind::kSize: {
if (ResolveSizeBound(constraint.get(), &(out->value.size)))
return true;
break;
}
case ConstraintKind::kNullability: {
if (ResolveAsOptional(constraint.get()))
return true;
break;
}
}
}
return false;
}
bool LibraryMediator::ResolveType(TypeConstructorOld* type) const {
return library_->CompileTypeConstructorOld(type);
}
bool LibraryMediator::ResolveType(TypeConstructorNew* type) const {
return library_->CompileTypeConstructorNew(type);
}
bool LibraryMediator::ResolveSizeBound(Constant* size_constant, const Size** out_size) const {
return library_->ResolveSizeBound(size_constant, out_size);
}
bool LibraryMediator::ResolveAsOptional(Constant* constant) const {
return library_->ResolveAsOptional(constant);
}
bool LibraryMediator::ResolveAsHandleSubtype(Resource* resource,
const std::unique_ptr<Constant>& constant,
uint32_t* out_obj_type) const {
return library_->ResolveHandleSubtypeIdentifier(resource, constant, out_obj_type);
}
bool LibraryMediator::ResolveAsHandleRights(Resource* resource, Constant* constant,
const HandleRights** out_rights) const {
return library_->ResolveHandleRightsConstant(resource, constant, out_rights);
}
template <typename... Args>
bool LibraryMediator::Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span,
const Args&... args) const {
return library_->Fail(err, span, args...);
}
Decl* LibraryMediator::LookupDeclByName(Name::Key name) const {
return library_->LookupDeclByName(name);
}
TypeConstructorNew* LiteralLayoutParameter::AsTypeCtor() const { return nullptr; }
TypeConstructorNew* TypeLayoutParameter::AsTypeCtor() const { return type_ctor.get(); }
TypeConstructorNew* IdentifierLayoutParameter::AsTypeCtor() const {
if (!as_type_ctor) {
std::vector<std::unique_ptr<LayoutParameter>> no_params;
std::vector<std::unique_ptr<Constant>> no_constraints;
as_type_ctor = std::make_unique<TypeConstructorNew>(
name, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt),
std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt));
}
return as_type_ctor.get();
}
Constant* LiteralLayoutParameter::AsConstant() const { return literal.get(); }
Constant* TypeLayoutParameter::AsConstant() const { return nullptr; }
Constant* IdentifierLayoutParameter::AsConstant() const {
if (!as_constant) {
as_constant = std::make_unique<IdentifierConstant>(name, span);
}
return as_constant.get();
}
bool LibraryMediator::CompileDecl(Decl* decl) const { return library_->CompileDecl(decl); }
} // namespace flat
} // namespace fidl