From 38a7c7812569ac68bf5c80b3d4fa9e9f4d92def8 Mon Sep 17 00:00:00 2001 From: Jane Losare-Lusby Date: Thu, 22 Feb 2024 14:28:28 -0800 Subject: [PATCH] Add support for calls with more than two path segments * Associate call resolution with outermost NodeId for namespaced calls * update codegen to check call resolution on outermost namespaced lookup --- docs/language_overview.md | 0 src/codegen.rs | 188 +++++++++++++++++++++---------------- src/compiler.rs | 5 + src/typechecker.rs | 105 ++++++++++++++++----- tests/integration_tests.rs | 18 +++- 5 files changed, 207 insertions(+), 109 deletions(-) create mode 100644 docs/language_overview.md diff --git a/docs/language_overview.md b/docs/language_overview.md new file mode 100644 index 0000000..e69de29 diff --git a/src/codegen.rs b/src/codegen.rs index 5e379d7..6edad5c 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,3 +1,5 @@ +use tracing::debug; + use crate::{ compiler::{CallTarget, Compiler}, lifetime_checker::AllocationLifetime, @@ -494,7 +496,11 @@ impl Codegen { output.extend_from_slice(format!("allocation_id + {} ", level).as_bytes()) } AllocationLifetime::Unknown => { - // panic!("found 'unknown' lifetime during codegen") + debug!( + ?node_id, + name = ?String::from_utf8_lossy(self.compiler.get_source(node_id)), + "found 'unknown' lifetime during codegen", + ); output.extend_from_slice(b"/* UNKNOWN, */ ") } } @@ -822,109 +828,129 @@ impl Codegen { panic!("internal error: expected allocation call during allocation") } } - AstNode::NamespacedLookup { item, .. } => match self.compiler.get_node(*item) { - AstNode::Call { head, args } => { - let call_target = self - .compiler - .call_resolution - .get(head) - .expect("internal error: missing call resolution in codegen"); - - match call_target { - CallTarget::Function(fun_id) => { - output.extend_from_slice(b"/* "); - output.extend_from_slice( - self.compiler - .get_source(self.compiler.functions[fun_id.0].name), - ); - output.extend_from_slice(b" */ "); + AstNode::NamespacedLookup { item, .. } => { + let mut codegen_call = |call_target, args: &[NodeId]| match call_target { + CallTarget::Function(fun_id) => { + output.extend_from_slice(b"/* "); + output.extend_from_slice( + self.compiler + .get_source(self.compiler.functions[fun_id.0].name), + ); + output.extend_from_slice(b" */ "); - output.extend_from_slice(b"function_"); - output.extend_from_slice(fun_id.0.to_string().as_bytes()); - output.push(b'('); + output.extend_from_slice(b"function_"); + output.extend_from_slice(fun_id.0.to_string().as_bytes()); + output.push(b'('); - self.codegen_annotation(node_id, output); + self.codegen_annotation(node_id, output); - for arg in args { - output.extend_from_slice(b", "); + for arg in args { + output.extend_from_slice(b", "); - self.codegen_node(*arg, local_inferences, output) - } - output.push(b')'); + self.codegen_node(*arg, local_inferences, output) } - CallTarget::EnumConstructor(target, offset) => { - output.extend_from_slice(b"enum_case_"); - output.extend_from_slice(target.0.to_string().as_bytes()); - output.push(b'_'); - output.extend_from_slice(offset.0.to_string().as_bytes()); - output.push(b'('); + output.push(b')'); + } + CallTarget::EnumConstructor(target, offset) => { + output.extend_from_slice(b"enum_case_"); + output.extend_from_slice(target.0.to_string().as_bytes()); + output.push(b'_'); + output.extend_from_slice(offset.0.to_string().as_bytes()); + output.push(b'('); - self.codegen_annotation(node_id, output); + self.codegen_annotation(node_id, output); - for arg in args { - output.extend_from_slice(b", "); + for arg in args { + output.extend_from_slice(b", "); - self.codegen_node(*arg, local_inferences, output) - } - output.push(b')'); - } - _ => { - unimplemented!("namedspaced lookup of non-namedspaced value") + self.codegen_node(*arg, local_inferences, output) } + output.push(b')'); } - } - AstNode::Name => { - let call_target = self - .compiler - .call_resolution - .get(item) - .expect("internal error: missing call resolution in codegen"); - - match call_target { - CallTarget::Function(fun_id) => { - output.extend_from_slice(b"/* "); - output.extend_from_slice( - self.compiler - .get_source(self.compiler.functions[fun_id.0].name), - ); - output.extend_from_slice(b" */ "); + _ => { + unimplemented!("namedspaced lookup of non-namedspaced value") + } + }; - output.extend_from_slice(b"function_"); - output.extend_from_slice(fun_id.0.to_string().as_bytes()); - output.push(b'('); + self.compiler.debug_node(node_id); - self.codegen_annotation(node_id, output); + if let Some(call_target) = self.compiler.call_resolution.get(&node_id) { + let mut curr_item = *item; - output.push(b')'); + let args = loop { + match self.compiler.get_node(curr_item) { + AstNode::NamespacedLookup { item, .. } => curr_item = *item, + AstNode::Call { args, .. } => break args.clone(), + _ => unreachable!("having a valid call target should guarantee that this namespaced lookup ends in a call"), } - CallTarget::EnumConstructor(target, offset) => { - output.extend_from_slice(b"enum_case_"); - output.extend_from_slice(target.0.to_string().as_bytes()); - output.push(b'_'); - output.extend_from_slice(offset.0.to_string().as_bytes()); - output.push(b'('); + }; - self.codegen_annotation(node_id, output); + codegen_call(*call_target, &args); + } else { + match self.compiler.get_node(*item) { + AstNode::Call { head, args } => { + let call_target = self + .compiler + .call_resolution + .get(head) + .expect("internal error: missing call resolution in codegen"); - output.push(b')'); + codegen_call(*call_target, args) } - CallTarget::NodeId(target) => { - output.push(b'('); - self.codegen_node(*target, local_inferences, output); - output.push(b')'); + AstNode::Name => { + let call_target = self + .compiler + .call_resolution + .get(item) + .expect("internal error: missing call resolution in codegen"); - output.push(b'('); + match call_target { + CallTarget::Function(fun_id) => { + output.extend_from_slice(b"/* "); + output.extend_from_slice( + self.compiler + .get_source(self.compiler.functions[fun_id.0].name), + ); + output.extend_from_slice(b" */ "); - self.codegen_annotation(node_id, output); + output.extend_from_slice(b"function_"); + output.extend_from_slice(fun_id.0.to_string().as_bytes()); + output.push(b'('); + + self.codegen_annotation(node_id, output); + + output.push(b')'); + } + CallTarget::EnumConstructor(target, offset) => { + output.extend_from_slice(b"enum_case_"); + output.extend_from_slice(target.0.to_string().as_bytes()); + output.push(b'_'); + output.extend_from_slice(offset.0.to_string().as_bytes()); + output.push(b'('); + + self.codegen_annotation(node_id, output); + + output.push(b')'); + } + CallTarget::NodeId(target) => { + output.push(b'('); + self.codegen_node(*target, local_inferences, output); + output.push(b')'); - output.push(b')'); + output.push(b'('); + + self.codegen_annotation(node_id, output); + + output.push(b')'); + } + } + } + _ => { + panic!("unsupported namespace lookup") } } } - _ => { - panic!("unsupported namespace lookup") - } - }, + } AstNode::NamedValue { value, .. } => { // FIXME: this should probably be handled cleanly via typecheck+codegen // rather than ignoring the name diff --git a/src/compiler.rs b/src/compiler.rs index c39aa7d..4b8b971 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -776,4 +776,9 @@ impl Compiler { self.module_resolution.insert(module_block, module_id); module_id } + + pub(crate) fn debug_node(&self, node_id: NodeId) { + let node = String::from_utf8_lossy(self.get_source(node_id)); + tracing::debug!(?node); + } } diff --git a/src/typechecker.rs b/src/typechecker.rs index f67f4a1..abf2c69 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -184,7 +184,6 @@ impl Scope { None } - } pub struct Typechecker { @@ -1225,6 +1224,7 @@ impl Typechecker { // TODO: do we want to wait until all params are checked // before we mark this as resolved? + self.compiler.debug_node(name); self.compiler .call_resolution .insert(name, CallTarget::Function(fun_id)); @@ -1740,7 +1740,7 @@ impl Typechecker { RANGE_I64_TYPE_ID } AstNode::NamespacedLookup { namespace, item } => { - self.typecheck_namespaced_lookup(*namespace, *item, local_inferences) + self.typecheck_namespaced_lookup(node_id, *namespace, *item, local_inferences) } AstNode::Use { path } => self.typecheck_module(*path, local_inferences), AstNode::Call { head, args } => { @@ -2370,12 +2370,19 @@ impl Typechecker { #[instrument(skip(self))] pub fn typecheck_namespaced_lookup( &mut self, + node_id: NodeId, namespace: NodeId, item: NodeId, local_inferences: &mut Vec, ) -> TypeId { if let Some(module_id) = self.find_module_in_scope(namespace) { - self.typecheck_namespaced_item_lookup(namespace, module_id, item, local_inferences) + self.typecheck_namespaced_item_lookup( + node_id, + namespace, + module_id, + item, + local_inferences, + ) } else if let Some(type_id) = self.find_type_in_scope(namespace) { self.typecheck_namespaced_type_lookup(namespace, type_id, item, local_inferences) } else { @@ -2387,36 +2394,58 @@ impl Typechecker { // enter the module's scope then recurse and call typecheck_namespaced_lookup? fn typecheck_namespaced_item_lookup( &mut self, + node_id: NodeId, namespace: NodeId, - module: ModuleId, + module_id: ModuleId, item: NodeId, local_inferences: &mut Vec, ) -> TypeId { - let module = self.compiler.get_module(module); - let node = self.compiler.get_node(item).clone(); - match &node { - AstNode::Call { head, args } => { - // self.typecheck_call(item, head, &args, local_inferences) - let name = self.compiler.get_source(*head); - debug!( - ?node, - "looking for {name} in module", - name = std::str::from_utf8(name).unwrap() - ); - let fun_id = module.scope.find_fun(name); - if let Some(fun_id) = fun_id { - self.typecheck_call_with_fun_id(*head, fun_id, args, None, local_inferences) - } else { + let mut module = self.compiler.get_module(module_id); + let mut node = self.compiler.get_node(item).clone(); + loop { + match &node { + AstNode::Call { head, args } => { + let name = self.compiler.get_source(*head); + debug!( + ?node, + "looking for {name} in module", + name = std::str::from_utf8(name).unwrap() + ); + let fun_id = module.scope.find_fun(name); + return if let Some(fun_id) = fun_id { + debug!( + ?fun_id, + "found {name}", + name = std::str::from_utf8(name).unwrap() + ); + self.typecheck_call_with_fun_id( + node_id, + fun_id, + args, + None, + local_inferences, + ) + } else { + debug!(?node); + self.error("could not find item in namespace", namespace); + VOID_TYPE_ID + }; + } + AstNode::NamespacedLookup { namespace, item } => { + let Some(inner_module_id) = self.find_module_in_module(*namespace, module) + else { + self.error("could not find module", *namespace); + return VOID_TYPE_ID; + }; + module = self.compiler.get_module(inner_module_id); + node = self.compiler.get_node(*item).clone(); + } + _ => { debug!(?node); self.error("could not find item in namespace", namespace); - VOID_TYPE_ID + return VOID_TYPE_ID; } } - _ => { - debug!(?node); - self.error("could not find item in namespace", namespace); - VOID_TYPE_ID - } } } @@ -3129,6 +3158,7 @@ impl Typechecker { if file_name == unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(name) } { + debug!(?module_id, "found module"); return Some(*module_id); } } @@ -3139,6 +3169,31 @@ impl Typechecker { None } + #[instrument(skip(self))] + pub fn find_module_in_module(&self, namespace: NodeId, module: &Module) -> Option { + let name = self.compiler.get_source(namespace); + debug!( + "searching for module \"{name}\"", + name = std::str::from_utf8(name).unwrap() + ); + let scope = &module.scope; + for (path, module_id) in scope.modules.iter() { + trace!(?path, ?module_id); + // definitely incorrect, but we currently only have one path segment + // this needs to somehow be able to resolve the path against all the segments of the path + for path in path.ancestors() { + if let Some(file_name) = path.file_stem() { + if file_name == unsafe { std::ffi::OsStr::from_encoded_bytes_unchecked(name) } { + debug!(?module_id, "found module"); + return Some(*module_id); + } + } + } + } + + None + } + pub fn find_type_in_scope(&self, type_name: NodeId) -> Option { let name = self.compiler.get_source(type_name); for scope in self.scope.iter().rev() { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0879588..8f1fbec 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -8,7 +8,7 @@ use std::{path::Path, process::Command}; fn test_example(test_name: &str) -> TestResult { use std::{ fs::{create_dir_all, File}, - io::{stdout, Write}, + io::Write, path::PathBuf, sync::Once, }; @@ -119,8 +119,20 @@ fn test_example(test_name: &str) -> TestResult { .unwrap(); if !compiler.status.success() { - let _ = stdout().write_all(&compiler.stdout); - let _ = stdout().write_all(&compiler.stderr); + let clang_err = String::from_utf8_lossy(&compiler.stderr); + let clang_out = String::from_utf8_lossy(&compiler.stdout); + Err(eyre!("Clang did not compile successfully")) + .with_section(|| { + String::from_utf8_lossy(&output.stdout) + .trim() + .to_string() + .header("Codegen:") + }) + .with_section(|| command_out.trim().to_string().header("June Stdout:")) + .with_section(|| command_err.trim().to_string().header("June Stderr:")) + .with_section(|| clang_out.trim().to_string().header("Clang Stdout:")) + .with_section(|| clang_err.trim().to_string().header("Clang Stderr:"))?; + panic!("Clang did not compile successfully"); }