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 4244 : ReadonlyChecker::ReadonlyChecker ()
37 4244 : : resolver (*Resolver::Resolver::get ()),
38 4244 : mappings (Analysis::Mappings::get ()),
39 8488 : context (*Resolver::TypeCheckContext::get ())
40 4244 : {}
41 :
42 : void
43 4244 : ReadonlyChecker::go (Crate &crate)
44 : {
45 21761 : for (auto &item : crate.get_items ())
46 17517 : item->accept_vis (*this);
47 4244 : }
48 :
49 : void
50 2410 : ReadonlyChecker::visit (AssignmentExpr &expr)
51 : {
52 2410 : Expr &lhs = expr.get_lhs ();
53 2410 : mutable_context.enter (expr.get_mappings ().get_hirid ());
54 2410 : lhs.accept_vis (*this);
55 2410 : mutable_context.exit ();
56 2410 : }
57 :
58 : void
59 28960 : ReadonlyChecker::visit (PathInExpression &expr)
60 : {
61 28960 : if (!mutable_context.is_in_context ())
62 26608 : return;
63 :
64 2352 : NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
65 2352 : NodeId def_id;
66 :
67 2352 : auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
68 2352 : if (auto id = nr_ctx.lookup (ast_node_id, Resolver2_0::Namespace::Values))
69 2352 : def_id = *id;
70 : else
71 0 : return;
72 :
73 2352 : auto hir_id = mappings.lookup_node_to_hir (def_id);
74 2352 : if (!hir_id)
75 : return;
76 :
77 : // Check if the local variable is mutable.
78 2352 : auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
79 2352 : if (maybe_pattern
80 2352 : && maybe_pattern.value ()->get_pattern_type ()
81 : == HIR::Pattern::PatternType::IDENTIFIER)
82 2287 : check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
83 : expr.get_locus ());
84 :
85 : // Check if the static item is mutable.
86 2352 : auto maybe_item = mappings.lookup_hir_item (*hir_id);
87 2352 : if (maybe_item
88 2352 : && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
89 : {
90 0 : auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
91 0 : 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 2352 : if (maybe_item
99 2352 : && 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 2287 : ReadonlyChecker::check_variable (IdentifierPattern *pattern,
109 : location_t assigned_loc)
110 : {
111 2287 : if (!mutable_context.is_in_context ())
112 1338 : return;
113 :
114 2287 : TyTy::BaseType *type;
115 2287 : if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
116 2287 : && is_mutable_type (type))
117 : return;
118 1586 : if (pattern->is_mut ())
119 : return;
120 :
121 949 : auto hir_id = pattern->get_mappings ().get_hirid ();
122 949 : if (already_assigned_variables.count (hir_id) > 0)
123 5 : rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
124 10 : pattern->to_string ().c_str ());
125 949 : already_assigned_variables.insert (hir_id);
126 : }
127 :
128 : void
129 12406 : ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
130 : bool has_init_expr)
131 : {
132 12406 : if (has_init_expr)
133 : {
134 11396 : HirId pattern_id = pattern.get_mappings ().get_hirid ();
135 11396 : already_assigned_variables.insert (pattern_id);
136 : }
137 12406 : }
138 :
139 : void
140 283 : ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
141 : bool has_init_expr)
142 : {
143 283 : switch (tuple_pattern.get_items ().get_item_type ())
144 : {
145 281 : case HIR::TuplePatternItems::ItemType::NO_REST:
146 281 : {
147 281 : auto &items_no_rest = static_cast<HIR::TuplePatternItemsNoRest &> (
148 281 : tuple_pattern.get_items ());
149 848 : for (auto &sub : items_no_rest.get_patterns ())
150 : {
151 567 : collect_assignment (*sub, has_init_expr);
152 : }
153 : }
154 : break;
155 2 : case HIR::TuplePatternItems::ItemType::HAS_REST:
156 2 : {
157 2 : auto &items_has_rest = static_cast<HIR::TuplePatternItemsHasRest &> (
158 2 : tuple_pattern.get_items ());
159 4 : for (auto &sub : items_has_rest.get_lower_patterns ())
160 2 : collect_assignment (*sub, has_init_expr);
161 4 : for (auto &sub : items_has_rest.get_upper_patterns ())
162 2 : collect_assignment (*sub, has_init_expr);
163 : }
164 : break;
165 : default:
166 : break;
167 : }
168 283 : }
169 :
170 : void
171 12973 : ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
172 : {
173 12973 : switch (pattern.get_pattern_type ())
174 : {
175 12406 : case HIR::Pattern::PatternType::IDENTIFIER:
176 12406 : {
177 12406 : collect_assignment_identifier (static_cast<IdentifierPattern &> (
178 : pattern),
179 : has_init_expr);
180 : }
181 12406 : break;
182 283 : case HIR::Pattern::PatternType::TUPLE:
183 283 : {
184 283 : auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
185 283 : collect_assignment_tuple (tuple_pattern, has_init_expr);
186 : }
187 283 : break;
188 : default:
189 : break;
190 : }
191 12973 : }
192 :
193 : void
194 12402 : ReadonlyChecker::visit (LetStmt &stmt)
195 : {
196 12402 : HIR::Pattern &pattern = stmt.get_pattern ();
197 12402 : collect_assignment (pattern, stmt.has_init_expr ());
198 12402 : }
199 :
200 : void
201 3358 : ReadonlyChecker::visit (FieldAccessExpr &expr)
202 : {
203 3358 : if (mutable_context.is_in_context ())
204 : {
205 800 : expr.get_receiver_expr ().accept_vis (*this);
206 : }
207 3358 : }
208 :
209 : void
210 528 : ReadonlyChecker::visit (TupleIndexExpr &expr)
211 : {
212 528 : if (mutable_context.is_in_context ())
213 : {
214 8 : expr.get_tuple_expr ().accept_vis (*this);
215 : }
216 528 : }
217 :
218 : void
219 116 : ReadonlyChecker::visit (ArrayIndexExpr &expr)
220 : {
221 116 : if (mutable_context.is_in_context ())
222 : {
223 10 : expr.get_array_expr ().accept_vis (*this);
224 : }
225 116 : }
226 :
227 : void
228 324 : ReadonlyChecker::visit (TupleExpr &expr)
229 : {
230 324 : if (mutable_context.is_in_context ())
231 : {
232 : // TODO: Add check for tuple expression
233 : }
234 324 : }
235 :
236 : void
237 9078 : ReadonlyChecker::visit (LiteralExpr &expr)
238 : {
239 9078 : if (mutable_context.is_in_context ())
240 : {
241 1 : rust_error_at (expr.get_locus (), "assignment of read-only location");
242 : }
243 9078 : }
244 :
245 : void
246 3711 : ReadonlyChecker::visit (DereferenceExpr &expr)
247 : {
248 3711 : if (!mutable_context.is_in_context ())
249 3654 : return;
250 57 : TyTy::BaseType *to_deref_type;
251 57 : auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
252 57 : if (!context.lookup_type (to_deref, &to_deref_type))
253 : return;
254 57 : 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 2344 : ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
260 : {
261 2344 : if (type->get_kind () == TyTy::TypeKind::REF)
262 847 : return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
263 1497 : 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
|