https://gcc.gnu.org/g:051e54e5fcf0b0b852fef0c7f0e53e62b19a4176
commit r15-8823-g051e54e5fcf0b0b852fef0c7f0e53e62b19a4176 Author: Arthur Cohen <arthur.co...@embecosm.com> Date: Thu Feb 20 17:01:28 2025 +0000 gccrs: derive(Hash): Add implementation. gcc/rust/ChangeLog: * Make-lang.in: Compile it. * expand/rust-derive.cc (DeriveVisitor::derive): Call it. * expand/rust-derive-hash.cc: New file. * expand/rust-derive-hash.h: New file. gcc/testsuite/ChangeLog: * rust/compile/derive-hash1.rs: New test. * rust/compile/nr2/exclude: Add testcase to exclusion list. Diff: --- gcc/rust/Make-lang.in | 1 + gcc/rust/expand/rust-derive-hash.cc | 293 +++++++++++++++++++++++++++++ gcc/rust/expand/rust-derive-hash.h | 61 ++++++ gcc/rust/expand/rust-derive.cc | 22 ++- gcc/testsuite/rust/compile/derive-hash1.rs | 91 +++++++++ gcc/testsuite/rust/compile/nr2/exclude | 1 + 6 files changed, 460 insertions(+), 9 deletions(-) diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in index 5e13c6cfc3ff..3e44b12c3d16 100644 --- a/gcc/rust/Make-lang.in +++ b/gcc/rust/Make-lang.in @@ -100,6 +100,7 @@ GRS_OBJS = \ rust/rust-derive-default.o \ rust/rust-derive-partial-eq.o \ rust/rust-derive-eq.o \ + rust/rust-derive-hash.o \ rust/rust-proc-macro.o \ rust/rust-macro-invoc-lexer.o \ rust/rust-proc-macro-invoc-lexer.o \ diff --git a/gcc/rust/expand/rust-derive-hash.cc b/gcc/rust/expand/rust-derive-hash.cc new file mode 100644 index 000000000000..0c9b0f7b1057 --- /dev/null +++ b/gcc/rust/expand/rust-derive-hash.cc @@ -0,0 +1,293 @@ +// 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/>. + +#include "rust-derive-hash.h" +#include "rust-ast.h" +#include "rust-expr.h" +#include "rust-item.h" +#include "rust-path.h" +#include "rust-pattern.h" +#include "rust-stmt.h" +#include "rust-system.h" + +namespace Rust { +namespace AST { + +DeriveHash::DeriveHash (location_t loc) : DeriveVisitor (loc) {} + +std::unique_ptr<AST::Item> +DeriveHash::go (Item &item) +{ + item.accept_vis (*this); + + return std::move (expanded); +} + +std::unique_ptr<Expr> +DeriveHash::hash_call (std::unique_ptr<Expr> &&value) +{ + auto hash + = builder.path_in_expression ({"core", "hash", "Hash", "hash"}, true); + + return builder.call (ptrify (hash), + vec (std::move (value), + builder.identifier (DeriveHash::state))); +} + +std::unique_ptr<AssociatedItem> +DeriveHash::hash_fn (std::unique_ptr<BlockExpr> &&block) +{ + auto hash_calls = std::vector<std::unique_ptr<Stmt>> (); + + auto state_type = std::unique_ptr<TypeNoBounds> ( + new TypePath (builder.type_path (DeriveHash::state_type))); + auto state_param = + + builder.function_param (builder.identifier_pattern (DeriveHash::state), + builder.reference_type (std::move (state_type), + true)); + + auto params = vec (builder.self_ref_param (), std::move (state_param)); + auto bounds = vec ( + builder.trait_bound (builder.type_path ({"core", "hash", "Hasher"}, true))); + auto generics = vec ( + builder.generic_type_param (DeriveHash::state_type, std::move (bounds))); + + return builder.function ("hash", std::move (params), nullptr, + std::move (block), std::move (generics)); +} + +std::unique_ptr<Item> +DeriveHash::hash_impl ( + std::unique_ptr<AssociatedItem> &&hash_fn, std::string name, + const std::vector<std::unique_ptr<GenericParam>> &type_generics) +{ + auto hash_path = builder.type_path ({"core", "hash", "Hash"}, true); + + auto trait_items = vec (std::move (hash_fn)); + + auto generics = setup_impl_generics (name, type_generics, + builder.trait_bound (hash_path)); + + return builder.trait_impl (hash_path, std::move (generics.self_type), + std::move (trait_items), + std::move (generics.impl)); +} + +void +DeriveHash::visit_struct (StructStruct &item) +{ + auto hash_calls = std::vector<std::unique_ptr<Stmt>> (); + + for (auto &field : item.get_fields ()) + { + auto value = builder.ref ( + builder.field_access (builder.identifier ("self"), + field.get_field_name ().as_string ())); + + auto stmt = builder.statementify (hash_call (std::move (value))); + + hash_calls.emplace_back (std::move (stmt)); + } + + auto block = builder.block (std::move (hash_calls)); + + expanded = hash_impl (hash_fn (std::move (block)), + item.get_identifier ().as_string (), + item.get_generic_params ()); +} + +void +DeriveHash::visit_tuple (TupleStruct &item) +{ + auto hash_calls = std::vector<std::unique_ptr<Stmt>> (); + + for (size_t idx = 0; idx < item.get_fields ().size (); idx++) + { + auto value = builder.ref (builder.tuple_idx ("self", idx)); + + auto stmt = builder.statementify (hash_call (std::move (value))); + + hash_calls.emplace_back (std::move (stmt)); + } + + auto block = builder.block (std::move (hash_calls)); + + expanded = hash_impl (hash_fn (std::move (block)), + item.get_identifier ().as_string (), + item.get_generic_params ()); +} + +MatchCase +DeriveHash::match_enum_tuple (PathInExpression variant_path, + const EnumItemTuple &variant) +{ + auto self_patterns = std::vector<std::unique_ptr<Pattern>> (); + auto hash_calls = std::vector<std::unique_ptr<Stmt>> (); + + for (size_t i = 0; i < variant.get_tuple_fields ().size (); i++) + { + auto pattern = "__self_" + std::to_string (i); + + auto call = hash_call (builder.ref (builder.identifier (pattern))); + + self_patterns.emplace_back (builder.identifier_pattern (pattern)); + hash_calls.emplace_back (builder.statementify (std::move (call))); + } + + auto patterns_elts = std::unique_ptr<TupleStructItems> ( + new TupleStructItemsNoRange (std::move (self_patterns))); + auto pattern = std::unique_ptr<Pattern> ( + new ReferencePattern (std::unique_ptr<Pattern> (new TupleStructPattern ( + variant_path, std::move (patterns_elts))), + false, false, loc)); + + auto block = builder.block (std::move (hash_calls)); + + return builder.match_case (std::move (pattern), std::move (block)); +} + +MatchCase +DeriveHash::match_enum_struct (PathInExpression variant_path, + const EnumItemStruct &variant) +{ + auto field_patterns = std::vector<std::unique_ptr<StructPatternField>> (); + auto hash_calls = std::vector<std::unique_ptr<Stmt>> (); + + for (const auto &field : variant.get_struct_fields ()) + { + auto call = hash_call (builder.ref ( + builder.identifier (field.get_field_name ().as_string ()))); + + field_patterns.emplace_back ( + std::unique_ptr<StructPatternField> (new StructPatternFieldIdent ( + field.get_field_name (), false /* is_ref? true? */, false, {}, loc))); + + hash_calls.emplace_back (builder.statementify (std::move (call))); + } + + auto pattern_elts = StructPatternElements (std::move (field_patterns)); + auto pattern = std::unique_ptr<Pattern> ( + new ReferencePattern (std::unique_ptr<Pattern> (new StructPattern ( + variant_path, loc, pattern_elts)), + false, false, loc)); + + auto block = builder.block (std::move (hash_calls)); + return builder.match_case (std::move (pattern), std::move (block)); +} + +void +DeriveHash::visit_enum (Enum &item) +{ + // Enums are a bit different: We start by hashing the discriminant value of + // the enum instance, and then hash all of the data contained in each of the + // enum's variants. For data-less variants, we don't have any data to hash, so + // hashing the discriminant value is enough. To access the rest of the + // variants' data, we create a match and destructure each internal field and + // hash it. + // + // So for example with the following enum: + // + // ```rust + // enum Foo { + // A, + // B(i32), + // C { a: i32 }, + // } + // ``` + // + // we create the following implementation: + // + // ```rust + // fn hash<H: Hasher>(&self, state: &mut H) { + // let discriminant = intrinsics::discriminant_value(&self); + // Hash::hash(&discriminant, state); + // + // match self { + // B(self_0) => { Hash::hash(self_0, state); }, + // C { a } => { Hash::hash(a, state); }, + // _ => {}, + // } + // } + // ``` + // + // Note the extra wildcard pattern to satisfy the exhaust checker. + + auto cases = std::vector<MatchCase> (); + auto type_name = item.get_identifier ().as_string (); + + auto intrinsic = ptrify ( + builder.path_in_expression ({"core", "intrinsics", "discriminant_value"}, + true)); + + auto let_discr + = builder.let (builder.identifier_pattern (DeriveHash::discr), nullptr, + builder.call (std::move (intrinsic), + builder.identifier ("self"))); + + auto discr_hash = builder.statementify ( + hash_call (builder.ref (builder.identifier (DeriveHash::discr)))); + + for (auto &variant : item.get_variants ()) + { + auto variant_path + = builder.variant_path (type_name, + variant->get_identifier ().as_string ()); + + switch (variant->get_enum_item_kind ()) + { + case EnumItem::Kind::Identifier: + case EnumItem::Kind::Discriminant: + // nothing to do in these cases, as we just need to hash the + // discriminant value + continue; + case EnumItem::Kind::Tuple: + cases.emplace_back ( + match_enum_tuple (variant_path, + static_cast<EnumItemTuple &> (*variant))); + break; + case EnumItem::Kind::Struct: + cases.emplace_back ( + match_enum_struct (variant_path, + static_cast<EnumItemStruct &> (*variant))); + break; + } + } + + // The extra empty wildcard case + cases.emplace_back ( + builder.match_case (builder.wildcard (), builder.block ())); + + auto match = builder.match (builder.identifier ("self"), std::move (cases)); + + auto block + = builder.block (vec (std::move (let_discr), std::move (discr_hash)), + std::move (match)); + + expanded = hash_impl (hash_fn (std::move (block)), type_name, + item.get_generic_params ()); +} + +void +DeriveHash::visit_union (Union &item) +{ + rust_error_at (item.get_locus (), "derive(Hash) cannot be used on unions"); +} + +} // namespace AST +} // namespace Rust diff --git a/gcc/rust/expand/rust-derive-hash.h b/gcc/rust/expand/rust-derive-hash.h new file mode 100644 index 000000000000..02b0bee10a5d --- /dev/null +++ b/gcc/rust/expand/rust-derive-hash.h @@ -0,0 +1,61 @@ +// 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_HASH_H +#define RUST_DERIVE_HASH_H + +#include "rust-derive.h" + +namespace Rust { +namespace AST { + +class DeriveHash : DeriveVisitor +{ +public: + DeriveHash (location_t loc); + + std::unique_ptr<AST::Item> go (Item &item); + +private: + std::unique_ptr<Item> expanded; + + constexpr static const char *state = "#state"; + constexpr static const char *state_type = "#__H"; + constexpr static const char *discr = "#discr"; + + std::unique_ptr<Expr> hash_call (std::unique_ptr<Expr> &&value); + std::unique_ptr<AssociatedItem> hash_fn (std::unique_ptr<BlockExpr> &&block); + std::unique_ptr<Item> + hash_impl (std::unique_ptr<AssociatedItem> &&hash_fn, std::string name, + const std::vector<std::unique_ptr<GenericParam>> &type_generics); + + 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_HASH_H diff --git a/gcc/rust/expand/rust-derive.cc b/gcc/rust/expand/rust-derive.cc index 39e03a67cd4a..015b81e90df2 100644 --- a/gcc/rust/expand/rust-derive.cc +++ b/gcc/rust/expand/rust-derive.cc @@ -23,6 +23,7 @@ #include "rust-derive-default.h" #include "rust-derive-eq.h" #include "rust-derive-partial-eq.h" +#include "rust-derive-hash.h" namespace Rust { namespace AST { @@ -35,29 +36,32 @@ std::vector<std::unique_ptr<Item>> DeriveVisitor::derive (Item &item, const Attribute &attr, BuiltinMacro to_derive) { + auto loc = attr.get_locus (); + switch (to_derive) { case BuiltinMacro::Clone: - return vec (DeriveClone (attr.get_locus ()).go (item)); + return vec (DeriveClone (loc).go (item)); case BuiltinMacro::Copy: - return vec (DeriveCopy (attr.get_locus ()).go (item)); + return vec (DeriveCopy (loc).go (item)); case BuiltinMacro::Debug: rust_warning_at ( - attr.get_locus (), 0, + loc, 0, "derive(Debug) is not fully implemented yet and has no effect - only a " "stub implementation will be generated"); - return vec (DeriveDebug (attr.get_locus ()).go (item)); + return vec (DeriveDebug (loc).go (item)); case BuiltinMacro::Default: - return vec (DeriveDefault (attr.get_locus ()).go (item)); + return vec (DeriveDefault (loc).go (item)); case BuiltinMacro::Eq: - return DeriveEq (attr.get_locus ()).go (item); + return DeriveEq (loc).go (item); case BuiltinMacro::PartialEq: - return DerivePartialEq (attr.get_locus ()).go (item); + return DerivePartialEq (loc).go (item); + case BuiltinMacro::Hash: + return vec (DeriveHash (loc).go (item)); case BuiltinMacro::Ord: case BuiltinMacro::PartialOrd: - case BuiltinMacro::Hash: default: - rust_sorry_at (attr.get_locus (), "unimplemented builtin derive macro"); + rust_sorry_at (loc, "unimplemented builtin derive macro"); return {}; }; } diff --git a/gcc/testsuite/rust/compile/derive-hash1.rs b/gcc/testsuite/rust/compile/derive-hash1.rs new file mode 100644 index 000000000000..80e1e2d2bda5 --- /dev/null +++ b/gcc/testsuite/rust/compile/derive-hash1.rs @@ -0,0 +1,91 @@ +#![feature(intrinsics)] + +#[lang = "sized"] +trait Sized {} + +pub mod core { + pub mod intrinsics { + #[lang = "discriminant_kind"] + pub trait DiscriminantKind { + #[lang = "discriminant_type"] + type Discriminant; + } + + extern "rust-intrinsic" { + pub fn discriminant_value<T>(v: &T) -> <T as DiscriminantKind>::Discriminant; + } + } + + pub mod hash { + pub trait Hasher {} + + pub trait Hash { + /// Feeds this value into the given [`Hasher`]. + /// + /// # Examples + /// + /// ``` + /// use std::collections::hash_map::DefaultHasher; + /// use std::hash::{Hash, Hasher}; + /// + /// let mut hasher = DefaultHasher::new(); + /// 7920.hash(&mut hasher); + /// println!("Hash is {:x}!", hasher.finish()); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + fn hash<H: Hasher>(&self, state: &mut H); + + /// Feeds a slice of this type into the given [`Hasher`]. + /// + /// # Examples + /// + /// ``` + /// use std::collections::hash_map::DefaultHasher; + /// use std::hash::{Hash, Hasher}; + /// + /// let mut hasher = DefaultHasher::new(); + /// let numbers = [6, 28, 496, 8128]; + /// Hash::hash_slice(&numbers, &mut hasher); + /// println!("Hash is {:x}!", hasher.finish()); + /// ``` + #[stable(feature = "hash_slice", since = "1.3.0")] + fn hash_slice<H: Hasher>(data: &[Self], state: &mut H) + where + Self: Sized, + { + // for piece in data { + // piece.hash(state); + // } + } + } + } +} + +impl core::hash::Hash for i32 { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) {} +} + +impl core::hash::Hash for i64 { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) {} +} + +// for the discriminant value +impl core::hash::Hash for isize { + fn hash<H: core::hash::Hasher>(&self, state: &mut H) {} +} + +#[derive(Hash)] +struct Foo { // { dg-warning "never constructed" } + a: i32, + b: i32, +} + +#[derive(Hash)] +struct Bar(i32, i64); // { dg-warning "never constructed" } + +#[derive(Hash)] +enum Baz { + A, + B(i32), + C { a: i64 } +} diff --git a/gcc/testsuite/rust/compile/nr2/exclude b/gcc/testsuite/rust/compile/nr2/exclude index 26b830eab7e9..345b1d516ef4 100644 --- a/gcc/testsuite/rust/compile/nr2/exclude +++ b/gcc/testsuite/rust/compile/nr2/exclude @@ -77,4 +77,5 @@ for-loop2.rs issue-3403.rs derive-eq-invalid.rs derive-partialeq1.rs +derive-hash1.rs # please don't delete the trailing newline