blob: 5a9a90fb8c5ffa65b1b56ad2f1719790d067a3a6 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/fidl/fidlc/src/compile_step.h"
#include <zircon/assert.h>
#include "tools/fidl/fidlc/src/attribute_schema.h"
#include "tools/fidl/fidlc/src/flat_ast.h"
#include "tools/fidl/fidlc/src/name.h"
#include "tools/fidl/fidlc/src/type_resolver.h"
namespace fidlc {
// See RFC-0132 for the origin of this table limit.
constexpr size_t kMaxTableOrdinals = 64;
void CompileStep::RunImpl() {
CompileAttributeList(library()->attributes.get());
for (auto& [name, decl] : library()->declarations.all) {
CompileDecl(decl);
}
}
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 {
ZX_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);
}
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>;
} // namespace
void CompileStep::CompileDecl(Decl* decl) {
if (decl->name.library() != library()) {
ZX_ASSERT_MSG(decl->state == Decl::State::kCompiled,
"decls in dependencies must already be compiled");
}
switch (decl->state) {
case Decl::State::kNotCompiled:
break;
case Decl::State::kCompiled:
return;
case Decl::State::kCompiling: {
auto it = std::find(decl_stack_.begin(), decl_stack_.end(), decl);
ZX_ASSERT_MSG(it != decl_stack_.end(), "kCompiling decl should be in decl_stack_");
std::vector<const Decl*> cycle(it, decl_stack_.end());
cycle.push_back(decl);
reporter()->Fail(ErrIncludeCycle, decl->name.span().value(), cycle);
return;
}
}
decl->state = Decl::State::kCompiling;
decl_stack_.push_back(decl);
bool no_resource = false;
if (decl->attributes->Get("no_resource")) {
no_resource_count_++;
no_resource = true;
}
switch (decl->kind) {
case Decl::Kind::kBuiltin:
// Nothing to do.
break;
case Decl::Kind::kBits:
CompileBits(static_cast<Bits*>(decl));
break;
case Decl::Kind::kConst:
CompileConst(static_cast<Const*>(decl));
break;
case Decl::Kind::kEnum:
CompileEnum(static_cast<Enum*>(decl));
break;
case Decl::Kind::kProtocol:
CompileProtocol(static_cast<Protocol*>(decl));
break;
case Decl::Kind::kResource:
CompileResource(static_cast<Resource*>(decl));
break;
case Decl::Kind::kService:
CompileService(static_cast<Service*>(decl));
break;
case Decl::Kind::kStruct:
CompileStruct(static_cast<Struct*>(decl));
break;
case Decl::Kind::kTable:
CompileTable(static_cast<Table*>(decl));
break;
case Decl::Kind::kUnion:
CompileUnion(static_cast<Union*>(decl));
break;
case Decl::Kind::kOverlay:
CompileOverlay(static_cast<Overlay*>(decl));
break;
case Decl::Kind::kAlias:
CompileAlias(static_cast<Alias*>(decl));
break;
case Decl::Kind::kNewType:
CompileNewType(static_cast<NewType*>(decl));
break;
} // switch
decl->state = Decl::State::kCompiled;
decl_stack_.pop_back();
if (no_resource) {
no_resource_count_--;
}
library()->declaration_order.push_back(decl);
}
bool CompileStep::ResolveOrOperatorConstant(Constant* constant, std::optional<const Type*> opt_type,
const ConstantValue& left_operand,
const ConstantValue& right_operand) {
ZX_ASSERT_MSG(left_operand.kind == right_operand.kind,
"left and right operands of or operator must be of the same kind");
ZX_ASSERT_MSG(opt_type, "type inference not implemented for or operator");
const auto type = UnderlyingType(opt_type.value());
if (type == nullptr)
return false;
if (type->kind != Type::Kind::kPrimitive) {
return reporter()->Fail(ErrOrOperatorOnNonPrimitiveValue, constant->span);
}
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(left_operand_u64->AsNumeric<uint64_t>().value() |
right_operand_u64->AsNumeric<uint64_t>().value());
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), type);
return true;
}
bool CompileStep::ResolveConstant(Constant* constant, std::optional<const Type*> opt_type) {
ZX_ASSERT(constant != nullptr);
// Prevent re-entry.
if (constant->compiled)
return constant->IsResolved();
constant->compiled = true;
switch (constant->kind) {
case Constant::Kind::kIdentifier:
return ResolveIdentifierConstant(static_cast<IdentifierConstant*>(constant), opt_type);
case Constant::Kind::kLiteral:
return ResolveLiteralConstant(static_cast<LiteralConstant*>(constant), opt_type);
case Constant::Kind::kBinaryOperator: {
auto binary_operator_constant = static_cast<BinaryOperatorConstant*>(constant);
if (!ResolveConstant(binary_operator_constant->left_operand.get(), opt_type)) {
return false;
}
if (!ResolveConstant(binary_operator_constant->right_operand.get(), opt_type)) {
return false;
}
switch (binary_operator_constant->op) {
case BinaryOperatorConstant::Operator::kOr:
return ResolveOrOperatorConstant(constant, opt_type,
binary_operator_constant->left_operand->Value(),
binary_operator_constant->right_operand->Value());
default:
ZX_PANIC("unhandled binary operator");
}
}
}
}
ConstantValue::Kind CompileStep::ConstantValuePrimitiveKind(
const PrimitiveSubtype primitive_subtype) {
switch (primitive_subtype) {
case PrimitiveSubtype::kBool:
return ConstantValue::Kind::kBool;
case PrimitiveSubtype::kInt8:
return ConstantValue::Kind::kInt8;
case PrimitiveSubtype::kInt16:
return ConstantValue::Kind::kInt16;
case PrimitiveSubtype::kInt32:
return ConstantValue::Kind::kInt32;
case PrimitiveSubtype::kInt64:
return ConstantValue::Kind::kInt64;
case PrimitiveSubtype::kUint8:
return ConstantValue::Kind::kUint8;
case PrimitiveSubtype::kZxUchar:
return ConstantValue::Kind::kZxUchar;
case PrimitiveSubtype::kUint16:
return ConstantValue::Kind::kUint16;
case PrimitiveSubtype::kUint32:
return ConstantValue::Kind::kUint32;
case PrimitiveSubtype::kUint64:
return ConstantValue::Kind::kUint64;
case PrimitiveSubtype::kZxUsize64:
return ConstantValue::Kind::kZxUsize64;
case PrimitiveSubtype::kZxUintptr64:
return ConstantValue::Kind::kZxUintptr64;
case PrimitiveSubtype::kFloat32:
return ConstantValue::Kind::kFloat32;
case PrimitiveSubtype::kFloat64:
return ConstantValue::Kind::kFloat64;
}
}
bool CompileStep::ResolveIdentifierConstant(IdentifierConstant* identifier_constant,
std::optional<const Type*> opt_type) {
if (opt_type) {
ZX_ASSERT_MSG(TypeCanBeConst(opt_type.value()),
"resolving identifier constant to non-const-able type");
}
auto& reference = identifier_constant->reference;
Decl* parent = reference.resolved().element_or_parent_decl();
Element* target = reference.resolved().element();
CompileDecl(parent);
const Type* const_type = nullptr;
const ConstantValue* const_val = nullptr;
switch (target->kind) {
case Element::Kind::kBuiltin: {
// TODO(https://fxbug.dev/42182133): In some cases we want to return a more specific
// error message from here, but right now we can't due to the way
// TypeResolver::ResolveConstraintAs tries multiple interpretations.
return false;
}
case Element::Kind::kConst: {
auto const_decl = static_cast<Const*>(target);
if (!const_decl->value->IsResolved()) {
return false;
}
const_type = const_decl->type_ctor->type;
const_val = &const_decl->value->Value();
break;
}
case Element::Kind::kEnumMember: {
ZX_ASSERT(parent->kind == Decl::Kind::kEnum);
const_type = static_cast<Enum*>(parent)->subtype_ctor->type;
auto member = static_cast<Enum::Member*>(target);
if (!member->value->IsResolved()) {
return false;
}
const_val = &member->value->Value();
break;
}
case Element::Kind::kBitsMember: {
ZX_ASSERT(parent->kind == Decl::Kind::kBits);
const_type = static_cast<Bits*>(parent)->subtype_ctor->type;
auto member = static_cast<Bits::Member*>(target);
if (!member->value->IsResolved()) {
return false;
}
const_val = &member->value->Value();
break;
}
default: {
return reporter()->Fail(ErrExpectedValueButGotType, reference.span(),
reference.resolved().name());
break;
}
}
ZX_ASSERT_MSG(const_val, "did not set const_val");
ZX_ASSERT_MSG(const_type, "did not set const_type");
std::unique_ptr<ConstantValue> resolved_val;
const auto type = opt_type ? opt_type.value() : const_type;
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);
CompileDecl(identifier_type->type_decl);
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);
if (!enum_decl->subtype_ctor->type) {
return false;
}
ZX_ASSERT(enum_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive);
primitive_type = static_cast<const PrimitiveType*>(enum_decl->subtype_ctor->type);
break;
}
case Decl::Kind::kBits: {
auto bits_decl = static_cast<const Bits*>(identifier_type->type_decl);
ZX_ASSERT(bits_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive);
if (!bits_decl->subtype_ctor->type) {
return false;
}
primitive_type = static_cast<const PrimitiveType*>(bits_decl->subtype_ctor->type);
break;
}
default: {
ZX_PANIC("identifier not of const-able type.");
}
}
auto fail_with_mismatched_type = [this, identifier_type,
identifier_constant](const Name& type_name) {
return reporter()->Fail(ErrMismatchedNameTypeAssignment, identifier_constant->span,
identifier_type->type_decl->name, type_name);
};
switch (parent->kind) {
case Decl::Kind::kConst: {
if (const_type->kind != Type::Kind::kIdentifier ||
static_cast<const IdentifierType*>(const_type)->type_decl !=
identifier_type->type_decl) {
return fail_with_mismatched_type(const_type->name);
}
break;
}
case Decl::Kind::kBits:
case Decl::Kind::kEnum: {
if (parent != identifier_type->type_decl)
return fail_with_mismatched_type(parent->name);
break;
}
default: {
ZX_PANIC("identifier not of const-able type.");
}
}
if (!const_val->Convert(ConstantValuePrimitiveKind(primitive_type->subtype), &resolved_val))
goto fail_cannot_convert;
break;
}
default: {
ZX_PANIC("identifier not of const-able type.");
}
}
identifier_constant->ResolveTo(std::move(resolved_val), type);
return true;
fail_cannot_convert:
return reporter()->Fail(ErrTypeCannotBeConvertedToType, reference.span(), identifier_constant,
const_type, type);
}
bool CompileStep::ResolveLiteralConstant(LiteralConstant* literal_constant,
std::optional<const Type*> opt_type) {
auto inferred_type = InferType(static_cast<Constant*>(literal_constant));
const Type* type = opt_type ? opt_type.value() : inferred_type;
if (!TypeIsConvertibleTo(inferred_type, type)) {
return reporter()->Fail(ErrTypeCannotBeConvertedToType, literal_constant->literal->span(),
literal_constant, inferred_type, type);
}
switch (literal_constant->literal->kind) {
case RawLiteral::Kind::kDocComment: {
auto doc_comment_literal =
static_cast<const RawDocCommentLiteral*>(literal_constant->literal);
literal_constant->ResolveTo(
std::make_unique<DocCommentConstantValue>(doc_comment_literal->value),
typespace()->GetUnboundedStringType());
return true;
}
case RawLiteral::Kind::kString: {
auto string_literal = static_cast<const RawStringLiteral*>(literal_constant->literal);
literal_constant->ResolveTo(std::make_unique<StringConstantValue>(string_literal->value),
typespace()->GetUnboundedStringType());
return true;
}
case RawLiteral::Kind::kBool: {
auto bool_literal = static_cast<const RawBoolLiteral*>(literal_constant->literal);
literal_constant->ResolveTo(std::make_unique<BoolConstantValue>(bool_literal->value),
typespace()->GetPrimitiveType(PrimitiveSubtype::kBool));
return true;
}
case RawLiteral::Kind::kNumeric: {
switch (type->kind) {
case Type::Kind::kPrimitive:
return ResolveLiteralConstantNumeric(literal_constant,
static_cast<const PrimitiveType*>(type));
case Type::Kind::kIdentifier: {
ZX_ASSERT(static_cast<const IdentifierType*>(type)->type_decl->kind == Decl::Kind::kBits);
auto underlying_type = UnderlyingType(type);
if (underlying_type->kind != Type::Kind::kPrimitive)
return false;
auto primitive_type = static_cast<const PrimitiveType*>(underlying_type);
if (!ResolveLiteralConstantNumeric(literal_constant, primitive_type))
return false;
auto number = literal_constant->Value().AsUnsigned();
if (!number.has_value())
return false;
// The only numeric literal allowed is 0, to represent an empty bits value.
if (number.value() != 0) {
return reporter()->Fail(ErrTypeCannotBeConvertedToType,
literal_constant->literal->span(), literal_constant,
inferred_type, type);
}
return true;
}
default:
ZX_PANIC("TypeIsConvertibleTo should have returned false");
}
}
} // switch
}
bool CompileStep::ResolveLiteralConstantNumeric(LiteralConstant* literal_constant,
const PrimitiveType* primitive_type) {
switch (primitive_type->subtype) {
case PrimitiveSubtype::kInt8:
return ResolveLiteralConstantNumericImpl<int8_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kInt16:
return ResolveLiteralConstantNumericImpl<int16_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kInt32:
return ResolveLiteralConstantNumericImpl<int32_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kInt64:
return ResolveLiteralConstantNumericImpl<int64_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kUint8:
case PrimitiveSubtype::kZxUchar:
return ResolveLiteralConstantNumericImpl<uint8_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kUint16:
return ResolveLiteralConstantNumericImpl<uint16_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kUint32:
return ResolveLiteralConstantNumericImpl<uint32_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kUint64:
case PrimitiveSubtype::kZxUsize64:
case PrimitiveSubtype::kZxUintptr64:
return ResolveLiteralConstantNumericImpl<uint64_t>(literal_constant, primitive_type);
case PrimitiveSubtype::kFloat32:
return ResolveLiteralConstantNumericImpl<float>(literal_constant, primitive_type);
case PrimitiveSubtype::kFloat64:
return ResolveLiteralConstantNumericImpl<double>(literal_constant, primitive_type);
default:
ZX_PANIC("should not have any other primitive type reachable");
}
}
template <typename NumericType>
bool CompileStep::ResolveLiteralConstantNumericImpl(LiteralConstant* literal_constant,
const PrimitiveType* primitive_type) {
NumericType value;
const auto span = literal_constant->literal->span();
std::string string_data(span.data().data(), span.data().data() + span.data().size());
switch (ParseNumeric(string_data, &value)) {
case ParseNumericResult::kSuccess:
literal_constant->ResolveTo(std::make_unique<NumericConstantValue<NumericType>>(value),
primitive_type);
return true;
case ParseNumericResult::kMalformed:
// The caller (ResolveLiteralConstant) ensures that the constant kind is
// a numeric literal, which means that it follows the grammar for
// numerical types. As a result, an error to parse the data here is due
// to the data being too large, rather than bad input.
[[fallthrough]];
case ParseNumericResult::kOutOfBounds:
return reporter()->Fail(ErrConstantOverflowsType, span, literal_constant, primitive_type);
}
}
const Type* CompileStep::InferType(Constant* constant) {
switch (constant->kind) {
case Constant::Kind::kLiteral: {
auto literal =
static_cast<const RawLiteral*>(static_cast<const LiteralConstant*>(constant)->literal);
switch (literal->kind) {
case RawLiteral::Kind::kString: {
auto string_literal = static_cast<const RawStringLiteral*>(literal);
auto inferred_size = StringLiteralLength(string_literal->span().data());
return typespace()->GetStringType(inferred_size);
}
case RawLiteral::Kind::kNumeric:
return typespace()->GetUntypedNumericType();
case RawLiteral::Kind::kBool:
return typespace()->GetPrimitiveType(PrimitiveSubtype::kBool);
case RawLiteral::Kind::kDocComment:
return typespace()->GetUnboundedStringType();
}
return nullptr;
}
case Constant::Kind::kIdentifier:
if (!ResolveConstant(constant, std::nullopt)) {
return nullptr;
}
return constant->type;
case Constant::Kind::kBinaryOperator:
ZX_PANIC("type inference not implemented for binops");
}
}
bool CompileStep::ResolveAsOptional(Constant* constant) {
ZX_ASSERT(constant);
if (constant->kind != Constant::Kind::kIdentifier)
return false;
auto identifier_constant = static_cast<IdentifierConstant*>(constant);
auto element = identifier_constant->reference.resolved().element();
if (element->kind != Element::Kind::kBuiltin)
return false;
auto builtin = static_cast<Builtin*>(element);
return builtin->id == Builtin::Identity::kOptional;
}
void CompileStep::CompileAttributeList(AttributeList* attributes) {
Scope<std::string> scope;
for (auto& attribute : attributes->attributes) {
const auto original_name = attribute->name.data();
const auto canonical_name = Canonicalize(original_name);
const auto result = scope.Insert(canonical_name, attribute->name);
if (!result.ok()) {
const auto previous_span = result.previous_occurrence();
if (original_name == previous_span.data()) {
reporter()->Fail(ErrDuplicateAttribute, attribute->name, original_name, previous_span);
} else {
reporter()->Fail(ErrDuplicateAttributeCanonical, attribute->name, original_name,
previous_span.data(), previous_span, canonical_name);
}
}
CompileAttribute(attribute.get());
}
}
void CompileStep::CompileAttribute(Attribute* attribute, bool early) {
if (attribute->compiled) {
return;
}
Scope<std::string> scope;
for (auto& arg : attribute->args) {
if (!arg->name.has_value()) {
continue;
}
const auto original_name = arg->name.value().data();
const auto canonical_name = Canonicalize(original_name);
const auto result = scope.Insert(canonical_name, arg->name.value());
if (!result.ok()) {
const auto previous_span = result.previous_occurrence();
if (original_name == previous_span.data()) {
reporter()->Fail(ErrDuplicateAttributeArg, attribute->span, attribute, original_name,
previous_span);
} else {
reporter()->Fail(ErrDuplicateAttributeArgCanonical, attribute->span, attribute,
original_name, previous_span.data(), previous_span, canonical_name);
}
}
}
const AttributeSchema& schema = all_libraries()->RetrieveAttributeSchema(attribute);
if (early) {
ZX_ASSERT_MSG(schema.IsCompileEarly(), "attribute is not allowed to be compiled early");
}
schema.ResolveArgs(this, attribute);
attribute->compiled = true;
}
// static
void CompileStep::CompileAttributeEarly(Compiler* compiler, Attribute* attribute) {
CompileStep(compiler).CompileAttribute(attribute, /* early = */ true);
}
void CompileStep::CompileModifierList(ModifierList* modifiers, OutModifiers out) {
for (auto& modifier : modifiers->modifiers) {
CompileAttributeList(modifier->attributes.get());
std::visit(overloaded{
[&](Strictness strictness) { *out.strictness = strictness; },
[&](Resourceness resourceness) {
*out.resourceness = resourceness;
if (resourceness == Resourceness::kResource && no_resource_count_) {
reporter()->Fail(ErrResourceForbiddenHere, modifier->name);
}
},
[&](Openness openness) { *out.openness = openness; },
},
modifier->value);
}
// This matches ConsumeStep::NeedMethodResultUnion which considers methods flexible by default.
if (out.strictness && !out.strictness->has_value())
*out.strictness = Strictness::kFlexible;
if (out.resourceness && !out.resourceness->has_value())
*out.resourceness = Resourceness::kValue;
if (out.openness && !out.openness->has_value())
*out.openness = Openness::kOpen;
}
const Type* CompileStep::UnderlyingType(const Type* type) {
if (type->kind != Type::Kind::kIdentifier) {
return type;
}
auto identifier_type = static_cast<const IdentifierType*>(type);
Decl* decl = identifier_type->type_decl;
CompileDecl(decl);
switch (decl->kind) {
case Decl::Kind::kBits:
return static_cast<const Bits*>(decl)->subtype_ctor->type;
case Decl::Kind::kEnum:
return static_cast<const Enum*>(decl)->subtype_ctor->type;
default:
return type;
}
}
bool CompileStep::TypeCanBeConst(const Type* type) {
switch (type->kind) {
case Type::Kind::kString:
return !type->IsNullable();
case Type::Kind::kPrimitive:
return true;
case 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 CompileStep::TypeIsConvertibleTo(const Type* from_type, const Type* to_type) {
switch (to_type->kind) {
case Type::Kind::kString: {
if (from_type->kind != Type::Kind::kString)
return false;
auto from_string_type = static_cast<const StringType*>(from_type);
auto to_string_type = static_cast<const StringType*>(to_type);
if (!to_string_type->IsNullable() && from_string_type->IsNullable())
return false;
if (to_string_type->MaxSize() < from_string_type->MaxSize())
return false;
return true;
}
case Type::Kind::kPrimitive: {
auto to_primitive_type = static_cast<const PrimitiveType*>(to_type);
switch (from_type->kind) {
case Type::Kind::kUntypedNumeric:
return to_primitive_type->subtype != PrimitiveSubtype::kBool;
case Type::Kind::kPrimitive:
break; // handled below
default:
return false;
}
auto from_primitive_type = static_cast<const PrimitiveType*>(from_type);
switch (to_primitive_type->subtype) {
case PrimitiveSubtype::kBool:
return from_primitive_type->subtype == PrimitiveSubtype::kBool;
default:
// TODO(https://fxbug.dev/42069446): be more precise about convertibility, e.g. it should
// not be allowed to convert a float to an int.
return from_primitive_type->subtype != PrimitiveSubtype::kBool;
}
}
case Type::Kind::kIdentifier: {
// Allow kUntypedNumeric for `const NONE BitsType = 0;`.
auto identifier_type = static_cast<const IdentifierType*>(to_type);
return identifier_type->type_decl->kind == Decl::Kind::kBits &&
from_type->kind == Type::Kind::kUntypedNumeric;
}
default:
return false;
} // switch
}
void CompileStep::CompileBits(Bits* bits_declaration) {
CompileAttributeList(bits_declaration->attributes.get());
for (auto& member : bits_declaration->members) {
CompileAttributeList(member.attributes.get());
}
CompileModifierList(bits_declaration->modifiers.get(),
OutModifiers{.strictness = &bits_declaration->strictness});
CompileTypeConstructor(bits_declaration->subtype_ctor.get());
if (!bits_declaration->subtype_ctor->type) {
return;
}
if (bits_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) {
reporter()->Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive,
bits_declaration->name.span().value(), bits_declaration->subtype_ctor->type);
return;
}
if (bits_declaration->strictness.value() == Strictness::kStrict &&
bits_declaration->members.empty()) {
reporter()->Fail(ErrMustHaveOneMember, bits_declaration->name.span().value());
}
// Validate constants.
auto primitive_type = static_cast<const PrimitiveType*>(bits_declaration->subtype_ctor->type);
switch (primitive_type->subtype) {
case PrimitiveSubtype::kUint8: {
uint8_t mask;
if (!ValidateBitsMembersAndCalcMask<uint8_t>(bits_declaration, &mask))
return;
bits_declaration->mask = mask;
break;
}
case PrimitiveSubtype::kUint16: {
uint16_t mask;
if (!ValidateBitsMembersAndCalcMask<uint16_t>(bits_declaration, &mask))
return;
bits_declaration->mask = mask;
break;
}
case PrimitiveSubtype::kUint32: {
uint32_t mask;
if (!ValidateBitsMembersAndCalcMask<uint32_t>(bits_declaration, &mask))
return;
bits_declaration->mask = mask;
break;
}
case PrimitiveSubtype::kUint64: {
uint64_t mask;
if (!ValidateBitsMembersAndCalcMask<uint64_t>(bits_declaration, &mask))
return;
bits_declaration->mask = mask;
break;
}
case PrimitiveSubtype::kBool:
case PrimitiveSubtype::kInt8:
case PrimitiveSubtype::kInt16:
case PrimitiveSubtype::kInt32:
case PrimitiveSubtype::kInt64:
case PrimitiveSubtype::kZxUchar:
case PrimitiveSubtype::kZxUsize64:
case PrimitiveSubtype::kZxUintptr64:
case PrimitiveSubtype::kFloat32:
case PrimitiveSubtype::kFloat64:
reporter()->Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive,
bits_declaration->name.span().value(), bits_declaration->subtype_ctor->type);
return;
}
}
void CompileStep::CompileConst(Const* const_declaration) {
CompileAttributeList(const_declaration->attributes.get());
CompileTypeConstructor(const_declaration->type_ctor.get());
const auto* const_type = const_declaration->type_ctor->type;
if (!const_type) {
return;
}
if (!TypeCanBeConst(const_type)) {
reporter()->Fail(ErrInvalidConstantType, const_declaration->name.span().value(), const_type);
} else if (!ResolveConstant(const_declaration->value.get(), const_type)) {
reporter()->Fail(ErrCannotResolveConstantValue, const_declaration->name.span().value());
}
}
void CompileStep::CompileEnum(Enum* enum_declaration) {
CompileAttributeList(enum_declaration->attributes.get());
for (auto& member : enum_declaration->members) {
CompileAttributeList(member.attributes.get());
}
CompileModifierList(enum_declaration->modifiers.get(),
OutModifiers{.strictness = &enum_declaration->strictness});
CompileTypeConstructor(enum_declaration->subtype_ctor.get());
if (!enum_declaration->subtype_ctor->type) {
return;
}
if (enum_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) {
reporter()->Fail(ErrEnumTypeMustBeIntegralPrimitive, enum_declaration->name.span().value(),
enum_declaration->subtype_ctor->type);
return;
}
if (enum_declaration->strictness.value() == Strictness::kStrict &&
enum_declaration->members.empty()) {
reporter()->Fail(ErrMustHaveOneMember, enum_declaration->name.span().value());
}
// Validate constants.
auto primitive_type = static_cast<const PrimitiveType*>(enum_declaration->subtype_ctor->type);
enum_declaration->type = primitive_type;
switch (primitive_type->subtype) {
case PrimitiveSubtype::kInt8: {
int8_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<int8_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_signed = unknown_value;
}
break;
}
case PrimitiveSubtype::kInt16: {
int16_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<int16_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_signed = unknown_value;
}
break;
}
case PrimitiveSubtype::kInt32: {
int32_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<int32_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_signed = unknown_value;
}
break;
}
case PrimitiveSubtype::kInt64: {
int64_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<int64_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_signed = unknown_value;
}
break;
}
case PrimitiveSubtype::kUint8: {
uint8_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<uint8_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_unsigned = unknown_value;
}
break;
}
case PrimitiveSubtype::kUint16: {
uint16_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<uint16_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_unsigned = unknown_value;
}
break;
}
case PrimitiveSubtype::kUint32: {
uint32_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<uint32_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_unsigned = unknown_value;
}
break;
}
case PrimitiveSubtype::kUint64: {
uint64_t unknown_value;
if (ValidateEnumMembersAndCalcUnknownValue<uint64_t>(enum_declaration, &unknown_value)) {
enum_declaration->unknown_value_unsigned = unknown_value;
}
break;
}
case PrimitiveSubtype::kBool:
case PrimitiveSubtype::kFloat32:
case PrimitiveSubtype::kFloat64:
case PrimitiveSubtype::kZxUsize64:
case PrimitiveSubtype::kZxUintptr64:
case PrimitiveSubtype::kZxUchar:
reporter()->Fail(ErrEnumTypeMustBeIntegralPrimitive, enum_declaration->name.span().value(),
enum_declaration->subtype_ctor->type);
break;
}
}
void CompileStep::CompileResource(Resource* resource_declaration) {
CompileAttributeList(resource_declaration->attributes.get());
CompileTypeConstructor(resource_declaration->subtype_ctor.get());
if (!resource_declaration->subtype_ctor->type) {
return;
}
if (resource_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive ||
static_cast<const PrimitiveType*>(resource_declaration->subtype_ctor->type)->subtype !=
PrimitiveSubtype::kUint32) {
reporter()->Fail(ErrResourceMustBeUint32Derived, resource_declaration->name.span().value(),
resource_declaration->name);
}
for (auto& property : resource_declaration->properties) {
CompileAttributeList(property.attributes.get());
CompileTypeConstructor(property.type_ctor.get());
}
// All properties have been compiled at this point, so we can reason about their types.
auto subtype_property = resource_declaration->LookupProperty("subtype");
if (subtype_property != nullptr) {
const Type* subtype_type = subtype_property->type_ctor->type;
// If the |subtype_type is a |nullptr|, we are in a cycle, which means that the |subtype|
// property could not possibly be an enum declaration.
if (subtype_type == nullptr || subtype_type->kind != Type::Kind::kIdentifier ||
static_cast<const IdentifierType*>(subtype_type)->type_decl->kind != Decl::Kind::kEnum) {
reporter()->Fail(ErrResourceSubtypePropertyMustReferToEnum, subtype_property->name,
resource_declaration->name);
}
} else {
reporter()->Fail(ErrResourceMissingSubtypeProperty, resource_declaration->name.span().value(),
resource_declaration->name);
}
auto rights_property = resource_declaration->LookupProperty("rights");
if (rights_property != nullptr) {
const Type* rights_type = rights_property->type_ctor->type;
const Type* rights_underlying_type = UnderlyingType(rights_type);
if (!(rights_underlying_type->kind == Type::Kind::kPrimitive &&
static_cast<const PrimitiveType*>(rights_underlying_type)->subtype ==
PrimitiveSubtype::kUint32)) {
reporter()->Fail(ErrResourceRightsPropertyMustReferToBits, rights_property->name,
resource_declaration->name);
}
}
}
void CompileStep::CompileResultUnion(Protocol::Method* method) {
using Ordinal = Protocol::Method::ResultUnionOrdinal;
if (method->kind != Protocol::Method::Kind::kTwoWay)
return;
if (method->strictness == Strictness::kStrict && !method->has_error)
return;
auto& response = method->maybe_response;
ZX_ASSERT(response && response->type);
ZX_ASSERT(response->type->kind == Type::Kind::kIdentifier);
auto identifier_type = static_cast<IdentifierType*>(response->type);
ZX_ASSERT(identifier_type->type_decl->kind == Decl::Kind::kUnion);
auto anonymous = identifier_type->type_decl->name.as_anonymous();
ZX_ASSERT(anonymous && anonymous->provenance == Name::Provenance::kGeneratedResultUnion);
auto decl = static_cast<Union*>(identifier_type->type_decl);
ZX_ASSERT(decl->members.size() == (method->has_error ? 3 : 2));
method->maybe_result_union = decl;
ZX_ASSERT(decl->members[0].ordinal->value == Ordinal::kSuccess);
method->result_success_type_ctor = decl->members[0].type_ctor.get();
if (method->has_error) {
ZX_ASSERT(decl->members[1].ordinal->value == Ordinal::kDomainError);
method->result_domain_error_type_ctor = decl->members[1].type_ctor.get();
}
// The ConsumeStep always adds a framework error because it doesn't know if
// method is strict or flexible. We remove it here if the method is strict.
// This will never mutate the same union twice because the ResolveStep adds
// edges from result unions to protocols, ensuring they get split together.
if (method->strictness == Strictness::kStrict) {
ZX_ASSERT(decl->members.back().ordinal->value == Ordinal::kFrameworkError);
decl->members.pop_back();
}
}
// Populates protocol->all_methods by recursively visiting composed protocols.
class PopulateAllMethods {
public:
PopulateAllMethods(std::vector<Protocol::MethodWithInfo>* all_methods, Reporter* reporter)
: all_methods_(all_methods), reporter_(reporter) {}
void Visit(Protocol* protocol, const Protocol::ComposedProtocol* composed = nullptr) {
for (const auto& member : protocol->composed_protocols) {
auto target = member.reference.resolved().element();
if (target->kind != Element::Kind::kProtocol)
continue;
auto target_protocol = static_cast<Protocol*>(target);
if (auto [it, inserted] = seen_.insert(target_protocol); inserted)
Visit(target_protocol, composed ? composed : &member);
}
for (auto& method : protocol->methods) {
auto original_name = method.name.data();
auto canonical_name = Canonicalize(original_name);
if (auto result = canonical_names_.Insert(canonical_name, method.name); !result.ok()) {
auto previous_span = result.previous_occurrence();
if (original_name == previous_span.data()) {
reporter_->Fail(ErrNameCollision, method.name, Element::Kind::kProtocolMethod,
original_name, Element::Kind::kProtocolMethod, previous_span);
} else {
reporter_->Fail(ErrNameCollisionCanonical, method.name, Element::Kind::kProtocolMethod,
original_name, Element::Kind::kProtocolMethod, previous_span.data(),
previous_span, canonical_name);
}
}
if (method.ordinal != 0) {
if (auto result = ordinals_.Insert(method.ordinal, method.name); !result.ok()) {
reporter_->Fail(ErrDuplicateMethodOrdinal, method.name, result.previous_occurrence());
}
}
all_methods_->push_back(
{.method = &method, .owning_protocol = protocol, .composed = composed});
}
}
private:
std::vector<Protocol::MethodWithInfo>* all_methods_;
Reporter* reporter_;
Scope<std::string> canonical_names_;
Ordinal64Scope ordinals_;
std::set<const Protocol*> seen_;
};
void CompileStep::CompileProtocol(Protocol* protocol_declaration) {
CompileAttributeList(protocol_declaration->attributes.get());
CompileModifierList(protocol_declaration->modifiers.get(),
OutModifiers{.openness = &protocol_declaration->openness});
auto openness = protocol_declaration->openness.value();
for (auto& composed : protocol_declaration->composed_protocols) {
CompileAttributeList(composed.attributes.get());
auto target = composed.reference.resolved().element();
if (target->kind != Element::Kind::kProtocol) {
reporter()->Fail(ErrComposingNonProtocol, composed.reference.span());
continue;
}
auto composed_protocol = static_cast<Protocol*>(target);
CompileDecl(composed_protocol);
if (no_resource_count_ && !composed_protocol->attributes->Get("no_resource")) {
reporter()->Fail(ErrNoResourceForbidsCompose, composed.reference.span(),
protocol_declaration->name.decl_name(), composed.GetName());
}
if (openness < composed_protocol->openness) {
reporter()->Fail(ErrComposedProtocolTooOpen, composed.reference.span(), openness,
protocol_declaration->name, composed_protocol->openness.value(),
composed_protocol->name);
}
}
for (auto& method : protocol_declaration->methods) {
CompileAttributeList(method.attributes.get());
CompileModifierList(method.modifiers.get(), OutModifiers{.strictness = &method.strictness});
ValidateSelectorAndCalcOrdinal(protocol_declaration->name, &method);
if (auto& type_ctor = method.maybe_request) {
CompileTypeConstructor(type_ctor.get());
ValidatePayload(type_ctor.get());
}
if (auto& type_ctor = method.maybe_response) {
CompileTypeConstructor(type_ctor.get());
ValidatePayload(type_ctor.get());
}
CompileResultUnion(&method);
if (auto* type_ctor = method.result_success_type_ctor)
ValidatePayload(type_ctor);
if (auto* type_ctor = method.result_domain_error_type_ctor)
ValidateDomainError(type_ctor);
bool flexible = method.strictness.value() == Strictness::kFlexible;
bool two_way = method.kind == Protocol::Method::Kind::kTwoWay;
if (flexible && two_way && openness != Openness::kOpen) {
reporter()->Fail(ErrFlexibleTwoWayMethodRequiresOpenProtocol, method.name, openness);
} else if (flexible && !two_way && openness == Openness::kClosed) {
reporter()->Fail(ErrFlexibleOneWayMethodInClosedProtocol, method.name, method.kind);
}
}
PopulateAllMethods(&protocol_declaration->all_methods, reporter()).Visit(protocol_declaration);
}
void CompileStep::ValidateSelectorAndCalcOrdinal(const Name& protocol_name,
Protocol::Method* method) {
std::string_view method_name = method->name.data();
if (auto attr = method->attributes->Get("selector")) {
if (auto arg = attr->GetArg(AttributeArg::kDefaultAnonymousName)) {
if (auto& constant = arg->value; constant && constant->IsResolved()) {
auto value = constant->Value().AsString().value();
if (IsValidFullyQualifiedMethodIdentifier(value)) {
method->selector = value;
} else if (IsValidIdentifierComponent(value)) {
method_name = value;
} else {
reporter()->Fail(ErrInvalidSelectorValue, arg->span);
return;
}
}
}
}
// TODO(https://fxbug.dev/42157659): Remove.
if (method->selector.empty() && library()->name == "fuchsia.io") {
reporter()->Fail(ErrFuchsiaIoExplicitOrdinals, method->name);
return;
}
if (method->selector.empty()) {
method->selector.append(protocol_name.library()->name);
method->selector.push_back('/');
method->selector.append(protocol_name.decl_name());
method->selector.push_back('.');
method->selector.append(method_name);
ZX_ASSERT(IsValidFullyQualifiedMethodIdentifier(method->selector));
}
method->ordinal = method_hasher()(method->selector);
if (method->ordinal == 0)
reporter()->Fail(ErrGeneratedZeroValueOrdinal, method->name);
}
void CompileStep::ValidatePayload(const TypeConstructor* type_ctor) {
const Type* type = type_ctor->type;
if (!type)
return;
if (type->kind != Type::Kind::kIdentifier) {
reporter()->Fail(ErrInvalidMethodPayloadType, type_ctor->span, type);
return;
}
auto decl = static_cast<const IdentifierType*>(type)->type_decl;
switch (decl->kind) {
case Decl::Kind::kStruct: {
auto empty = static_cast<const Struct*>(decl)->members.empty();
auto anonymous = decl->name.as_anonymous();
auto compiler_generated =
anonymous && anonymous->provenance == Name::Provenance::kGeneratedEmptySuccessStruct;
if (empty && !compiler_generated) {
reporter()->Fail(ErrEmptyPayloadStructs, type_ctor->span);
}
for (auto& member : static_cast<const Struct*>(decl)->members) {
if (member.maybe_default_value) {
reporter()->Fail(ErrPayloadStructHasDefaultMembers, member.name);
break;
}
}
break;
}
case Decl::Kind::kTable:
case Decl::Kind::kUnion:
break;
default:
reporter()->Fail(ErrInvalidMethodPayloadLayoutClass, type_ctor->span, decl->kind);
break;
}
}
void CompileStep::ValidateDomainError(const TypeConstructor* type_ctor) {
if (experimental_flags().IsEnabled(ExperimentalFlag::kAllowArbitraryErrorTypes))
return;
const Type* type = type_ctor->type;
if (!type)
return;
const PrimitiveType* error_primitive = nullptr;
if (type->kind == Type::Kind::kPrimitive) {
error_primitive = static_cast<const PrimitiveType*>(type);
} else if (type->kind == Type::Kind::kIdentifier) {
auto identifier_type = static_cast<const IdentifierType*>(type);
if (identifier_type->type_decl->kind == Decl::Kind::kEnum) {
auto error_enum = static_cast<const Enum*>(identifier_type->type_decl);
ZX_ASSERT(error_enum->subtype_ctor->type->kind == Type::Kind::kPrimitive);
error_primitive = static_cast<const PrimitiveType*>(error_enum->subtype_ctor->type);
}
}
if (!error_primitive || (error_primitive->subtype != PrimitiveSubtype::kInt32 &&
error_primitive->subtype != PrimitiveSubtype::kUint32)) {
reporter()->Fail(ErrInvalidErrorType, type_ctor->span);
}
}
void CompileStep::CompileService(Service* service_decl) {
std::string_view associated_transport;
std::string_view first_member_with_that_transport;
CompileAttributeList(service_decl->attributes.get());
for (auto& member : service_decl->members) {
CompileAttributeList(member.attributes.get());
CompileTypeConstructor(member.type_ctor.get());
if (!member.type_ctor->type) {
continue;
}
if (member.type_ctor->type->kind != Type::Kind::kTransportSide) {
reporter()->Fail(ErrOnlyClientEndsInServices, member.name);
continue;
}
const auto transport_side_type = static_cast<const TransportSideType*>(member.type_ctor->type);
if (transport_side_type->end != TransportSide::kClient) {
reporter()->Fail(ErrOnlyClientEndsInServices, member.name);
}
if (member.type_ctor->type->IsNullable()) {
reporter()->Fail(ErrOptionalServiceMember, member.name);
}
// Enforce that all client_end members are over the same transport.
// TODO(https://fxbug.dev/42057496): We may need to revisit this restriction.
if (associated_transport.empty()) {
associated_transport = transport_side_type->protocol_transport;
first_member_with_that_transport = member.name.data();
continue;
}
if (associated_transport != transport_side_type->protocol_transport) {
reporter()->Fail(ErrMismatchedTransportInServices, member.name, member.name.data(),
transport_side_type->protocol_transport, first_member_with_that_transport,
associated_transport);
}
}
}
void CompileStep::CompileStruct(Struct* struct_declaration) {
CompileAttributeList(struct_declaration->attributes.get());
CompileModifierList(struct_declaration->modifiers.get(),
OutModifiers{.resourceness = &struct_declaration->resourceness});
for (auto& member : struct_declaration->members) {
CompileAttributeList(member.attributes.get());
CompileTypeConstructor(member.type_ctor.get());
if (!member.type_ctor->type) {
continue;
}
if (member.maybe_default_value) {
const auto* default_value_type = member.type_ctor->type;
if (!TypeCanBeConst(default_value_type)) {
reporter()->Fail(ErrInvalidStructMemberType, struct_declaration->name.span().value(),
member.name.data(), default_value_type);
} else if (!ResolveConstant(member.maybe_default_value.get(), default_value_type)) {
reporter()->Fail(ErrCouldNotResolveMemberDefault, member.name, member.name.data());
}
}
}
}
void CompileStep::CompileTable(Table* table_declaration) {
Ordinal64Scope ordinal_scope;
CompileAttributeList(table_declaration->attributes.get());
CompileModifierList(table_declaration->modifiers.get(),
OutModifiers{.strictness = &table_declaration->strictness,
.resourceness = &table_declaration->resourceness});
for (size_t i = 0; i < table_declaration->members.size(); i++) {
auto& member = table_declaration->members[i];
CompileAttributeList(member.attributes.get());
const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
if (!ordinal_result.ok()) {
reporter()->Fail(ErrDuplicateTableFieldOrdinal, member.ordinal->span(),
ordinal_result.previous_occurrence());
}
if (member.ordinal->value > kMaxTableOrdinals) {
reporter()->Fail(ErrTableOrdinalTooLarge, member.ordinal->span());
}
CompileTypeConstructor(member.type_ctor.get());
if (!member.type_ctor->type) {
continue;
}
if (member.type_ctor->type->IsNullable()) {
reporter()->Fail(ErrOptionalTableMember, member.name);
}
if (i == kMaxTableOrdinals - 1) {
if (member.type_ctor->type->kind != Type::Kind::kIdentifier) {
reporter()->Fail(ErrMaxOrdinalNotTable, member.name);
} else {
auto identifier_type = static_cast<const IdentifierType*>(member.type_ctor->type);
if (identifier_type->type_decl->kind != Decl::Kind::kTable) {
reporter()->Fail(ErrMaxOrdinalNotTable, member.name);
}
}
}
}
}
void CompileStep::CompileUnion(Union* union_declaration) {
Ordinal64Scope ordinal_scope;
CompileAttributeList(union_declaration->attributes.get());
CompileModifierList(union_declaration->modifiers.get(),
OutModifiers{.strictness = &union_declaration->strictness,
.resourceness = &union_declaration->resourceness});
auto anon = union_declaration->name.as_anonymous();
bool infer_resourceness = anon && anon->provenance == Name::Provenance::kGeneratedResultUnion;
auto resourceness = Resourceness::kValue;
for (const auto& member : union_declaration->members) {
CompileAttributeList(member.attributes.get());
const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
if (!ordinal_result.ok()) {
reporter()->Fail(ErrDuplicateUnionMemberOrdinal, member.ordinal->span(),
ordinal_result.previous_occurrence());
}
CompileTypeConstructor(member.type_ctor.get());
if (!member.type_ctor->type) {
continue;
}
if (member.type_ctor->type->IsNullable()) {
reporter()->Fail(ErrOptionalUnionMember, member.name);
}
if (infer_resourceness && member.type_ctor->type->Resourceness() == Resourceness::kResource) {
resourceness = Resourceness::kResource;
}
}
if (infer_resourceness)
union_declaration->resourceness = resourceness;
if (union_declaration->strictness.value() == Strictness::kStrict &&
union_declaration->members.empty()) {
reporter()->Fail(ErrMustHaveOneMember, union_declaration->name.span().value());
}
}
void CompileStep::CompileOverlay(Overlay* overlay_declaration) {
Ordinal64Scope ordinal_scope;
CompileAttributeList(overlay_declaration->attributes.get());
CompileModifierList(overlay_declaration->modifiers.get(),
OutModifiers{.strictness = &overlay_declaration->strictness,
.resourceness = &overlay_declaration->resourceness});
if (overlay_declaration->strictness.value() != Strictness::kStrict) {
reporter()->Fail(ErrOverlayMustBeStrict, overlay_declaration->name.span().value());
}
if (overlay_declaration->resourceness.value() == Resourceness::kResource) {
reporter()->Fail(ErrOverlayMustBeValue, overlay_declaration->name.span().value());
}
for (const auto& member : overlay_declaration->members) {
CompileAttributeList(member.attributes.get());
const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
if (!ordinal_result.ok()) {
// TODO(https://fxbug.dev/42074906): Consolidate errors for duplicate member ordinals.
reporter()->Fail(ErrDuplicateUnionMemberOrdinal, member.ordinal->span(),
ordinal_result.previous_occurrence());
}
CompileTypeConstructor(member.type_ctor.get());
if (!member.type_ctor->type) {
continue;
}
}
}
void CompileStep::CompileAlias(Alias* alias) {
CompileAttributeList(alias->attributes.get());
CompileTypeConstructor(alias->partial_type_ctor.get());
}
void CompileStep::CompileNewType(NewType* new_type) {
CompileAttributeList(new_type->attributes.get());
CompileTypeConstructor(new_type->type_ctor.get());
}
void CompileStep::CompileTypeConstructor(TypeConstructor* type_ctor, bool compile_decls) {
if (type_ctor->type != nullptr) {
return;
}
TypeResolver type_resolver(this);
type_ctor->type =
typespace()->Create(&type_resolver, type_ctor->layout, *type_ctor->parameters,
*type_ctor->constraints, compile_decls, &type_ctor->resolved_params);
}
bool CompileStep::ResolveHandleRightsConstant(Resource* resource, Constant* constant,
const HandleRightsValue** out_rights) {
auto rights_property = resource->LookupProperty("rights");
if (!rights_property) {
return false;
}
ZX_ASSERT_MSG(rights_property->type_ctor->type, "resource must already be compiled");
if (!ResolveConstant(constant, rights_property->type_ctor->type)) {
return false;
}
if (out_rights) {
*out_rights = static_cast<const HandleRightsValue*>(&constant->Value());
}
return true;
}
bool CompileStep::ResolveHandleSubtypeIdentifier(Resource* resource, Constant* constant,
HandleSubtype* out_obj_type) {
ZX_ASSERT_MSG(resource != nullptr, "must pass resource");
auto subtype_property = resource->LookupProperty("subtype");
if (!subtype_property) {
return false;
}
ZX_ASSERT_MSG(subtype_property->type_ctor->type, "resource must already be compiled");
if (!ResolveConstant(constant, subtype_property->type_ctor->type)) {
return false;
}
if (out_obj_type) {
auto constant_value = static_cast<const HandleSubtypeValue*>(&constant->Value());
*out_obj_type = static_cast<HandleSubtype>(constant_value->value);
}
return true;
}
bool CompileStep::ResolveSizeBound(Constant* size_constant, const SizeValue** out_size) {
if (size_constant->kind == Constant::Kind::kIdentifier) {
auto identifier_constant = static_cast<IdentifierConstant*>(size_constant);
auto target = identifier_constant->reference.resolved().element();
if (target->kind == Element::Kind::kBuiltin &&
static_cast<Builtin*>(target)->id == Builtin::Identity::kMax) {
size_constant->ResolveTo(std::make_unique<SizeValue>(kMaxSize),
typespace()->GetPrimitiveType(PrimitiveSubtype::kUint32));
}
}
if (!size_constant->IsResolved()) {
if (!ResolveConstant(size_constant, typespace()->GetPrimitiveType(PrimitiveSubtype::kUint32))) {
return false;
}
}
if (out_size) {
*out_size = static_cast<const SizeValue*>(&size_constant->Value());
}
return true;
}
template <typename DeclType, typename MemberType>
bool CompileStep::ValidateMembers(DeclType* decl, MemberValidator<MemberType> validator) {
ZX_ASSERT(decl != nullptr);
auto checkpoint = reporter()->Checkpoint();
Scope<MemberType> value_scope;
for (const auto& member : decl->members) {
ZX_ASSERT_MSG(member.value != nullptr, "member value is null");
if (!ResolveConstant(member.value.get(), decl->subtype_ctor->type)) {
reporter()->Fail(ErrCouldNotResolveMember, member.name, decl->kind);
continue;
}
MemberType value = member.value->Value().template AsNumeric<MemberType>().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
reporter()->Fail(ErrDuplicateMemberValue, member.name, decl->kind, member.name.data(),
previous_span.data(), previous_span);
}
auto err = validator(value, member.attributes.get(), member.name);
if (err) {
reporter()->Report(std::move(err));
}
}
return checkpoint.NoNewErrors();
}
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 CompileStep::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 AttributeList*,
SourceSpan span) -> std::unique_ptr<Diagnostic> {
if (!IsPowerOfTwo(member)) {
return Diagnostic::MakeError(ErrBitsMemberMustBePowerOfTwo, span);
}
mask |= member;
return nullptr;
};
if (!ValidateMembers<Bits, MemberType>(bits_decl, validator)) {
return false;
}
*out_mask = mask;
return true;
}
template <typename MemberType>
bool CompileStep::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");
const auto default_unknown_value = std::numeric_limits<MemberType>::max();
std::optional<MemberType> explicit_unknown_value;
for (const auto& member : enum_decl->members) {
if (!ResolveConstant(member.value.get(), enum_decl->subtype_ctor->type)) {
// ValidateMembers will resolve each member and report errors.
continue;
}
if (member.attributes->Get("unknown") != nullptr) {
if (explicit_unknown_value.has_value()) {
return reporter()->Fail(ErrUnknownAttributeOnMultipleEnumMembers, member.name);
}
explicit_unknown_value = member.value->Value().AsNumeric<MemberType>().value();
}
}
auto validator = [enum_decl, &explicit_unknown_value](
MemberType member, const AttributeList* attributes,
SourceSpan span) -> std::unique_ptr<Diagnostic> {
switch (enum_decl->strictness.value()) {
case Strictness::kStrict:
if (attributes->Get("unknown") != nullptr) {
return Diagnostic::MakeError(ErrUnknownAttributeOnStrictEnumMember, span);
}
return nullptr;
case Strictness::kFlexible:
if (member == default_unknown_value && !explicit_unknown_value.has_value()) {
return Diagnostic::MakeError(ErrFlexibleEnumMemberWithMaxValue, span,
std::to_string(default_unknown_value));
}
return nullptr;
}
};
if (!ValidateMembers<Enum, MemberType>(enum_decl, validator)) {
return false;
}
*out_unknown_value = explicit_unknown_value.value_or(default_unknown_value);
return true;
}
} // namespace fidlc