@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
22
33use crate :: checks;
44use crate :: diagnostic:: { Diagnostic , code} ;
5- use crate :: hir:: { DuplicateSymbolKind , Hir , HirTypeKind } ;
5+ use crate :: hir:: { CallResolution , DuplicateSymbolKind , Hir , HirTypeKind , ResolvedCalleeKind } ;
66use crate :: interfaces:: CORE_INTERFACES ;
77use crate :: lexer:: { Token , lex} ;
88use crate :: syntax:: ast:: {
@@ -73,6 +73,7 @@ impl Analyzer<'_> {
7373 self . check_duplicate_declarations ( ) ;
7474 self . check_signature_explicitness ( ) ;
7575 self . check_generic_constraints ( ) ;
76+ self . check_pure_bodies ( ) ;
7677 self . check_noalloc_allocations ( ) ;
7778 self . check_try_operator_result_returns ( ) ;
7879 self . check_resource_fields ( ) ;
@@ -747,6 +748,95 @@ impl Analyzer<'_> {
747748 }
748749 }
749750
751+ fn check_pure_bodies ( & mut self ) {
752+ let items = self . syntax_program . items . clone ( ) ;
753+ for item in & items {
754+ let Item :: Function ( function) = item else {
755+ continue ;
756+ } ;
757+ if !function_has_effect ( function, "pure" ) {
758+ continue ;
759+ }
760+ self . check_pure_block ( & function. name , & function. body ) ;
761+ }
762+ }
763+
764+ fn check_pure_block ( & mut self , function_name : & str , block : & Block ) {
765+ for statement in & block. statements {
766+ self . check_pure_stmt ( function_name, statement) ;
767+ }
768+ }
769+
770+ fn check_pure_stmt ( & mut self , function_name : & str , statement : & Stmt ) {
771+ match statement {
772+ Stmt :: Let ( stmt) => {
773+ if let Some ( value) = & stmt. value {
774+ self . check_pure_expr ( function_name, value) ;
775+ }
776+ }
777+ Stmt :: Return ( stmt) => {
778+ if let Some ( value) = & stmt. value {
779+ self . check_pure_expr ( function_name, value) ;
780+ }
781+ }
782+ Stmt :: Expr ( value) => self . check_pure_expr ( function_name, value) ,
783+ Stmt :: With ( stmt) => {
784+ self . check_pure_expr ( function_name, & stmt. resource ) ;
785+ self . check_pure_block ( function_name, & stmt. body ) ;
786+ }
787+ Stmt :: If ( stmt) => {
788+ self . check_pure_expr ( function_name, & stmt. condition ) ;
789+ self . check_pure_block ( function_name, & stmt. then_body ) ;
790+ if let Some ( else_body) = & stmt. else_body {
791+ self . check_pure_block ( function_name, else_body) ;
792+ }
793+ }
794+ Stmt :: Loop ( stmt) => {
795+ if let Some ( condition) = & stmt. condition {
796+ self . check_pure_expr ( function_name, condition) ;
797+ }
798+ self . check_pure_block ( function_name, & stmt. body ) ;
799+ }
800+ Stmt :: Break ( _) | Stmt :: Continue ( _) | Stmt :: Unknown ( _) => { }
801+ }
802+ }
803+
804+ fn check_pure_expr ( & mut self , function_name : & str , expr : & Expr ) {
805+ match expr {
806+ Expr :: Call {
807+ callee, args, span, ..
808+ } => {
809+ match self . hir . resolve_call ( callee) {
810+ CallResolution :: Resolved { signature, kind } => {
811+ if !matches ! ( kind, ResolvedCalleeKind :: Constructor { .. } )
812+ && !signature. effects . iter ( ) . any ( |effect| effect == "pure" )
813+ {
814+ self . non_pure_call_diagnostic ( function_name, callee, span) ;
815+ }
816+ }
817+ CallResolution :: EnumVariant | CallResolution :: Unknown => { }
818+ }
819+ for arg in args {
820+ self . check_pure_expr ( function_name, & arg. value ) ;
821+ }
822+ }
823+ Expr :: Effect { value, .. } | Expr :: Manage { value, .. } | Expr :: Try { value, .. } => {
824+ self . check_pure_expr ( function_name, value) ;
825+ }
826+ Expr :: Binary { left, right, .. } => {
827+ self . check_pure_expr ( function_name, left) ;
828+ self . check_pure_expr ( function_name, right) ;
829+ }
830+ Expr :: Field { base, .. } => self . check_pure_expr ( function_name, base) ,
831+ Expr :: Index { base, index, .. } => {
832+ self . check_pure_expr ( function_name, base) ;
833+ self . check_pure_expr ( function_name, index) ;
834+ }
835+ Expr :: Closure { body, .. } => self . check_pure_block ( function_name, body) ,
836+ Expr :: Ident ( _, _) | Expr :: Number ( _, _) | Expr :: String ( _, _) | Expr :: Unknown ( _) => { }
837+ }
838+ }
839+
750840 fn check_duplicate_declarations ( & mut self ) {
751841 for duplicate in self . hir . duplicate_symbols ( ) {
752842 self . diagnostics . push (
@@ -1126,6 +1216,33 @@ impl Analyzer<'_> {
11261216 ) ,
11271217 ) ;
11281218 }
1219+
1220+ fn non_pure_call_diagnostic (
1221+ & mut self ,
1222+ function_name : & str ,
1223+ callee : & Callee ,
1224+ span : & crate :: diagnostic:: Span ,
1225+ ) {
1226+ self . diagnostics . push (
1227+ Diagnostic :: error (
1228+ code:: INVALID_PURE_EFFECT ,
1229+ format ! (
1230+ "`{function_name}` is declared pure but calls non-pure function `{}`." ,
1231+ callee_display( callee)
1232+ ) ,
1233+ span. clone ( ) ,
1234+ "non-pure call in pure function" ,
1235+ )
1236+ . with_cause (
1237+ "A `pure` function may only call constructors, enum variants, or functions also declared `effects(pure)`." ,
1238+ )
1239+ . with_fix (
1240+ "remove_pure_or_call_pure" ,
1241+ "Remove `pure`, or call only APIs whose signatures are declared `effects(pure)`." ,
1242+ "manual" ,
1243+ ) ,
1244+ ) ;
1245+ }
11291246}
11301247
11311248fn resource_pool_namespace_arg ( namespace : & str ) -> Option < & str > {
@@ -1195,6 +1312,13 @@ fn function_has_effect(function: &crate::syntax::ast::FunctionDecl, effect_name:
11951312 . any ( |effect| matches ! ( effect, EffectDecl :: Name ( name) if name == effect_name) )
11961313}
11971314
1315+ fn callee_display ( callee : & Callee ) -> String {
1316+ match callee {
1317+ Callee :: Name ( name) => name. clone ( ) ,
1318+ Callee :: Qualified { namespace, name } => format ! ( "{namespace}.{name}" ) ,
1319+ }
1320+ }
1321+
11981322fn removed_runtime_effect_replacement ( effect_name : & str ) -> Option < & ' static str > {
11991323 match effect_name {
12001324 "io" => Some (
0 commit comments