diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 35a3e6d4..4273f8d4 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -85,11 +85,15 @@ impl SourceMap { } } - fn insert(&mut self, path: CanonPath) -> usize { + fn insert(&mut self, path: CanonPath) { let id = self.paths.len(); self.ids.insert(path.clone(), id); self.paths.push(path); - id + debug_assert_eq!(self.ids.len(), self.paths.len()); + } + + fn len(&self) -> usize { + self.paths.len() } pub fn get_id(&self, path: &CanonPath) -> Option { @@ -137,10 +141,10 @@ pub(crate) struct DependencyGraph { /// Fast bidirectional lookup between `CanonPath` and Module IDs. source_map: SourceMap, - /// Memoized results of [`crate::resolution::DependencyMap::resolve_path`] to avoid - /// resolving the same [`parse::UseDecl`] twice — once during the driver phase - /// and once during the building phase after linearization. - use_cache: HashMap, + /// Memoizes [`crate::resolution::DependencyMap::resolve_path_internal`] results, + /// keyed by the source [`Span`] of each `use` declaration, so the resolver runs + /// once per occurrence and the linearization phase can look the result up directly. + use_cache: HashMap, // TODO: Consider to optimising this with `Vec` instead of `HashMap` /// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports. @@ -241,45 +245,8 @@ impl DependencyGraph { Ok((!handler.has_errors()).then_some(graph)) } - /// This helper cleanly encapsulates the process of loading source text, parsing it - /// into an `parse::Program`, and combining them so the compiler can easily work with the file. - /// If the file is missing or contains syntax errors, it logs the diagnostic to the - /// `ErrorCollector` and safely returns `None`. - fn parse_and_get_source_module( - path: &CanonPath, - importer_source: &CanonSourceFile, - span: Span, - handler: &mut ErrorCollector, - unstable_features: &UnstableFeatures, - ) -> Option { - let Ok(content) = std::fs::read_to_string(path.as_path()) else { - let err = RichError::new( - Error::FileNotFound { - filename: PathBuf::from(path.as_path()), - }, - span, - ) - .with_source(importer_source.clone()); - - handler.push(err); - return None; - }; - - let mut error_handler = ErrorCollector::new(); - let source = CanonSourceFile::new(path.clone(), Arc::from(content)); - - let ast = parse::Program::parse_from_str_with_errors( - source.clone(), - unstable_features, - &mut error_handler, - ); - - if error_handler.has_errors() { - handler.extend_with_handler(source, &error_handler); - None - } else { - ast.map(|program| SourceModule { source, program }) - } + pub fn source_map(&self) -> &SourceMap { + &self.source_map } /// PHASE 1 OF GRAPH CONSTRUCTION: Resolves all `use` declarations within a single @@ -292,7 +259,7 @@ impl DependencyGraph { current_program: &parse::Program, current_module: &CurrentModule, dependency_map: &DependencyMap, - use_cache: &mut HashMap, + use_cache: &mut HashMap, handler: &mut ErrorCollector, ) -> Vec<(CanonPath, Span)> { let mut ctx = ImportContext { @@ -330,7 +297,10 @@ impl DependencyGraph { continue; } + let new_id = self.source_map.len(); + let Some(module) = Self::parse_and_get_source_module( + new_id, &path, ¤t.source, import_span, @@ -342,7 +312,7 @@ impl DependencyGraph { continue; }; - let new_id = self.source_map.insert(path.clone()); + self.source_map.insert(path.clone()); self.modules.push(module); self.dependencies @@ -353,8 +323,46 @@ impl DependencyGraph { } } - pub fn source_map(&self) -> &SourceMap { - &self.source_map + /// This helper cleanly encapsulates the process of loading source text, parsing it + /// into an `parse::Program`, and combining them so the compiler can easily work with the file. + /// If the file is missing or contains syntax errors, it logs the diagnostic to the + /// `ErrorCollector` and safely returns `None`. + fn parse_and_get_source_module( + new_id: usize, + path: &CanonPath, + importer_source: &CanonSourceFile, + span: Span, + handler: &mut ErrorCollector, + unstable_features: &UnstableFeatures, + ) -> Option { + let Ok(content) = std::fs::read_to_string(path.as_path()) else { + let err = RichError::new( + Error::FileNotFound { + filename: PathBuf::from(path.as_path()), + }, + span, + ) + .with_source(importer_source.clone()); + + handler.push(err); + return None; + }; + + let mut error_handler = ErrorCollector::new(); + let source = CanonSourceFile::new(path.clone(), Arc::from(content)); + let ast = parse::Program::parse_from_str_with_errors( + new_id, + source.clone(), + unstable_features, + &mut error_handler, + ); + + if error_handler.has_errors() { + handler.extend_with_handler(source, &error_handler); + return None; + } + + ast.map(|program| SourceModule { source, program }) } } @@ -363,11 +371,27 @@ impl DependencyGraph { struct ImportContext<'a> { current: CurrentModule, dependency_map: &'a DependencyMap, - use_cache: &'a mut HashMap, + use_cache: &'a mut HashMap, handler: &'a mut ErrorCollector, } impl<'a> ImportContext<'a> { + /// Recursively walks an item, collecting resolved imports. + /// Recurses into inline `mod` blocks. + fn process_item(&mut self, item: &parse::Item, valid_imports: &mut Vec<(CanonPath, Span)>) { + match item { + parse::Item::Use(use_decl) => valid_imports.extend(self.resolve_single(use_decl)), + parse::Item::Module(module) => { + for item in module.items() { + self.process_item(item, valid_imports); + } + } + + // These items carry no import information at this stage and can be safely skipped. + parse::Item::TypeAlias(_) | parse::Item::Function(_) | parse::Item::Ignored => {} + } + } + /// Resolves a single `use` declaration, caches the result for reuse during /// later graph construction phases, and returns the resolved path and span. /// Returns `None` and reports to `handler` if resolution fails. @@ -384,18 +408,12 @@ impl<'a> ImportContext<'a> { } }; - // We assign a file ID to prevent collisions between identical `use` statements across different files. - // For instance, `use crate::A::foo;` in the local workspace and in an external dependency - // might resolve to completely different implementations of the `foo` function. - let mut use_decl = use_decl.clone(); let span = *use_decl.span(); - use_decl.set_file_id(self.current.id); - let result: (CanonPath, Span) = (resolved.path.clone(), span); // Since we found an error, when we can reevalute the result, we do not want to break it again // So, add error to prevent similar cases in the future - if let Some(old_value) = self.use_cache.insert(use_decl, resolved) { + if let Some(old_value) = self.use_cache.insert(span, resolved) { let msg = format!( "Reevaluated an existing use_decl. Old value was: {:?}", old_value @@ -406,26 +424,6 @@ impl<'a> ImportContext<'a> { } Some(result) } - - /// Recursively walks an item, collecting resolved imports. - /// Recurses into inline `mod` blocks. - fn process_item(&mut self, item: &parse::Item, valid_imports: &mut Vec<(CanonPath, Span)>) { - match item { - parse::Item::Use(use_decl) => { - if let Some(import) = self.resolve_single(use_decl) { - valid_imports.push(import); - } - } - parse::Item::Module(module) => { - for item in module.items() { - self.process_item(item, valid_imports); - } - } - - // These items carry no import information at this stage and can be safely skipped. - parse::Item::TypeAlias(_) | parse::Item::Function(_) | parse::Item::Ignored => {} - } - } } /// Shared mutable state threaded through dependency loading. @@ -508,6 +506,7 @@ pub(crate) mod tests { let main_canon_source = CanonSourceFile::new(root_p, Arc::from(root_content)); let main_program_option = parse::Program::parse_from_str_with_errors( + MAIN_MODULE, main_canon_source.clone(), &UnstableFeatures::all(), &mut handler, diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index 27c7965c..ef7e41b9 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -16,10 +16,6 @@ impl DependencyGraph { } } - fn get_module_name(source_id: usize) -> Identifier { - Identifier::from_str_unchecked(format!("unit_{}", source_id).as_str()) - } - /// Constructs the unified array of items for the entire multi-program. fn build_program( &self, @@ -35,7 +31,7 @@ impl DependencyGraph { .program .items() .iter() - .filter_map(|item| self.rewrite_item(source_id, item)) + .filter_map(|item| self.rewrite_item(item)) .collect(); if source_id == MAIN_MODULE { @@ -64,49 +60,40 @@ impl DependencyGraph { } /// Rewrites a single item for the flattened single-file representation. - fn rewrite_item(&self, source_id: usize, item: &parse::Item) -> Option { + fn rewrite_item(&self, item: &parse::Item) -> Option { match item { - parse::Item::TypeAlias(alias) => { - let mut alias = alias.clone(); - alias.set_file_id(source_id); - Some(parse::Item::TypeAlias(alias)) - } - parse::Item::Function(function) => { - let mut function = function.clone(); - function.set_file_id(source_id); - Some(parse::Item::Function(function)) - } - parse::Item::Use(use_decl) => Some(self.rewrite_use(source_id, use_decl)), + parse::Item::Use(use_decl) => Some(self.rewrite_use(use_decl)), parse::Item::Module(module) => { let items: Vec = module .items() .iter() - .filter_map(|inner_item| self.rewrite_item(source_id, inner_item)) + .filter_map(|inner_item| self.rewrite_item(inner_item)) .collect(); Some(parse::Item::Module(parse::Module::new( - source_id, + module.span().file_id, module.visibility().clone(), module.name().clone(), &items, ))) } + parse::Item::TypeAlias(_) | parse::Item::Function(_) => Some(item.clone()), parse::Item::Ignored => None, } } - /// Rewrites a `use` declaration by replacing the drp alias with the canonical - /// `file_N` module name, prepending it to the remaining `mod_path` from the cache. - /// If the target is the `MAIN_MODULE`, the `file_N` segment is safely omitted. + /// Rewrites a `use` declaration to its canonical `crate`-rooted form. /// - /// ## Example + /// The resolved path becomes `crate::::`, where + /// `` is `file_N` for dependency files and is omitted when the + /// target is `MAIN_MODULE` (via `get_module_name`). /// - /// `use base_math::simple_op::hash` into `use file_2::hash` - /// `use crate::inline_mod::item` into `use crate::inline_mod::item` - fn rewrite_use(&self, source_id: usize, use_decl: &parse::UseDecl) -> parse::Item { - let mut use_decl = use_decl.clone(); - use_decl.set_file_id(source_id); - let resolved = &self.use_cache[&use_decl]; + /// ## Examples + /// + /// - `use base_math::simple_op::hash` → `use crate::file_2::hash` + /// - `use some_dep::item` (target = `MAIN_MODULE`) → `use crate::item` + fn rewrite_use(&self, use_decl: &parse::UseDecl) -> parse::Item { + let resolved = &self.use_cache[use_decl.span()]; let target_id = self .source_map .get_id(&resolved.path) @@ -114,13 +101,17 @@ impl DependencyGraph { let mut new_path = Vec::with_capacity(resolved.mod_path.len() + 2); new_path.push(Identifier::from_str_unchecked(CRATE_STR)); - new_path.push(Self::get_module_name(target_id)); new_path.extend(resolved.mod_path.iter().cloned()); + let mut use_decl = use_decl.clone(); use_decl.set_path(&new_path); parse::Item::Use(use_decl) } + + fn get_module_name(source_id: usize) -> Identifier { + Identifier::from_str_unchecked(format!("unit_{}", source_id).as_str()) + } } #[cfg(test)] diff --git a/src/error.rs b/src/error.rs index dc63c10c..2bcb11d4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use chumsky::error::Error as ChumskyError; use chumsky::input::ValueInput; use chumsky::label::LabelError; +use chumsky::span::SimpleSpan; use chumsky::text::Char; use chumsky::util::MaybeRef; use chumsky::DefaultExpected; @@ -23,6 +24,8 @@ use crate::unstable::UnstableFeature; /// Area that an object spans inside a file. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct Span { + /// Identifier of the source file this span refers to. + pub file_id: usize, /// Position where the object starts, inclusively. pub start: usize, /// Position where the object ends, exclusively. @@ -30,18 +33,30 @@ pub struct Span { } impl Span { - /// A dummy span. - #[cfg(feature = "arbitrary")] - pub(crate) const DUMMY: Self = Self::new(0, 0); + pub(crate) const DUMMY: Self = Self::new(0, 0..0); /// Create a new span. /// /// ## Panics /// - /// Start comes after end. - pub const fn new(start: usize, end: usize) -> Self { - assert!(start <= end, "Start cannot come after end"); - Self { start, end } + /// Panics if `start > end`. + pub const fn new(file_id: usize, range: Range) -> Self { + assert!(range.start <= range.end, "Start cannot come after end"); + Self { + file_id, + start: range.start, + end: range.end, + } + } + + /// EOF sentinel: zero-width position at the end of `file_id`'s contents + pub const fn eof(file_id: usize, source_len: usize) -> Self { + // start == end is intentional + Self::new(file_id, source_len..source_len) + } + + pub const fn from_chumsky(file_id: usize, span: SimpleSpan) -> Self { + Self::new(file_id, span.start..span.end) } /// Return a slice from the given `file` that corresponds to the span. @@ -51,18 +66,16 @@ impl Span { } impl chumsky::span::Span for Span { - type Context = (); - + type Context = usize; type Offset = usize; - fn new((): Self::Context, range: Range) -> Self { - Self { - start: range.start, - end: range.end, - } + fn new(file_id: Self::Context, range: Range) -> Self { + Self::new(file_id, range) } - fn context(&self) -> Self::Context {} + fn context(&self) -> Self::Context { + self.file_id + } fn start(&self) -> Self::Offset { self.start @@ -75,29 +88,19 @@ impl chumsky::span::Span for Span { impl fmt::Display for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}..{}", self.start, self.end)?; - Ok(()) - } -} - -impl From for Span { - fn from(span: chumsky::span::SimpleSpan) -> Self { - Self { - start: span.start, - end: span.end, - } + write!(f, "{}..{}", self.start, self.end) } } -impl From> for Span { - fn from(range: Range) -> Self { - Self::new(range.start, range.end) +impl From> for Span { + fn from(span: SimpleSpan) -> Self { + Self::new(span.context, span.start..span.end) } } impl From<&str> for Span { fn from(s: &str) -> Self { - Span::new(0, s.len()) + Span::new(crate::driver::MAIN_MODULE, 0..s.len()) } } @@ -208,7 +211,7 @@ impl RichError { error: Box::new(Error::CannotParse { msg: reason.to_string(), }), - span: Span::new(0, 0), + span: Span::DUMMY, source: None, } } @@ -927,8 +930,16 @@ impl From for Error { #[cfg(test)] mod tests { + use crate::driver::MAIN_MODULE; + use super::*; + impl Span { + pub const fn new_in_default_file(range: Range) -> Self { + Self::new(MAIN_MODULE, range) + } + } + const CONTENT: &str = r#"let a1: List = None; let x: u32 = Left( Right(0) @@ -938,7 +949,7 @@ let x: u32 = Left( #[test] fn display_single_line() { let error = Error::ListBoundPow2 { bound: 5 } - .with_span(Span::new(13, 19)) + .with_span(Span::new_in_default_file(13..19)) .with_content(Arc::from(CONTENT)); let expected = r#" | @@ -952,7 +963,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "Expected value of type `u32`, got `Either, _>`".to_string(), } - .with_span(Span::new(41, CONTENT.len())) + .with_span(Span::new_in_default_file(41..CONTENT.len())) .with_content(Arc::from(CONTENT)); let expected = r#" | @@ -992,7 +1003,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "This error has no file".to_string(), } - .with_span(Span::new(5, 10)); + .with_span(Span::new_in_default_file(5..10)); assert_eq!(&expected, &error.to_string()); } @@ -1013,7 +1024,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "number too large to fit in target type".to_string(), } - .with_span(Span::new(21, 26)) + .with_span(Span::new_in_default_file(21..26)) .with_content(Arc::from(file)); let expected = r#" @@ -1055,7 +1066,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "number too large to fit in target type".to_string(), } - .with_span(Span::new(12, 17)) + .with_span(Span::new_in_default_file(12..17)) .with_content(Arc::from(file)); let expected = r#" @@ -1072,7 +1083,7 @@ let x: u32 = Left( let error = Error::Grammar { msg: "Error span at (0,0)".to_string(), } - .with_span(Span::new(0, 0)) + .with_span(Span::new_in_default_file(0..0)) .with_content(Arc::from(file)); let expected = r#" @@ -1088,7 +1099,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "eof".to_string(), } - .with_span(Span::new(file.len(), file.len())) + .with_span(Span::new_in_default_file(file.len()..file.len())) .with_content(Arc::from(file)); let expected = r#" @@ -1104,7 +1115,7 @@ let x: u32 = Left( fn display_single_line_with_file() { let source = SourceFile::new(std::path::Path::new("src/main.simf"), Arc::from(CONTENT)); let error = Error::ListBoundPow2 { bound: 5 } - .with_span(Span::new(13, 19)) + .with_span(Span::new_in_default_file(13..19)) .with_source(source); let expected = r#" @@ -1121,7 +1132,7 @@ let x: u32 = Left( let error = Error::CannotParse { msg: "Expected value of type `u32`, got `Either, _>`".to_string(), } - .with_span(Span::new(41, CONTENT.len())) + .with_span(Span::new_in_default_file(41..CONTENT.len())) .with_source(source); let expected = r#" diff --git a/src/lexer.rs b/src/lexer.rs index 06d63adc..b98ef3ea 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -4,6 +4,7 @@ use chumsky::prelude::{any, choice, end, just, recursive, skip_then_retry_until} use chumsky::{error::Rich, extra, span::SimpleSpan, text, IterParser, Parser}; use crate::driver::CRATE_STR; +use crate::error::{Error, RichError, Span}; use crate::str::{Binary, Decimal, Hexadecimal}; pub type Spanned = (T, SimpleSpan); @@ -234,27 +235,29 @@ pub fn lexer<'src>( /// Lexes an input string into a stream of tokens with spans. /// /// All comments in the input code are discarded. -pub fn lex<'src>(input: &'src str) -> (Option>, Vec) { +pub fn lex(file_id: usize, input: &str) -> (Option>, Vec) { let (tokens, errors) = lexer().parse(input).into_output_errors(); - ( - tokens.map(|vec| { - vec.into_iter() - .map(|(tok, span)| (tok, crate::error::Span::from(span))) - .filter(|(tok, _)| !matches!(tok, Token::Comment | Token::BlockComment)) - .collect::>() - }), - errors - .iter() - .map(|err| { - crate::error::RichError::new( - crate::error::Error::CannotParse { - msg: err.reason().to_string(), - }, - (*err.span()).into(), - ) - }) - .collect::>(), - ) + + let tokens = tokens.map(|vec| { + vec.into_iter() + .filter(|(tok, _)| !matches!(tok, Token::Comment | Token::BlockComment)) + .map(|(tok, span)| (tok, Span::from_chumsky(file_id, span))) + .collect() + }); + + let errors = errors + .into_iter() + .map(|err| { + RichError::new( + Error::CannotParse { + msg: err.reason().to_string(), + }, + Span::from_chumsky(file_id, *err.span()), + ) + }) + .collect(); + + (tokens, errors) } /// A list of all reserved keywords. diff --git a/src/lib.rs b/src/lib.rs index 46a9836d..ee036498 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,7 @@ pub extern crate simplicity; pub use simplicity::elements; use crate::debug::DebugSymbols; -use crate::driver::{DependencyGraph, SourceMap}; +use crate::driver::{DependencyGraph, SourceMap, MAIN_MODULE}; use crate::error::{ErrorCollector, WithContent, WithSource as _}; use crate::parse::ParseFromStrWithErrors; use crate::resolution::DependencyMap; @@ -153,13 +153,14 @@ impl TemplateProgram { let source = SourceFile::anonymous(file.clone()); let mut error_handler = ErrorCollector::new(); - let parse_program = parse::Program::parse_from_str_with_errors( + let parsed_program = parse::Program::parse_from_str_with_errors( + MAIN_MODULE, source, unstable_features, &mut error_handler, ); - if let Some(program) = parse_program { + if let Some(program) = parsed_program { let Ok(ast_program) = ast::Program::analyze(&program, jet_hinter.clone_box()) .with_content(Arc::clone(&file)) .map_err(|e| error_handler.push(e)) @@ -185,9 +186,13 @@ impl TemplateProgram { unstable_features: &UnstableFeatures, handler: &mut ErrorCollector, ) -> Result<(Option, SourceMap), String> { - let program = - parse::Program::parse_from_str_with_errors(source.clone(), unstable_features, handler) - .ok_or_else(|| handler.to_string())?; + let program = parse::Program::parse_from_str_with_errors( + MAIN_MODULE, + source.clone(), + unstable_features, + handler, + ) + .ok_or_else(|| handler.to_string())?; // TODO: we should remove this errors push after refactoring errors let graph = DependencyGraph::new( @@ -589,6 +594,7 @@ pub(crate) mod tests { let mut error_handler = ErrorCollector::new(); let parse_program = parse::Program::parse_from_str_with_errors( + MAIN_MODULE, source, &UnstableFeatures::all(), &mut error_handler, diff --git a/src/parse.rs b/src/parse.rs index 61eede9d..57fe3a60 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -106,8 +106,6 @@ pub enum Visibility { /// ``` #[derive(Clone, Debug)] pub struct UseDecl { - file_id: usize, - /// The visibility of the import (e.g., `pub use` vs `use`). visibility: Visibility, @@ -134,19 +132,6 @@ impl_require_feature!(UseItems { }); impl UseDecl { - /// The driver uses this to ensure imports conform to the flattened program structure. - pub(crate) fn set_file_id(&mut self, file_id: usize) { - self.file_id = file_id; - } - - pub(crate) fn set_path(&mut self, path: &[Identifier]) { - self.path = Vec::from(path) - } - - pub fn file_id(&self) -> usize { - self.file_id - } - pub fn visibility(&self) -> &Visibility { &self.visibility } @@ -159,6 +144,14 @@ impl UseDecl { &self.path } + pub fn items(&self) -> &UseItems { + &self.items + } + + pub fn span(&self) -> &Span { + &self.span + } + pub fn str_path(&self) -> String { let path: PathBuf = self.path().iter().map(|iden| iden.as_inner()).collect(); path.display().to_string() @@ -179,24 +172,19 @@ impl UseDecl { }) } - pub fn items(&self) -> &UseItems { - &self.items - } - - pub fn span(&self) -> &Span { - &self.span + pub(crate) fn set_path(&mut self, path: &[Identifier]) { + self.path = Vec::from(path) } } -// `file_id` and `span` are required because `UseDecl` hashing is context-dependent. +// `span` are required because `UseDecl` hashing is context-dependent. // For instance, identical `use crate::...` paths differ between binary and library roots. // Tested by: `functional_tests::identical_crate_uses_in_different_package_roots_do_not_poison_resolution_cache`. -impl_eq_hash!(UseDecl; file_id, visibility, path, items, span); +impl_eq_hash!(UseDecl; visibility, path, items, span); #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for UseDecl { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let file_id = u.int_in_range(0..=3)?; let visibility = Visibility::arbitrary(u)?; let path_len = u.int_in_range(2..=4)?; let path = (0..path_len) @@ -205,7 +193,6 @@ impl<'a> arbitrary::Arbitrary<'a> for UseDecl { let items = UseItems::arbitrary(u)?; Ok(Self { - file_id, visibility, path, items, @@ -240,7 +227,6 @@ pub enum UseItems { #[derive(Clone, Debug)] pub struct Function { - file_id: usize, // The field required for the driver visibility: Visibility, name: FunctionName, params: Arc<[FunctionParam]>, @@ -250,14 +236,6 @@ pub struct Function { } impl Function { - pub fn file_id(&self) -> usize { - self.file_id - } - - pub(crate) fn set_file_id(&mut self, file_id: usize) { - self.file_id = file_id; - } - pub fn visibility(&self) -> &Visibility { &self.visibility } @@ -448,7 +426,6 @@ impl_require_feature!(CallName { #[derive(Clone, Debug)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct TypeAlias { - file_id: usize, // The field required for the driver visibility: Visibility, name: AliasName, ty: AliasedType, @@ -456,14 +433,6 @@ pub struct TypeAlias { } impl TypeAlias { - pub fn file_id(&self) -> usize { - self.file_id - } - - pub(crate) fn set_file_id(&mut self, file_id: usize) { - self.file_id = file_id; - } - pub fn visibility(&self) -> &Visibility { &self.visibility } @@ -752,7 +721,6 @@ impl MatchPattern { #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct Module { - file_id: usize, visibility: Visibility, name: ModuleName, items: Arc<[Item]>, @@ -773,11 +741,10 @@ impl Module { items: &[Item], ) -> Module { Self { - file_id, visibility, name, items: Arc::from(items), - span: Span::new(0, 0), + span: Span::new(file_id, 0..0), } } @@ -1219,6 +1186,7 @@ pub trait ParseFromStrWithErrors: Sized { /// Feature-gated syntax in the parsed AST is checked against /// `unstable_features`; uses of disabled features are pushed to `handler`. fn parse_from_str_with_errors( + file_id: usize, source: impl Into, unstable_features: &UnstableFeatures, handler: &mut ErrorCollector, @@ -1239,7 +1207,7 @@ type ParseError<'src> = extra::Err; /// This implementation only returns first encountered error. impl ParseFromStr for A { fn parse_from_str(s: &str) -> Result { - let (tokens, mut lex_errs) = crate::lexer::lex(s); + let (tokens, mut lex_errs) = crate::lexer::lex(MAIN_MODULE, s); let Some(tokens) = tokens else { return Err(lex_errs.pop().unwrap_or(RichError::parsing_error( @@ -1252,7 +1220,7 @@ impl ParseFromStr for A { .parse( tokens .as_slice() - .map((s.len()..s.len()).into(), |(t, s)| (t, s)), + .map(Span::eof(MAIN_MODULE, s.len()), |(t, s)| (t, s)), ) .into_output_errors(); @@ -1269,6 +1237,7 @@ impl ParseF for A { fn parse_from_str_with_errors( + file_id: usize, source: impl Into, unstable_features: &UnstableFeatures, handler: &mut ErrorCollector, @@ -1276,12 +1245,12 @@ impl ParseF let source: SourceFile = source.into(); let src = source.content().to_string(); - let (tokens, lex_errs) = crate::lexer::lex(&src); + let (tokens, lex_errs) = crate::lexer::lex(file_id, &src); let lex_ok = lex_errs.is_empty(); handler.extend(source.clone(), lex_errs); let tokens = tokens?; - let eoi = (src.len()..src.len()).into(); + let eoi = Span::eof(file_id, src.len()); let (ast, parse_errs) = A::parser() .parse(tokens.as_slice().map(eoi, |(t, s)| (t, s))) .into_output_errors(); @@ -1587,7 +1556,6 @@ impl ChumskyParse for Function { .then(ret) .then(body) .map_with(|((((visibility, name), params), ret), body), e| Self { - file_id: MAIN_MODULE, visibility, name, params, @@ -1652,7 +1620,6 @@ impl ChumskyParse for UseDecl { .then(items) .then_ignore(just(Token::Semi)) .map_with(|((visibility, path), items), e| Self { - file_id: MAIN_MODULE, visibility, path, items, @@ -1941,7 +1908,6 @@ impl ChumskyParse for TypeAlias { .then_ignore(just(Token::Semi)), ) .map_with(|(visibility, (name, ty)), e| Self { - file_id: MAIN_MODULE, visibility, name: name.0, ty, @@ -2232,7 +2198,7 @@ impl Match { } _ => { let match_arm_fallback = MatchArm { - expression: Arc::new(Expression::empty(Span::new(0, 0))), + expression: Arc::new(Expression::empty(Span::DUMMY)), pattern: MatchPattern::False, }; @@ -2283,7 +2249,6 @@ impl Module { visibility .then(just(Token::Mod).ignore_then(name).then(items)) .map_with(|(visibility, (name, items)), e| Self { - file_id: MAIN_MODULE, visibility, name: name.0, items, @@ -2430,7 +2395,6 @@ impl crate::ArbitraryRec for Function { fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { use arbitrary::Arbitrary; - let file_id = u.int_in_range(0..=3)?; let visibility = Visibility::arbitrary(u)?; let name = FunctionName::arbitrary(u)?; let len = u.int_in_range(0..=6)?; @@ -2440,7 +2404,6 @@ impl crate::ArbitraryRec for Function { let ret = Option::::arbitrary(u)?; let body = Expression::arbitrary_rec(u, budget).map(Expression::into_block)?; Ok(Self { - file_id, visibility, name, params, @@ -2454,15 +2417,11 @@ impl crate::ArbitraryRec for Function { #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Module { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - // A small range allows us to test scenarios where two - // randomly generated modules end up in the same file. - let file_id = u.int_in_range(0..=3)?; let visibility = Visibility::arbitrary(u)?; let name = ModuleName::arbitrary(u)?; let items_vec = generate_arbitrary_items(u)?; Ok(Self { - file_id, visibility, name, items: items_vec.into(), @@ -2675,11 +2634,10 @@ mod test { /// Creates a dummy `UseDecl` specifically for testing `DependencyMap` resolution. pub fn dummy_path(path: Vec) -> Self { Self { - file_id: MAIN_MODULE, visibility: Visibility::default(), path, items: UseItems::List(Vec::new()), - span: Span::new(0, 0), + span: Span::DUMMY, } } } @@ -2700,13 +2658,14 @@ mod test { let input = "fn main() { let ab: u8 = <(u4, u4)> : :into((0b1011, 0b1101)); }"; let source = SourceFile::anonymous(Arc::from(input)); let mut error_handler = ErrorCollector::new(); - let parse_program = Program::parse_from_str_with_errors( + let parsed_program = Program::parse_from_str_with_errors( + MAIN_MODULE, source, &UnstableFeatures::all(), &mut error_handler, ); - assert!(parse_program.is_none()); + assert!(parsed_program.is_none()); assert!(ErrorCollector::to_string(&error_handler).contains("Expected '::', found ':'")); } @@ -2715,13 +2674,14 @@ mod test { let input = "fn main() { let pk: Pubkey = witnes::::PK; }"; let source = SourceFile::anonymous(Arc::from(input)); let mut error_handler = ErrorCollector::new(); - let parse_program = Program::parse_from_str_with_errors( + let parsed_program = Program::parse_from_str_with_errors( + MAIN_MODULE, source, &UnstableFeatures::all(), &mut error_handler, ); - assert!(parse_program.is_none()); + assert!(parsed_program.is_none()); assert!(ErrorCollector::to_string(&error_handler).contains("Expected ';', found '::'")); } @@ -2729,7 +2689,8 @@ mod test { fn parse_with(input: &str, features: &UnstableFeatures) -> (bool, String) { let source = SourceFile::anonymous(Arc::from(input)); let mut handler = ErrorCollector::new(); - let program = Program::parse_from_str_with_errors(source, features, &mut handler); + let program = + Program::parse_from_str_with_errors(MAIN_MODULE, source, features, &mut handler); (program.is_none(), ErrorCollector::to_string(&handler)) } diff --git a/src/unstable.rs b/src/unstable.rs index 05bcb31f..90dcc516 100644 --- a/src/unstable.rs +++ b/src/unstable.rs @@ -360,7 +360,10 @@ mod tests { impl RequireFeature for Requires { fn feature_requirements(&self, out: &mut Vec) { - out.push(FeatureRequirement::new(self.0, Span::new(0, 3))); + out.push(FeatureRequirement::new( + self.0, + Span::new_in_default_file(0..3), + )); } }