Line data Source code
1 : // Copyright (C) 2025-2026 Free Software Foundation, Inc.
2 :
3 : // This file is part of GCC.
4 :
5 : // GCC is free software; you can redistribute it and/or modify it under
6 : // the terms of the GNU General Public License as published by the Free
7 : // Software Foundation; either version 3, or (at your option) any later
8 : // version.
9 :
10 : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 : // for more details.
14 :
15 : // You should have received a copy of the GNU General Public License
16 : // along with GCC; see the file COPYING3. If not see
17 : // <http://www.gnu.org/licenses/>.
18 :
19 : #include "rust-readonly-check.h"
20 : #include "rust-hir-expr.h"
21 : #include "rust-hir-node.h"
22 : #include "rust-hir-path.h"
23 : #include "rust-hir-map.h"
24 : #include "rust-hir-pattern.h"
25 : #include "rust-mapping-common.h"
26 : #include "rust-rib.h"
27 : #include "rust-system.h"
28 : #include "rust-finalized-name-resolution-context.h"
29 : #include "rust-tyty.h"
30 :
31 : namespace Rust {
32 : namespace HIR {
33 :
34 : static std::set<HirId> already_assigned_variables = {};
35 :
36 4446 : ReadonlyChecker::ReadonlyChecker ()
37 4446 : : resolver (*Resolver::Resolver::get ()),
38 4446 : mappings (Analysis::Mappings::get ()),
39 8892 : context (*Resolver::TypeCheckContext::get ())
40 4446 : {}
41 :
42 : void
43 4446 : ReadonlyChecker::go (Crate &crate)
44 : {
45 23469 : for (auto &item : crate.get_items ())
46 19023 : item->accept_vis (*this);
47 4446 : }
48 :
49 : void
50 2479 : ReadonlyChecker::visit (AssignmentExpr &expr)
51 : {
52 2479 : Expr &lhs = expr.get_lhs ();
53 2479 : mutable_context.enter (expr.get_mappings ().get_hirid ());
54 2479 : lhs.accept_vis (*this);
55 2479 : mutable_context.exit ();
56 2479 : }
57 :
58 : void
59 31957 : ReadonlyChecker::visit (PathInExpression &expr)
60 : {
61 31957 : if (!mutable_context.is_in_context ())
62 29543 : return;
63 :
64 2414 : NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
65 2414 : NodeId def_id;
66 :
67 2414 : auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
68 2414 : if (auto id = nr_ctx.lookup (ast_node_id, Resolver2_0::Namespace::Values))
69 2414 : def_id = *id;
70 : else
71 0 : return;
72 :
73 2414 : auto hir_id = mappings.lookup_node_to_hir (def_id);
74 2414 : if (!hir_id)
75 : return;
76 :
77 : // Check if the local variable is mutable.
78 2414 : auto maybe_pattern = mappings.hir.patterns.lookup (*hir_id);
79 2414 : if (maybe_pattern
80 2414 : && maybe_pattern.value ()->get_pattern_type ()
81 : == HIR::Pattern::PatternType::IDENTIFIER)
82 2335 : check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
83 : expr.get_locus ());
84 :
85 : // Check if the static item is mutable.
86 2414 : auto maybe_item = mappings.hir.items.lookup (*hir_id);
87 2414 : if (maybe_item
88 2414 : && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
89 : {
90 14 : auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
91 14 : if (!static_item->is_mut ())
92 0 : rust_error_at (expr.get_locus (),
93 : "assignment of read-only location '%s'",
94 0 : static_item->get_identifier ().as_string ().c_str ());
95 : }
96 :
97 : // Check if the constant item is mutable.
98 2414 : if (maybe_item
99 2414 : && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
100 : {
101 1 : auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
102 1 : rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
103 2 : const_item->get_identifier ().as_string ().c_str ());
104 : }
105 : }
106 :
107 : void
108 2335 : ReadonlyChecker::check_variable (IdentifierPattern *pattern,
109 : location_t assigned_loc)
110 : {
111 2335 : if (!mutable_context.is_in_context ())
112 1384 : return;
113 :
114 2335 : TyTy::BaseType *type;
115 2335 : if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
116 2335 : && is_mutable_type (type))
117 : return;
118 1634 : if (pattern->is_mut ())
119 : return;
120 :
121 951 : auto hir_id = pattern->get_mappings ().get_hirid ();
122 951 : if (already_assigned_variables.count (hir_id) > 0)
123 6 : rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
124 12 : pattern->to_string ().c_str ());
125 951 : already_assigned_variables.insert (hir_id);
126 : }
127 :
128 : void
129 13022 : ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
130 : bool has_init_expr)
131 : {
132 13022 : if (has_init_expr)
133 : {
134 12007 : HirId pattern_id = pattern.get_mappings ().get_hirid ();
135 12007 : already_assigned_variables.insert (pattern_id);
136 : }
137 13022 : }
138 :
139 : void
140 368 : ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
141 : bool has_init_expr)
142 : {
143 368 : switch (tuple_pattern.get_items ().get_item_type ())
144 : {
145 363 : case HIR::TuplePatternItems::ItemType::NO_REST:
146 363 : {
147 363 : auto &items_no_rest = static_cast<HIR::TuplePatternItemsNoRest &> (
148 363 : tuple_pattern.get_items ());
149 1095 : for (auto &sub : items_no_rest.get_patterns ())
150 : {
151 732 : collect_assignment (*sub, has_init_expr);
152 : }
153 : }
154 : break;
155 5 : case HIR::TuplePatternItems::ItemType::HAS_REST:
156 5 : {
157 5 : auto &items_has_rest = static_cast<HIR::TuplePatternItemsHasRest &> (
158 5 : tuple_pattern.get_items ());
159 8 : for (auto &sub : items_has_rest.get_lower_patterns ())
160 3 : collect_assignment (*sub, has_init_expr);
161 8 : for (auto &sub : items_has_rest.get_upper_patterns ())
162 3 : collect_assignment (*sub, has_init_expr);
163 : }
164 : break;
165 : default:
166 : break;
167 : }
168 368 : }
169 :
170 : void
171 13723 : ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
172 : {
173 13723 : switch (pattern.get_pattern_type ())
174 : {
175 13022 : case HIR::Pattern::PatternType::IDENTIFIER:
176 13022 : {
177 13022 : collect_assignment_identifier (static_cast<IdentifierPattern &> (
178 : pattern),
179 : has_init_expr);
180 : }
181 13022 : break;
182 368 : case HIR::Pattern::PatternType::TUPLE:
183 368 : {
184 368 : auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
185 368 : collect_assignment_tuple (tuple_pattern, has_init_expr);
186 : }
187 368 : break;
188 : default:
189 : break;
190 : }
191 13723 : }
192 :
193 : void
194 12985 : ReadonlyChecker::visit (LetStmt &stmt)
195 : {
196 12985 : HIR::Pattern &pattern = stmt.get_pattern ();
197 12985 : collect_assignment (pattern, stmt.has_init_expr ());
198 12985 : }
199 :
200 : void
201 3412 : ReadonlyChecker::visit (FieldAccessExpr &expr)
202 : {
203 3412 : if (mutable_context.is_in_context ())
204 : {
205 808 : expr.get_receiver_expr ().accept_vis (*this);
206 : }
207 3412 : }
208 :
209 : void
210 553 : ReadonlyChecker::visit (TupleIndexExpr &expr)
211 : {
212 553 : if (mutable_context.is_in_context ())
213 : {
214 8 : expr.get_tuple_expr ().accept_vis (*this);
215 : }
216 553 : }
217 :
218 : void
219 122 : ReadonlyChecker::visit (ArrayIndexExpr &expr)
220 : {
221 122 : if (mutable_context.is_in_context ())
222 : {
223 10 : expr.get_array_expr ().accept_vis (*this);
224 : }
225 122 : }
226 :
227 : void
228 382 : ReadonlyChecker::visit (TupleExpr &expr)
229 : {
230 382 : if (mutable_context.is_in_context ())
231 : {
232 : // TODO: Add check for tuple expression
233 : }
234 382 : }
235 :
236 : void
237 9908 : ReadonlyChecker::visit (LiteralExpr &expr)
238 : {
239 9908 : if (mutable_context.is_in_context ())
240 : {
241 1 : rust_error_at (expr.get_locus (), "assignment of read-only location");
242 : }
243 9908 : }
244 :
245 : void
246 4054 : ReadonlyChecker::visit (DereferenceExpr &expr)
247 : {
248 4054 : if (!mutable_context.is_in_context ())
249 3990 : return;
250 64 : TyTy::BaseType *to_deref_type;
251 64 : auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
252 64 : if (!context.lookup_type (to_deref, &to_deref_type))
253 : return;
254 64 : if (!is_mutable_type (to_deref_type))
255 0 : rust_error_at (expr.get_locus (), "assignment of read-only location");
256 : }
257 :
258 : bool
259 2399 : ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
260 : {
261 2399 : if (type->get_kind () == TyTy::TypeKind::REF)
262 854 : return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
263 1545 : if (type->get_kind () == TyTy::TypeKind::POINTER)
264 2 : return static_cast<TyTy::PointerType *> (type)->is_mutable ();
265 : return false;
266 : }
267 : } // namespace HIR
268 : } // namespace Rust
|