// 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 // . #include "rust-ast-resolve-pattern.h" #include "rust-ast-resolve-path.h" namespace Rust { namespace Resolver { void PatternDeclaration::go (AST::Pattern *pattern, Rib::ItemType type) { std::vector bindings = {PatternBinding (PatternBoundCtx::Product, std::set ())}; PatternDeclaration::go (pattern, type, bindings); } void PatternDeclaration::go (AST::Pattern *pattern, Rib::ItemType type, std::vector &bindings) { PatternDeclaration resolver (bindings, type); pattern->accept_vis (resolver); for (auto &map_entry : resolver.missing_bindings) { auto ident = map_entry.first; // key auto info = map_entry.second; // value rust_error_at (info.get_locus (), ErrorCode::E0408, "variable '%s' is not bound in all patterns", ident.as_string ().c_str ()); } for (auto &map_entry : resolver.inconsistent_bindings) { auto ident = map_entry.first; // key auto info = map_entry.second; // value rust_error_at ( info.get_locus (), ErrorCode::E0409, "variable '%s' is bound inconsistently across pattern alternatives", ident.as_string ().c_str ()); } } void PatternDeclaration::visit (AST::IdentifierPattern &pattern) { Mutability mut = pattern.get_is_mut () ? Mutability::Mut : Mutability::Imm; add_new_binding (pattern.get_ident (), pattern.get_node_id (), BindingTypeInfo (mut, pattern.get_is_ref (), pattern.get_locus ())); } void PatternDeclaration::visit (AST::GroupedPattern &pattern) { pattern.get_pattern_in_parens ()->accept_vis (*this); } void PatternDeclaration::visit (AST::ReferencePattern &pattern) { pattern.get_referenced_pattern ()->accept_vis (*this); } void PatternDeclaration::visit (AST::PathInExpression &pattern) { ResolvePath::go (&pattern); } void PatternDeclaration::visit (AST::TupleStructPattern &pattern) { ResolvePath::go (&pattern.get_path ()); std::unique_ptr &items = pattern.get_items (); switch (items->get_item_type ()) { case AST::TupleStructItems::RANGE: { // TODO rust_unreachable (); } break; case AST::TupleStructItems::NO_RANGE: { AST::TupleStructItemsNoRange &items_no_range = static_cast (*items.get ()); for (auto &inner_pattern : items_no_range.get_patterns ()) { inner_pattern.get ()->accept_vis (*this); } } break; } } void PatternDeclaration::visit (AST::StructPattern &pattern) { ResolvePath::go (&pattern.get_path ()); auto &struct_pattern_elems = pattern.get_struct_pattern_elems (); for (auto &field : struct_pattern_elems.get_struct_pattern_fields ()) { switch (field->get_item_type ()) { case AST::StructPatternField::ItemType::TUPLE_PAT: { AST::StructPatternFieldTuplePat &tuple = static_cast (*field); tuple.get_index_pattern ()->accept_vis (*this); } break; case AST::StructPatternField::ItemType::IDENT_PAT: { AST::StructPatternFieldIdentPat &ident = static_cast (*field); ident.get_ident_pattern ()->accept_vis (*this); } break; case AST::StructPatternField::ItemType::IDENT: { AST::StructPatternFieldIdent &ident = static_cast (*field.get ()); Mutability mut = ident.is_mut () ? Mutability::Mut : Mutability::Imm; add_new_binding (ident.get_identifier (), ident.get_node_id (), BindingTypeInfo (mut, ident.is_ref (), ident.get_locus ())); } break; } } } void PatternDeclaration::visit (AST::TuplePattern &pattern) { std::unique_ptr &items = pattern.get_items (); switch (items->get_pattern_type ()) { case AST::TuplePatternItems::TuplePatternItemType::MULTIPLE: { AST::TuplePatternItemsMultiple &ref = *static_cast ( pattern.get_items ().get ()); for (auto &p : ref.get_patterns ()) p->accept_vis (*this); } break; case AST::TuplePatternItems::TuplePatternItemType::RANGED: { AST::TuplePatternItemsRanged &ref = *static_cast ( pattern.get_items ().get ()); for (auto &p : ref.get_lower_patterns ()) p->accept_vis (*this); for (auto &p : ref.get_upper_patterns ()) p->accept_vis (*this); } break; } } void PatternDeclaration::visit (AST::AltPattern &pattern) { // push a new set of 'Or' bindings to the stack. Accounts for the // alternatives. e.g. in `p_0 | p_1`, bindings to the same identifier between // p_0 and p_1 shouldn't cause an error. bindings_with_ctx.push_back ( PatternBinding (PatternBoundCtx::Or, std::set ())); // This is a hack to avoid creating a separate visitor class for the // consistency checks. We empty out the binding_info_map before each iteration // to separate between the alts' binding_maps. And right after the alt // visit... auto tmp_binding_map = binding_info_map; binding_info_map.clear (); std::vector alts_binding_maps; for (auto &alt : pattern.get_alts ()) { // before this visit, the binding_info_map is guaranteed to be empty rust_assert (binding_info_map.empty ()); // push a new `Product` context to correctly reject multiple bindings // within this single alt. bindings_with_ctx.push_back ( PatternBinding (PatternBoundCtx::Product, std::set ())); alt->accept_vis (*this); // ...the binding_info_map is (potentially) populated. We copy it to the // vector, and empty it out to be ready for the next iteration. And after // all the iterations are finished... alts_binding_maps.push_back (binding_info_map); binding_info_map.clear (); // Remove the last (i.e. `Product`) context and add the bindings from the // visited alt to the one before last (i.e. `Or`). Now (after checking // with the alt internally), the bindings from this alt will reside in the // `Or` context. auto last_bound_idents = bindings_with_ctx.back ().idents; bindings_with_ctx.pop_back (); for (auto &ident : last_bound_idents) { bindings_with_ctx.back ().idents.insert (ident); } } // Now we can finally check for consistency. check_bindings_consistency (alts_binding_maps); // Now we remove the `Or` context we pushed earlier. // e.g. in `(a, (p_0 | p_1), c)`: after finishing up inside the alt pattern, // we return to the tuple (`Product`) context and push the new bindings. auto idents = bindings_with_ctx.back ().idents; bindings_with_ctx.pop_back (); for (auto &ident : idents) bindings_with_ctx.back ().idents.insert (ident.as_string ()); // ...we repopulate the binding_info_map correctly (the initial bindings // stored in the tmp_binding_map + all the bindings from all the alts) binding_info_map = tmp_binding_map; for (auto &alt_map : alts_binding_maps) for (auto &map_entry : alt_map) binding_info_map.insert (map_entry); } void PatternDeclaration::add_new_binding (Identifier ident, NodeId node_id, BindingTypeInfo info) { bool has_binding_ctx = bindings_with_ctx.size () > 0; rust_assert (has_binding_ctx); bool identifier_or_bound = false, identifier_product_bound = false; for (auto binding : bindings_with_ctx) { bool identifier_bound_here = (binding.idents.find (ident) != binding.idents.end ()); if (identifier_bound_here) { identifier_product_bound |= binding.ctx == PatternBoundCtx::Product; identifier_or_bound |= binding.ctx == PatternBoundCtx::Or; } } if (identifier_product_bound) { if (type == Rib::ItemType::Param) { rust_error_at (info.get_locus (), ErrorCode::E0415, "identifier '%s' is bound more than once in the " "same parameter list", ident.as_string ().c_str ()); } else { rust_error_at ( info.get_locus (), ErrorCode::E0416, "identifier '%s' is bound more than once in the same pattern", ident.as_string ().c_str ()); } return; } if (!identifier_or_bound) { bindings_with_ctx.back ().idents.insert (ident); resolver->get_name_scope ().insert ( CanonicalPath::new_seg (node_id, ident.as_string ()), node_id, info.get_locus (), type); } binding_info_map.insert ({ident, info}); } // Verifies that all the alts in an AltPattern have the same set of bindings // with the same mutability and reference states. void PatternDeclaration::check_bindings_consistency ( std::vector &binding_maps) { for (size_t i = 0; i < binding_maps.size (); i++) { auto &outer_bindings_map = binding_maps[i]; for (size_t j = 0; j < binding_maps.size (); j++) { // skip comparing the current outer map with itself. if (j == i) continue; auto &inner_bindings_map = binding_maps[j]; // iterate over the inner map entries and check if they exist in outer // map for (auto map_entry : inner_bindings_map) { auto ident = map_entry.first; // key auto inner_info = map_entry.second; // value bool ident_is_outer_bound = outer_bindings_map.count (ident); if (!ident_is_outer_bound && !missing_bindings.count (ident)) missing_bindings.insert ({ident, inner_info}); else if (outer_bindings_map[ident] != inner_info && !inconsistent_bindings.count (ident)) inconsistent_bindings.insert ({ident, inner_info}); } } } } static void resolve_range_pattern_bound (AST::RangePatternBound *bound) { switch (bound->get_bound_type ()) { case AST::RangePatternBound::RangePatternBoundType::LITERAL: // Nothing to resolve for a literal. break; case AST::RangePatternBound::RangePatternBoundType::PATH: { AST::RangePatternBoundPath &ref = *static_cast (bound); ResolvePath::go (&ref.get_path ()); } break; case AST::RangePatternBound::RangePatternBoundType::QUALPATH: { AST::RangePatternBoundQualPath &ref = *static_cast (bound); ResolvePath::go (&ref.get_qualified_path ()); } break; } } void PatternDeclaration::visit (AST::RangePattern &pattern) { resolve_range_pattern_bound (pattern.get_upper_bound ().get ()); resolve_range_pattern_bound (pattern.get_lower_bound ().get ()); } void PatternDeclaration::visit (AST::SlicePattern &pattern) { for (auto &p : pattern.get_items ()) { p->accept_vis (*this); } } } // namespace Resolver } // namespace Rust