Skip to content

Commit 9bd1eec

Browse files
committed
fix: function return values, for-loop range(), and proper diagnostic system
- Fix user-defined function return values showing as opaque: infer Dynamic return type when function has `return <expr>` but no type annotation - Fix for-loop range() infinite loop: desugar `for i in range(start, end)` into C-style loop in TypedCfgBuilder - Fix void/returning function detection: use HirFunction signature as source of truth instead of original_return_type - Replace all log::warn! in lowering.rs with proper Diagnostic system from typed_ast/diagnostics.rs (DiagnosticCollector, ConsoleDiagnosticDisplay) - Add W0001/W0002 warning codes for missing param/return type annotations - Display lowering diagnostics after lower_program in both compiler and runtime paths - Suppress internal compiler warnings (symbol table, generic type params) from end-user output - Fix hello.zynml and hello_no_fstring.zynml (replace undefined println_any)
1 parent 07f30f3 commit 9bd1eec

8 files changed

Lines changed: 463 additions & 148 deletions

File tree

crates/compiler/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,9 @@ pub fn compile_to_hir(
14361436
);
14371437
let mut hir_module = lowering_ctx.lower_program(program)?;
14381438

1439+
// Display any lowering diagnostics (warnings/errors) using the proper formatter
1440+
lowering_ctx.display_diagnostics(program);
1441+
14391442
// Step 2: Async transformation (if async runtime is configured)
14401443
if let Some(runtime_type) = config.async_runtime {
14411444
// Transform async functions into state machines

crates/compiler/src/lowering.rs

Lines changed: 176 additions & 78 deletions
Large diffs are not rendered by default.

crates/compiler/src/ssa.rs

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ pub struct SsaBuilder {
100100
/// Maps function name -> Vec<TypedParameter> (for filling in defaults at call sites).
101101
function_default_params:
102102
IndexMap<InternedString, Vec<zyntax_typed_ast::typed_ast::TypedParameter>>,
103+
/// Return types for user-defined functions.
104+
/// Maps function name -> Type (for resolving call expression types when parser sets Unit).
105+
function_return_types: IndexMap<InternedString, Type>,
103106
}
104107

105108
/// Context for pattern matching
@@ -348,6 +351,7 @@ impl SsaBuilder {
348351
compute_yield_stack: Vec::new(),
349352
simd_continue_block: None,
350353
function_default_params: IndexMap::new(),
354+
function_return_types: IndexMap::new(),
351355
}
352356
}
353357

@@ -385,6 +389,7 @@ impl SsaBuilder {
385389
compute_yield_stack: Vec::new(),
386390
simd_continue_block: None,
387391
function_default_params: IndexMap::new(),
392+
function_return_types: IndexMap::new(),
388393
function,
389394
};
390395
// Pre-register all existing blocks in the definitions map
@@ -442,6 +447,15 @@ impl SsaBuilder {
442447
self
443448
}
444449

450+
/// Set return types for user-defined functions (for call-site type resolution)
451+
pub fn with_function_return_types(
452+
mut self,
453+
return_types: IndexMap<InternedString, Type>,
454+
) -> Self {
455+
self.function_return_types = return_types;
456+
self
457+
}
458+
445459
/// Build SSA form from CFG
446460
pub fn build_from_cfg(mut self, cfg: &ControlFlowGraph) -> CompilerResult<SsaForm> {
447461
// Initialize blocks
@@ -843,15 +857,14 @@ impl SsaBuilder {
843857
}
844858
}
845859

846-
// Check if the function returns void - if so, ignore the expression value
847-
let is_void_return = matches!(
848-
&self.original_return_type,
849-
Some(Type::Primitive(zyntax_typed_ast::PrimitiveType::Unit)) | None
850-
);
860+
// Check whether the function actually returns a value.
861+
// The HirFunction signature is the source of truth — it was set by
862+
// convert_function_signature which already detects `return <expr>`
863+
// in functions with no type annotation.
864+
let is_void_return = self.function.signature.returns.is_empty();
851865

