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 4305 : ReadonlyChecker::ReadonlyChecker ()
37 4305 : : resolver (*Resolver::Resolver::get ()),
38 4305 : mappings (Analysis::Mappings::get ()),
39 8610 : context (*Resolver::TypeCheckContext::get ())
40 4305 : {}
41 :
42 : void
43 4305 : ReadonlyChecker::go (Crate &crate)
44 : {
45 22114 : for (auto &item : crate.get_items ())
46 17809 : item->accept_vis (*this);
47 4305 : }
48 :
49 : void
50 2411 : ReadonlyChecker::visit (AssignmentExpr &expr)
51 : {
52 2411 : Expr &lhs = expr.get_lhs ();
53 2411 : mutable_context.enter (expr.get_mappings ().get_hirid ());
54 2411 : lhs.accept_vis (*this);
55 2411 : mutable_context.exit ();
56 2411 : }
57 :
58 : void
59 29208 : ReadonlyChecker::visit (PathInExpression &expr)
60 : {
61 29208 : if (!mutable_context.is_in_context ())
62 26855 : return;
63 :
64 2353 : NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
65 2353 : NodeId def_id;
66 :
67 2353 : auto &nr_ctx = Resolver2_0::FinalizedNameResolutionContext::get ();
68 2353 : if (auto id = nr_ctx.lookup (ast_node_id, Resolver2_0::Namespace::Values))
69 2353 : def_id = *id;
70 : else
71 0 : return;
72 :
73 2353 : auto hir_id = mappings.lookup_node_to_hir (def_id);
74 2353 : if (!hir_id)
75 : return;
76 :
77 : // Check if the local variable is mutable.
78 2353 : auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
79 2353 : if (maybe_pattern
80 2353 : && maybe_pattern.value ()->get_pattern_type ()
81 : == HIR::Pattern::PatternType::IDENTIFIER)
82 2288 : check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
83 : expr.get_locus ());
84 :
85 : // Check if the static item is mutable.
86 2353 : auto maybe_item = mappings.lookup_hir_item (*hir_id);
87 2353 : if (maybe_item
88 2353 : && 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 2353 : if (maybe_item
99 2353 : && 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 2288 : ReadonlyChecker::check_variable (IdentifierPattern *pattern,
109 : location_t assigned_loc)
110 : {
111 2288 : if (!mutable_context.is_in_context ())
112 1339 : return;
113 :
114 2288 : TyTy::BaseType *type;
115 2288 : if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
116 2288 : && is_mutable_type (type))
117 : return;
118 1587 : 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 12518 : ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
130 : bool has_init_expr)
131 : {
132 12518 : if (has_init_expr)
133 : {
134 11507 : HirId pattern_id = pattern.get_mappings ().get_hirid ();
135 11507 : already_assigned_variables.insert (pattern_id);
136 : }
137 12518 : }
138 :
139 : void
140 293 : ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
141 : bool has_init_expr)
142 : {
143 293 : switch (tuple_pattern.get_items ().get_item_type ())
144 : {
145 288 : case HIR::TuplePatternItems::ItemType::NO_REST:
146 288 : {
147 288 : auto &items_no_rest = static_cast<HIR::TuplePatternItemsNoRest &> (
148 288 : tuple_pattern.get_items ());
149 869 : for (auto &sub : items_no_rest.get_patterns ())
150 : {
151 581 : 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 293 : }
169 :
170 : void
171 13099 : ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
172 : {
173 13099 : switch (pattern.get_pattern_type ())
174 : {
175 12518 : case HIR::Pattern::PatternType::IDENTIFIER:
176 12518 : {
177 12518 : collect_assignment_identifier (static_cast<IdentifierPattern &> (
178 : pattern),
179 : has_init_expr);
180 : }
181 12518 : break;
182 293 : case HIR::Pattern::PatternType::TUPLE:
183 293 : {
184 293 : auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
185 293 : collect_assignment_tuple (tuple_pattern, has_init_expr);
186 : }
187 293 : break;
188 : default:
189 : break;
190 : }
191 13099 : }
192 :
193 : void
194 12512 : ReadonlyChecker::visit (LetStmt &stmt)
195 : {
196 12512 : HIR::Pattern &pattern = stmt.get_pattern ();
197 12512 : collect_assignment (pattern, stmt.has_init_expr ());
198 12512 : }
199 :
200 : void
201 3362 : ReadonlyChecker::visit (FieldAccessExpr &expr)
202 : {
203 3362 : if (mutable_context.is_in_context ())
204 : {
205 800 : expr.get_receiver_expr ().accept_vis (*this);
206 : }
207 3362 : }
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 325 : ReadonlyChecker::visit (TupleExpr &expr)
229 : {
230 325 : if (mutable_context.is_in_context ())
231 : {
232 : // TODO: Add check for tuple expression
233 : }
234 325 : }
235 :
236 : void
237 9204 : ReadonlyChecker::visit (LiteralExpr &expr)
238 : {
239 9204 : if (mutable_context.is_in_context ())
240 : {
241 1 : rust_error_at (expr.get_locus (), "assignment of read-only location");
242 : }
243 9204 : }
244 :
245 : void
246 3712 : ReadonlyChecker::visit (DereferenceExpr &expr)
247 : {
248 3712 : if (!mutable_context.is_in_context ())
249 3655 : 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 2345 : ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
260 : {
261 2345 : if (type->get_kind () == TyTy::TypeKind::REF)
262 847 : return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
263 1498 : 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
|