Skip to content

Commit

Permalink
Support/fix public type aliases to units.
Browse files Browse the repository at this point in the history
An alias like `public type Unit1 = Unit2` used to lead to C++-side
compiler errors, which this fixes. We also fully support this now by
making both `Unit1` and `Unit2` available for parsing to host
applications. Internally, the `Unit1` parser is just a small facade
pointing to the parsing functions for `Unit2`.

Closes #1493.
  • Loading branch information
rsmmr committed Mar 27, 2024
1 parent 18a7377 commit 6bd760c
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 236 deletions.
3 changes: 3 additions & 0 deletions hilti/toolchain/include/ast/type.h
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ class QualifiedType : public Node {
/** Returns the type's "sideness". */
auto side() const { return _side; }

/** Shortcut to try-cast to `type::Name`. */
type::Name* alias() const;

/**
* Sets the constness of the type.
*
Expand Down
10 changes: 6 additions & 4 deletions hilti/toolchain/src/ast/ast-context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,12 @@ struct VisitorCheckIDs : hilti::visitor::PreOrder {
logger().internalError(util::fmt("declaration without canonical ID found: %s", n->id()));
}

if ( ! n->fullyQualifiedID() ) {
hilti::detail::ast_dumper::dump(std::cerr, n->parent()->as<Node>());
logger().internalError(util::fmt("declaration without fully qualified ID found: %s", n->id()));
}
/*
* if ( ! n->fullyQualifiedID() ) {
* hilti::detail::ast_dumper::dump(std::cerr, n->parent()->as<Node>());
* logger().internalError(util::fmt("declaration without fully qualified ID found: %s", n->id()));
* }
*/
}
};

