From 8bc4b373a80a095ede6a1bc827c66bb44e436fac Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 26 Feb 2026 15:29:04 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Add=20LSP=20inlay=20hints=20for?= =?UTF-8?q?=20variable=20and=20function=20return=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ndc_lib/src/ast/expression.rs | 6 +- ndc_lib/src/ast/parser.rs | 1 + ndc_lib/src/interpreter/evaluate/mod.rs | 6 +- ndc_lib/src/interpreter/mod.rs | 23 ++- ndc_lib/src/interpreter/semantic/analyser.rs | 9 +- ndc_lsp/src/backend.rs | 197 ++++++++++++++++--- 6 files changed, 206 insertions(+), 36 deletions(-) diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index 6490812..cec3b0d 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -144,6 +144,8 @@ pub enum Lvalue { Identifier { identifier: String, resolved: Option, + span: Span, + inferred_type: Option, }, // Example: `foo()[1] = ...` Index { @@ -151,7 +153,7 @@ pub enum Lvalue { index: Box, }, // Example: `let a, b = ...` - Sequence(Vec), + Sequence(Vec), } impl Eq for Expression {} @@ -252,6 +254,8 @@ impl TryFrom for Lvalue { } => Ok(Self::Identifier { identifier, resolved: None, + span: value.span, + inferred_type: None, }), Expression::Index { value, index } => Ok(Self::Index { value, index }), Expression::List { values } | Expression::Tuple { values } => Ok(Self::Sequence( diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index 534455a..e5de23a 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -659,6 +659,7 @@ impl Parser { let Lvalue::Identifier { identifier, resolved: None, + .. } = identifier else { unreachable!("Guaranteed to match by previous call to require_identifier") diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 5a24b6f..b93a1ab 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -87,6 +87,7 @@ pub(crate) fn evaluate_expression( Lvalue::Identifier { identifier, resolved: resolved_l_value, + .. } => { let resolved_l_value = resolved_l_value.expect("lvalue must be resolved"); let rhs = evaluate_expression(r_value, environment)?; @@ -675,10 +676,7 @@ fn declare_or_assign_variable( span: Span, ) -> EvaluationResult { match l_value { - Lvalue::Identifier { - identifier: _, - resolved, - } => { + Lvalue::Identifier { resolved, .. } => { environment .borrow_mut() .set(resolved.expect("must be resolved"), value.clone()); diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index ba01635..983fdd5 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -45,6 +45,26 @@ impl Interpreter { self.environment } + pub fn analyse_document( + &mut self, + input: &str, + ) -> Result, InterpreterError> { + let scanner = Lexer::new(input); + let tokens = scanner.collect::, _>>()?; + let mut parser = crate::ast::Parser::from_tokens(tokens); + let mut expressions = parser.parse()?; + + let checkpoint = self.analyser.checkpoint(); + for e in &mut expressions { + if let Err(e) = self.analyser.analyse(e) { + self.analyser.restore(checkpoint); + return Err(e.into()); + } + } + + Ok(expressions) + } + pub fn run_str(&mut self, input: &str, debug: bool) -> Result { let scanner = Lexer::new(input); let tokens = scanner.collect::, _>>()?; @@ -73,9 +93,6 @@ impl Interpreter { } } - //dbg!(&expressions); - //dbg!(&self.analyser); - let final_value = self.interpret(expressions.into_iter())?; if debug { diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 44f0ac9..6360751 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -101,6 +101,7 @@ impl Analyser { resolved_name, parameters, body, + return_type: return_type_slot, .. } => { // TODO: figuring out the type signature of function declarations is the rest of the owl @@ -132,6 +133,7 @@ impl Analyser { // the body and then figuring out the LUB. let return_type = self.analyse(body)?; self.scope_tree.destroy_scope(); + *return_type_slot = Some(return_type.clone()); let function_type = StaticType::Function { parameters: Some(param_types.clone()), @@ -403,6 +405,7 @@ impl Analyser { Lvalue::Identifier { identifier, resolved, + .. } => { let Some(target) = self.scope_tree.get_binding_any(identifier) else { return Err(AnalysisError::identifier_not_previously_declared( @@ -433,6 +436,7 @@ impl Analyser { Lvalue::Identifier { identifier, resolved, + .. } => { let Some(target) = self.scope_tree.get_binding_any(identifier) else { return Err(AnalysisError::identifier_not_previously_declared( @@ -508,11 +512,14 @@ impl Analyser { Lvalue::Identifier { identifier, resolved, + inferred_type, + .. } => { *resolved = Some( self.scope_tree - .create_local_binding(identifier.clone(), typ), + .create_local_binding(identifier.clone(), typ.clone()), ); + *inferred_type = Some(typ); } Lvalue::Index { index, value } => { self.analyse(index)?; diff --git a/ndc_lsp/src/backend.rs b/ndc_lsp/src/backend.rs index 889e43c..167f745 100644 --- a/ndc_lsp/src/backend.rs +++ b/ndc_lsp/src/backend.rs @@ -1,26 +1,31 @@ -use tower_lsp::lsp_types::{ - DidChangeTextDocumentParams, DidOpenTextDocumentParams, InitializeParams, InitializeResult, - InitializedParams, MessageType, ServerCapabilities, TextDocumentSyncCapability, - TextDocumentSyncKind, Url, -}; +use std::collections::HashMap; +use ndc_lib::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue}; use ndc_lib::interpreter::Interpreter; use ndc_lib::lexer::{Lexer, Span, TokenLocation}; +use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result as JsonRPCResult; use tower_lsp::lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, DiagnosticSeverity, Documentation, - MarkupContent, MarkupKind, Position, Range, WorkDoneProgressOptions, + InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintKind, + InlayHintLabel, InlayHintParams, MarkupContent, MarkupKind, MessageType, OneOf, Position, + Range, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, Url, + WorkDoneProgressOptions, }; use tower_lsp::{Client, LanguageServer}; pub struct Backend { pub client: Client, + documents: Mutex>>, } impl Backend { pub fn new(client: Client) -> Self { - Self { client } + Self { + client, + documents: Mutex::new(HashMap::new()), + } } async fn validate(&self, uri: &Url, text: &str) { @@ -70,6 +75,25 @@ impl Backend { self.client .publish_diagnostics(uri.clone(), diagnostics, None) .await; + + // Run full semantic analysis and collect inlay hints from the annotated AST. + // The interpreter uses Rc internally (non-Send), so it must be fully dropped + // before the next await point. + let hints = { + let mut interpreter = Interpreter::new(Vec::new()); + match interpreter.analyse_document(text) { + Ok(expressions) => { + let mut hints = Vec::new(); + for expr in &expressions { + collect_hints(expr, text, &mut hints); + } + hints + } + Err(_) => Vec::new(), + } + }; + + self.documents.lock().await.insert(uri.clone(), hints); } } @@ -82,20 +106,47 @@ impl LanguageServer for Backend { TextDocumentSyncKind::FULL, )), completion_provider: Some(CompletionOptions { - resolve_provider: Some(false), // or true if you want to support `completionItem/resolve` - trigger_characters: Some(vec![".".to_string()]), // characters that trigger completion + resolve_provider: Some(false), + trigger_characters: Some(vec![".".to_string()]), all_commit_characters: None, work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, completion_item: None, }), + inlay_hint_provider: Some(OneOf::Left(true)), ..Default::default() }, ..Default::default() }) } + async fn initialized(&self, _: InitializedParams) { + self.client + .log_message(MessageType::INFO, "server initialized") + .await; + } + + async fn shutdown(&self) -> JsonRPCResult<()> { + Ok(()) + } + + async fn did_open(&self, params: tower_lsp::lsp_types::DidOpenTextDocumentParams) { + self.validate(¶ms.text_document.uri, ¶ms.text_document.text) + .await; + } + + async fn did_change(&self, params: tower_lsp::lsp_types::DidChangeTextDocumentParams) { + for change in params.content_changes { + self.validate(¶ms.text_document.uri, &change.text).await; + } + } + + async fn inlay_hint(&self, params: InlayHintParams) -> JsonRPCResult>> { + let hints = self.documents.lock().await; + Ok(hints.get(¶ms.text_document.uri).cloned()) + } + async fn completion( &self, _params: CompletionParams, @@ -105,7 +156,6 @@ impl LanguageServer for Backend { let functions = env.borrow().get_all_functions(); let items = functions.iter().filter_map(|fun| { - // Ignore operators if !is_normal_ident(fun.name()) { return None; } @@ -141,32 +191,125 @@ impl LanguageServer for Backend { Ok(Some(CompletionResponse::Array(items.collect()))) } - // Optional: resolve more details on selected item (THIS IS DISABLED FOR NOW??!) async fn completion_resolve( &self, item: CompletionItem, ) -> Result { - Ok(item) // add documentation, detail, etc. if needed - } - - async fn initialized(&self, _: InitializedParams) { - self.client - .log_message(MessageType::INFO, "server initialized") - .await; - } - async fn shutdown(&self) -> JsonRPCResult<()> { - Ok(()) + Ok(item) } +} - async fn did_open(&self, params: DidOpenTextDocumentParams) { - self.validate(¶ms.text_document.uri, ¶ms.text_document.text) - .await; +/// Recursively walk an analysed AST node and collect inlay hints from places where +/// the analyser stored type information: `Lvalue::Identifier.inferred_type` (variable +/// and for-loop declarations) and `FunctionDeclaration.return_type`. +fn collect_hints(expr: &ExpressionLocation, text: &str, hints: &mut Vec) { + match &expr.expression { + Expression::VariableDeclaration { l_value, value } => { + collect_hints_from_lvalue(l_value, text, hints); + collect_hints(value, text, hints); + } + Expression::FunctionDeclaration { + return_type, + parameters, + body, + .. + } => { + if let Some(rt) = return_type { + hints.push(InlayHint { + position: position_from_offset(text, parameters.span.end()), + label: InlayHintLabel::String(format!(" -> {rt}")), + kind: Some(InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + } + collect_hints(body, text, hints); + } + Expression::Statement(inner) => collect_hints(inner, text, hints), + Expression::Grouping(inner) => collect_hints(inner, text, hints), + Expression::Block { statements } => { + for s in statements { + collect_hints(s, text, hints); + } + } + Expression::If { + condition, + on_true, + on_false, + } => { + collect_hints(condition, text, hints); + collect_hints(on_true, text, hints); + if let Some(f) = on_false { + collect_hints(f, text, hints); + } + } + Expression::While { + expression, + loop_body, + } => { + collect_hints(expression, text, hints); + collect_hints(loop_body, text, hints); + } + Expression::For { iterations, body } => { + for iteration in iterations { + match iteration { + ForIteration::Iteration { l_value, sequence } => { + collect_hints_from_lvalue(l_value, text, hints); + collect_hints(sequence, text, hints); + } + ForIteration::Guard(expr) => collect_hints(expr, text, hints), + } + } + match body.as_ref() { + ForBody::Block(e) | ForBody::List(e) => collect_hints(e, text, hints), + ForBody::Map { + key, + value, + default, + } => { + collect_hints(key, text, hints); + if let Some(v) = value { + collect_hints(v, text, hints); + } + if let Some(d) = default { + collect_hints(d, text, hints); + } + } + } + } + Expression::Return { value } => collect_hints(value, text, hints), + // Literals, identifiers, ranges, calls etc. contain no declaration sites + _ => {} } +} - async fn did_change(&self, params: DidChangeTextDocumentParams) { - for change in params.content_changes { - self.validate(¶ms.text_document.uri, &change.text).await; +fn collect_hints_from_lvalue(lvalue: &Lvalue, text: &str, hints: &mut Vec) { + match lvalue { + Lvalue::Identifier { + inferred_type: Some(typ), + span, + .. + } => { + hints.push(InlayHint { + position: position_from_offset(text, span.end()), + label: InlayHintLabel::String(format!(": {typ}")), + kind: Some(InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: Some(true), + data: None, + }); + } + Lvalue::Sequence(lvalues) => { + for lv in lvalues { + collect_hints_from_lvalue(lv, text, hints); + } } + _ => {} } } From 224138469f7bf2b15a1133da6ddc14ec820c3899 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 26 Feb 2026 15:37:56 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20parse=5Fand?= =?UTF-8?q?=5Fanalyse,=20remove=20debug=20flag=20from=20run=5Fstr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benches/src/benchmark.rs | 2 +- ndc_bin/src/main.rs | 7 ++--- ndc_bin/src/repl.rs | 4 +-- ndc_lib/src/interpreter/mod.rs | 53 +++++++++------------------------- tests/src/programs.rs | 2 +- 5 files changed, 19 insertions(+), 49 deletions(-) diff --git a/benches/src/benchmark.rs b/benches/src/benchmark.rs index 5496dc5..c27f745 100644 --- a/benches/src/benchmark.rs +++ b/benches/src/benchmark.rs @@ -9,7 +9,7 @@ use std::time::Duration; fn run_string(input: &str) -> Result { let buf: Vec = vec![]; let mut interpreter = Interpreter::new(buf); - interpreter.run_str(std::hint::black_box(input), false) + interpreter.run_str(std::hint::black_box(input)) } #[allow(unused)] diff --git a/ndc_bin/src/main.rs b/ndc_bin/src/main.rs index 6fe89f9..d77ac8c 100644 --- a/ndc_bin/src/main.rs +++ b/ndc_bin/src/main.rs @@ -22,9 +22,6 @@ mod highlighter; #[command(version = "0.2.0")] #[command(about = "An interpreter for the Andy C++ language")] struct Cli { - #[arg(long)] - debug: bool, - #[arg(short = 'C', long, default_value_t = 1)] context_lines: usize, @@ -122,7 +119,7 @@ fn main() -> anyhow::Result<()> { let stdout = std::io::stdout(); let mut interpreter = Interpreter::new(stdout); - match into_miette_result(interpreter.run_str(&string, cli.debug)) { + match into_miette_result(interpreter.run_str(&string)) { // we can just ignore successful runs because we have print statements Ok(_final_value) => {} Err(report) => { @@ -147,7 +144,7 @@ fn main() -> anyhow::Result<()> { } Action::Docs(query) => return docs(query.as_deref()), Action::StartRepl => { - repl::run(cli.debug)?; + repl::run()?; } Action::RunLsp => start_lsp(), } diff --git a/ndc_bin/src/repl.rs b/ndc_bin/src/repl.rs index d12c181..9679198 100644 --- a/ndc_bin/src/repl.rs +++ b/ndc_bin/src/repl.rs @@ -28,7 +28,7 @@ impl rustyline::highlight::Highlighter for RustlylineHelper { } } -pub fn run(debug: bool) -> anyhow::Result<()> { +pub fn run() -> anyhow::Result<()> { let h = RustlylineHelper {}; let mut rl = Editor::new()?; @@ -44,7 +44,7 @@ pub fn run(debug: bool) -> anyhow::Result<()> { let _ = rl.add_history_entry(line.as_str()); // Run the line we just read through the interpreter - match into_miette_result(interpreter.run_str(line.as_str(), debug)) { + match into_miette_result(interpreter.run_str(line.as_str())) { Ok(output) => { if !output.is_empty() { println!("{output}") diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 983fdd5..138897a 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -24,7 +24,6 @@ pub struct Interpreter { analyser: Analyser, } -#[allow(clippy::dbg_macro, clippy::print_stdout, clippy::print_stderr)] impl Interpreter { #[must_use] pub fn new(dest: T) -> Self @@ -49,41 +48,21 @@ impl Interpreter { &mut self, input: &str, ) -> Result, InterpreterError> { - let scanner = Lexer::new(input); - let tokens = scanner.collect::, _>>()?; - let mut parser = crate::ast::Parser::from_tokens(tokens); - let mut expressions = parser.parse()?; - - let checkpoint = self.analyser.checkpoint(); - for e in &mut expressions { - if let Err(e) = self.analyser.analyse(e) { - self.analyser.restore(checkpoint); - return Err(e.into()); - } - } - - Ok(expressions) + self.parse_and_analyse(input) } - pub fn run_str(&mut self, input: &str, debug: bool) -> Result { - let scanner = Lexer::new(input); - let tokens = scanner.collect::, _>>()?; - - if debug { - for token in &tokens { - eprintln!("{token:?}"); - } - } - - let mut parser = crate::ast::Parser::from_tokens(tokens); - - let mut expressions = parser.parse()?; + pub fn run_str(&mut self, input: &str) -> Result { + let expressions = self.parse_and_analyse(input)?; + let final_value = self.interpret(expressions.into_iter())?; + Ok(format!("{final_value}")) + } - if debug { - for s in &expressions { - println!("{s:#?}"); - } - } + fn parse_and_analyse( + &mut self, + input: &str, + ) -> Result, InterpreterError> { + let tokens = Lexer::new(input).collect::, _>>()?; + let mut expressions = crate::ast::Parser::from_tokens(tokens).parse()?; let checkpoint = self.analyser.checkpoint(); for e in &mut expressions { @@ -93,13 +72,7 @@ impl Interpreter { } } - let final_value = self.interpret(expressions.into_iter())?; - - if debug { - dbg!(&final_value, final_value.static_type()); - } - - Ok(format!("{final_value}")) + Ok(expressions) } fn interpret( diff --git a/tests/src/programs.rs b/tests/src/programs.rs index 5c482ab..648a44a 100644 --- a/tests/src/programs.rs +++ b/tests/src/programs.rs @@ -24,7 +24,7 @@ fn run_ndc_test(path: PathBuf) -> Result<(), std::io::Error> { print!("Running {path:?}..."); let mut interpreter = Interpreter::new(Vec::new()); - let interpreter_result = interpreter.run_str(&contents, false); + let interpreter_result = interpreter.run_str(&contents); let program_had_error = interpreter_result.is_err(); let actual_error = interpreter_result.unwrap_or_else(|err| format!("{err:?}")); From cbf2ecdcb1d3df619edaa0d2d2e67c0267923f36 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 26 Feb 2026 15:39:46 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20analyse=5Fdoc?= =?UTF-8?q?ument=20to=20analyse=5Fstr=20for=20consistency=20with=20run=5Fs?= =?UTF-8?q?tr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ndc_lib/src/interpreter/mod.rs | 2 +- ndc_lsp/src/backend.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 138897a..76764ae 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -44,7 +44,7 @@ impl Interpreter { self.environment } - pub fn analyse_document( + pub fn analyse_str( &mut self, input: &str, ) -> Result, InterpreterError> { diff --git a/ndc_lsp/src/backend.rs b/ndc_lsp/src/backend.rs index 167f745..7c47ccf 100644 --- a/ndc_lsp/src/backend.rs +++ b/ndc_lsp/src/backend.rs @@ -81,7 +81,7 @@ impl Backend { // before the next await point. let hints = { let mut interpreter = Interpreter::new(Vec::new()); - match interpreter.analyse_document(text) { + match interpreter.analyse_str(text) { Ok(expressions) => { let mut hints = Vec::new(); for expr in &expressions { From 838a1a075df3a10be887bda08dbbf5783a855e7e Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 26 Feb 2026 15:52:21 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20Lvalue::new=5Fid?= =?UTF-8?q?entifier=20constructor=20and=20minor=20refactors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ndc_lib/src/ast/expression.rs | 18 ++++++++++-------- ndc_lib/src/ast/parser.rs | 7 +------ ndc_lib/src/interpreter/semantic/analyser.rs | 11 ++++++----- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index cec3b0d..84289da 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -242,6 +242,15 @@ impl Lvalue { _ => false, } } + + pub fn new_identifier(identifier: String, span: Span) -> Self { + Self::Identifier { + identifier, + resolved: None, + span, + inferred_type: None, + } + } } impl TryFrom for Lvalue { @@ -249,14 +258,7 @@ impl TryFrom for Lvalue { fn try_from(value: ExpressionLocation) -> Result { match value.expression { - Expression::Identifier { - name: identifier, .. - } => Ok(Self::Identifier { - identifier, - resolved: None, - span: value.span, - inferred_type: None, - }), + Expression::Identifier { name, .. } => Ok(Self::new_identifier(name, value.span)), Expression::Index { value, index } => Ok(Self::Index { value, index }), Expression::List { values } | Expression::Tuple { values } => Ok(Self::Sequence( values diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index e5de23a..7289a25 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -656,12 +656,7 @@ impl Parser { let identifier_span = l_value.span; let first_argument_span = expr.span; let identifier = Lvalue::try_from(l_value)?; - let Lvalue::Identifier { - identifier, - resolved: None, - .. - } = identifier - else { + let Lvalue::Identifier { identifier, .. } = identifier else { unreachable!("Guaranteed to match by previous call to require_identifier") }; diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 6360751..d6856c0 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -128,16 +128,17 @@ impl Analyser { self.scope_tree.new_scope(); let param_types = self.resolve_parameters_declarative(parameters)?; - // TODO: instead of just hardcoding the return type of every function to StaticType::Any - // we should somehow collect all the returns that were encountered while analysing - // the body and then figuring out the LUB. let return_type = self.analyse(body)?; self.scope_tree.destroy_scope(); - *return_type_slot = Some(return_type.clone()); + *return_type_slot = Some(return_type); let function_type = StaticType::Function { parameters: Some(param_types.clone()), - return_type: Box::new(return_type), + return_type: Box::new( + return_type_slot + .clone() + .expect("must have a value at this point"), + ), }; if let Some(slot) = pre_slot {