Skip to content

Commit

Permalink
derive(PartialEq): Add partial implementation
Browse files Browse the repository at this point in the history
We are still missing some deriving for enums, as part of our codegen and nameres for rebinding struct
field patterns is missing.

gcc/rust/ChangeLog:

	* expand/rust-derive-partial-eq.cc: New file.
	* expand/rust-derive-partial-eq.h: New file.
	* expand/rust-derive.cc (DeriveVisitor::derive): Call them.
	* Make-lang.in: Compile them.

gcc/testsuite/ChangeLog:

	* rust/compile/derive-eq-invalid.rs: Mark PartialEq def as a lang item.
	* rust/compile/derive-partialeq1.rs: New test.
	* rust/execute/torture/derive-partialeq1.rs: New test.
	* rust/compile/nr2/exclude: Exclude all of them.
  • Loading branch information
CohenArthur committed Feb 4, 2025
1 parent ddd37fb commit 5b6bdff
Show file tree
Hide file tree
Showing 8 changed files with 520 additions and 5 deletions.
1 change: 1 addition & 0 deletions gcc/rust/Make-lang.in
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ GRS_OBJS = \
rust/rust-derive-copy.o \
rust/rust-derive-debug.o \
rust/rust-derive-default.o \
rust/rust-derive-partial-eq.o \
rust/rust-derive-eq.o \
rust/rust-proc-macro.o \
rust/rust-macro-invoc-lexer.o \
Expand Down
308 changes: 308 additions & 0 deletions gcc/rust/expand/rust-derive-partial-eq.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// Copyright (C) 2020-2024 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/>.

#include "rust-derive-partial-eq.h"
#include "rust-ast.h"
#include "rust-expr.h"
#include "rust-item.h"
#include "rust-operators.h"
#include "rust-path.h"
#include "rust-pattern.h"
#include "rust-system.h"