Expand Down
2 changes: 2 additions & 0 deletions hilti/toolchain/src/ast/type.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ bool QualifiedType::isResolved(node::CycleDetector* cd) const {

bool QualifiedType::isAuto() const { return type()->isA<type::Auto>(); }

type::Name* QualifiedType::alias() const { return _type()->tryAs<type::Name>(); }

hilti::node::Properties QualifiedType::properties() const {
auto side = (_side == Side::LHS ? "lhs" : "rhs");
auto constness = (_constness == Constness::Const ? "true" : "false");
Expand Down
2 changes: 1 addition & 1 deletion hilti/toolchain/src/compiler/resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ struct VisitorPass2 : visitor::MutatingPostOrder {
setFqID(n, m->scopeID() + n->id()); // global scope
}

if ( ! n->declarationIndex() ) {
if ( ! n->declarationIndex() && ! n->type()->alias() ) {
auto index = context()->register_(n);
recordChange(n->type()->type(), util::fmt("set type's declaration to %s", index));
}
Expand Down
4 changes: 4 additions & 0 deletions spicy/toolchain/include/compiler/detail/codegen/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class CodeGen {
type::Unit* unit,
bool declare_only = true); // Compiles a Unit type into its HILTI struct representation.

/** For a public unit type alias, creates the runtime code to register the parser under the alias name. */
void compilePublicUnitAlias(hilti::declaration::Module* module, const ID& alias_id, type::Unit* unit);

hilti::declaration::Function* compileHook(const type::Unit& unit, const ID& id, type::unit::item::Field* field,
bool foreach, bool debug, hilti::type::function::Parameters params,
hilti::Statement* body, Expression* priority, const hilti::Meta& meta);
Expand Down Expand Up @@ -93,6 +96,7 @@ class CodeGen {
private:
bool _compileModule(hilti::declaration::Module* module, int pass);
void _updateDeclarations(visitor::MutatingPostOrder* v, hilti::declaration::Module* module);
void _compileParserRegistration(const ID& public_id, const ID& struct_id, type::Unit* unit);

Builder* _builder;
codegen::GrammarBuilder _gb;
Expand Down
15 changes: 12 additions & 3 deletions spicy/toolchain/src/compiler/codegen/codegen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,24 @@ struct VisitorPass1 : public visitor::MutatingPostOrder {

CodeGen* cg;
hilti::declaration::Module* module = nullptr;
ID module_id = ID("<no module>");

void operator()(hilti::declaration::Type* n) final {
// Replace unit type with compiled struct type.
auto u = n->type()->type()->tryAs<type::Unit>();
if ( ! u )
return;

if ( n->type()->alias() ) {
// Special case: For an alias, if it's public, we just need to
// register the unit under the alias name as well.
if ( n->linkage() == hilti::declaration::Linkage::Public )
cg->compilePublicUnitAlias(module, n->fullyQualifiedID(), u);

n->type()->type(false)->as<hilti::type::Name>()->clearResolvedTypeIndex(); // will rebind to new struct
return;
}

// Replace unit type with compiled struct type.

if ( auto r = cg->grammarBuilder()->run(u); ! r ) {
hilti::logger().error(r.error().description(), n->location());
return;
Expand Down Expand Up @@ -92,7 +102,6 @@ struct VisitorPass2 : public visitor::MutatingPostOrder {

CodeGen* cg;
hilti::declaration::Module* module = nullptr;
ID module_id = ID("<no module>");

Expression* argument(Expression* args, unsigned int i, std::optional<Expression*> def = {}) {
auto ctor = args->as<hilti::expression::Ctor>()->ctor();
Expand Down
189 changes: 106 additions & 83 deletions spicy/toolchain/src/compiler/codegen/unit-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -336,96 +336,119 @@ UnqualifiedType* CodeGen::compileUnit(type::Unit* unit, bool declare_only) {
auto s = builder()->typeStruct(unit->parameters(), std::move(v.fields));
_pb.addParserMethods(s, unit, declare_only);

if ( ! declare_only ) {
auto description = unit->propertyItem("%description");
auto mime_types =
hilti::node::transform(unit->propertyItems("%mime-type"), [](const auto& p) { return p->expression(); });
auto ports = hilti::node::transform(unit->propertyItems("%port"), [this](auto p) -> Expression* {
auto dir = ID("spicy_rt::Direction::Both");

if ( const auto& attrs = p->attributes() ) {
auto orig = attrs->find("&originator");
auto resp = attrs->find("&responder");

if ( orig && ! resp )
dir = ID("spicy_rt::Direction::Originator");

else if ( resp && ! orig )
dir = ID("spicy_rt::Direction::Responder");
}
if ( ! declare_only )
_compileParserRegistration(unit->typeID(), unit->typeID(), unit);

return builder()->tuple({p->expression(), builder()->expressionName(dir)});
});
return s;
}

Expression* parse1 = builder()->null();
Expression* parse3 = builder()->null();
void CodeGen::compilePublicUnitAlias(hilti::declaration::Module* module, const ID& alias_id, type::Unit* unit) {
// We create a mini parser struct here that just contains the `__parser` field for runtime registration.
auto attrs = builder()->attributeSet(
{builder()->attribute("&static"), builder()->attribute("&internal"),
builder()->attribute("&needed-by-feature", builder()->stringLiteral("supports_filters"))});

auto parser_field = builder()->declarationField(ID("__parser"),
builder()->qualifiedType(builder()->typeName("spicy_rt::Parser"),
hilti::Constness::Mutable),
attrs);

auto struct_id = ID(alias_id.namespace_(), "__parser_" + alias_id.local().str());
auto struct_decl = builder()->declarationType(struct_id.local(),
builder()->qualifiedType(builder()->typeStruct({parser_field}),
hilti::Constness::Mutable),
hilti::declaration::Linkage::Public, unit->meta());
module->add(context(), struct_decl);

_compileParserRegistration(alias_id, struct_id, unit);
}

// Only create `parse1` and `parse3` if the unit can be default constructed.
const auto& parameters = unit->parameters();
if ( std::all_of(parameters.begin(), parameters.end(), [](const auto& p) { return p->default_(); }) ) {
parse1 = _pb.parseMethodExternalOverload1(*unit);
parse3 = _pb.parseMethodExternalOverload3(*unit);
}
void CodeGen::_compileParserRegistration(const ID& public_id, const ID& struct_id, type::Unit* unit) {
auto description = unit->propertyItem("%description");
auto mime_types =
hilti::node::transform(unit->propertyItems("%mime-type"), [](const auto& p) { return p->expression(); });
auto ports = hilti::node::transform(unit->propertyItems("%port"), [this](auto p) -> Expression* {
auto dir = ID("spicy_rt::Direction::Both");

Expression* context_new = builder()->null();
if ( const auto& attrs = p->attributes() ) {
auto orig = attrs->find("&originator");
auto resp = attrs->find("&responder");

if ( unit->contextType() )
context_new = _pb.contextNewFunction(*unit);
if ( orig && ! resp )
dir = ID("spicy_rt::Direction::Originator");

_pb.pushBuilder();
else if ( resp && ! orig )
dir = ID("spicy_rt::Direction::Responder");
}

return builder()->tuple({p->expression(), builder()->expressionName(dir)});
});

// Register the parser if the `is_filter` or `supports_sinks` features are
// active; `public` units we always register (by passing an empty list of
// features to the feature guard).
const auto dependentFeatureFlags = unit->isPublic() ?
std::vector<std::string_view>{} :
std::vector<std::string_view>({"is_filter", "supports_sinks"});

_pb.guardFeatureCode(unit, dependentFeatureFlags, [&]() {
auto ty_mime_types = builder()->typeVector(
builder()->qualifiedType(builder()->typeName("spicy_rt::MIMEType"), hilti::Constness::Const));
auto ty_ports = builder()->typeVector(
builder()->qualifiedType(builder()->typeName("spicy_rt::ParserPort"), hilti::Constness::Const));

auto parser = builder()->struct_(
{builder()->ctorStructField(ID("name"), builder()->stringLiteral(unit->typeID().str())),
builder()->ctorStructField(ID("is_public"), builder()->bool_(unit->isPublic())),
builder()->ctorStructField(ID("parse1"), parse1),
builder()->ctorStructField(ID("parse2"), _pb.parseMethodExternalOverload2(*unit)),
builder()->ctorStructField(ID("parse3"), parse3),
builder()->ctorStructField(ID("context_new"), context_new),
builder()->ctorStructField(ID("type_info"), builder()->typeinfo(builder()->id(unit->typeID()))),
// We emit different string types for generated and user-provided strings. The distinction
// is whether they have a location, so set a dummy location so both branches behave
// identically.
builder()->ctorStructField(ID("description"),
(description ? description->expression() : builder()->stringMutable(""))),
builder()->ctorStructField(ID("mime_types"),
builder()->vector(builder()->qualifiedType(ty_mime_types,
hilti::Constness::Const),
std::move(mime_types))),
builder()->ctorStructField(ID("ports"),
builder()->vector(builder()->qualifiedType(ty_ports,
hilti::Constness::Const),
std::move(ports)))},
unit->meta());

_pb.builder()->addAssign(builder()->id(ID(unit->typeID(), "__parser")), parser);

_pb.builder()->addExpression(
builder()->call("spicy_rt::registerParser",
{builder()->id(ID(unit->typeID(), "__parser")), builder()->scope(),
builder()->strongReference(builder()->qualifiedType(unit, hilti::Constness::Const))}));
});

auto block = _pb.popBuilder()->block();

auto register_unit =
builder()->function(ID(fmt("__register_%s_%s", hiltiModule()->uid(), unit->typeID().local())),
builder()->qualifiedType(builder()->typeVoid(), hilti::Constness::Const), {}, block,
hilti::type::function::Flavor::Standard, hilti::declaration::Linkage::Init);
addDeclaration(register_unit);
Expression* parse1 = builder()->null();
Expression* parse3 = builder()->null();

// Only create `parse1` and `parse3` if the unit can be default constructed.
const auto& parameters = unit->parameters();
if ( std::all_of(parameters.begin(), parameters.end(), [](const auto& p) { return p->default_(); }) ) {
parse1 = _pb.parseMethodExternalOverload1(*unit);
parse3 = _pb.parseMethodExternalOverload3(*unit);
}

return s;
Expression* context_new = builder()->null();

if ( unit->contextType() )
context_new = _pb.contextNewFunction(*unit);

_pb.pushBuilder();

// Register the parser if the `is_filter` or `supports_sinks` features are
// active; `public` units we always register (by passing an empty list of
// features to the feature guard).
const auto dependentFeatureFlags = unit->isPublic() ?
std::vector<std::string_view>{} :
std::vector<std::string_view>({"is_filter", "supports_sinks"});

_pb.guardFeatureCode(unit, dependentFeatureFlags, [&]() {
auto ty_mime_types = builder()->typeVector(
builder()->qualifiedType(builder()->typeName("spicy_rt::MIMEType"), hilti::Constness::Const));
auto ty_ports = builder()->typeVector(
builder()->qualifiedType(builder()->typeName("spicy_rt::ParserPort"), hilti::Constness::Const));

auto parser = builder()->struct_(
{builder()->ctorStructField(ID("name"), builder()->stringLiteral(public_id.str())),
builder()->ctorStructField(ID("is_public"), builder()->bool_(unit->isPublic())),
builder()->ctorStructField(ID("parse1"), parse1),
builder()->ctorStructField(ID("parse2"), _pb.parseMethodExternalOverload2(*unit)),
builder()->ctorStructField(ID("parse3"), parse3),
builder()->ctorStructField(ID("context_new"), context_new),
builder()->ctorStructField(ID("type_info"), builder()->typeinfo(builder()->id(unit->typeID()))),
// We emit different string types for generated and user-provided strings. The distinction
// is whether they have a location, so set a dummy location so both branches behave
// identically.
builder()->ctorStructField(ID("description"),
(description ? description->expression() : builder()->stringMutable(""))),
builder()->ctorStructField(ID("mime_types"),
builder()->vector(builder()->qualifiedType(ty_mime_types,
hilti::Constness::Const),
std::move(mime_types))),
builder()->ctorStructField(ID("ports"),
builder()->vector(builder()->qualifiedType(ty_ports, hilti::Constness::Const),
std::move(ports)))},
unit->meta());

_pb.builder()->addAssign(builder()->id(ID(struct_id, "__parser")), parser);

_pb.builder()->addExpression(
builder()->call("spicy_rt::registerParser",
{builder()->id(ID(struct_id, "__parser")), builder()->scope(),
builder()->strongReference(builder()->qualifiedType(unit, hilti::Constness::Const))}));
});

auto block = _pb.popBuilder()->block();

auto register_unit =
builder()->function(ID(fmt("__register_%s_%s", hiltiModule()->uid(), public_id.local())),
builder()->qualifiedType(builder()->typeVoid(), hilti::Constness::Const), {}, block,
hilti::type::function::Flavor::Standard, hilti::declaration::Linkage::Init);
addDeclaration(register_unit);
}
2 changes: 1 addition & 1 deletion spicy/toolchain/src/compiler/resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ struct VisitorPass2 : visitor::MutatingPostOrder {
}

void operator()(hilti::declaration::Type* n) final {
if ( auto u = n->type()->type()->tryAs<type::Unit>() ) {
if ( auto u = n->type()->type()->tryAs<type::Unit>(); u && ! n->type()->alias() ) {
if ( n->linkage() == hilti::declaration::Linkage::Public && ! u->isPublic() ) {
recordChange(n, "set public");
u->setPublic(true);
Expand Down
7 changes: 7 additions & 0 deletions spicy/toolchain/src/compiler/validator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ struct VisitorPost : visitor::PreOrder, hilti::validator::VisitorMixIn {
error(fmt("unknown property '%s'", n->id().str()), n);
}

void operator()(hilti::declaration::Type* n) final {
if ( n->linkage() == hilti::declaration::Linkage::Public && n->type()->alias() ) {
if ( n->type()->alias()->resolvedDeclaration()->linkage() != hilti::declaration::Linkage::Public )
error("public unit alias cannot refer to a non-public type", n);
}
}

void operator()(spicy::type::unit::item::Property* n) final {
if ( n->id().str() == "%random-access" ) {
if ( n->expression() )
Expand Down
Loading

0 comments on commit 6bd760c

Please # to comment.