1use std::collections::HashMap;
17
18use thiserror::Error;
19
20use super::lex::tokens::{ANDAND, EQ, GE, GT, LE, LT, MINUS, NE, OROR, PERCENT, PLUS, SLASH, STAR};
21use super::parse::{Expr, Module, Stmt, Type};
22
23pub type BindingId = usize;
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct Binding {
29 pub name: String,
31 pub mutable: bool,
33 pub ty: Type,
35 pub def_offset: u32,
37 pub function: usize,
39}
40
41#[derive(Debug, Clone, Default)]
44pub struct Resolution {
45 pub bindings: Vec<Binding>,
47 pub uses: HashMap<u32, BindingId>,
49 pub calls: HashMap<u32, usize>,
51}
52
53#[derive(Debug, Clone, Error)]
55pub enum RustSemaError {
56 #[error("cannot find value `{name}` in this scope (byte {offset})")]
58 UnresolvedName {
59 name: String,
61 offset: u32,
63 },
64 #[error("cannot find function `{name}` in this scope (byte {offset})")]
66 UnknownFunction {
67 name: String,
69 offset: u32,
71 },
72 #[error("cannot borrow `{name}` as mutable, as it is not declared as mutable (byte {offset})")]
74 CannotBorrowImmutableAsMutable {
75 name: String,
77 offset: u32,
79 },
80 #[error(
82 "cannot return a reference to a local value; it does not live long enough (byte {offset})"
83 )]
84 ReturnsReferenceToLocal {
85 offset: u32,
87 },
88 #[error("cannot borrow as mutable more than once at a time (byte {offset})")]
90 MultipleMutableBorrows {
91 offset: u32,
93 },
94 #[error("cannot borrow as mutable because it is also borrowed as immutable (byte {offset})")]
96 MutableAndSharedBorrow {
97 offset: u32,
99 },
100 #[error("mismatched types in {context}: expected `{expected}`, found `{found}`")]
102 TypeMismatch {
103 context: String,
105 expected: String,
107 found: String,
109 },
110 #[error("type `{found}` cannot be dereferenced; only references can")]
112 CannotDeref {
113 found: String,
115 },
116 #[error("`if` condition must be `bool`, found `{found}`")]
118 NonBooleanCondition {
119 found: String,
121 },
122 #[error("function `{function}` expects {expected} argument(s), found {found}")]
124 ArgCountMismatch {
125 function: String,
127 expected: usize,
129 found: usize,
131 },
132 #[error("function `{function}` must return `{expected}` on all paths")]
134 MissingReturn {
135 function: String,
137 expected: String,
139 },
140 #[error("cannot assign twice to immutable variable `{name}`")]
142 AssignToImmutable {
143 name: String,
145 },
146 #[error("internal Rust semantic invariant failed: {message}")]
148 InternalInvariant {
149 message: String,
151 },
152}
153
154fn ident_at(source: &[u8], offset: u32) -> String {
159 let start = (offset as usize).min(source.len());
160 let mut end = start;
161 while end < source.len() && (source[end].is_ascii_alphanumeric() || source[end] == b'_') {
162 end += 1;
163 }
164 String::from_utf8_lossy(&source[start..end]).into_owned()
165}
166
167fn coerces(found: &Type, expected: &Type) -> bool {
172 match (found, expected) {
173 (
174 Type::Ref {
175 mutable: fm,
176 inner: fi,
177 },
178 Type::Ref {
179 mutable: em,
180 inner: ei,
181 },
182 ) => (fm == em || (*fm && !*em)) && fi == ei,
183 _ => found == expected,
184 }
185}
186
187fn type_str(ty: &Type) -> String {
189 match ty {
190 Type::I32 => "i32".to_string(),
191 Type::Bool => "bool".to_string(),
192 Type::Unit => "()".to_string(),
193 Type::Ref { mutable, inner } => {
194 format!("&{}{}", if *mutable { "mut " } else { "" }, type_str(inner))
195 }
196 }
197}
198
199struct Resolver<'a> {
204 source: &'a [u8],
205 fn_index: &'a HashMap<String, usize>,
206 bindings: Vec<Binding>,
207 uses: HashMap<u32, BindingId>,
208 calls: HashMap<u32, usize>,
209 scopes: Vec<HashMap<String, BindingId>>,
210 function: usize,
211}
212
213impl Resolver<'_> {
214 fn declare(
215 &mut self,
216 name: String,
217 mutable: bool,
218 ty: Type,
219 def_offset: u32,
220 ) -> Result<(), RustSemaError> {
221 let id = self.bindings.len();
222 self.bindings.push(Binding {
223 name: name.clone(),
224 mutable,
225 ty,
226 def_offset,
227 function: self.function,
228 });
229 let scope = self
230 .scopes
231 .last_mut()
232 .ok_or_else(|| RustSemaError::InternalInvariant {
233 message:
234 "resolver scope stack was empty while declaring a binding; seed the function scope before resolution"
235 .to_string(),
236 })?;
237 scope.insert(name, id);
238 Ok(())
239 }
240
241 fn lookup(&self, name: &str) -> Option<BindingId> {
242 self.scopes
243 .iter()
244 .rev()
245 .find_map(|frame| frame.get(name).copied())
246 }
247
248 fn resolve_expr(&mut self, expr: &Expr) -> Result<(), RustSemaError> {
249 match expr {
250 Expr::LiteralInt(..) | Expr::LiteralBool(..) => Ok(()),
251 Expr::Var(offset) => {
252 let name = ident_at(self.source, *offset);
253 match self.lookup(&name) {
254 Some(id) => {
255 self.uses.insert(*offset, id);
256 Ok(())
257 }
258 None => Err(RustSemaError::UnresolvedName {
259 name,
260 offset: *offset,
261 }),
262 }
263 }
264 Expr::Binary { lhs, rhs, .. } => {
265 self.resolve_expr(lhs)?;
266 self.resolve_expr(rhs)
267 }
268 Expr::Borrow { expr, .. } => self.resolve_expr(expr),
269 Expr::Deref(inner) => self.resolve_expr(inner),
270 Expr::Not(inner) => self.resolve_expr(inner),
271 Expr::Neg(inner) => self.resolve_expr(inner),
272 Expr::Call { name, args } => {
273 let fname = ident_at(self.source, *name);
274 match self.fn_index.get(&fname) {
275 Some(&idx) => {
276 self.calls.insert(*name, idx);
277 }
278 None => {
279 return Err(RustSemaError::UnknownFunction {
280 name: fname,
281 offset: *name,
282 })
283 }
284 }
285 for arg in args {
286 self.resolve_expr(arg)?;
287 }
288 Ok(())
289 }
290 Expr::Block(stmts) => {
291 self.scopes.push(HashMap::new());
292 let result = self.resolve_block(stmts);
293 self.scopes.pop();
294 result
295 }
296 Expr::If {
297 cond,
298 then_block,
299 else_block,
300 } => {
301 self.resolve_expr(cond)?;
302 self.resolve_expr(then_block)?;
303 if let Some(else_block) = else_block {
304 self.resolve_expr(else_block)?;
305 }
306 Ok(())
307 }
308 }
309 }
310
311 fn resolve_block(&mut self, stmts: &[Stmt]) -> Result<(), RustSemaError> {
312 for stmt in stmts {
313 match stmt {
314 Stmt::Let {
315 mutable,
316 name,
317 ty,
318 init,
319 } => {
320 self.resolve_expr(init)?;
321 let recovered = ident_at(self.source, *name);
322 self.declare(recovered, *mutable, ty.clone(), *name)?;
323 }
324 Stmt::Expr(expr) => self.resolve_expr(expr)?,
325 Stmt::Assign { name, value } => {
326 self.resolve_expr(value)?;
327 let n = ident_at(self.source, *name);
328 match self.lookup(&n) {
329 Some(id) => {
330 self.uses.insert(*name, id);
331 }
332 None => {
333 return Err(RustSemaError::UnresolvedName {
334 name: n,
335 offset: *name,
336 })
337 }
338 }
339 }
340 Stmt::Return(Some(expr)) => self.resolve_expr(expr)?,
341 Stmt::Return(None) => {}
342 Stmt::While { cond, body } => {
343 self.resolve_expr(cond)?;
344 self.scopes.push(HashMap::new());
345 let r = self.resolve_block(body);
346 self.scopes.pop();
347 r?;
348 }
349 Stmt::For {
350 name,
351 start,
352 end,
353 body,
354 } => {
355 self.resolve_expr(start)?;
356 self.resolve_expr(end)?;
357 self.scopes.push(HashMap::new());
358 let recovered = ident_at(self.source, *name);
359 self.declare(recovered, false, Type::I32, *name)?;
360 let r = self.resolve_block(body);
361 self.scopes.pop();
362 r?;
363 }
364 }
365 }
366 Ok(())
367 }
368}
369
370pub fn resolve(module: &Module, source: &[u8]) -> Result<Resolution, RustSemaError> {
380 let fn_index: HashMap<String, usize> = module
381 .functions
382 .iter()
383 .enumerate()
384 .map(|(i, f)| (ident_at(source, f.name), i))
385 .collect();
386
387 let mut resolver = Resolver {
388 source,
389 fn_index: &fn_index,
390 bindings: Vec::new(),
391 uses: HashMap::new(),
392 calls: HashMap::new(),
393 scopes: Vec::new(),
394 function: 0,
395 };
396
397 for (index, func) in module.functions.iter().enumerate() {
398 resolver.function = index;
399 resolver.scopes = vec![HashMap::new()];
400 for (offset, ty) in &func.params {
401 let name = ident_at(source, *offset);
402 resolver.declare(name, false, ty.clone(), *offset)?;
403 }
404 resolver.resolve_block(&func.body)?;
405 }
406
407 Ok(Resolution {
408 bindings: resolver.bindings,
409 uses: resolver.uses,
410 calls: resolver.calls,
411 })
412}
413
414struct FnSig {
419 params: Vec<Type>,
420 ret: Type,
421}
422
423struct TypeCk<'a> {
424 source: &'a [u8],
425 resolution: &'a Resolution,
426 sigs: &'a HashMap<String, FnSig>,
427 ret: &'a Type,
428}
429
430impl TypeCk<'_> {
431 fn type_of(&self, expr: &Expr) -> Result<Type, RustSemaError> {
432 match expr {
433 Expr::LiteralInt(..) => Ok(Type::I32),
434 Expr::LiteralBool(..) => Ok(Type::Bool),
435 Expr::Var(offset) => {
436 let id = *self.resolution.uses.get(offset).ok_or_else(|| {
437 RustSemaError::InternalInvariant {
438 message: format!(
439 "resolve did not record variable use at byte {offset} before typeck"
440 ),
441 }
442 })?;
443 Ok(self.resolution.bindings[id].ty.clone())
444 }
445 Expr::Binary { op, lhs, rhs } => {
446 let lt = self.type_of(lhs)?;
447 let rt = self.type_of(rhs)?;
448 match *op {
449 PLUS | MINUS | STAR | SLASH | PERCENT => {
450 self.require(<, &Type::I32, "arithmetic operand")?;
451 self.require(&rt, &Type::I32, "arithmetic operand")?;
452 Ok(Type::I32)
453 }
454 LT | GT | LE | GE => {
455 self.require(<, &Type::I32, "comparison operand")?;
456 self.require(&rt, &Type::I32, "comparison operand")?;
457 Ok(Type::Bool)
458 }
459 EQ | NE => {
460 if lt != rt {
461 return Err(RustSemaError::TypeMismatch {
462 context: "equality operands".to_string(),
463 expected: type_str(<),
464 found: type_str(&rt),
465 });
466 }
467 Ok(Type::Bool)
468 }
469 ANDAND | OROR => {
470 self.require(<, &Type::Bool, "logical operand")?;
471 self.require(&rt, &Type::Bool, "logical operand")?;
472 Ok(Type::Bool)
473 }
474 _ => Ok(Type::I32),
475 }
476 }
477 Expr::Borrow { mutable, expr } => {
478 let inner = self.type_of(expr)?;
479 Ok(Type::Ref {
480 mutable: *mutable,
481 inner: Box::new(inner),
482 })
483 }
484 Expr::Deref(inner) => match self.type_of(inner)? {
485 Type::Ref { inner, .. } => Ok(*inner),
486 other => Err(RustSemaError::CannotDeref {
487 found: type_str(&other),
488 }),
489 },
490 Expr::Not(inner) => {
491 let it = self.type_of(inner)?;
492 self.require(&it, &Type::Bool, "logical-not operand")?;
493 Ok(Type::Bool)
494 }
495 Expr::Neg(inner) => {
496 let it = self.type_of(inner)?;
497 self.require(&it, &Type::I32, "arithmetic-negation operand")?;
498 Ok(Type::I32)
499 }
500 Expr::Call { name, args } => {
501 let fname = ident_at(self.source, *name);
502 let sig = self
503 .sigs
504 .get(&fname)
505 .ok_or(RustSemaError::UnknownFunction {
506 name: fname.clone(),
507 offset: *name,
508 })?;
509 if args.len() != sig.params.len() {
510 return Err(RustSemaError::ArgCountMismatch {
511 function: fname,
512 expected: sig.params.len(),
513 found: args.len(),
514 });
515 }
516 for (arg, param_ty) in args.iter().zip(&sig.params) {
517 let at = self.type_of(arg)?;
518 self.require(&at, param_ty, "function argument")?;
519 }
520 Ok(sig.ret.clone())
521 }
522 Expr::Block(stmts) => {
523 self.check_block(stmts)?;
524 Ok(Type::Unit)
525 }
526 Expr::If {
527 cond,
528 then_block,
529 else_block,
530 } => {
531 let ct = self.type_of(cond)?;
532 if ct != Type::Bool {
533 return Err(RustSemaError::NonBooleanCondition {
534 found: type_str(&ct),
535 });
536 }
537 let tt = self.type_of(then_block)?;
538 let et = match else_block {
539 Some(else_block) => self.type_of(else_block)?,
540 None => Type::Unit,
541 };
542 if tt != et {
543 return Err(RustSemaError::TypeMismatch {
544 context: "if/else branches".to_string(),
545 expected: type_str(&tt),
546 found: type_str(&et),
547 });
548 }
549 Ok(tt)
550 }
551 }
552 }
553
554 fn require(&self, found: &Type, expected: &Type, context: &str) -> Result<(), RustSemaError> {
555 if coerces(found, expected) {
556 Ok(())
557 } else {
558 Err(RustSemaError::TypeMismatch {
559 context: context.to_string(),
560 expected: type_str(expected),
561 found: type_str(found),
562 })
563 }
564 }
565
566 fn check_block(&self, stmts: &[Stmt]) -> Result<(), RustSemaError> {
567 for stmt in stmts {
568 match stmt {
569 Stmt::Let { ty, init, .. } => {
570 let it = self.type_of(init)?;
571 self.require(&it, ty, "let binding")?;
572 }
573 Stmt::Expr(expr) => {
574 self.type_of(expr)?;
575 }
576 Stmt::Assign { name, value } => {
577 let id = self.resolution.uses[name];
578 let (mutable, target_ty, bname) = {
579 let b = &self.resolution.bindings[id];
580 (b.mutable, b.ty.clone(), b.name.clone())
581 };
582 if !mutable {
583 return Err(RustSemaError::AssignToImmutable { name: bname });
584 }
585 let vt = self.type_of(value)?;
586 self.require(&vt, &target_ty, "assignment")?;
587 }
588 Stmt::Return(Some(expr)) => {
589 let rt = self.type_of(expr)?;
590 self.require(&rt, self.ret, "return value")?;
591 }
592 Stmt::Return(None) => {
593 self.require(&Type::Unit, self.ret, "return value")?;
594 }
595 Stmt::While { cond, body } => {
596 let ct = self.type_of(cond)?;
597 if ct != Type::Bool {
598 return Err(RustSemaError::NonBooleanCondition {
599 found: type_str(&ct),
600 });
601 }
602 self.check_block(body)?;
603 }
604 Stmt::For {
605 start, end, body, ..
606 } => {
607 let st = self.type_of(start)?;
608 self.require(&st, &Type::I32, "for range start")?;
609 let et = self.type_of(end)?;
610 self.require(&et, &Type::I32, "for range end")?;
611 self.check_block(body)?;
612 }
613 }
614 }
615 Ok(())
616 }
617}
618
619pub fn typeck(
628 module: &Module,
629 source: &[u8],
630 resolution: &Resolution,
631) -> Result<(), RustSemaError> {
632 let sigs: HashMap<String, FnSig> = module
633 .functions
634 .iter()
635 .map(|f| {
636 (
637 ident_at(source, f.name),
638 FnSig {
639 params: f.params.iter().map(|(_, t)| t.clone()).collect(),
640 ret: f.ret.clone(),
641 },
642 )
643 })
644 .collect();
645
646 for func in &module.functions {
647 let ck = TypeCk {
648 source,
649 resolution,
650 sigs: &sigs,
651 ret: &func.ret,
652 };
653 ck.check_block(&func.body)?;
654 if func.ret != Type::Unit && !block_diverges(&func.body) {
655 return Err(RustSemaError::MissingReturn {
656 function: ident_at(source, func.name),
657 expected: type_str(&func.ret),
658 });
659 }
660 }
661 Ok(())
662}
663
664fn block_diverges(stmts: &[Stmt]) -> bool {
666 stmts.iter().any(stmt_diverges)
667}
668
669fn stmt_diverges(stmt: &Stmt) -> bool {
670 match stmt {
671 Stmt::Return(_) => true,
672 Stmt::Expr(expr) => expr_diverges(expr),
673 Stmt::Assign { .. } => false,
674 Stmt::While { .. } => false,
675 Stmt::For { .. } => false,
676 Stmt::Let { init, .. } => expr_diverges(init),
677 }
678}
679
680fn expr_diverges(expr: &Expr) -> bool {
681 match expr {
682 Expr::Block(stmts) => block_diverges(stmts),
683 Expr::If {
684 then_block,
685 else_block: Some(else_block),
686 ..
687 } => expr_diverges(then_block) && expr_diverges(else_block),
688 _ => false,
689 }
690}
691
692pub fn borrow_check(module: &Module, resolution: &Resolution) -> Result<(), RustSemaError> {
709 check_mutability(module, resolution)?;
710 check_escape(module, resolution)?;
711 check_conflicts(module, resolution)?;
712 Ok(())
713}
714
715pub fn check_mutability(module: &Module, resolution: &Resolution) -> Result<(), RustSemaError> {
724 for func in &module.functions {
725 check_mut_stmts(&func.body, resolution)?;
726 }
727 Ok(())
728}
729
730fn check_mut_stmts(stmts: &[Stmt], resolution: &Resolution) -> Result<(), RustSemaError> {
731 for stmt in stmts {
732 match stmt {
733 Stmt::Let { init, .. } => check_mut_expr(init, resolution)?,
734 Stmt::Expr(expr) => check_mut_expr(expr, resolution)?,
735 Stmt::Assign { value, .. } => check_mut_expr(value, resolution)?,
736 Stmt::While { cond, body } => {
737 check_mut_expr(cond, resolution)?;
738 check_mut_stmts(body, resolution)?;
739 }
740 Stmt::For {
741 start, end, body, ..
742 } => {
743 check_mut_expr(start, resolution)?;
744 check_mut_expr(end, resolution)?;
745 check_mut_stmts(body, resolution)?;
746 }
747 Stmt::Return(Some(expr)) => check_mut_expr(expr, resolution)?,
748 Stmt::Return(None) => {}
749 }
750 }
751 Ok(())
752}
753
754fn check_mut_expr(expr: &Expr, resolution: &Resolution) -> Result<(), RustSemaError> {
755 match expr {
756 Expr::Borrow { mutable, expr } => {
757 if *mutable {
758 check_mutable_place(expr, resolution)?;
759 }
760 check_mut_expr(expr, resolution)
761 }
762 Expr::Binary { lhs, rhs, .. } => {
763 check_mut_expr(lhs, resolution)?;
764 check_mut_expr(rhs, resolution)
765 }
766 Expr::Deref(inner) => check_mut_expr(inner, resolution),
767 Expr::Not(inner) => check_mut_expr(inner, resolution),
768 Expr::Neg(inner) => check_mut_expr(inner, resolution),
769 Expr::Call { args, .. } => {
770 for arg in args {
771 check_mut_expr(arg, resolution)?;
772 }
773 Ok(())
774 }
775 Expr::Block(stmts) => check_mut_stmts(stmts, resolution),
776 Expr::If {
777 cond,
778 then_block,
779 else_block,
780 } => {
781 check_mut_expr(cond, resolution)?;
782 check_mut_expr(then_block, resolution)?;
783 if let Some(else_block) = else_block {
784 check_mut_expr(else_block, resolution)?;
785 }
786 Ok(())
787 }
788 Expr::LiteralInt(..) | Expr::LiteralBool(..) | Expr::Var(..) => Ok(()),
789 }
790}
791
792fn check_mutable_place(place: &Expr, resolution: &Resolution) -> Result<(), RustSemaError> {
794 match place {
795 Expr::Var(offset) => {
796 if let Some(&id) = resolution.uses.get(offset) {
797 let binding = &resolution.bindings[id];
798 if !binding.mutable {
799 return Err(RustSemaError::CannotBorrowImmutableAsMutable {
800 name: binding.name.clone(),
801 offset: *offset,
802 });
803 }
804 }
805 Ok(())
806 }
807 Expr::Deref(inner) => {
808 if let Expr::Var(offset) = inner.as_ref() {
809 if let Some(&id) = resolution.uses.get(offset) {
810 let binding = &resolution.bindings[id];
811 if let Type::Ref { mutable: false, .. } = binding.ty {
812 return Err(RustSemaError::CannotBorrowImmutableAsMutable {
813 name: binding.name.clone(),
814 offset: *offset,
815 });
816 }
817 }
818 }
819 Ok(())
820 }
821 _ => Ok(()),
822 }
823}
824
825pub fn check_escape(module: &Module, resolution: &Resolution) -> Result<(), RustSemaError> {
837 let def_to_id: HashMap<u32, BindingId> = resolution
838 .bindings
839 .iter()
840 .enumerate()
841 .map(|(id, b)| (b.def_offset, id))
842 .collect();
843
844 for func in &module.functions {
845 let returns_ref = matches!(func.ret, Type::Ref { .. });
846 let mut borrows_local: HashMap<BindingId, bool> = HashMap::new();
850 for (offset, _ty) in &func.params {
851 if let Some(&id) = def_to_id.get(offset) {
852 borrows_local.insert(id, false);
853 }
854 }
855 walk_escape(
856 &func.body,
857 returns_ref,
858 &def_to_id,
859 resolution,
860 &mut borrows_local,
861 )?;
862 }
863 Ok(())
864}
865
866fn walk_escape(
867 stmts: &[Stmt],
868 returns_ref: bool,
869 def_to_id: &HashMap<u32, BindingId>,
870 resolution: &Resolution,
871 borrows_local: &mut HashMap<BindingId, bool>,
872) -> Result<(), RustSemaError> {
873 for stmt in stmts {
874 match stmt {
875 Stmt::Let { name, ty, init, .. } => {
876 if let Some(&id) = def_to_id.get(name) {
877 let escapes = matches!(ty, Type::Ref { .. })
878 && escapes_offset(init, resolution, borrows_local).is_some();
879 borrows_local.insert(id, escapes);
880 }
881 descend_escape(init, returns_ref, def_to_id, resolution, borrows_local)?;
882 }
883 Stmt::Expr(expr) => {
884 descend_escape(expr, returns_ref, def_to_id, resolution, borrows_local)?;
885 }
886 Stmt::Assign { value, .. } => {
887 descend_escape(value, returns_ref, def_to_id, resolution, borrows_local)?;
888 }
889 Stmt::While { cond, body } => {
890 descend_escape(cond, returns_ref, def_to_id, resolution, borrows_local)?;
891 walk_escape(body, returns_ref, def_to_id, resolution, borrows_local)?;
892 }
893 Stmt::For {
894 start, end, body, ..
895 } => {
896 descend_escape(start, returns_ref, def_to_id, resolution, borrows_local)?;
897 descend_escape(end, returns_ref, def_to_id, resolution, borrows_local)?;
898 walk_escape(body, returns_ref, def_to_id, resolution, borrows_local)?;
899 }
900 Stmt::Return(Some(expr)) => {
901 if returns_ref {
902 if let Some(offset) = escapes_offset(expr, resolution, borrows_local) {
903 return Err(RustSemaError::ReturnsReferenceToLocal { offset });
904 }
905 }
906 descend_escape(expr, returns_ref, def_to_id, resolution, borrows_local)?;
907 }
908 Stmt::Return(None) => {}
909 }
910 }
911 Ok(())
912}
913
914fn descend_escape(
915 expr: &Expr,
916 returns_ref: bool,
917 def_to_id: &HashMap<u32, BindingId>,
918 resolution: &Resolution,
919 borrows_local: &mut HashMap<BindingId, bool>,
920) -> Result<(), RustSemaError> {
921 match expr {
922 Expr::Block(stmts) => walk_escape(stmts, returns_ref, def_to_id, resolution, borrows_local),
923 Expr::If {
924 cond,
925 then_block,
926 else_block,
927 } => {
928 descend_escape(cond, returns_ref, def_to_id, resolution, borrows_local)?;
929 descend_escape(
930 then_block,
931 returns_ref,
932 def_to_id,
933 resolution,
934 borrows_local,
935 )?;
936 if let Some(else_block) = else_block {
937 descend_escape(
938 else_block,
939 returns_ref,
940 def_to_id,
941 resolution,
942 borrows_local,
943 )?;
944 }
945 Ok(())
946 }
947 Expr::Binary { lhs, rhs, .. } => {
948 descend_escape(lhs, returns_ref, def_to_id, resolution, borrows_local)?;
949 descend_escape(rhs, returns_ref, def_to_id, resolution, borrows_local)
950 }
951 Expr::Borrow { expr, .. } => {
952 descend_escape(expr, returns_ref, def_to_id, resolution, borrows_local)
953 }
954 Expr::Deref(inner) => {
955 descend_escape(inner, returns_ref, def_to_id, resolution, borrows_local)
956 }
957 Expr::Not(inner) => {
958 descend_escape(inner, returns_ref, def_to_id, resolution, borrows_local)
959 }
960 Expr::Neg(inner) => {
961 descend_escape(inner, returns_ref, def_to_id, resolution, borrows_local)
962 }
963 Expr::Call { args, .. } => {
964 for arg in args {
965 descend_escape(arg, returns_ref, def_to_id, resolution, borrows_local)?;
966 }
967 Ok(())
968 }
969 Expr::Var(..) | Expr::LiteralInt(..) | Expr::LiteralBool(..) => Ok(()),
970 }
971}
972
973fn escapes_offset(
976 expr: &Expr,
977 resolution: &Resolution,
978 borrows_local: &HashMap<BindingId, bool>,
979) -> Option<u32> {
980 match expr {
981 Expr::Borrow { expr, .. } => match expr.as_ref() {
982 Expr::Var(offset) => Some(*offset),
984 Expr::Deref(inner) => {
986 if let Expr::Var(offset) = inner.as_ref() {
987 let id = resolution.uses.get(offset)?;
988 if *borrows_local.get(id).unwrap_or(&false) {
989 Some(*offset)
990 } else {
991 None
992 }
993 } else {
994 None
995 }
996 }
997 _ => None,
998 },
999 Expr::Var(offset) => {
1001 let id = resolution.uses.get(offset)?;
1002 if *borrows_local.get(id).unwrap_or(&false) {
1003 Some(*offset)
1004 } else {
1005 None
1006 }
1007 }
1008 _ => None,
1009 }
1010}
1011
1012pub fn check_conflicts(module: &Module, resolution: &Resolution) -> Result<(), RustSemaError> {
1030 use crate::{analyze_borrow_facts as analyze, ConflictKind};
1031
1032 let def_to_id: HashMap<u32, BindingId> = resolution
1033 .bindings
1034 .iter()
1035 .enumerate()
1036 .map(|(id, b)| (b.def_offset, id))
1037 .collect();
1038
1039 for func in &module.functions {
1040 let facts = build_borrow_facts(func, resolution, &def_to_id);
1041 if let Some(conflict) = analyze(&facts).into_iter().next() {
1042 return Err(match conflict.kind {
1043 ConflictKind::TwoMutable => RustSemaError::MultipleMutableBorrows {
1044 offset: conflict.offset,
1045 },
1046 ConflictKind::MutableAndShared => RustSemaError::MutableAndSharedBorrow {
1047 offset: conflict.offset,
1048 },
1049 });
1050 }
1051 }
1052 Ok(())
1053}
1054
1055fn build_borrow_facts(
1058 func: &super::parse::Function,
1059 resolution: &Resolution,
1060 def_to_id: &HashMap<u32, BindingId>,
1061) -> crate::borrowck::BorrowFacts {
1062 let mut builder = FactBuilder {
1063 resolution,
1064 def_to_id,
1065 facts: crate::borrowck::BorrowFacts::default(),
1066 binding_to_loan: HashMap::new(),
1067 };
1068 builder.build_block(&func.body, &[]);
1069 builder.facts
1070}
1071
1072struct FactBuilder<'a> {
1073 resolution: &'a Resolution,
1074 def_to_id: &'a HashMap<u32, BindingId>,
1075 facts: crate::borrowck::BorrowFacts,
1076 binding_to_loan: HashMap<BindingId, crate::borrowck::Loan>,
1077}
1078
1079impl FactBuilder<'_> {
1080 fn alloc_point(&mut self) -> u32 {
1081 let point = self.facts.point_count;
1082 self.facts.point_count += 1;
1083 point
1084 }
1085
1086 fn build_block(&mut self, stmts: &[Stmt], preds: &[u32]) -> Vec<u32> {
1089 let mut cur: Vec<u32> = preds.to_vec();
1090 for stmt in stmts {
1091 let point = self.alloc_point();
1092 for &pred in &cur {
1093 self.facts.cfg_edges.push((pred, point));
1094 }
1095 match stmt {
1096 Stmt::Let { name, ty, init, .. } => {
1097 self.record_uses(init, point);
1098 self.record_loan(name, ty, init, point);
1099 cur = vec![point];
1100 }
1101 Stmt::Return(value) => {
1102 if let Some(expr) = value {
1103 self.record_uses(expr, point);
1104 }
1105 cur = Vec::new();
1106 }
1107 Stmt::Assign { value, .. } => {
1108 self.record_uses(value, point);
1109 cur = vec![point];
1110 }
1111 Stmt::While { cond, body } => {
1112 self.record_uses(cond, point);
1113 let out = self.build_block(body, &[point]);
1114 for &b in &out {
1115 self.facts.cfg_edges.push((b, point));
1116 }
1117 cur = vec![point];
1118 }
1119 Stmt::For {
1120 start, end, body, ..
1121 } => {
1122 self.record_uses(start, point);
1123 self.record_uses(end, point);
1124 let out = self.build_block(body, &[point]);
1125 for &b in &out {
1126 self.facts.cfg_edges.push((b, point));
1127 }
1128 cur = vec![point];
1129 }
1130 Stmt::Expr(Expr::If {
1131 cond,
1132 then_block,
1133 else_block,
1134 }) => {
1135 self.record_uses(cond, point);
1136 let mut out = self.build_block(block_stmts(then_block), &[point]);
1137 match else_block {
1138 Some(else_block) => {
1139 out.extend(self.build_block(block_stmts(else_block), &[point]))
1140 }
1141 None => out.push(point),
1142 }
1143 cur = out;
1144 }
1145 Stmt::Expr(expr) => {
1146 self.record_uses(expr, point);
1147 cur = vec![point];
1148 }
1149 }
1150 }
1151 cur
1152 }
1153
1154 fn record_loan(&mut self, name: &u32, ty: &Type, init: &Expr, point: u32) {
1164 let (place_off, mutable) = match init {
1165 Expr::Borrow { mutable, expr } => {
1166 let off = match expr.as_ref() {
1167 Expr::Var(off) => Some(*off),
1168 Expr::Deref(inner) => match inner.as_ref() {
1169 Expr::Var(off) => Some(*off),
1170 _ => None,
1171 },
1172 _ => None,
1173 };
1174 match off {
1175 Some(off) => (off, *mutable),
1176 None => return,
1177 }
1178 }
1179 Expr::Var(off) => match ty {
1180 Type::Ref { mutable, .. } => (*off, *mutable),
1181 _ => return,
1182 },
1183 _ => return,
1184 };
1185 if let (Some(&place), Some(&binding)) = (
1186 self.resolution.uses.get(&place_off),
1187 self.def_to_id.get(name),
1188 ) {
1189 let loan = self.facts.loan_place.len() as crate::borrowck::Loan;
1190 self.facts.loan_place.push(place as crate::borrowck::Place);
1191 self.facts.loan_kind.push(if mutable {
1192 crate::borrowck::LoanKind::Mut
1193 } else {
1194 crate::borrowck::LoanKind::Shared
1195 });
1196 self.facts.loan_issued_at.push(point);
1197 self.facts.loan_offset.push(*name);
1198 self.binding_to_loan.insert(binding, loan);
1199 }
1200 }
1201
1202 fn record_uses(&mut self, expr: &Expr, point: u32) {
1205 let mut used = Vec::new();
1206 collect_expr_uses(expr, self.resolution, &mut used);
1207 for binding in used {
1208 if let Some(&loan) = self.binding_to_loan.get(&binding) {
1209 self.facts.loan_used_at.push((loan, point));
1210 }
1211 }
1212 }
1213}
1214
1215fn block_stmts(expr: &Expr) -> &[Stmt] {
1217 match expr {
1218 Expr::Block(stmts) => stmts,
1219 _ => &[],
1220 }
1221}
1222
1223fn collect_expr_uses(expr: &Expr, resolution: &Resolution, into: &mut Vec<BindingId>) {
1224 match expr {
1225 Expr::Var(off) => {
1226 if let Some(&id) = resolution.uses.get(off) {
1227 into.push(id);
1228 }
1229 }
1230 Expr::Binary { lhs, rhs, .. } => {
1231 collect_expr_uses(lhs, resolution, into);
1232 collect_expr_uses(rhs, resolution, into);
1233 }
1234 Expr::Borrow { expr, .. } => collect_expr_uses(expr, resolution, into),
1235 Expr::Deref(inner) => collect_expr_uses(inner, resolution, into),
1236 Expr::Not(inner) => collect_expr_uses(inner, resolution, into),
1237 Expr::Neg(inner) => collect_expr_uses(inner, resolution, into),
1238 Expr::Call { args, .. } => {
1239 for arg in args {
1240 collect_expr_uses(arg, resolution, into);
1241 }
1242 }
1243 Expr::Block(..) | Expr::If { .. } | Expr::LiteralInt(..) | Expr::LiteralBool(..) => {}
1246 }
1247}