Skip to content

Commit c519844

Browse files
authored
Merge pull request #43 from Tcode-Motion/refactor/resolve-expression-1067453118403306836
🧹 refactor(semantic): extract complex resolution arms into helper methods
2 parents ddf52e1 + 7474a64 commit c519844

1 file changed

Lines changed: 178 additions & 149 deletions

File tree

‎compiler/semantic/src/passes/resolve.rs‎

Lines changed: 178 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)