Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 390 additions & 0 deletions gcc/rust/parse/rust-parse-impl-attribute.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
// Copyright (C) 2025 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.

/* DO NOT INCLUDE ANYWHERE - this is automatically included
* by rust-parse-impl*.h
* This is also the reason why there are no include guards. */

#include "rust-parse.h"
#include "rust-parse-error.h"
#include "rust-attribute-values.h"
#include "expected.h"

namespace Rust {

// Parse a inner or outer doc comment into an doc attribute
template <typename ManagedTokenSource>
Parse::AttributeBody
Parser<ManagedTokenSource>::parse_doc_comment ()
{
const_TokenPtr token = lexer.peek_token ();
location_t locus = token->get_locus ();
AST::SimplePathSegment segment (Values::Attributes::DOC, locus);
std::vector<AST::SimplePathSegment> segments;
segments.push_back (std::move (segment));
AST::SimplePath attr_path (std::move (segments), false, locus);
AST::LiteralExpr lit_expr (token->get_str (), AST::Literal::STRING,
PrimitiveCoreType::CORETYPE_STR, {}, locus);
std::unique_ptr<AST::AttrInput> attr_input (
new AST::AttrInputLiteral (std::move (lit_expr)));
lexer.skip_token ();

return Parse::AttributeBody{std::move (attr_path), std::move (attr_input),
locus};
}

// Parse a single inner attribute.
template <typename ManagedTokenSource>
tl::expected<AST::Attribute, Parse::Error::Attribute>
Parser<ManagedTokenSource>::parse_inner_attribute ()
{
if (lexer.peek_token ()->get_id () == INNER_DOC_COMMENT)
{
auto body = parse_doc_comment ();
return AST::Attribute (std::move (body.path), std::move (body.input),
body.locus, true);
}

rust_assert (lexer.peek_token ()->get_id () == HASH);

lexer.skip_token ();

if (lexer.peek_token ()->get_id () != EXCLAM)
{
Error error (lexer.peek_token ()->get_locus (),
"expected %<!%> or %<[%> for inner attribute");
add_error (std::move (error));

return Parse::Error::Attribute::make_malformed ();
}
lexer.skip_token ();

if (!skip_token (LEFT_SQUARE))
return Parse::Error::Attribute::make_malformed ();

auto body_res = parse_attribute_body ();
if (!body_res)
return Parse::Error::Attribute::make_malformed ();
auto body = std::move (body_res.value ());

auto actual_attribute
= AST::Attribute (std::move (body.path), std::move (body.input), body.locus,
true);

if (!skip_token (RIGHT_SQUARE))
return Parse::Error::Attribute::make_malformed ();

return actual_attribute;
}

// Parse a single outer attribute.
template <typename ManagedTokenSource>
tl::expected<AST::Attribute, Parse::Error::Attribute>
Parser<ManagedTokenSource>::parse_outer_attribute ()
{
if (lexer.peek_token ()->get_id () == OUTER_DOC_COMMENT)
{
auto body = parse_doc_comment ();
return AST::Attribute (std::move (body.path), std::move (body.input),
body.locus, false);
}

if (lexer.peek_token ()->get_id () == INNER_DOC_COMMENT)
{
Error error (
lexer.peek_token ()->get_locus (), ErrorCode::E0753,
"expected outer doc comment, inner doc (%<//!%> or %</*!%>) only "
"allowed at start of item "
"and before any outer attribute or doc (%<#[%>, %<///%> or %</**%>)");
add_error (std::move (error));
lexer.skip_token ();
return Parse::Error::Attribute::make_unexpected_inner ();
}

/* OuterAttribute -> '#' '[' Attr ']' */

if (lexer.peek_token ()->get_id () != HASH)
return Parse::Error::Attribute::make_malformed ();

lexer.skip_token ();

TokenId id = lexer.peek_token ()->get_id ();
if (id != LEFT_SQUARE)
{
if (id == EXCLAM)
{
// this is inner attribute syntax, so throw error
// inner attributes were either already parsed or not allowed here.
Error error (
lexer.peek_token ()->get_locus (),
"token %<!%> found, indicating inner attribute definition. Inner "
"attributes are not possible at this location");
add_error (std::move (error));
}
return Parse::Error::Attribute::make_unexpected_inner ();
}

lexer.skip_token ();

auto body_res = parse_attribute_body ();
if (!body_res)
return Parse::Error::Attribute::make_malformed_body ();
auto body = std::move (body_res.value ());

auto actual_attribute
= AST::Attribute (std::move (body.path), std::move (body.input), body.locus,
false);

if (lexer.peek_token ()->get_id () != RIGHT_SQUARE)
return Parse::Error::Attribute::make_malformed ();

lexer.skip_token ();

return actual_attribute;
}

// Parses the body of an attribute (inner or outer).
template <typename ManagedTokenSource>
tl::expected<Parse::AttributeBody, Parse::Error::AttributeBody>
Parser<ManagedTokenSource>::parse_attribute_body ()
{
location_t locus = lexer.peek_token ()->get_locus ();

auto attr_path = parse_simple_path ();
// ensure path is valid to parse attribute input
if (!attr_path)
{
Error error (lexer.peek_token ()->get_locus (),
"empty simple path in attribute");
add_error (std::move (error));

// Skip past potential further info in attribute (i.e. attr_input)
skip_after_end_attribute ();
return Parse::Error::AttributeBody::make_invalid_path ();
}

auto attr_input = parse_attr_input ();
// AttrInput is allowed to be null, so no checks here
if (attr_input)
return Parse::AttributeBody{std::move (attr_path.value ()),
std::move (attr_input.value ()), locus};
else if (attr_input.error ().kind == Parse::Error::AttrInput::Kind::MISSING)
return Parse::AttributeBody{std::move (attr_path.value ()), nullptr, locus};
else
return Parse::Error::AttributeBody::make_invalid_attrinput ();
}

// Parse a contiguous block of inner attributes.
template <typename ManagedTokenSource>
AST::AttrVec
Parser<ManagedTokenSource>::parse_inner_attributes ()
{
AST::AttrVec inner_attributes;

auto has_valid_inner_attribute_prefix = [&] () {
auto id = lexer.peek_token ()->get_id ();
/* Outer attribute `#[` is not allowed, only accepts `#!` */
return (id == HASH && lexer.peek_token (1)->get_id () == EXCLAM)
|| id == INNER_DOC_COMMENT;
};

while (has_valid_inner_attribute_prefix ())
{
auto inner_attr = parse_inner_attribute ();

/* Ensure only valid inner attributes are added to the inner_attributes
* list */
if (inner_attr)
{
inner_attributes.push_back (std::move (inner_attr.value ()));
}
else
{
/* If no more valid inner attributes, break out of loop (only
* contiguous inner attributes parsed). */
break;
}
}

inner_attributes.shrink_to_fit ();
return inner_attributes;
}

// Parses a contiguous block of outer attributes.
template <typename ManagedTokenSource>
AST::AttrVec
Parser<ManagedTokenSource>::parse_outer_attributes ()
{
AST::AttrVec outer_attributes;

auto has_valid_attribute_prefix = [&] () {
auto id = lexer.peek_token ()->get_id ();
/* We allow inner attributes `#!` and catch the error later */
return id == HASH || id == OUTER_DOC_COMMENT || id == INNER_DOC_COMMENT;
};

while (has_valid_attribute_prefix ()) /* For error handling. */
{
auto outer_attr = parse_outer_attribute ();

/* Ensure only valid outer attributes are added to the outer_attributes
* list */
if (outer_attr)
{
outer_attributes.push_back (std::move (outer_attr.value ()));
}
else
{
/* If no more valid outer attributes, break out of loop (only
* contiguous outer attributes parsed). */
break;
}
}

outer_attributes.shrink_to_fit ();
return outer_attributes;

/* TODO: this shares basically all code with parse_inner_attributes except
* function call - find way of making it more modular? function pointer? */
}

// Parses an AttrInput AST node (polymorphic, as AttrInput is abstract)
template <typename ManagedTokenSource>
tl::expected<std::unique_ptr<AST::AttrInput>, Parse::Error::AttrInput>
Parser<ManagedTokenSource>::parse_attr_input ()
{
const_TokenPtr t = lexer.peek_token ();
switch (t->get_id ())
{
case LEFT_PAREN:
case LEFT_SQUARE:
case LEFT_CURLY:
{
auto dtoken_tree = parse_delim_token_tree ();
if (!dtoken_tree)
return Parse::Error::AttrInput::make_bad_token_tree ();

// must be a delimited token tree, so parse that
std::unique_ptr<AST::AttrInput> input_tree (
new AST::DelimTokenTree (dtoken_tree.value ()));

return tl::expected<std::unique_ptr<AST::AttrInput>,
Parse::Error::AttrInput>{std::move (input_tree)};
}
case EQUAL:
{
// = LiteralExpr
lexer.skip_token ();

t = lexer.peek_token ();

// attempt to parse macro
// TODO: macros may/may not be allowed in attributes
// this is needed for "#[doc = include_str!(...)]"
if (Parse::Utils::is_simple_path_segment (t->get_id ()))
{
std::unique_ptr<AST::MacroInvocation> invoke
= parse_macro_invocation ({});

if (!invoke)
return Parse::Error::AttrInput::make_bad_macro_invocation ();

return std::unique_ptr<AST::AttrInput> (
new AST::AttrInputMacro (std::move (invoke)));
}

/* Ensure token is a "literal expression" (literally only a literal
* token of any type) */
if (!t->is_literal ())
{
Error error (
t->get_locus (),
"unknown token %qs in attribute body - literal expected",
t->get_token_description ());
add_error (std::move (error));

skip_after_end_attribute ();
return Parse::Error::AttrInput::make_malformed ();
}

AST::Literal::LitType lit_type = AST::Literal::STRING;
// Crappy mapping of token type to literal type
switch (t->get_id ())
{
case INT_LITERAL:
lit_type = AST::Literal::INT;
break;
case FLOAT_LITERAL:
lit_type = AST::Literal::FLOAT;
break;
case CHAR_LITERAL:
lit_type = AST::Literal::CHAR;
break;
case BYTE_CHAR_LITERAL:
lit_type = AST::Literal::BYTE;
break;
case BYTE_STRING_LITERAL:
lit_type = AST::Literal::BYTE_STRING;
break;
case RAW_STRING_LITERAL:
lit_type = AST::Literal::RAW_STRING;
break;
case STRING_LITERAL:
default:
lit_type = AST::Literal::STRING;
break; // TODO: raw string? don't eliminate it from lexer?
}

// create actual LiteralExpr
AST::LiteralExpr lit_expr (t->get_str (), lit_type, t->get_type_hint (),
{}, t->get_locus ());
lexer.skip_token ();

std::unique_ptr<AST::AttrInput> attr_input_lit (
new AST::AttrInputLiteral (std::move (lit_expr)));

// do checks or whatever? none required, really

// FIXME: shouldn't a skip token be required here?

return tl::expected<std::unique_ptr<AST::AttrInput>,
Parse::Error::AttrInput>{
std::move (attr_input_lit)};
}
break;
case RIGHT_PAREN:
case RIGHT_SQUARE:
case RIGHT_CURLY:
case END_OF_FILE:
// means AttrInput is missing, which is allowed
return Parse::Error::AttrInput::make_missing_attrinput ();
default:
add_error (
Error (t->get_locus (),
"unknown token %qs in attribute body - attribute input or "
"none expected",
t->get_token_description ()));

skip_after_end_attribute ();
return Parse::Error::AttrInput::make_malformed ();
}
rust_unreachable ();
// TODO: find out how to stop gcc error on "no return value"
}

} // namespace Rust
Loading