namespace Rust {
namespace AST {
DerivePartialEq::DerivePartialEq (location_t loc)
: DeriveVisitor (loc), expanded (nullptr)
{}

std::unique_ptr<AST::Item>
DerivePartialEq::go (Item &item)
{
item.accept_vis (*this);

rust_assert (expanded);

return std::move (expanded);
}

std::unique_ptr<Item>
DerivePartialEq::partial_eq_impl (
std::unique_ptr<AssociatedItem> &&eq_fn, std::string name,
const std::vector<std::unique_ptr<GenericParam>> &type_generics)
{
auto eq = builder.type_path (LangItem::Kind::EQ);

auto trait_items = vec (std::move (eq_fn));

auto generics
= setup_impl_generics (name, type_generics, builder.trait_bound (eq));

return builder.trait_impl (eq, std::move (generics.self_type),
std::move (trait_items),
std::move (generics.impl));
}

std::unique_ptr<AssociatedItem>
DerivePartialEq::eq_fn (std::unique_ptr<Expr> &&cmp_expression,
std::string type_name)
{
auto block = builder.block (tl::nullopt, std::move (cmp_expression));

auto self_type
= std::unique_ptr<TypeNoBounds> (new TypePath (builder.type_path ("Self")));

auto params
= vec (builder.self_ref_param (),
builder.function_param (builder.identifier_pattern ("other"),
builder.reference_type (
std::move (self_type))));

return builder.function ("eq", std::move (params),
builder.single_type_path ("bool"),
std::move (block));
}

DerivePartialEq::SelfOther
DerivePartialEq::tuple_indexes (int idx)
{
return SelfOther{
builder.tuple_idx ("self", idx),
builder.tuple_idx ("other", idx),
};
}

DerivePartialEq::SelfOther
DerivePartialEq::field_acccesses (const std::string &field_name)
{
return SelfOther{
builder.field_access (builder.identifier ("self"), field_name),
builder.field_access (builder.identifier ("other"), field_name),
};
}

std::unique_ptr<Expr>
DerivePartialEq::build_eq_expression (
std::vector<SelfOther> &&field_expressions)
{
// for unit structs or empty tuples, this is always true
if (field_expressions.empty ())
return builder.literal_bool (true);

auto cmp_expression
= builder.comparison_expr (std::move (field_expressions.at (0).self_expr),
std::move (field_expressions.at (0).other_expr),
ComparisonOperator::EQUAL);

for (size_t i = 1; i < field_expressions.size (); i++)
{
auto tmp = builder.comparison_expr (
std::move (field_expressions.at (i).self_expr),
std::move (field_expressions.at (i).other_expr),
ComparisonOperator::EQUAL);

cmp_expression
= builder.boolean_operation (std::move (cmp_expression),
std::move (tmp),
LazyBooleanOperator::LOGICAL_AND);
}

return cmp_expression;
}

void
DerivePartialEq::visit_tuple (TupleStruct &item)
{
auto type_name = item.get_struct_name ().as_string ();
auto fields = std::vector<SelfOther> ();

for (size_t idx = 0; idx < item.get_fields ().size (); idx++)
fields.emplace_back (tuple_indexes (idx));

auto fn = eq_fn (build_eq_expression (std::move (fields)), type_name);

expanded
= partial_eq_impl (std::move (fn), type_name, item.get_generic_params ());
}

void
DerivePartialEq::visit_struct (StructStruct &item)
{
auto type_name = item.get_struct_name ().as_string ();
auto fields = std::vector<SelfOther> ();

for (auto &field : item.get_fields ())
fields.emplace_back (
field_acccesses (field.get_field_name ().as_string ()));

auto fn = eq_fn (build_eq_expression (std::move (fields)), type_name);

expanded
= partial_eq_impl (std::move (fn), type_name, item.get_generic_params ());
}

MatchCase
DerivePartialEq::match_enum_identifier (
PathInExpression variant_path, const std::unique_ptr<EnumItem> &variant)
{
auto inner_ref_patterns
= vec (builder.ref_pattern (
std::unique_ptr<Pattern> (new PathInExpression (variant_path))),
builder.ref_pattern (
std::unique_ptr<Pattern> (new PathInExpression (variant_path))));

auto tuple_items = std::make_unique<TuplePatternItemsMultiple> (
std::move (inner_ref_patterns));

auto pattern = std::make_unique<TuplePattern> (std::move (tuple_items), loc);

return builder.match_case (std::move (pattern), builder.literal_bool (true));
}

MatchCase
DerivePartialEq::match_enum_tuple (PathInExpression variant_path,
const EnumItemTuple &variant)
{
auto self_patterns = std::vector<std::unique_ptr<Pattern>> ();
auto other_patterns = std::vector<std::unique_ptr<Pattern>> ();

auto self_other_exprs = std::vector<SelfOther> ();

for (size_t i = 0; i < variant.get_tuple_fields ().size (); i++)
{
// The patterns we're creating for each field are `self_<i>` and
// `other_<i>` where `i` is the index of the field. It doesn't actually
// matter what we use, as long as it's ordered, unique, and that we can
// reuse it in the match case's return expression to check that they are
// equal.

auto self_pattern_str = "__self_" + std::to_string (i);
auto other_pattern_str = "__other_" + std::to_string (i);

rust_debug ("]ARTHUR[ %s", self_pattern_str.c_str ());

self_patterns.emplace_back (
builder.identifier_pattern (self_pattern_str));
other_patterns.emplace_back (
builder.identifier_pattern (other_pattern_str));

self_other_exprs.emplace_back (SelfOther{
builder.identifier (self_pattern_str),
builder.identifier (other_pattern_str),
});
}

auto self_pattern_items = std::unique_ptr<TupleStructItems> (
new TupleStructItemsNoRange (std::move (self_patterns)));
auto other_pattern_items = std::unique_ptr<TupleStructItems> (
new TupleStructItemsNoRange (std::move (other_patterns)));

auto self_pattern = std::unique_ptr<Pattern> (
new ReferencePattern (std::unique_ptr<Pattern> (new TupleStructPattern (
variant_path, std::move (self_pattern_items))),
false, false, loc));
auto other_pattern = std::unique_ptr<Pattern> (
new ReferencePattern (std::unique_ptr<Pattern> (new TupleStructPattern (
variant_path, std::move (other_pattern_items))),
false, false, loc));

auto tuple_items = std::make_unique<TuplePatternItemsMultiple> (
vec (std::move (self_pattern), std::move (other_pattern)));

auto pattern = std::make_unique<TuplePattern> (std::move (tuple_items), loc);

auto expr = build_eq_expression (std::move (self_other_exprs));

return builder.match_case (std::move (pattern), std::move (expr));
}

MatchCase
DerivePartialEq::match_enum_struct (PathInExpression variant_path,
const EnumItemStruct &variant)
{
// NOTE: We currently do not support compiling struct patterns where an
// identifier is assigned a new pattern, e.g. Bloop { f0: x }
// This is what we should be using to compile PartialEq for enum struct
// variants, as we need to be comparing the field of each instance meaning we
// need to give two different names to two different instances of the same
// field. We cannot just use the field's name like we do when deriving
// `Clone`.

rust_unreachable ();
}

void
DerivePartialEq::visit_enum (Enum &item)
{
auto cases = std::vector<MatchCase> ();

for (auto &variant : item.get_variants ())
{
auto variant_path
= builder.variant_path (item.get_identifier ().as_string (),
variant->get_identifier ().as_string ());

switch (variant->get_enum_item_kind ())
{
case EnumItem::Kind::Identifier:
case EnumItem::Kind::Discriminant:
cases.emplace_back (match_enum_identifier (variant_path, variant));
break;
case EnumItem::Kind::Tuple:
cases.emplace_back (
match_enum_tuple (variant_path,
static_cast<EnumItemTuple &> (*variant)));
break;
case EnumItem::Kind::Struct:
rust_sorry_at (
item.get_locus (),
"cannot derive(PartialEq) for enum struct variants yet");
break;
}
}

// NOTE: Mention using discriminant_value and skipping that last case, and
// instead skipping all identifiers/discriminant enum items and returning
// `true` in the wildcard case

// In case the two instances of `Self` don't have the same discriminant,
// automatically return false.
cases.emplace_back (
builder.match_case (builder.wildcard (), builder.literal_bool (false)));

auto match
= builder.match (builder.tuple (vec (builder.identifier ("self"),
builder.identifier ("other"))),
std::move (cases));

auto fn = eq_fn (std::move (match), item.get_identifier ().as_string ());

expanded
= partial_eq_impl (std::move (fn), item.get_identifier ().as_string (),
item.get_generic_params ());
}

void
DerivePartialEq::visit_union (Union &item)
{
rust_error_at (item.get_locus (),
"derive(PartialEq) cannot be used on unions");
}

} // namespace AST
} // namespace Rust
81 changes: 81 additions & 0 deletions gcc/rust/expand/rust-derive-partial-eq.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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/>.

