@@ -30,6 +30,12 @@ impl<'a> Checker<'a> {
3030 st : Option < & FnState > ,
3131 ) {
3232 let resolved = self . resolve_type_alias ( ty) ;
33+ if let TypeExpr :: Option { elem, .. } = & resolved {
34+ if let Some ( inner) = self . unwrap_some_call_arg ( value, st) {
35+ self . check_const_overflow_init ( name, elem, inner, st) ;
36+ }
37+ return ;
38+ }
3339 let TypeExpr :: Named {
3440 name : ty_name,
3541 span : ty_span,
@@ -39,6 +45,7 @@ impl<'a> Checker<'a> {
3945 } ;
4046 if ty_name == "any" || ty_name == "interface{}" {
4147 self . check_default_int_overflow ( name, value, st) ;
48+ self . check_default_float_overflow ( name, value, st) ;
4249 return ;
4350 }
4451 if let Some ( is_f32) = is_float_type ( ty_name) {
@@ -187,6 +194,12 @@ impl<'a> Checker<'a> {
187194 st : Option < & FnState > ,
188195 ) {
189196 let resolved = self . resolve_type_alias ( ty) ;
197+ if let TypeExpr :: Option { elem, .. } = & resolved {
198+ if let Some ( inner) = self . unwrap_some_call_arg ( value, st) {
199+ self . check_const_overflow_assign ( target, elem, inner, st) ;
200+ }
201+ return ;
202+ }
190203 let TypeExpr :: Named {
191204 name : ty_name,
192205 span : ty_span,
@@ -196,6 +209,7 @@ impl<'a> Checker<'a> {
196209 } ;
197210 if ty_name == "any" || ty_name == "interface{}" {
198211 self . check_default_int_overflow ( target, value, st) ;
212+ self . check_default_float_overflow ( target, value, st) ;
199213 return ;
200214 }
201215 if let Some ( is_f32) = is_float_type ( ty_name) {
@@ -333,10 +347,9 @@ impl<'a> Checker<'a> {
333347 value : & Expr ,
334348 st : Option < & FnState > ,
335349 ) {
336- if let Some ( st) = st {
337- if self . fixed_width_int_ty_of ( st, value) . is_some ( ) {
338- return ;
339- }
350+ let value = self . unwrap_some_call_arg ( value, st) . unwrap_or ( value) ;
351+ if self . has_explicit_fixed_int_ty ( st, value) {
352+ return ;
340353 }
341354 let ( lo, hi) = target_int_range ( ) ;
342355 match self . const_int_fold_expr ( value, st) {
@@ -346,6 +359,9 @@ impl<'a> Checker<'a> {
346359 }
347360
348361 let span = value. span ( ) ;
362+ if self . diags . iter ( ) . any ( |d| d. code == "HYB-E6002" && d. span == span) {
363+ return ;
364+ }
349365 let msg = format ! (
350366 "constant expression `{}` overflows default type `int` of `{}` (HYB-E6002)" ,
351367 value. render( ) ,
@@ -372,12 +388,16 @@ impl<'a> Checker<'a> {
372388 value : & Expr ,
373389 st : Option < & FnState > ,
374390 ) {
375- if !is_float_const_expr ( value, & self . const_values , 0 , st) {
391+ let value = self . unwrap_some_call_arg ( value, st) . unwrap_or ( value) ;
392+ if self . expr_has_local_or_binder ( value) || !is_float_const_expr ( value, & self . const_values , 0 , st) {
376393 return ;
377394 }
378395 if let Some ( f) = self . const_float_fold ( value, st) {
379396 if f. is_infinite ( ) {
380397 let span = value. span ( ) ;
398+ if self . diags . iter ( ) . any ( |d| d. code == "HYB-E6002" && d. span == span) {
399+ return ;
400+ }
381401 let msg = format ! (
382402 "constant expression `{}` overflows default type `float64` of `{}` (HYB-E6002)" ,
383403 value. render( ) ,
@@ -459,7 +479,9 @@ impl<'a> Checker<'a> {
459479 }
460480 return self . const_int_fold_at ( init_expr, st, depth + 1 ) ;
461481 }
462- if st. is_some_and ( |s| s. env_lookup ( name) . is_some ( ) ) {
482+ if st. is_some_and ( |s| s. env_lookup ( name) . is_some ( ) )
483+ || self . is_lexical_binder_at ( name, id_span. start )
484+ {
463485 return ConstFold :: NotConst ;
464486 }
465487 if let Some ( v) = self . const_values . get ( name) {
@@ -685,16 +707,13 @@ impl<'a> Checker<'a> {
685707 self . check_expr_type_conversions ( hi) ;
686708 }
687709 }
688- Expr :: Call {
689- callee,
690- args,
691- span,
692- } => {
710+ Expr :: Call { callee, args, span } => {
693711 self . check_expr_type_conversions ( callee) ;
694712 for a in args {
695713 self . check_expr_type_conversions ( & a. value ) ;
696714 }
697715 self . check_conversion_call ( callee, args, * span) ;
716+ self . check_any_call_arg_overflow ( callee, args) ;
698717 }
699718 Expr :: StructLit { fields, .. } => {
700719 for f in fields {
@@ -758,9 +777,17 @@ impl<'a> Checker<'a> {
758777 return ;
759778 }
760779 }
761- let Expr :: Ident { name, .. } = callee else {
780+ let Expr :: Ident { name, span : id_span } = callee else {
762781 return ;
763782 } ;
783+ if matches ! ( self . resolve_type_alias( & TypeExpr :: Named { name: name. clone( ) , span: * id_span } ) , TypeExpr :: Named { name: ref rn, .. } if matches!( rn. as_str( ) , "any" | "interface{}" ) )
784+ && !self . is_shadowed_ident_at ( name, id_span. start )
785+ {
786+ let desc = format ! ( "conversion to `{}`" , name) ;
787+ self . check_default_int_overflow ( & desc, arg, None ) ;
788+ self . check_default_float_overflow ( & desc, arg, None ) ;
789+ return ;
790+ }
764791 if !is_conversion_target ( name) {
765792 return ;
766793 }
@@ -770,6 +797,136 @@ impl<'a> Checker<'a> {
770797 self . check_conv_against_target ( name, & c, arg, span) ;
771798 }
772799
800+ fn unwrap_some_call_arg < ' b > ( & self , expr : & ' b Expr , st : Option < & FnState > ) -> Option < & ' b Expr > {
801+ let Expr :: Call { callee, args, .. } = unwrap_parens ( expr) else { return None } ;
802+ if args. len ( ) != 1 || args[ 0 ] . spread { return None ; }
803+ let is_some = match unwrap_parens ( callee) {
804+ Expr :: Ident { name, span } if name == "some" => st. map_or ( true , |s| s. env_lookup ( "some" ) . is_none ( ) ) && !self . is_shadowed_ident_at ( "some" , span. start ) ,
805+ Expr :: Field { base, name, .. } if name == "some" => matches ! ( unwrap_parens( base) , Expr :: Ident { name: pkg, span } if pkg == "option" && st. map_or( true , |s| s. env_lookup( "option" ) . is_none( ) ) && !self . is_shadowed_ident_at( "option" , span. start) ) ,
806+ _ => false ,
807+ } ;
808+ is_some. then_some ( & args[ 0 ] . value )
809+ }
810+
811+ fn has_explicit_fixed_int_ty ( & self , st : Option < & FnState > , e : & Expr ) -> bool {
812+ self . has_explicit_fixed_int_ty_at ( st, e, 0 )
813+ }
814+
815+ fn has_explicit_fixed_int_ty_at ( & self , st : Option < & FnState > , e : & Expr , depth : u32 ) -> bool {
816+ if depth >= 16 { return false ; }
817+ if st. is_some_and ( |st| self . fixed_width_int_ty_of ( st, e) . is_some ( ) ) { return true ; }
818+ let is_fixed = |ty : & TypeExpr | matches ! ( self . resolve_type_alias( ty) , TypeExpr :: Named { name: ref tn, .. } if fixed_width_int_range( tn) . is_some( ) ) ;
819+ match unwrap_parens ( e) {
820+ Expr :: Call { callee, args, .. } if args. len ( ) == 1 && !args[ 0 ] . spread => {
821+ let Expr :: Ident { name, span } = unwrap_parens ( callee) else { return false } ;
822+ !self . is_shadowed_ident_at ( name, span. start ) && is_fixed ( & TypeExpr :: Named { name : name. clone ( ) , span : * span } )
823+ }
824+ Expr :: Ident { name, span } => {
825+ if let Some ( sight) = self . resolve_local_binding_at ( name, span. start ) {
826+ if let Some ( ty) = & sight. ty { return is_fixed ( ty) ; }
827+ return sight. constant_initializer ( ) . is_some_and ( |init| ( sight. pos . line , sight. pos . col ) != ( span. start . line , span. start . col ) && self . has_explicit_fixed_int_ty_at ( st, init, depth + 1 ) ) ;
828+ }
829+ if st. is_some_and ( |s| s. env_lookup ( name) . is_some ( ) ) || self . is_lexical_binder_at ( name, span. start ) { return false ; }
830+ self . file . decls . iter ( ) . any ( |d| matches ! ( d, hy_syntax:: Decl :: Const ( c) if c. name == * name && c. ty. as_ref( ) . map_or_else( || self . has_explicit_fixed_int_ty_at( st, & c. value, depth + 1 ) , is_fixed) ) )
831+ }
832+ Expr :: Unary { op : UnOp :: Neg , operand, .. } => self . has_explicit_fixed_int_ty_at ( st, operand, depth + 1 ) ,
833+ Expr :: Binary { op : BinOp :: Shl | BinOp :: Shr , lhs, .. } => self . has_explicit_fixed_int_ty_at ( st, lhs, depth + 1 ) ,
834+ Expr :: Binary { lhs, rhs, .. } => self . has_explicit_fixed_int_ty_at ( st, lhs, depth + 1 ) || self . has_explicit_fixed_int_ty_at ( st, rhs, depth + 1 ) ,
835+ _ => false ,
836+ }
837+ }
838+
839+ fn is_lexical_binder_at ( & self , name : & str , use_pos : hy_diag:: Pos ) -> bool {
840+ fn has ( s : Span , p : hy_diag:: Pos ) -> bool { ( p. line , p. col ) >= ( s. start . line , s. start . col ) && ( p. line , p. col ) <= ( s. end . line , s. end . col ) }
841+ fn expr_binds ( e : & Expr , name : & str , p : hy_diag:: Pos ) -> bool {
842+ has ( e. span ( ) , p) && match e {
843+ Expr :: FnLit { params, results, body, .. } => params. iter ( ) . any ( |x| x. name == name) || results. iter ( ) . any ( |r| r. name . as_deref ( ) == Some ( name) ) || block_binds ( body, name, p) ,
844+ Expr :: Binary { lhs, rhs, .. } | Expr :: Index { base : lhs, index : rhs, .. } => expr_binds ( lhs, name, p) || expr_binds ( rhs, name, p) ,
845+ Expr :: Unary { operand : x, .. } | Expr :: AddrOf { operand : x, .. } | Expr :: Move { operand : x, .. } | Expr :: Try { operand : x, .. } | Expr :: Paren { inner : x, .. } | Expr :: Field { base : x, .. } | Expr :: Receive { chan : x, .. } => expr_binds ( x, name, p) ,
846+ Expr :: Slice { base, lo, hi, .. } => expr_binds ( base, name, p) || lo. as_deref ( ) . is_some_and ( |x| expr_binds ( x, name, p) ) || hi. as_deref ( ) . is_some_and ( |x| expr_binds ( x, name, p) ) ,
847+ Expr :: Call { callee, args, .. } => expr_binds ( callee, name, p) || args. iter ( ) . any ( |a| expr_binds ( & a. value , name, p) ) ,
848+ Expr :: StructLit { fields, .. } => fields. iter ( ) . any ( |f| expr_binds ( & f. value , name, p) ) ,
849+ Expr :: SliceLit { items, .. } => items. iter ( ) . any ( |i| expr_binds ( i, name, p) ) ,
850+ Expr :: MapLit { entries, .. } => entries. iter ( ) . any ( |( k, v) | expr_binds ( k, name, p) || expr_binds ( v, name, p) ) ,
851+ Expr :: Interp { parts, .. } => parts. iter ( ) . any ( |pt| matches ! ( pt, InterpPart :: Expr ( x) if expr_binds( x, name, p) ) ) ,
852+ Expr :: Ident { .. } | Expr :: Lit { .. } => false ,
853+ }
854+ }
855+ fn block_binds ( b : & hy_syntax:: Block , name : & str , p : hy_diag:: Pos ) -> bool { b. stmts . iter ( ) . any ( |s| stmt_binds ( s, name, p) ) }
856+ fn stmt_binds ( s : & Stmt , name : & str , p : hy_diag:: Pos ) -> bool {
857+ match s {
858+ Stmt :: Local ( l) => l. values . iter ( ) . any ( |v| expr_binds ( v, name, p) ) ,
859+ Stmt :: Assign { values, .. } | Stmt :: Return { values, .. } => values. iter ( ) . any ( |v| expr_binds ( v, name, p) ) ,
860+ Stmt :: Expr { expr : x, .. } | Stmt :: Defer { call : x, .. } => expr_binds ( x, name, p) ,
861+ Stmt :: Send { chan, value, .. } => expr_binds ( chan, name, p) || expr_binds ( value, name, p) ,
862+ Stmt :: Task { callee, args, .. } => expr_binds ( callee, name, p) || args. iter ( ) . any ( |a| expr_binds ( & a. value , name, p) ) ,
863+ Stmt :: If { init, cond, then, otherwise, span } if has ( * span, p) => {
864+ init. as_deref ( ) . is_some_and ( |i| matches ! ( i, Stmt :: Local ( l) if l. names. iter( ) . any( |n| n == name) ) || stmt_binds ( i, name, p) ) || expr_binds ( cond, name, p) || ( has ( then. span , p) && block_binds ( then, name, p) ) || otherwise. as_deref ( ) . is_some_and ( |o| stmt_binds ( o, name, p) )
865+ }
866+ Stmt :: For { kind, body, span, .. } if has ( * span, p) => {
867+ let bound = match kind {
868+ ForKind :: Range { var, lo, hi, .. } => expr_binds ( lo, name, p) || expr_binds ( hi, name, p) || ( has ( body. span , p) && var == name) ,
869+ ForKind :: Iterate { vars, expr } => expr_binds ( expr, name, p) || ( has ( body. span , p) && vars. iter ( ) . any ( |v| v == name) ) ,
870+ ForKind :: While { cond } => expr_binds ( cond, name, p) ,
871+ ForKind :: Infinite => false ,
872+ } ;
873+ bound || ( has ( body. span , p) && block_binds ( body, name, p) )
874+ }
875+ Stmt :: Match { subject, arms, span } if has ( * span, p) => expr_binds ( subject, name, p) || arms. iter ( ) . any ( |a| ( has ( a. body . span , p) || a. guard . as_ref ( ) . is_some_and ( |g| has ( g. span ( ) , p) ) ) && ( matches ! ( & a. pattern, hy_syntax:: MatchPattern :: Path { binding: Some ( b) , .. } if b == name) || a. guard . as_ref ( ) . is_some_and ( |g| expr_binds ( g, name, p) ) || block_binds ( & a. body , name, p) ) ) ,
876+ Stmt :: Select { arms, span } if has ( * span, p) => arms. iter ( ) . any ( |a| has ( a. body . span , p) && ( matches ! ( & a. kind, SelectArmKind :: Recv { names, .. } if names. iter( ) . any( |n| n == name) ) || block_binds ( & a. body , name, p) ) ) ,
877+ Stmt :: Managed { body, span, .. } | Stmt :: Block { block : body, span, .. } if has ( * span, p) => block_binds ( body, name, p) ,
878+ _ => false ,
879+ }
880+ }
881+ self . file . decls . iter ( ) . any ( |d| matches ! ( d, hy_syntax:: Decl :: Fn ( f) if has( f. span, use_pos) && ( f. receiver. as_ref( ) . is_some_and( |r| r. name == name) || f. params. iter( ) . any( |p| p. name == name) || f. results. iter( ) . any( |r| r. name. as_deref( ) == Some ( name) ) || f. body. as_ref( ) . is_some_and( |b| block_binds( b, name, use_pos) ) ) ) )
882+ }
883+
884+ fn expr_has_local_or_binder ( & self , e : & Expr ) -> bool {
885+ match unwrap_parens ( e) {
886+ Expr :: Ident { name, span } => self . resolve_local_binding_at ( name, span. start ) . is_some ( ) || self . is_lexical_binder_at ( name, span. start ) ,
887+ Expr :: Unary { operand, .. } => self . expr_has_local_or_binder ( operand) ,
888+ Expr :: Binary { lhs, rhs, .. } => self . expr_has_local_or_binder ( lhs) || self . expr_has_local_or_binder ( rhs) ,
889+ _ => false ,
890+ }
891+ }
892+
893+ fn is_shadowed_ident_at ( & self , name : & str , use_pos : hy_diag:: Pos ) -> bool {
894+ self . resolve_local_binding_at ( name, use_pos) . is_some ( ) || self . is_lexical_binder_at ( name, use_pos) || self . fns . contains_key ( name) || self . const_values . contains_key ( name)
895+ }
896+
897+ fn check_any_call_arg_overflow ( & mut self , callee : & Expr , args : & [ CallArg ] ) {
898+ match unwrap_parens ( callee) {
899+ Expr :: Ident { name, span } if matches ! ( name. as_str( ) , "print" | "println" | "panic" ) && !self . is_shadowed_ident_at ( name, span. start ) => {
900+ let desc = format ! ( "argument to `{}`" , name) ;
901+ for a in args. iter ( ) . filter ( |a| !a. spread ) {
902+ self . check_default_int_overflow ( & desc, & a. value , None ) ;
903+ self . check_default_float_overflow ( & desc, & a. value , None ) ;
904+ }
905+ }
906+ Expr :: Field { base, name : member, .. } => {
907+ let Expr :: Ident { name : pkg, span : pkg_span } = unwrap_parens ( base) else { return } ;
908+ if self . is_shadowed_ident_at ( pkg, pkg_span. start ) { return ; }
909+ let m = member. to_ascii_lowercase ( ) ;
910+ let skip = match ( pkg. as_str ( ) , m. as_str ( ) ) {
911+ ( "fmt" , "print" | "println" | "sprint" | "sprintln" | "eprint" | "eprintln" ) => Some ( 0 ) ,
912+ ( "fmt" , "printf" | "sprintf" | "eprintf" | "errorf" | "fprint" | "fprintln" | "append" | "appendln" ) => Some ( 1 ) ,
913+ ( "fmt" , "fprintf" | "appendf" ) => Some ( 2 ) ,
914+ ( "log" , "print" | "println" | "fatal" | "fatalln" | "panic" | "panicln" ) => Some ( 0 ) ,
915+ ( "log" , "printf" | "fatalf" | "panicf" ) => Some ( 1 ) ,
916+ _ => None ,
917+ } ;
918+ if let Some ( skip) = skip {
919+ let desc = format ! ( "argument to `{}.{}`" , pkg, member) ;
920+ for a in args. iter ( ) . skip ( skip) . filter ( |a| !a. spread ) {
921+ self . check_default_int_overflow ( & desc, & a. value , None ) ;
922+ self . check_default_float_overflow ( & desc, & a. value , None ) ;
923+ }
924+ }
925+ }
926+ _ => { }
927+ }
928+ }
929+
773930 fn check_slice_conversion ( & mut self , elem : & TypeExpr , arg : & Expr , span : Span ) {
774931 let TypeExpr :: Named { name : slice_elem, .. } = elem else {
775932 return ; // []T with a non-named element + a run-time operand
0 commit comments