852866
// Check if the expression set a continuation block (for control flow expressions)
853867
if let Some(continuation) = self.continuation_block.take() {
854-
// Control flow expression - set Return on continuation block, not entry block
855868
let cont_block =
856869
self.function.blocks.get_mut(&continuation).ok_or_else(|| {
857870
crate::CompilerError::Analysis(
@@ -865,18 +878,12 @@ impl SsaBuilder {
865878
values: vec![value_id],
866879
}
867880
};
868-
869-
// Entry block already has correct terminator (Branch/CondBranch), so return None
870-
// to signal that we shouldn't overwrite it
871881
return Ok(());
882+
} else if is_void_return {
883+
HirTerminator::Return { values: vec![] }
872884
} else {
873-
// Regular expression - return normally from entry block
874-
if is_void_return {
875-
HirTerminator::Return { values: vec![] }
876-
} else {
877-
HirTerminator::Return {
878-
values: vec![value_id],
879-
}
885+
HirTerminator::Return {
886+
values: vec![value_id],
880887
}
881888
}
882889
} else {
@@ -3001,8 +3008,43 @@ impl SsaBuilder {
30013008
.map(|ty| self.convert_type(ty))
30023009
.collect();
30033010

3004-
// Create call instruction
3005-
let result_type = self.convert_type(&expr.ty);
3011+
// Create call instruction — resolve return type from function table
3012+
// when the parser hasn't propagated it (expr.ty is Unit/Unknown/Any)
3013+
let result_type = {
3014+
let raw = self.convert_type(&expr.ty);
3015+
if matches!(raw, HirType::Void) || matches!(&expr.ty, Type::Unknown | Type::Any) {
3016+
// Try to resolve from function_return_types
3017+
let from_table = callee_func_key.and_then(|fk| {
3018+
self.function_return_types.get(&fk).map(|rt| self.convert_type(rt))
3019+
});
3020+
if let Some(resolved) = from_table {
3021+
if resolved != HirType::Void {
3022+
resolved
3023+
} else {
3024+
// Parser defaulted to Unit — for user-defined functions
3025+
// that aren't known to be void (println, eprintln, etc.),
3026+
// default to I64 since all Cranelift calls return i64
3027+
let is_builtin_void = callee_func_key.map_or(false, |fk| {
3028+
let name = fk.resolve_global().unwrap_or_default();
3029+
matches!(
3030+
name.as_str(),
3031+
"println" | "print" | "eprintln" | "eprint"
3032+
)
3033+
});
3034+
if is_builtin_void {
3035+
raw
3036+
} else {
3037+
// User function with no return type annotation — assume I64
3038+
HirType::I64
3039+
}
3040+
}
3041+
} else {
3042+
raw
3043+
}
3044+
} else {
3045+
raw
3046+
}
3047+
};
30063048
let result = if result_type != HirType::Void {
30073049
Some(self.create_value(result_type.clone(), HirValueKind::Instruction))
30083050
} else {

crates/compiler/src/typed_cfg.rs

Lines changed: 202 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -744,46 +744,214 @@ impl TypedCfgBuilder {
744744
}
745745

746746
TypedStatement::For(for_stmt) => {
747-
// For-each loop (separate statement type)
748-
// Same structure as TypedLoop::ForEach
749-
let header_id = self.new_block_id();
750-
let body_id = self.new_block_id();
751-
let after_id = self.new_block_id();
747+
// Try to desugar `for i in range(start, end)` into a C-style for loop.
748+
// Extract loop variable name from pattern.
749+
let loop_var = match &for_stmt.pattern.node {
750+
TypedPattern::Identifier { name, .. } => Some(*name),
751+
_ => None,
752+
};
752753

753-
all_blocks.push(TypedBasicBlock {
754-
id: current_block_id,
755-
label: None,
756-
statements: current_statements.clone(),
757-
terminator: TypedTerminator::Jump(header_id),
758-
pattern_check: None,
759-
});
754+
// Detect `range(start, end)` or `range(start, end, step)` call patterns.
755+
let range_args = match &for_stmt.iterator.node {
756+
TypedExpression::Call(call) => {
757+
let is_range = match &call.callee.node {
758+
TypedExpression::Variable(name) => {
759+
let mut arena =
760+
zyntax_typed_ast::arena::AstArena::new();
761+
let range_sym = arena.intern_string("range");
762+
*name == range_sym
763+
}
764+
_ => false,
765+
};
766+
if is_range && (call.positional_args.len() == 2 || call.positional_args.len() == 3) {
767+
Some(call.positional_args.clone())
768+
} else {
769+
None
770+
}
771+
}
772+
_ => None,
773+
};
760774

761-
// Header (iterator protocol handled by SSA)
762-
all_blocks.push(TypedBasicBlock {
763-
id: header_id,
764-
label: None,
765-
statements: vec![],
766-
terminator: TypedTerminator::Jump(body_id),
767-
pattern_check: None,
768-
});
775+
if let (Some(var_name), Some(args)) = (loop_var, range_args) {
776+
// Desugar: for i in range(start, end) => C-style for loop
777+
let span = for_stmt.iterator.span;
778+
let start_expr = args[0].clone();
779+
let end_expr = args[1].clone();
780+
let step_expr = if args.len() == 3 {
781+
Some(args[2].clone())
782+
} else {
783+
None
784+
};
769785

770-
self.loop_stack.push((header_id, after_id));
771-
let (body_blocks, _, body_exit) =
772-
self.split_at_control_flow(&for_stmt.body, body_id, false)?;
773-
all_blocks.extend(body_blocks);
774-
self.loop_stack.pop();
786+
// Init: let mut i = start
787+
let init_stmt = typed_node(
788+
TypedStatement::Let(zyntax_typed_ast::typed_ast::TypedLet {
789+
name: var_name,
790+
ty: start_expr.ty.clone(),
791+
mutability: zyntax_typed_ast::Mutability::Mutable,
792+
initializer: Some(Box::new(start_expr.clone())),
793+
span,
794+
}),
795+
Type::Primitive(zyntax_typed_ast::PrimitiveType::Unit),
796+
span,
797+
);
798+
current_statements.push(init_stmt);
799+
800+
// Condition: i < end
801+
let cond_expr = typed_node(
802+
TypedExpression::Binary(zyntax_typed_ast::typed_ast::TypedBinary {
803+
op: zyntax_typed_ast::typed_ast::BinaryOp::Lt,
804+
left: Box::new(typed_node(
805+
TypedExpression::Variable(var_name),
806+
start_expr.ty.clone(),
807+
span,
808+
)),
809+
right: Box::new(end_expr),
810+
}),
811+
Type::Primitive(zyntax_typed_ast::PrimitiveType::Bool),
812+
span,
813+
);
775814

776-
if let Some(last_block) =
777-
all_blocks.iter_mut().rev().find(|b| b.id == body_exit)
778-
{
779-
if matches!(last_block.terminator, TypedTerminator::Unreachable) {
780-
last_block.terminator = TypedTerminator::Jump(header_id);
815+
// Update: i = i + step (default step = 1)
816+
let step = step_expr.unwrap_or_else(|| {
817+
typed_node(
818+
TypedExpression::Literal(
819+
zyntax_typed_ast::typed_ast::TypedLiteral::Integer(1),
820+
),
821+
start_expr.ty.clone(),
822+
span,
823+
)
824+
});
825+
let update_expr = typed_node(
826+
TypedExpression::Binary(zyntax_typed_ast::typed_ast::TypedBinary {
827+
op: zyntax_typed_ast::typed_ast::BinaryOp::Assign,
828+
left: Box::new(typed_node(
829+
TypedExpression::Variable(var_name),
830+
start_expr.ty.clone(),
831+
span,
832+
)),
833+
right: Box::new(typed_node(
834+
TypedExpression::Binary(
835+
zyntax_typed_ast::typed_ast::TypedBinary {
836+
op: zyntax_typed_ast::typed_ast::BinaryOp::Add,
837+
left: Box::new(typed_node(
838+
TypedExpression::Variable(var_name),
839+
start_expr.ty.clone(),
840+
span,
841+
)),
842+
right: Box::new(step),
843+
},
844+
),
845+
start_expr.ty.clone(),
846+
span,
847+
)),
848+
}),
849+
start_expr.ty.clone(),
850+
span,
851+
);
852+
853+
// Build the C-style for loop block structure
854+
let header_id = self.new_block_id();
855+
let body_id = self.new_block_id();
856+
let update_id = self.new_block_id();
857+
let after_id = self.new_block_id();
858+
859+
// Entry block → header
860+
all_blocks.push(TypedBasicBlock {
861+
id: current_block_id,
862+
label: None,
863+
statements: current_statements.clone(),
864+
terminator: TypedTerminator::Jump(header_id),
865+
pattern_check: None,
866+
});
867+
868+
// Header: conditional branch (i < end)
869+
all_blocks.push(TypedBasicBlock {
870+
id: header_id,
871+
label: None,
872+
statements: vec![],
873+
terminator: TypedTerminator::CondBranch {
874+
condition: Box::new(cond_expr),
875+
true_target: body_id,
876+
false_target: after_id,
877+
},
878+
pattern_check: None,
879+
});
880+
881+
// Body
882+
self.loop_stack.push((update_id, after_id));
883+
let (body_blocks, _, body_exit) =
884+
self.split_at_control_flow(&for_stmt.body, body_id, false)?;
885+
all_blocks.extend(body_blocks);
886+
self.loop_stack.pop();
887+
888+
// Body exit → update
889+
if let Some(last_block) =
890+
all_blocks.iter_mut().rev().find(|b| b.id == body_exit)
891+
{
892+
if matches!(last_block.terminator, TypedTerminator::Unreachable) {
893+
last_block.terminator = TypedTerminator::Jump(update_id);
894+
}
781895
}
782-
}
783896

784-
current_statements = Vec::new();
785-
current_block_id = after_id;
786-
exit_id = after_id;
897+
// Update block: i = i + 1; jump header
898+
all_blocks.push(TypedBasicBlock {
899+
id: update_id,
900+
label: None,
901+
statements: vec![typed_node(
902+
TypedStatement::Expression(Box::new(update_expr)),
903+
Type::Primitive(zyntax_typed_ast::PrimitiveType::Unit),
904+
span,
905+
)],
906+
terminator: TypedTerminator::Jump(header_id),
907+
pattern_check: None,
908+
});
909+
910+
current_statements = Vec::new();
911+
current_block_id = after_id;
912+
exit_id = after_id;
913+
} else {
914+
// General for-each loop (iterator protocol)
915+
// TODO: Implement general iterator desugaring
916+
// For now, emit unconditional loop (matches previous behavior)
917+
let header_id = self.new_block_id();
918+
let body_id = self.new_block_id();
919+
let after_id = self.new_block_id();
920+
921+
all_blocks.push(TypedBasicBlock {
922+
id: current_block_id,
923+
label: None,
924+
statements: current_statements.clone(),
925+
terminator: TypedTerminator::Jump(header_id),
926+
pattern_check: None,
927+
});
928+
929+
all_blocks.push(TypedBasicBlock {
930+
id: header_id,
931+
label: None,
932+
statements: vec![],
933+
terminator: TypedTerminator::Jump(body_id),
934+
pattern_check: None,
935+
});
936+
937+
self.loop_stack.push((header_id, after_id));
938+
let (body_blocks, _, body_exit) =
939+
self.split_at_control_flow(&for_stmt.body, body_id, false)?;
940+
all_blocks.extend(body_blocks);
941+
self.loop_stack.pop();
942+
943+
if let Some(last_block) =
944+
all_blocks.iter_mut().rev().find(|b| b.id == body_exit)
945+
{
946+
if matches!(last_block.terminator, TypedTerminator::Unreachable) {
947+
last_block.terminator = TypedTerminator::Jump(header_id);
948+
}
949+
}
950+
951+
current_statements = Vec::new();
952+
current_block_id = after_id;
953+
exit_id = after_id;
954+
}
787955
}
788956

789957
TypedStatement::ForCStyle(for_c_stmt) => {

0 commit comments

Comments
 (0)