#ifndef RUST_DERIVE_PARTIAL_EQ_H
#define RUST_DERIVE_PARTIAL_EQ_H

#include "rust-derive.h"
#include "rust-path.h"

namespace Rust {
namespace AST {

class DerivePartialEq : DeriveVisitor
{
public:
DerivePartialEq (location_t loc);

std::unique_ptr<AST::Item> go (Item &item);

private:
std::unique_ptr<Item> expanded;

std::unique_ptr<Item> partial_eq_impl (
std::unique_ptr<AssociatedItem> &&eq_fn, std::string name,
const std::vector<std::unique_ptr<GenericParam>> &type_generics);

std::unique_ptr<AssociatedItem> eq_fn (std::unique_ptr<Expr> &&cmp_expression,
std::string type_name);

/**
* A pair of two expressions from each instance being compared. E.g. this
* could be `self.0` and `other.0`, or `self.field` and `other.field`
*/
struct SelfOther
{
std::unique_ptr<Expr> self_expr;
std::unique_ptr<Expr> other_expr;
};

SelfOther tuple_indexes (int idx);
SelfOther field_acccesses (const std::string &field_name);

/**
* Build a suite of equality arithmetic expressions chained together by a
* boolean AND operator
*/
std::unique_ptr<Expr>
build_eq_expression (std::vector<SelfOther> &&field_expressions);

MatchCase match_enum_identifier (PathInExpression variant_path,
const std::unique_ptr<EnumItem> &variant);
MatchCase match_enum_tuple (PathInExpression variant_path,
const EnumItemTuple &variant);
MatchCase match_enum_struct (PathInExpression variant_path,
const EnumItemStruct &variant);

virtual void visit_struct (StructStruct &item);
virtual void visit_tuple (TupleStruct &item);
virtual void visit_enum (Enum &item);
virtual void visit_union (Union &item);
};

} // namespace AST
} // namespace Rust

#endif // ! RUST_DERIVE_PARTIAL_EQ_H
Loading

0 comments on commit 5b6bdff

Please sign in to comment.