@@ -560,160 +560,15 @@ impl ResolveSymbols {
560560 context. node_types . insert ( lit. id , ty) ;
561561 Ok ( ty)
562562 }
563- Expression :: Identifier ( ident) => {
564- let name = ident. name . clone ( ) ;
565- if name == "self" {
566- if context. current_model . is_none ( ) {
567- let diag = Diagnostic :: new (
568- DiagnosticLevel :: Error ,
569- ErrorCode :: E0320 ,
570- "Cannot reference 'self' outside model context" . to_string ( ) ,
571- ident. span ,
572- ) ;
573- context. diagnostics . push ( diag) ;
574- return Err ( ( ) ) ;
575- }
576- return Ok ( context. interner . any ( ) ) ;
577- }
578-
579- if let Some ( symbol) = context. symbol_table . lookup ( & name) {
580- return Ok ( symbol. type_id ) ;
581- }
582-
583- // Unresolved variable: compute Levenshtein suggestion on-demand
584- let suggestion = find_suggestion ( & name, context) ;
585- let message = if let Some ( suggest) = suggestion {
586- format ! ( "Undefined variable '{}'. Did you mean '{}'?" , name, suggest)
587- } else {
588- format ! ( "Undefined variable '{}'" , name)
589- } ;
590-
591- let diag = Diagnostic :: new (
592- DiagnosticLevel :: Error ,
593- ErrorCode :: E0300 ,
594- message,
595- ident. span ,
596- ) ;
597- context. diagnostics . push ( diag) ;
598- Err ( ( ) )
599- }
600- Expression :: Binary ( bin) => {
601- if bin. op == "?." {
602- let _left_ty = self . resolve_expression ( & bin. left , context) ?;
603- match & * bin. right {
604- Expression :: Identifier ( _) => { }
605- Expression :: Call ( call) => {
606- if let Expression :: Identifier ( _) = * call. callee {
607- // Member call
608- } else {
609- let _ = self . resolve_expression ( & call. callee , context) ;
610- }
611- for arg in & call. args {
612- let _ = self . resolve_expression ( arg, context) ;
613- }
614- }
615- Expression :: Member ( mem) => {
616- let _ = self . resolve_expression ( & mem. object , context) ;
617- }
618- other => {
619- let _ = self . resolve_expression ( other, context) ;
620- }
621- }
622- let res_ty = context. interner . any ( ) ;
623- context. node_types . insert ( bin. id , res_ty) ;
624- return Ok ( res_ty) ;
625- }
626-
627- let left_ty = self . resolve_expression ( & bin. left , context) ?;
628- let right_ty = self . resolve_expression ( & bin. right , context) ?;
629-
630- // Implicit numeric coercions: if either operand is Float, output is Float.
631- let res_ty = if left_ty == context. interner . float ( )
632- || right_ty == context. interner . float ( )
633- || bin. op == "/"
634- {
635- context. interner . float ( )
636- } else if bin. op == "//" {
637- context. interner . int ( )
638- } else {
639- left_ty
640- } ;
641- context. node_types . insert ( bin. id , res_ty) ;
642- Ok ( res_ty)
643- }
563+ Expression :: Identifier ( ident) => self . resolve_identifier ( ident, context) ,
564+ Expression :: Binary ( bin) => self . resolve_binary ( bin, context) ,
644565 Expression :: Unary ( un) => {
645566 let ty = self . resolve_expression ( & un. right , context) ?;
646567 context. node_types . insert ( un. id , ty) ;
647568 Ok ( ty)
648569 }
649- Expression :: Assignment ( assign) => {
650- if let Expression :: Identifier ( ref ident) = * assign. target {
651- if context. symbol_table . lookup ( & ident. name ) . is_none ( ) {
652- // Automatically declare the variable on first assignment
653- let symbol = Symbol :: new (
654- ident. name . clone ( ) ,
655- false , // is_constant
656- false , // is_function
657- false , // is_type
658- context. interner . any ( ) ,
659- ) ;
660- context. symbol_table . register ( ident. name . clone ( ) , symbol) ;
661- } else {
662- // Check mutation of constant variable
663- if let Some ( symbol) = context. symbol_table . lookup ( & ident. name ) {
664- if symbol. is_constant {
665- let diag = Diagnostic :: new (
666- DiagnosticLevel :: Error ,
667- ErrorCode :: E0302 ,
668- format ! ( "Cannot reassign constant variable '{}'" , ident. name) ,
669- ident. span ,
670- ) ;
671- context. diagnostics . push ( diag) ;
672- return Err ( ( ) ) ;
673- }
674- }
675- }
676- }
677-
678- let _left_ty = self . resolve_expression ( & assign. target , context) ?;
679- let right_ty = self . resolve_expression ( & assign. value , context) ?;
680- context. node_types . insert ( assign. id , right_ty) ;
681- Ok ( right_ty)
682- }
683- Expression :: Call ( call) => {
684- let _callee_ty = self . resolve_expression ( & call. callee , context) ?;
685-
686- // Validate call arity check if callee is a known hoisted function
687- if let Expression :: Identifier ( ref ident) = * call. callee {
688- if let Some ( symbol) = context. symbol_table . lookup ( & ident. name ) {
689- if symbol. is_function {
690- let function_type = context. interner . get ( symbol. type_id ) ;
691- if let Type :: Function { ref params, .. } = function_type {
692- // For TechScript 2.0 simple checking, let's compare argument counts.
693- // (min, max checking can be detailed as we expand)
694- // Runtime callables know their required/minimum arity;
695- // this preserves default-parameter functions.
696- if call. args . len ( ) > params. len ( ) {
697- let diag = Diagnostic :: new (
698- DiagnosticLevel :: Error ,
699- ErrorCode :: E0311 ,
700- format ! ( "Too many arguments in call to '{}'. Expected {}, found {}" , ident. name, params. len( ) , call. args. len( ) ) ,
701- call. span ,
702- ) ;
703- context. diagnostics . push ( diag) ;
704- }
705- }
706- }
707- }
708- }
709-
710- for arg in & call. args {
711- let _ = self . resolve_expression ( arg, context) ;
712- }
713-
714- context. node_types . insert ( call. id , context. interner . any ( ) ) ;
715- Ok ( context. interner . any ( ) )
716- }
570+ Expression :: Assignment ( assign) => self . resolve_assignment ( assign, context) ,
571+ Expression :: Call ( call) => self . resolve_call ( call, context) ,
717572 Expression :: Member ( mem) => {
718573 let _ = self . resolve_expression ( & mem. object , context) ?;
719574 context. node_types . insert ( mem. id , context. interner . any ( ) ) ;
@@ -784,6 +639,180 @@ impl ResolveSymbols {
784639 _ => Ok ( context. interner . any ( ) ) ,
785640 }
786641 }
642+
643+ fn resolve_identifier (
644+ & self ,
645+ ident : & techscript_common:: Ident ,
646+ context : & mut SemanticContext ,
647+ ) -> Result < crate :: types:: TypeId , ( ) > {
648+ let name = ident. name . clone ( ) ;
649+ if name == "self" {
650+ if context. current_model . is_none ( ) {
651+ let diag = Diagnostic :: new (
652+ DiagnosticLevel :: Error ,
653+ ErrorCode :: E0320 ,
654+ "Cannot reference 'self' outside model context" . to_string ( ) ,
655+ ident. span ,
656+ ) ;
657+ context. diagnostics . push ( diag) ;
658+ return Err ( ( ) ) ;
659+ }
660+ return Ok ( context. interner . any ( ) ) ;
661+ }
662+
663+ if let Some ( symbol) = context. symbol_table . lookup ( & name) {
664+ return Ok ( symbol. type_id ) ;
665+ }
666+
667+ // Unresolved variable: compute Levenshtein suggestion on-demand
668+ let suggestion = find_suggestion ( & name, context) ;
669+ let message = if let Some ( suggest) = suggestion {
670+ format ! ( "Undefined variable '{}'. Did you mean '{}'?" , name, suggest)
671+ } else {
672+ format ! ( "Undefined variable '{}'" , name)
673+ } ;
674+
675+ let diag = Diagnostic :: new (
676+ DiagnosticLevel :: Error ,
677+ ErrorCode :: E0300 ,
678+ message,
679+ ident. span ,
680+ ) ;
681+ context. diagnostics . push ( diag) ;
682+ Err ( ( ) )
683+ }
684+
685+ fn resolve_binary (
686+ & self ,
687+ bin : & techscript_ast:: BinaryExpr ,
688+ context : & mut SemanticContext ,
689+ ) -> Result < crate :: types:: TypeId , ( ) > {
690+ if bin. op == "?." {
691+ let _left_ty = self . resolve_expression ( & bin. left , context) ?;
692+ match & * bin. right {
693+ Expression :: Identifier ( _) => { }
694+ Expression :: Call ( call) => {
695+ if let Expression :: Identifier ( _) = * call. callee {
696+ // Member call
697+ } else {
698+ let _ = self . resolve_expression ( & call. callee , context) ;
699+ }
700+ for arg in & call. args {
701+ let _ = self . resolve_expression ( arg, context) ;
702+ }
703+ }
704+ Expression :: Member ( mem) => {
705+ let _ = self . resolve_expression ( & mem. object , context) ;
706+ }
707+ other => {
708+ let _ = self . resolve_expression ( other, context) ;
709+ }
710+ }
711+ let res_ty = context. interner . any ( ) ;
712+ context. node_types . insert ( bin. id , res_ty) ;
713+ return Ok ( res_ty) ;
714+ }
715+
716+ let left_ty = self . resolve_expression ( & bin. left , context) ?;
717+ let right_ty = self . resolve_expression ( & bin. right , context) ?;
718+
719+ // Implicit numeric coercions: if either operand is Float, output is Float.
720+ let res_ty = if left_ty == context. interner . float ( )
721+ || right_ty == context. interner . float ( )
722+ || bin. op == "/"
723+ {
724+ context. interner . float ( )
725+ } else if bin. op == "//" {
726+ context. interner . int ( )
727+ } else {
728+ left_ty
729+ } ;
730+ context. node_types . insert ( bin. id , res_ty) ;
731+ Ok ( res_ty)
732+ }
733+
734+ fn resolve_assignment (
735+ & self ,
736+ assign : & techscript_ast:: AssignmentExpr ,
737+ context : & mut SemanticContext ,
738+ ) -> Result < crate :: types:: TypeId , ( ) > {
739+ if let Expression :: Identifier ( ref ident) = * assign. target {
740+ if context. symbol_table . lookup ( & ident. name ) . is_none ( ) {
741+ // Automatically declare the variable on first assignment
742+ let symbol = Symbol :: new (
743+ ident. name . clone ( ) ,
744+ false , // is_constant
745+ false , // is_function
746+ false , // is_type
747+ context. interner . any ( ) ,
748+ ) ;
749+ context. symbol_table . register ( ident. name . clone ( ) , symbol) ;
750+ } else {
751+ // Check mutation of constant variable
752+ if let Some ( symbol) = context. symbol_table . lookup ( & ident. name ) {
753+ if symbol. is_constant {
754+ let diag = Diagnostic :: new (
755+ DiagnosticLevel :: Error ,
756+ ErrorCode :: E0302 ,
757+ format ! ( "Cannot reassign constant variable '{}'" , ident. name) ,
758+ ident. span ,
759+ ) ;
760+ context. diagnostics . push ( diag) ;
761+ return Err ( ( ) ) ;
762+ }
763+ }
764+ }
765+ }
766+
767+ let _left_ty = self . resolve_expression ( & assign. target , context) ?;
768+ let right_ty = self . resolve_expression ( & assign. value , context) ?;
769+ context. node_types . insert ( assign. id , right_ty) ;
770+ Ok ( right_ty)
771+ }
772+
773+ fn resolve_call (
774+ & self ,
775+ call : & techscript_ast:: CallExpr ,
776+ context : & mut SemanticContext ,
777+ ) -> Result < crate :: types:: TypeId , ( ) > {
778+ let _callee_ty = self . resolve_expression ( & call. callee , context) ?;
779+
780+ // Validate call arity check if callee is a known hoisted function
781+ if let Expression :: Identifier ( ref ident) = * call. callee {
782+ if let Some ( symbol) = context. symbol_table . lookup ( & ident. name ) {
783+ if symbol. is_function {
784+ let function_type = context. interner . get ( symbol. type_id ) ;
785+ if let Type :: Function { ref params, .. } = function_type {
786+ // For TechScript 2.0 simple checking, let's compare argument counts.
787+ // (min, max checking can be detailed as we expand)
788+ // Runtime callables know their required/minimum arity;
789+ // this preserves default-parameter functions.
790+ if call. args . len ( ) > params. len ( ) {
791+ let diag = Diagnostic :: new (
792+ DiagnosticLevel :: Error ,
793+ ErrorCode :: E0311 ,
794+ format ! (
795+ "Too many arguments in call to '{}'. Expected {}, found {}" ,
796+ ident. name,
797+ params. len( ) ,
798+ call. args. len( )
799+ ) ,
800+ call. span ,
801+ ) ;
802+ context. diagnostics . push ( diag) ;
803+ }
804+ }
805+ }
806+ }
807+ }
808+
809+ for arg in & call. args {
810+ let _ = self . resolve_expression ( arg, context) ;
811+ }
812+
813+ context. node_types . insert ( call. id , context. interner . any ( ) ) ;
814+ Ok ( context. interner . any ( ) )
815+ }
787816}
788817
789818#[ allow( clippy:: needless_range_loop) ]
0 commit comments