From 1820f90ac1f3bb6310ed2ceabc335ad5ee608c66 Mon Sep 17 00:00:00 2001 From: Tobias Reiher Date: Fri, 18 Oct 2024 16:10:50 +0200 Subject: [PATCH] Improve representation of locations Ref. eng/recordflux/RecordFlux#1785 --- CHANGELOG.md | 1 + Makefile | 11 +- librapidflux/src/diagnostics/errors.rs | 279 +++++++++---------- librapidflux/src/diagnostics/locations.rs | 305 ++++++++++++--------- librapidflux/src/identifier.rs | 150 +++++----- librapidflux/src/source_code.rs | 14 +- librapidflux/src/ty.rs | 79 +++--- rapidflux/src/diagnostics/errors.rs | 12 +- rapidflux/src/diagnostics/locations.rs | 91 +++--- rapidflux/src/identifier.rs | 13 +- rapidflux/src/lib.rs | 5 +- rapidflux/src/ty.rs | 30 +- rflx/cli.py | 12 +- rflx/error.py | 21 +- rflx/expr.py | 6 +- rflx/generator/allocator.py | 6 +- rflx/generator/generator.py | 15 +- rflx/generator/state_machine.py | 8 +- rflx/graph.py | 6 +- rflx/ir.py | 10 +- rflx/ls/model.py | 2 +- rflx/ls/server.py | 4 +- rflx/model/cache.py | 5 +- rflx/model/declaration.py | 14 +- rflx/model/message.py | 18 +- rflx/model/state_machine.py | 8 +- rflx/model/statement.py | 16 +- rflx/model/top_level_declaration.py | 6 +- rflx/model/type_decl.py | 16 +- rflx/pyrflx/error.py | 4 +- rflx/rapidflux/__init__.pyi | 8 +- rflx/rapidflux/ty.pyi | 10 +- rflx/specification/parser.py | 14 +- tests/property/strategies.py | 4 +- tests/unit/cli_test.py | 11 +- tests/unit/generator/allocator_test.py | 26 +- tests/unit/generator/state_machine_test.py | 24 +- tests/unit/identifier_test.py | 4 +- tests/unit/ir_test.py | 8 +- tests/unit/ls/model_test.py | 18 +- tests/unit/ls/server_test.py | 4 +- tests/unit/model/message_test.py | 10 +- tests/unit/rapidflux/__init___test.py | 4 +- tools/test_rapidflux_coverage.sh | 11 + 44 files changed, 703 insertions(+), 620 deletions(-) create mode 100755 tools/test_rapidflux_coverage.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index df1894b8f..e8ab24223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fatal error caused by variable in case expression (eng/recordflux/RecordFlux#1800) - Simplification of expressions with a unary minus operator (eng/recordflux/RecordFlux#1595, eng/recordflux/RecordFlux#1797) - Evaluation of unary minus applied to binary expressions (eng/recordflux/RecordFlux#1797) +- Fatal errors caused by missing locations (eng/recordflux/RecordFlux#1785) ### Changed diff --git a/Makefile b/Makefile index 7e7f4d3f4..3c54d7c83 100644 --- a/Makefile +++ b/Makefile @@ -364,22 +364,15 @@ test: test_rflx test_rapidflux test_examples test_rflx: test_coverage test_unit_coverage test_per_unit_coverage test_language_coverage test_end_to_end test_property test_tools test_ide test_optimized test_compilation test_binary_size - test_rapidflux_coverage: rapidflux_devel - cargo llvm-cov \ - nextest \ - --package librapidflux \ - --fail-under-lines 100 \ - --show-missing-lines \ - --skip-functions \ - --no-fail-fast # Do not stop on the first failure for CI runs + @tools/test_rapidflux_coverage.sh # nextest cannot be used with `doctests` with stable Rust. # See: https://github.com/nextest-rs/nextest/issues/16 test_rapidflux_doc: rapidflux_devel cargo test --package librapidflux --doc --no-fail-fast -test_rapidflux: test_rapidflux_coverage test_rapidflux_mutation test_rapidflux_doc +test_rapidflux: test_rapidflux_coverage test_rapidflux_doc test_rapidflux_mutation test_rapidflux_mutation: rapidflux_devel cargo mutants -j 4 --package librapidflux --timeout 300 --output $(BUILD_DIR) diff --git a/librapidflux/src/diagnostics/errors.rs b/librapidflux/src/diagnostics/errors.rs index cd38998d9..30011fbaa 100644 --- a/librapidflux/src/diagnostics/errors.rs +++ b/librapidflux/src/diagnostics/errors.rs @@ -1,20 +1,17 @@ use std::{ fmt::{Debug, Display}, io::{self, BufRead, Write}, - path::PathBuf, - str::FromStr, sync::atomic::{AtomicU64, Ordering}, }; #[cfg(not(test))] use annotate_snippets::renderer::{Color, Style}; use annotate_snippets::Message; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use crate::source_code; -use super::Location; +use super::{Location, NO_SOURCE}; #[cfg(not(test))] mod colors { @@ -178,7 +175,7 @@ impl Annotation { pub struct ErrorEntry { message: String, severity: Severity, - location: Option, + location: Location, annotations: Vec, generate_default: bool, } @@ -189,7 +186,11 @@ impl Display for ErrorEntry { write!( f, "{}{}: {}{}{}", - self.location().map_or(String::new(), |l| format!("{l}: ")), + if matches!(self.location(), Location::None) { + String::new() + } else { + format!("{}: ", self.location()) + }, self.severity, self.message, if self.annotations().iter().any(|a| a.label().is_some()) { @@ -213,7 +214,11 @@ impl Display for ErrorEntry { write!( f, "{}{}: {}{}{}", - self.location().map_or(String::new(), |l| format!("{l}: ")), + if matches!(self.location(), Location::None) { + String::new() + } else { + format!("{}: ", self.location()) + }, self.severity, self.message.bold(), if self.annotations().iter().any(|a| a.label().is_some()) { @@ -234,7 +239,7 @@ impl ErrorEntry { pub fn new( message: String, severity: Severity, - location: Option, + location: Location, annotations: Vec, generate_default: bool, ) -> Self { @@ -259,8 +264,8 @@ impl ErrorEntry { self.severity } - pub fn location(&self) -> Option<&Location> { - self.location.as_ref() + pub fn location(&self) -> &Location { + &self.location } pub fn annotations(&self) -> &[Annotation] { @@ -287,25 +292,15 @@ impl ErrorEntry { Severity::Note => annotate_snippets::Level::Note.title(&self.message), }; - match self.location() { - Some(location) if self.generate_default => { - let default_annotation = Annotation::new(None, self.severity, location.clone()); + if self.generate_default && !matches!(self.location(), Location::None) { + let default_annotation = Annotation::new(None, self.severity, self.location().clone()); - // Add squiggles below the actual error. Without this, the user won't be able to - // see the error location (e.g. `foo.rflx:3:4`). - self.annotations.insert(0, default_annotation); - } - _ => (), + // Add squiggles below the actual error. Without this, the user won't be able to + // see the error location (e.g. `foo.rflx:3:4`). + self.annotations.insert(0, default_annotation); }; - if self.annotations.is_empty() - || source.is_empty() - || self.location.as_ref().is_some_and(|l| { - l.source - .as_ref() - .is_some_and(|s| s == &PathBuf::from_str("").expect("unreachable")) - }) - { + if self.annotations.is_empty() || source.is_empty() { return None; } @@ -313,13 +308,10 @@ impl ErrorEntry { .fold(true) .annotations(self.annotations.iter().map(|a| a.to_annotation(source))); - Some(message.snippet( - if let Some(Some(source_file)) = self.location.as_ref().map(|l| l.source.as_ref()) { - snippet.origin(source_file.to_str().unwrap_or("")) - } else { - snippet - }, - )) + Some(message.snippet(match &self.location { + Location::File { source, .. } => snippet.origin(source.to_str().unwrap_or(NO_SOURCE)), + _ => snippet, + })) } } @@ -401,24 +393,15 @@ impl RapidFluxError { /// Print all messages to `stdout` /// /// # Errors + /// /// Source code needs to be retrieved and error message displayed. This function /// might return an `io::Error` if any io operation failed. pub fn print_messages(&mut self, stream: &mut T) -> io::Result<()> { - lazy_static! { - static ref STDIN_PATH: PathBuf = PathBuf::from_str("").expect("unreachable"); - } - for entry in &mut self.entries { - let source_code = - if let Some(Some(source_path)) = entry.location().map(|l| l.source.as_ref()) { - if source_path == STDIN_PATH.as_path() { - None - } else { - source_code::retrieve(source_path) - } - } else { - None - }; + let source_code = match entry.location() { + Location::File { source, .. } => source_code::retrieve(source), + _ => None, + }; match entry.to_message_mut(&source_code.unwrap_or_default()) { Some(msg) => Self::print_without_trailing_whitespaces(stream, msg)?, @@ -464,7 +447,6 @@ mod tests { use std::{ io::{self, Read, Seek}, path::PathBuf, - str::FromStr, }; use indoc::indoc; @@ -494,9 +476,8 @@ mod tests { Annotation::new( None, Severity::Error, - Location { - source: Some(PathBuf::from_str("foo.rflx") - .expect("failed to create source path")), + Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), }, @@ -507,9 +488,8 @@ mod tests { Annotation::new( Some("some. terrible. error".to_string()), Severity::Error, - Location { - source: Some(PathBuf::from_str("foo.rflx") - .expect("failed to create source path")), + Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), }, @@ -520,8 +500,7 @@ mod tests { Annotation::new( Some("some. terrible. error".to_string()), Severity::Error, - Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), }, @@ -551,8 +530,8 @@ mod tests { let annotation = Annotation::new( label.clone(), severity, - Location { - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create source path")), + Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 5), }, @@ -575,8 +554,7 @@ mod tests { let annotation = Annotation::new( Some("label".to_string()), Severity::Error, - Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), }, @@ -584,8 +562,7 @@ mod tests { assert_eq!( annotation.location(), - &Location { - source: None, + &Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), } @@ -600,12 +577,11 @@ mod tests { let error_entry = ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - None, + Location::None, vec![Annotation::new( Some("Look here".to_string()), Severity::Info, - Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 2), end: FilePosition::new(3, 4), }, @@ -615,14 +591,13 @@ mod tests { assert_eq!(error_entry.severity(), Severity::Error); assert_eq!(error_entry.message(), "Some terrible error"); - assert!(error_entry.location().is_none()); + assert_eq!(*error_entry.location(), Location::None); assert_eq!( error_entry.annotations(), vec![Annotation::new( Some("Look here".to_string()), Severity::Info, - Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 2), end: FilePosition::new(3, 4), }, @@ -636,12 +611,11 @@ mod tests { let mut entry = ErrorEntry::new( "entry".to_string(), Severity::Error, - None, + Location::None, Vec::new(), false, ); - let annotation = - Annotation::new(Some("a".to_string()), Severity::Error, Location::default()); + let annotation = Annotation::new(Some("a".to_string()), Severity::Error, location()); assert!(entry.annotations.is_empty()); entry.extend([annotation.clone()]); assert_eq!(entry.annotations, &[annotation.clone()]); @@ -652,7 +626,7 @@ mod tests { let mut entry = ErrorEntry::new( "entry".to_string(), Severity::Error, - None, + Location::None, Vec::new(), false, ); @@ -666,12 +640,11 @@ mod tests { let mut entry = ErrorEntry::new( "entry".to_string(), Severity::Error, - None, + Location::None, Vec::new(), false, ); - let annotation = - Annotation::new(Some("a".to_string()), Severity::Error, Location::default()); + let annotation = Annotation::new(Some("a".to_string()), Severity::Error, location()); assert!(entry.annotations.is_empty()); entry.extend([annotation.clone(), annotation.clone()]); assert_eq!(entry.annotations, &[annotation.clone(), annotation.clone()]); @@ -693,7 +666,7 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - None, + Location::None, Vec::new(), true, ), @@ -704,7 +677,7 @@ mod tests { ErrorEntry::new( "info".to_string(), Severity::Info, - None, + Location::None, Vec::new(), true, ), @@ -715,7 +688,7 @@ mod tests { ErrorEntry::new( "help".to_string(), Severity::Help, - None, + Location::None, Vec::new(), true, ), @@ -726,7 +699,7 @@ mod tests { ErrorEntry::new( "warning".to_string(), Severity::Warning, - None, + Location::None, Vec::new(), true, ), @@ -737,7 +710,7 @@ mod tests { ErrorEntry::new( "note".to_string(), Severity::Note, - None, + Location::None, Vec::new(), true, ), @@ -748,11 +721,10 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, ), @@ -765,30 +737,15 @@ mod tests { |" }, )] - #[case::error_entry_stdin( - ErrorEntry::new( - "Some terrible error".to_string(), - Severity::Error, - Some(Location { - source: Some("".into()), - start: FilePosition::new(1, 1), - end: FilePosition::new(1, 8), - }), - Vec::new(), - true, - ), - "package Test is end Test;", - ":1:1: error: Some terrible error", - )] #[case::error_entry_with_location_and_source_file( ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: Some(PathBuf::from_str("test.rflx").expect("failed to create path")), + Location::File { + source: PathBuf::from("test.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, ), @@ -806,17 +763,16 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: Some(PathBuf::from_str("test.rflx").expect("failed to create path")), + Location::File { + source: PathBuf::from("test.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, vec![ Annotation::new( Some("some help".to_string()), Severity::Help, - Location { - source: None, + Location::Stdin { start: FilePosition::new(2, 1), end: FilePosition::new(2, 4), }, @@ -860,11 +816,10 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, ), @@ -874,11 +829,11 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")), + Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, ), @@ -888,16 +843,16 @@ mod tests { ErrorEntry::new( "Some terrible error".to_string(), Severity::Error, - Some(Location { - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")), + Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), - }), + }, vec![ Annotation { severity: Severity::Info, - location: Location { - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")), + location: Location::File { + source: PathBuf::from("foo.rflx"), start: FilePosition::new(1, 1), end: FilePosition::new(1, 8), }, @@ -921,7 +876,7 @@ mod tests { entries: vec![ErrorEntry::new( "first".to_string(), Severity::Error, - None, + Location::None, Vec::new(), true, )], @@ -931,7 +886,7 @@ mod tests { vec![ErrorEntry::new( "first".to_string(), Severity::Error, - None, + Location::None, Vec::new(), true, )] @@ -941,14 +896,14 @@ mod tests { #[rstest] #[case::errors( vec![ - ErrorEntry::new("okay".to_string(), Severity::Info, None, Vec::new(), true), - ErrorEntry::new("ooof".to_string(), Severity::Error, None, Vec::new(), true), + ErrorEntry::new("okay".to_string(), Severity::Info, Location::None, Vec::new(), true), + ErrorEntry::new("ooof".to_string(), Severity::Error, Location::None, Vec::new(), true), ], true, )] #[case::no_errors( vec![ - ErrorEntry::new("okay".to_string(), Severity::Info, None, Vec::new(), true), + ErrorEntry::new("okay".to_string(), Severity::Info, Location::None, Vec::new(), true), ], false, )] @@ -964,11 +919,17 @@ mod tests { fn test_rapid_flux_error_display() { let error = RapidFluxError { entries: vec![ - ErrorEntry::new("first".to_string(), Severity::Error, None, Vec::new(), true), + ErrorEntry::new( + "first".to_string(), + Severity::Error, + Location::None, + Vec::new(), + true, + ), ErrorEntry::new( "second".to_string(), Severity::Warning, - None, + Location::None, Vec::new(), true, ), @@ -985,14 +946,14 @@ mod tests { ErrorEntry::new( "first".to_string(), Severity::Error, - None, - vec![Annotation::new(None, Severity::Error, Location::default())], + Location::None, + vec![Annotation::new(None, Severity::Error, location())], true, ), ErrorEntry::new( "second".to_string(), Severity::Warning, - None, + Location::None, Vec::new(), true, ), @@ -1002,8 +963,8 @@ mod tests { assert_eq!( format!("{error:?}").as_str(), "[ErrorEntry { message: \"first\", severity: Error, location: None, annotations: \ - [Annotation { label: None, severity: Error, location: Location { start: \ - FilePosition(0, 0), end: FilePosition(0, 0), source: None } }], generate_default: true }, \ + [Annotation { label: None, severity: Error, location: File { start: \ + FilePosition(1, 1), end: FilePosition(2, 2), source: \"file\" } }], generate_default: true }, \ ErrorEntry { message: \"second\", severity: Warning, location: None, \ annotations: [], generate_default: true }]" ); @@ -1028,8 +989,8 @@ mod tests { let entry = ErrorEntry { message: "dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }; @@ -1048,8 +1009,8 @@ mod tests { let entry = ErrorEntry { message: "dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }; @@ -1067,15 +1028,15 @@ mod tests { let entry = ErrorEntry { message: "dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }; let second_entry = ErrorEntry { message: "other dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }; @@ -1092,15 +1053,15 @@ mod tests { ErrorEntry { message: "dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }, ErrorEntry { message: "other dummy".to_string(), severity: Severity::Error, - annotations: vec![Annotation::new(None, Severity::Error, Location::default())], - location: Some(Location::default()), + annotations: vec![Annotation::new(None, Severity::Error, location())], + location: location(), generate_default: true, }, ]; @@ -1136,23 +1097,23 @@ mod tests { #[rstest] #[case::rapidfluxerror_oneline_error( - vec![ErrorEntry::new("Simple error".to_string(), Severity::Error, None, Vec::new(), true)].into(), + vec![ErrorEntry::new("Simple error".to_string(), Severity::Error, Location::None, Vec::new(), true)].into(), "error: Simple error\n", )] #[case::rapidfluxerror_oneline_warning( - vec![ErrorEntry::new("Simple warning".to_string(), Severity::Warning, None, Vec::new(), true)].into(), + vec![ErrorEntry::new("Simple warning".to_string(), Severity::Warning, Location::None, Vec::new(), true)].into(), "warning: Simple warning\n", )] #[case::rapidfluxerror_oneline_note( - vec![ErrorEntry::new("Simple note".to_string(), Severity::Note, None, Vec::new(), true)].into(), + vec![ErrorEntry::new("Simple note".to_string(), Severity::Note, Location::None, Vec::new(), true)].into(), "note: Simple note\n", )] #[case::rapidfluxerror_oneline_help( - vec![ErrorEntry::new("Simple help".to_string(), Severity::Help, None, Vec::new(), true)].into(), + vec![ErrorEntry::new("Simple help".to_string(), Severity::Help, Location::None, Vec::new(), true)].into(), "help: Simple help\n", )] #[case::rapidfluxerror_oneline_info( - vec![ErrorEntry::new("Simple info".to_string(), Severity::Info, None, Vec::new(), true)].into(), + vec![ErrorEntry::new("Simple info".to_string(), Severity::Info, Location::None, Vec::new(), true)].into(), "info: Simple info\n", )] #[case::rapidfluxerror_location_from_stdin( @@ -1160,11 +1121,11 @@ mod tests { ErrorEntry::new( "Annotated error".to_string(), Severity::Error, - Some(Location { + Location::File { start: FilePosition::new(1, 1), - source: Some(PathBuf::from_str("").unwrap()), + source: PathBuf::from(""), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, ) @@ -1197,15 +1158,15 @@ mod tests { #[allow(clippy::items_after_statements)] #[serial] fn test_rapid_flux_error_print_message_default_annotation() { - let file_path = PathBuf::from_str("tests/data/sample.rflx").unwrap(); + let file_path = PathBuf::from("tests/data/sample.rflx"); let mut error: RapidFluxError = vec![ErrorEntry::new( "Annotated error".to_string(), Severity::Error, - Some(Location { + Location::File { start: FilePosition::new(1, 1), - source: Some(file_path.clone()), + source: file_path.clone(), end: FilePosition::new(1, 8), - }), + }, Vec::new(), true, )] @@ -1248,10 +1209,10 @@ mod tests { severity: Severity::Error, annotations: vec![Annotation { severity: Severity::Error, - location: Location::default(), + location: location(), label: None, }], - location: None, + location: Location::None, generate_default: true, }, ErrorEntry { @@ -1274,4 +1235,12 @@ mod tests { assert_ne!(addr_of!(error), addr_of!(cloned)); } + + fn location() -> Location { + Location::File { + start: FilePosition::new(1, 1), + end: FilePosition::new(2, 2), + source: PathBuf::from("file"), + } + } } diff --git a/librapidflux/src/diagnostics/locations.rs b/librapidflux/src/diagnostics/locations.rs index 1a4c20b55..9027714ab 100644 --- a/librapidflux/src/diagnostics/locations.rs +++ b/librapidflux/src/diagnostics/locations.rs @@ -1,16 +1,10 @@ use core::fmt; use std::{fmt::Display, path::PathBuf}; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; -lazy_static! { - pub static ref UNKNOWN_LOCATION: Location = Location::new( - FilePosition(1, 1), - FilePosition(1, 1), - Some(PathBuf::from("")) - ); -} +pub const NO_SOURCE: &str = ""; +pub const STDIN_SOURCE: &str = ""; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default, Debug)] pub struct FilePosition(u32, u32); @@ -78,10 +72,18 @@ impl Display for FilePosition { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)] -pub struct Location { - pub start: FilePosition, - pub end: FilePosition, - pub source: Option, +pub enum Location { + #[default] + None, + Stdin { + start: FilePosition, + end: FilePosition, + }, + File { + start: FilePosition, + end: FilePosition, + source: PathBuf, + }, } impl Location { @@ -92,7 +94,15 @@ impl Location { /// This function will panic if the end is before the start. pub fn new(start: FilePosition, end: FilePosition, source: Option) -> Self { assert!(start <= end); - Location { start, end, source } + assert!( + source.is_none() + || matches!(source, Some(ref s) if s.to_string_lossy() != STDIN_SOURCE && s.to_string_lossy() != NO_SOURCE) + ); + if let Some(source) = source { + Location::File { start, end, source } + } else { + Location::Stdin { start, end } + } } /// Merges a list of locations into a single location. @@ -107,79 +117,126 @@ impl Location { /// # Panics /// /// This function will panic if attempting to merge locations with and without a source file. - pub fn merge(locations: &[Self]) -> Option { - assert!( - locations + pub fn merge(locations: &[Self]) -> Self { + || -> Option<_> { + let known_locations = locations .iter() - .all(|l| l == &*UNKNOWN_LOCATION || l.source.is_none()) - || locations.iter().all(|l| l.source.is_some()), - "attempted to merge locations with and without source file" - ); - let first_location = locations.first()?; - let filter_first_path = |l: &&Location| l.source.as_ref() == first_location.source.as_ref(); - - let min_loc = locations - .iter() - .filter(filter_first_path) - .map(|l| l.start) - .chain(locations.iter().filter(filter_first_path).map(|l| l.end)) - .min()?; - - let max_loc = locations - .iter() - .filter(filter_first_path) - .map(|l| l.start) - .chain(locations.iter().filter(filter_first_path).map(|l| l.end)) - .max() - .expect("unreachable"); - - Some(Self { - start: min_loc, - end: max_loc, - source: locations.first()?.source.clone(), - }) + .filter(|l| **l != Location::None) + .collect::>(); + + assert!( + known_locations + .iter() + .all(|l| matches!(l, Location::Stdin { .. })) + || known_locations + .iter() + .all(|l| matches!(l, Location::File { .. })), + "attempted to merge locations with and without source file" + ); + + let first_location = known_locations.first()?; + let filter_first_path = |l: &&&Location| match (l, first_location) { + (Location::Stdin { .. }, Location::Stdin { .. }) => true, + ( + Location::File { + source: l_source, .. + }, + Location::File { + source: first_source, + .. + }, + ) => l_source == first_source, + _ => unreachable!(), + }; + let filtered_locations = known_locations + .iter() + .filter(filter_first_path) + .collect::>(); + + let get_start = |l: &&&Location| match l { + Location::Stdin { start, .. } | Location::File { start, .. } => *start, + Location::None => unreachable!(), + }; + let get_end = |l: &&&Location| match l { + Location::Stdin { end, .. } | Location::File { end, .. } => *end, + Location::None => unreachable!(), + }; + + let min_loc = filtered_locations + .iter() + .map(get_start) + .chain(filtered_locations.iter().map(get_end)) + .min()?; + let max_loc = filtered_locations + .iter() + .map(get_start) + .chain(filtered_locations.iter().map(get_end)) + .max() + .expect("unreachable"); + + match first_location { + Location::Stdin { .. } => Some(Location::Stdin { + start: min_loc, + end: max_loc, + }), + Location::File { source, .. } => Some(Location::File { + start: min_loc, + end: max_loc, + source: source.clone(), + }), + Location::None => unreachable!(), + } + }() + .unwrap_or(Location::None) } /// Retrieve a `Range` representing the location to annotate in an error. /// /// # Panics - /// This function is called for a location that references no source file. + /// + /// This function will panic if it is called for `Location::None`. pub fn to_file_offset(&self, source: &str) -> std::ops::Range { - let start_offset = self.start.get_offset(source); - let end_offset = self.end.get_offset(source); - - std::ops::Range { - start: start_offset - 1, - end: end_offset - 1, + match self { + Location::Stdin { start, end, .. } | Location::File { start, end, .. } => { + let start_offset = start.get_offset(source); + let end_offset = end.get_offset(source); + + std::ops::Range { + start: start_offset - 1, + end: end_offset - 1, + } + } + Location::None => { + panic!("attempted to get file offset without location") + } } } pub fn has_source(&self) -> bool { - self.source.is_some() + matches!(self, Location::File { .. }) } } impl fmt::Display for Location { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}:{}", - self.source - .as_ref() - .map_or("".to_string(), |p| p.to_string_lossy().to_string()), - self.start - ) + match self { + Location::None => write!(f, "{NO_SOURCE}"), + Location::Stdin { start, .. } => write!(f, "{STDIN_SOURCE}:{start}"), + Location::File { start, source, .. } => { + write!(f, "{}:{start}", source.to_string_lossy()) + } + } } } #[cfg(test)] mod tests { - use std::{ops::Range, path::PathBuf, str::FromStr}; + use std::{ops::Range, path::PathBuf}; use bincode::{deserialize, serialize}; use rstest::rstest; - use crate::diagnostics::{FilePosition, Location, UNKNOWN_LOCATION}; + use crate::diagnostics::{FilePosition, Location}; #[test] #[should_panic( @@ -214,14 +271,19 @@ Third", #[rstest] #[case::location_with_start_and_end( - Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 3) }, "foo code", 0usize..2usize )] + #[should_panic(expected = "attempted to get file offset without location")] + #[case::no_location( + Location::None, + "foo code", + 0usize..0usize + )] fn test_location_to_file_offset( #[case] location: Location, #[case] source_code: &str, @@ -231,6 +293,7 @@ Third", } #[rstest] + #[case::location_none(Location::None, "")] #[case::location_start( Location::new(FilePosition::new(1, 2), FilePosition::new(1, 2), None), ":1:2" @@ -260,7 +323,7 @@ Third", Location::new( FilePosition::new(1, 1), FilePosition::new(1, 1), - Some(PathBuf::from_str("foo.rflx").expect("failed to create path")), + Some(PathBuf::from("foo.rflx")), ), true )] @@ -275,151 +338,145 @@ Third", #[rstest] #[case::location_different_line_with_end( &[ - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - ..Location::default() }, - Location { + Location::Stdin { start: FilePosition::new(3, 1), end: FilePosition::new(3, 10), - ..Default::default() } ], - Some(Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(3, 10), - ..Default::default() - }), + }, )] #[case::location_same_line_with_end( &[ - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - ..Location::default() }, - Location { + Location::Stdin { start: FilePosition::new(1, 4), end: FilePosition::new(1, 27), - ..Default::default() } ], - Some(Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 27), - ..Default::default() - }), + }, )] #[case::location_overlap( &[ - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - ..Location::default() }, - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(28, 4), - ..Location::default() }, - Location { + Location::Stdin { start: FilePosition::new(1, 4), end: FilePosition::new(1, 27), - ..Default::default() } ], - Some(Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(28, 4), - ..Default::default() - }), + }, )] - #[case::location_one_element( + #[case::one_element( &[ - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - ..Location::default() }, ], - Some(Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - ..Default::default() - }), + }, )] - #[case::location_with_source( + #[case::files( &[ - Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")) + source: PathBuf::from("foo.rflx") }, - Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")) + source: PathBuf::from("foo.rflx") }, ], - Some(Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")) - }), + source: PathBuf::from("foo.rflx") + }, )] - #[case::location_merge_first_filename( + #[case::files_with_mixed_sources( &[ - Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 17), - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")) + source: PathBuf::from("foo.rflx") }, - Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("bar.rflx").expect("failed to create path")) + source: PathBuf::from("bar.rflx") }, ], - Some(Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 17), - source: Some(PathBuf::from_str("foo.rflx").expect("failed to create path")) - }), + source: PathBuf::from("foo.rflx") + }, )] #[should_panic(expected = "attempted to merge locations with and without source file")] - #[case::location_merge_source_present_and_absent( + #[case::stdin_and_file( &[ - Location { + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 17), - source: None, }, - Location { + Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("bar.rflx").expect("failed to create path")) + source: PathBuf::from("bar.rflx") }, ], - None, + Location::None, )] - #[case::location_merge_unknown( + #[case::none_and_file( &[ - Location { + Location::None, + Location::File { start: FilePosition::new(1, 4), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("bar.rflx").expect("failed to create path")) + source: PathBuf::from("bar.rflx") }, - UNKNOWN_LOCATION.clone(), + Location::None, ], - Some(Location { + Location::File { start: FilePosition::new(1, 4), end: FilePosition::new(1, 10), - source: Some(PathBuf::from_str("bar.rflx").expect("failed to create path")) - }), + source: PathBuf::from("bar.rflx") + }, + )] + #[case::none( + &[ + Location::None, + ], + Location::None, )] - #[case::location_no_elements(&[], None)] - fn test_location_merge(#[case] locations: &[Location], #[case] expected: Option) { + #[case::no_elements(&[], Location::None)] + fn test_location_merge(#[case] locations: &[Location], #[case] expected: Location) { assert_eq!(Location::merge(locations), expected); } diff --git a/librapidflux/src/identifier.rs b/librapidflux/src/identifier.rs index 696beb27d..8482367b3 100644 --- a/librapidflux/src/identifier.rs +++ b/librapidflux/src/identifier.rs @@ -3,7 +3,7 @@ use std::{fmt, string::ToString}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::diagnostics::{Location, UNKNOWN_LOCATION}; +use crate::diagnostics::Location; pub const ID_SEP: &str = "::"; const ALT_ID_SEP: &str = "."; @@ -14,14 +14,14 @@ pub type IDResult = Result; #[derive(Clone, Serialize, Deserialize, Eq, Debug)] pub struct ID { identifier: String, - location: Option, + location: Location, } impl ID { /// # Errors /// /// Will return `IDError::InvalidIdentifier` if identifier is invalid. - pub fn new(identifier: &str, location: Option) -> IDResult { + pub fn new(identifier: &str, location: Location) -> IDResult { let identifier = identifier.replace(ALT_ID_SEP, ID_SEP); if !identifier.is_empty() && identifier.split(ID_SEP).all(|p| { @@ -77,10 +77,10 @@ impl ID { pub fn join(&self, id: &ID) -> IDResult { Self::new( &(self.identifier.clone() + ID_SEP + id.identifier()), - if self.location.is_some() { - self.location.clone() - } else { + if self.location == Location::None { id.location.clone() + } else { + self.location.clone() }, ) } @@ -90,11 +90,7 @@ impl ID { } pub fn location(&self) -> &Location { - if let Some(location) = &self.location { - location - } else { - &UNKNOWN_LOCATION - } + &self.location } pub fn parts(&self) -> Vec<&str> { @@ -115,12 +111,12 @@ impl ID { .rsplit_once(ID_SEP) .map(|(_, name)| name) .expect("invalid identifier"), - location: self.location.as_ref(), + location: &self.location, } } else { IDRef { identifier: &self.identifier, - location: self.location.as_ref(), + location: &self.location, } } } @@ -139,7 +135,7 @@ impl ID { .rsplit_once(ID_SEP) .map(|(parent, _)| parent) .expect("invalid identifier"), - location: self.location.as_ref(), + location: &self.location, }) } else { None @@ -182,14 +178,14 @@ impl std::hash::Hash for ID { #[derive(Debug, PartialEq)] pub struct IDRef<'a> { identifier: &'a str, - location: Option<&'a Location>, + location: &'a Location, } impl IDRef<'_> { pub fn to_owned(&self) -> ID { ID { identifier: self.identifier.to_string(), - location: self.location.cloned(), + location: self.location.clone(), } } } @@ -211,13 +207,14 @@ pub enum IDError { /// # Examples /// /// ```rust -/// use librapidflux::identifier::ID; /// use librapidflux::create_id; +/// use librapidflux::diagnostics::Location; +/// use librapidflux::identifier::ID; /// -/// let id = create_id!(["A", "B"], None); +/// let id = create_id!(["A", "B"], Location::None); /// /// assert_eq!(id.identifier(), "A::B"); -/// assert_eq!(id.location(), None); +/// assert_eq!(*id.location(), Location::None); /// ``` #[macro_export] macro_rules! create_id { @@ -233,7 +230,7 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::diagnostics::{FilePosition, Location, UNKNOWN_LOCATION}; + use crate::diagnostics::{FilePosition, Location}; use super::{IDError, IDRef, ID}; @@ -244,10 +241,10 @@ mod tests { #[case("A.B::C", "A::B::C")] fn test_id_new(#[case] identifier: &str, #[case] expected: &str) { assert_eq!( - ID::new(identifier, None), + ID::new(identifier, Location::None), Ok(ID { identifier: expected.to_string(), - location: None + location: Location::None }) ); } @@ -262,18 +259,21 @@ mod tests { #[case::colon_in_part_2("A:::B")] #[case::unicode_character("🐛")] fn test_id_new_invalid(#[case] identifier: &str) { - assert_eq!(ID::new(identifier, None), Err(IDError::InvalidIdentifier)); + assert_eq!( + ID::new(identifier, Location::None), + Err(IDError::InvalidIdentifier) + ); } #[test] fn test_id_identifier() { - assert_eq!(id("A::B", None).identifier(), "A::B"); + assert_eq!(id("A::B", Location::None).identifier(), "A::B"); } #[rstest] - #[case(None, &UNKNOWN_LOCATION)] - #[case(Some(location(1)), &location(1))] - fn test_id_location(#[case] loc: Option, #[case] expected: &Location) { + #[case(Location::None, &Location::None)] + #[case(location(1), &location(1))] + fn test_id_location(#[case] loc: Location, #[case] expected: &Location) { assert_eq!(id("A::B", loc.clone()).location(), expected); } @@ -282,7 +282,7 @@ mod tests { #[case("A::B", &["A", "B"])] #[case("A::B::C", &["A", "B", "C"])] fn test_id_parts(#[case] identifier: &str, #[case] expected: &[&str]) { - assert_eq!(id(identifier, None).parts(), expected); + assert_eq!(id(identifier, Location::None).parts(), expected); } #[rstest] @@ -291,10 +291,10 @@ mod tests { #[case("A::B::C", "C")] fn test_id_name(#[case] identifier: &str, #[case] expected: &str) { assert_eq!( - id(identifier, None).name(), + id(identifier, Location::None).name(), IDRef { identifier: expected, - location: None + location: &Location::None } ); } @@ -304,17 +304,17 @@ mod tests { #[case("A::B::C", "A::B")] fn test_id_parent(#[case] identifier: &str, #[case] expected: &str) { assert_eq!( - id(identifier, None).parent().expect("no parent"), + id(identifier, Location::None).parent().expect("no parent"), IDRef { identifier: expected, - location: None + location: &Location::None } ); } #[test] fn test_id_parent_none() { - assert_eq!(id("A", None).parent(), None); + assert_eq!(id("A", Location::None).parent(), None); } #[rstest] @@ -322,7 +322,7 @@ mod tests { #[case("A::B", "A_B")] #[case("A::B::C", "A_B_C")] fn test_id_flat(#[case] identifier: &str, #[case] expected: &str) { - assert_eq!(id(identifier, None).flat(), expected); + assert_eq!(id(identifier, Location::None).flat(), expected); } #[rstest] @@ -330,12 +330,12 @@ mod tests { #[case("A::B", "A.B")] #[case("A::B::C", "A.B.C")] fn test_id_ada_str(#[case] identifier: &str, #[case] expected: &str) { - assert_eq!(id(identifier, None).to_ada_string(), expected); + assert_eq!(id(identifier, Location::None).to_ada_string(), expected); } #[test] fn test_id_serde() { - let id = id("A::B", None); + let id = id("A::B", Location::None); let bytes = bincode::serialize(&id).expect("failed to serialize"); let deserialized_id = bincode::deserialize(&bytes).expect("failed to deserialize"); assert_eq!(id, deserialized_id); @@ -347,29 +347,41 @@ mod tests { #[case("A::B", "C", "A::BC")] #[case("A::B", "C::D", "A::BC::D")] fn test_id_prefix_suffix(#[case] left: &str, #[case] right: &str, #[case] expected: &str) { - assert_eq!(id(left, None).suffix(right), Ok(id(expected, None))); - assert_eq!(id(right, None).prefix(left), Ok(id(expected, None))); + assert_eq!( + id(left, Location::None).suffix(right), + Ok(id(expected, Location::None)) + ); + assert_eq!( + id(right, Location::None).prefix(left), + Ok(id(expected, Location::None)) + ); } #[rstest] #[case("A", "", "A")] #[case("A", "::", "A")] fn test_id_prefix_ident(#[case] left: &str, #[case] right: &str, #[case] expected: &str) { - assert_eq!(id(left, None).suffix(right), Ok(id(expected, None))); - assert_eq!(id(left, None).prefix(right), Ok(id(expected, None))); + assert_eq!( + id(left, Location::None).suffix(right), + Ok(id(expected, Location::None)) + ); + assert_eq!( + id(left, Location::None).prefix(right), + Ok(id(expected, Location::None)) + ); } #[rstest] - #[case(None, &UNKNOWN_LOCATION)] - #[case(Some(location(1)), &location(1))] - fn test_id_prefix_location(#[case] loc: Option, #[case] expected: &Location) { + #[case(Location::None, &Location::None)] + #[case(location(1), &location(1))] + fn test_id_prefix_location(#[case] loc: Location, #[case] expected: &Location) { assert_eq!(id("B", loc).prefix("A").unwrap().location(), expected); } #[rstest] - #[case(None, &UNKNOWN_LOCATION)] - #[case(Some(location(1)), &location(1))] - fn test_id_suffix_location(#[case] loc: Option, #[case] expected: &Location) { + #[case(Location::None, &Location::None)] + #[case(location(1), &location(1))] + fn test_id_suffix_location(#[case] loc: Location, #[case] expected: &Location) { assert_eq!(id("A", loc).suffix("B").unwrap().location(), expected); } @@ -380,19 +392,19 @@ mod tests { #[case("A::B", "C::D", "A::B::C::D")] fn test_id_join(#[case] left: &str, #[case] right: &str, #[case] expected: &str) { assert_eq!( - id(left, None).join(&id(right, None)), - Ok(id(expected, None)) + id(left, Location::None).join(&id(right, Location::None)), + Ok(id(expected, Location::None)) ); } #[rstest] - #[case(Some(location(1)), Some(location(2)), &location(1))] - #[case(Some(location(1)), None, &location(1))] - #[case(None, Some(location(2)), &location(2))] - #[case(None, None, &UNKNOWN_LOCATION)] + #[case(location(1), location(2), &location(1))] + #[case(location(1), Location::None, &location(1))] + #[case(Location::None, location(2), &location(2))] + #[case(Location::None, Location::None, &Location::None)] fn test_id_join_location( - #[case] left: Option, - #[case] right: Option, + #[case] left: Location, + #[case] right: Location, #[case] expected: &Location, ) { assert_eq!( @@ -405,19 +417,19 @@ mod tests { #[case("A")] #[case("A::B")] fn test_id_display(#[case] identifier: &str) { - assert_eq!(id(identifier, None).to_string(), identifier); + assert_eq!(id(identifier, Location::None).to_string(), identifier); } #[test] fn test_id_hash() { let mut a1_hasher = DefaultHasher::new(); - id("A", None).hash(&mut a1_hasher); + id("A", Location::None).hash(&mut a1_hasher); let mut a2_hasher = DefaultHasher::new(); - id("A", None).hash(&mut a2_hasher); + id("A", Location::None).hash(&mut a2_hasher); assert_eq!(a1_hasher.finish(), a2_hasher.finish(),); let mut b_hasher = DefaultHasher::new(); - id("B", None).hash(&mut b_hasher); + id("B", Location::None).hash(&mut b_hasher); assert_ne!(a1_hasher.finish(), b_hasher.finish(),); } @@ -427,14 +439,13 @@ mod tests { #[case::not_equal("A::B", "A::A", false)] fn test_id_eq(#[case] left: &str, #[case] right: &str, #[case] expected: bool) { assert_eq!( - id(left, None) + id(left, Location::None) == id( right, - Some(Location { - source: None, + Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1) - }) + } ), expected ); @@ -442,7 +453,7 @@ mod tests { #[test] fn test_id_as_ref() { - assert_eq!(id("foo", None).as_ref(), "foo"); + assert_eq!(id("foo", Location::None).as_ref(), "foo"); } #[test] @@ -451,11 +462,11 @@ mod tests { let location = location(1); let id_ref = IDRef { identifier: &identifier, - location: Some(&location), + location: &location, }; let id = id_ref.to_owned(); assert_eq!(id.identifier, identifier); - assert_eq!(id.location, Some(location)); + assert_eq!(id.location, location); } #[test] @@ -466,21 +477,20 @@ mod tests { #[test] fn test_create_id() { assert_eq!( - create_id!(["A", "B"], Some(location(1))), + create_id!(["A", "B"], location(1)), ID { identifier: "A::B".to_string(), - location: Some(location(1)) + location: location(1) } ); } - fn id(identifier: &str, location: Option) -> ID { + fn id(identifier: &str, location: Location) -> ID { ID::new(identifier, location).expect("invalid identifier") } fn location(value: u32) -> Location { - Location { - source: None, + Location::Stdin { start: FilePosition::new(value, value), end: FilePosition::new(value, value), } diff --git a/librapidflux/src/source_code.rs b/librapidflux/src/source_code.rs index 579f858a8..db059f99a 100644 --- a/librapidflux/src/source_code.rs +++ b/librapidflux/src/source_code.rs @@ -61,7 +61,7 @@ pub fn clear() { mod tests { #![allow(clippy::used_underscore_binding)] - use std::{path::PathBuf, str::FromStr, sync::Arc}; + use std::{path::PathBuf, sync::Arc}; use rstest::{fixture, rstest}; use serial_test::serial; @@ -78,7 +78,7 @@ mod tests { #[rstest] #[serial] fn test_register_source_file(_cleanup: ()) { - let source_path = PathBuf::from_str("foo.rflx").expect("failed to create path"); + let source_path = PathBuf::from("foo.rflx"); register(source_path.clone(), "some source code".to_string()); let locked_map = SOURCE_CODE_MAP.lock().expect("mutex is poisoned"); @@ -91,13 +91,13 @@ mod tests { #[rstest] #[serial] fn test_retrieve_empty(_cleanup: ()) { - assert!(retrieve(&PathBuf::from_str("foo.rflx").expect("failed to create path")).is_none()); + assert!(retrieve(&PathBuf::from("foo.rflx")).is_none()); } #[rstest] #[serial] fn test_retrieve_source_code(_cleanup: ()) { - let source_path = PathBuf::from_str("foo.rflx").expect("failed to create path"); + let source_path = PathBuf::from("foo.rflx"); { let mut locked = SOURCE_CODE_MAP.lock().expect("mutex is poisoned"); @@ -117,7 +117,7 @@ mod tests { #[rstest] #[serial] fn test_retrieve_source_code_non_existent(_cleanup: ()) { - let source_path = PathBuf::from_str("foo.rflx").expect("failed to create path"); + let source_path = PathBuf::from("foo.rflx"); { let mut locked = SOURCE_CODE_MAP.lock().expect("mutex is poisoned"); @@ -127,13 +127,13 @@ mod tests { ); } - assert!(retrieve(&PathBuf::from_str("bar.rflx").expect("failed to create path")).is_none()); + assert!(retrieve(&PathBuf::from("bar.rflx")).is_none()); } #[test] #[serial] fn test_clear_source_code_map() { - let source_path = PathBuf::from_str("foo.rflx").expect("failed to create path"); + let source_path = PathBuf::from("foo.rflx"); { let mut locked = SOURCE_CODE_MAP.lock().expect("mutex is poisoned"); diff --git a/librapidflux/src/ty.rs b/librapidflux/src/ty.rs index c88e3670f..2865fe94c 100644 --- a/librapidflux/src/ty.rs +++ b/librapidflux/src/ty.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, fmt::Display, path::PathBuf, str::FromStr}; +use std::{collections::HashSet, fmt::Display, path::PathBuf}; use indexmap::IndexMap; use lazy_static::lazy_static; @@ -260,7 +260,7 @@ pub fn common_type(types: &[Ty]) -> Ty { pub fn check_type( actual: &Ty, expected: &[Ty], - location: Option<&Location>, + location: &Location, description: &str, ) -> RapidFluxError { assert!(!expected.is_empty()); @@ -279,11 +279,11 @@ pub fn check_type( error.push(ErrorEntry::new( format!("expected {desc}"), Severity::Error, - location.cloned(), + location.clone(), vec![Annotation::new( Some(format!("found {actual}")), Severity::Error, - location.cloned().unwrap(), + location.clone(), )], false, )); @@ -300,7 +300,7 @@ pub fn check_type( pub fn check_type_instance( actual: &Ty, expected: &[TyDiscriminants], - location: Option<&Location>, + location: &Location, description: &str, additional_annotations: &[Annotation], ) -> RapidFluxError { @@ -324,13 +324,13 @@ pub fn check_type_instance( annotations.push(Annotation::new( Some(format!("found {actual}")), Severity::Error, - location.cloned().unwrap(), + location.clone(), )); annotations.extend(additional_annotations.iter().cloned()); error.push(ErrorEntry::new( format!("expected {desc}"), Severity::Error, - location.cloned(), + location.clone(), annotations, false, )); @@ -339,7 +339,7 @@ pub fn check_type_instance( error } -fn undefined_type(location: Option<&Location>, description: &str) -> RapidFluxError { +fn undefined_type(location: &Location, description: &str) -> RapidFluxError { let description = if description.is_empty() { String::new() } else { @@ -348,7 +348,7 @@ fn undefined_type(location: Option<&Location>, description: &str) -> RapidFluxEr RapidFluxError::from(vec![ErrorEntry::new( format!("undefined{description}"), Severity::Error, - location.cloned(), + location.clone(), vec![], true, )]) @@ -359,7 +359,7 @@ pub struct Enumeration { pub id: ID, pub literals: Vec, pub always_valid: bool, - pub location: Option, + pub location: Location, } impl Enumeration { @@ -397,7 +397,7 @@ impl Display for UniversalInteger { pub struct Integer { pub id: ID, pub bounds: Bounds, - pub location: Option, + pub location: Location, } impl Integer { @@ -638,64 +638,67 @@ pub const UNIVERSAL_INTEGER: Ty = Ty::UniversalInteger(UniversalInteger { }); lazy_static! { - static ref BUILTINS_LOCATION: Location = Location { + static ref BUILTINS_LOCATION: Location = Location::File { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), - source: Some(PathBuf::from_str(consts::BUILTINS_PACKAGE).expect("failed to create path")), + source: PathBuf::from(consts::BUILTINS_PACKAGE), }; pub static ref BOOLEAN: Ty = Ty::Enumeration(Enumeration { id: create_id!( [consts::BUILTINS_PACKAGE, "Boolean"], - Some(BUILTINS_LOCATION.clone()) + BUILTINS_LOCATION.clone() ), - literals: vec![create_id!(["False"], None), create_id!(["True"], None)], + literals: vec![ + create_id!(["False"], Location::None), + create_id!(["True"], Location::None) + ], always_valid: false, - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); pub static ref INDEX: Ty = Ty::Integer(Integer { id: create_id!( [consts::BUILTINS_PACKAGE, "Index"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), bounds: Bounds::new(1, LENGTH_BOUNDS.upper()), - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); pub static ref BIT_LENGTH: Ty = Ty::Integer(Integer { id: create_id!( [consts::BUILTINS_PACKAGE, "Bit_Length"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), bounds: BIT_LENGTH_BOUNDS.clone(), - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); pub static ref BIT_INDEX: Ty = Ty::Integer(Integer { id: create_id!( [consts::BUILTINS_PACKAGE, "Bit_Index"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), bounds: Bounds::new(1, BIT_LENGTH_BOUNDS.upper()), - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); pub static ref BASE_INTEGER: Ty = Ty::Integer(Integer { id: create_id!( [consts::BUILTINS_PACKAGE, "Base_Integer"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), bounds: Bounds::new(0, i128::pow(2, consts::MAX_SCALAR_SIZE) - 1), - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); static ref BYTE: Ty = Ty::Integer(Integer { id: create_id!( [consts::INTERNAL_PACKAGE, "Byte"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), bounds: Bounds::new(0, 255), - location: Some(BUILTINS_LOCATION.clone()), + location: BUILTINS_LOCATION.clone(), }); pub static ref OPAQUE: Ty = Ty::Sequence(Sequence { id: create_id!( [consts::INTERNAL_PACKAGE, "Opaque"], - Some(BUILTINS_LOCATION.clone()), + BUILTINS_LOCATION.clone(), ), element: Box::new(BYTE.clone()), }); @@ -721,29 +724,29 @@ mod tests { }; lazy_static! { - static ref A: ID = create_id!(["A"], None); - static ref B: ID = create_id!(["B"], None); + static ref A: ID = create_id!(["A"], Location::None); + static ref B: ID = create_id!(["B"], Location::None); static ref ENUM_A: Ty = Ty::Enumeration(Enumeration { id: A.clone(), literals: vec![], always_valid: true, - location: None, + location: Location::None, }); static ref ENUM_B: Ty = Ty::Enumeration(Enumeration { id: B.clone(), literals: vec![], always_valid: true, - location: None, + location: Location::None, }); static ref INT_A: Ty = Ty::Integer(Integer { id: A.clone(), bounds: Bounds::new(1, 5), - location: None, + location: Location::None, }); static ref INT_B: Ty = Ty::Integer(Integer { id: B.clone(), bounds: Bounds::new(5, 9), - location: None, + location: Location::None, }); static ref UNIV_INT_1_3: Ty = Ty::UniversalInteger(UniversalInteger { bounds: Bounds::new(1, 3), @@ -1026,11 +1029,10 @@ mod tests { check_type( actual, &types.iter().copied().cloned().collect::>(), - Some(&Location { + &Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), - source: None, - }), + }, "foo" ) .to_string(), @@ -1064,11 +1066,10 @@ mod tests { check_type_instance( actual, types, - Some(&Location { + &Location::Stdin { start: FilePosition::new(1, 1), end: FilePosition::new(1, 1), - source: None, - }), + }, "", &[] ) diff --git a/rapidflux/src/diagnostics/errors.rs b/rapidflux/src/diagnostics/errors.rs index bd3c2fc2c..4e3866506 100644 --- a/rapidflux/src/diagnostics/errors.rs +++ b/rapidflux/src/diagnostics/errors.rs @@ -216,7 +216,7 @@ impl ErrorEntry { #[pyo3(signature = ( message, severity, - location = None, + location, annotations = Vec::new(), generate_default_annotation = true ) @@ -224,14 +224,14 @@ impl ErrorEntry { pub fn new( message: String, severity: Severity, - location: Option, + location: Location, annotations: Vec, generate_default_annotation: bool, ) -> Self { Self(lib::ErrorEntry::new( message, severity.into(), - location.map(|l| l.0), + location.0, annotations.into_iter().map(|a| a.0).collect(), generate_default_annotation, )) @@ -271,7 +271,7 @@ impl ErrorEntry { ) -> ( String, Borrowed<'py, 'py, PyAny>, - Option, + Location, Vec, bool, ) { @@ -301,8 +301,8 @@ impl ErrorEntry { } #[getter] - fn location(&self) -> Option { - self.0.location().map(|l| Location(l.clone())) + fn location(&self) -> Location { + Location(self.0.location().clone()) } #[getter] diff --git a/rapidflux/src/diagnostics/locations.rs b/rapidflux/src/diagnostics/locations.rs index 59ce31ade..731996449 100644 --- a/rapidflux/src/diagnostics/locations.rs +++ b/rapidflux/src/diagnostics/locations.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use bincode::{deserialize, serialize}; -use lazy_static::lazy_static; use librapidflux::diagnostics as lib; use pyo3::{ prelude::*, @@ -11,9 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::impl_states; -lazy_static! { - pub static ref UNKNOWN_LOCATION: Location = Location(lib::UNKNOWN_LOCATION.clone()); -} +pub const NO_LOCATION: Location = Location(lib::Location::None); #[pyclass(module = "rflx.rapidflux")] #[derive(Clone, PartialEq, Serialize, Deserialize)] @@ -23,6 +20,9 @@ pub struct Location(pub(crate) lib::Location); impl Location { #[new] fn new(start: (u32, u32), source: Option, end: Option<(u32, u32)>) -> Self { + if matches!(&source, Some(s) if s.to_string_lossy() == lib::NO_SOURCE) { + return Location(lib::Location::None); + } Location(lib::Location::new( start.into(), if let Some(e) = end { @@ -30,7 +30,11 @@ impl Location { } else { start.into() }, - source, + if matches!(&source, Some(s) if s.to_string_lossy() == lib::STDIN_SOURCE) { + None + } else { + source + }, )) } @@ -39,18 +43,22 @@ impl Location { } pub(crate) fn __repr__(&self) -> String { - format!( - "Location({:?}, {}, {:?})", - std::convert::Into::<(u32, u32)>::into(self.0.start), - self.0 - .source - .as_ref() - .map_or("None".to_string(), |s| format!( - "\"{}\"", - s.to_string_lossy() - )), - std::convert::Into::<(u32, u32)>::into(self.0.end) - ) + match &self.0 { + lib::Location::None => { + format!("Location((1, 1), \"{}\", (1, 1))", lib::NO_SOURCE) + } + lib::Location::Stdin { start, end } => format!( + "Location({:?}, None, {:?})", + std::convert::Into::<(u32, u32)>::into(*start), + std::convert::Into::<(u32, u32)>::into(*end) + ), + lib::Location::File { start, end, source } => format!( + "Location({:?}, \"{}\", {:?})", + std::convert::Into::<(u32, u32)>::into(*start), + source.to_string_lossy(), + std::convert::Into::<(u32, u32)>::into(*end) + ), + } } fn __str__(&self) -> String { @@ -71,30 +79,31 @@ impl Location { fn __lt__(&self, other: &Bound<'_, PyAny>, py: Python<'_>) -> PyResult { if other.is_instance_of::() { let other_location = other.extract::()?; - Ok(PyBool::new_bound(py, self.0.start < other_location.0.start) - .to_owned() - .into()) + Ok( + PyBool::new_bound(py, self.get_start() < other_location.get_start()) + .to_owned() + .into(), + ) } else { Ok(PyNotImplemented::get_bound(py).to_owned().into()) } } fn __getnewargs__(&self) -> ((u32, u32), Option, Option<(u32, u32)>) { - ( - self.0.start.into(), - self.0.source.clone(), - Some(self.0.end.into()), - ) + (self.get_start(), self._get_source(), Some(self.get_end())) } #[getter] fn get_start(&self) -> (u32, u32) { - self.0.start.into() + match self.0 { + lib::Location::None => (1, 1), + lib::Location::Stdin { start, .. } | lib::Location::File { start, .. } => start.into(), + } } #[getter] fn get_source(&self, py: Python<'_>) -> PyResult { - match &self.0.source { + match &self._get_source() { None => Ok(PyNone::get_bound(py).to_owned().into()), Some(source) => { let pathlib = py.import_bound("pathlib")?; @@ -106,34 +115,42 @@ impl Location { } } + fn _get_source(&self) -> Option { + match &self.0 { + lib::Location::None => Some(PathBuf::from(lib::NO_SOURCE)), + lib::Location::Stdin { .. } => None, + lib::Location::File { source, .. } => Some(source.clone()), + } + } + #[getter] fn get_end(&self) -> (u32, u32) { - self.0.start.into() + match self.0 { + lib::Location::None => (1, 1), + lib::Location::Stdin { end, .. } | lib::Location::File { end, .. } => end.into(), + } } #[getter] fn short(&self) -> Location { Location::new( - self.0.start.into(), - self.0 - .source + self.get_start(), + self._get_source() .clone() .map(|source| PathBuf::from(source.file_name().unwrap_or_default())), - Some(self.0.end.into()), + Some(self.get_end()), ) } #[staticmethod] - fn merge(positions: Vec>) -> Option { - lib::Location::merge( + fn merge(positions: Vec) -> Self { + Location(lib::Location::merge( positions .into_iter() - .flatten() .map(|l| l.0) .collect::>() .as_slice(), - ) - .map(Location) + )) } } diff --git a/rapidflux/src/identifier.rs b/rapidflux/src/identifier.rs index 634b8e10a..3d84f7eba 100644 --- a/rapidflux/src/identifier.rs +++ b/rapidflux/src/identifier.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use librapidflux::identifier as lib; use crate::{ - diagnostics::{FatalError, Location, UNKNOWN_LOCATION}, + diagnostics::{FatalError, Location, NO_LOCATION}, impl_states, }; @@ -112,7 +112,7 @@ impl ID { } if other.is_instance_of::() { let other_id: ID = other.extract()?; - return Ok(if self.location() == *UNKNOWN_LOCATION { + return Ok(if self.location() == NO_LOCATION { other_id._prefix(self.0.identifier()) } else { self._suffix(other_id.0.identifier()) @@ -131,7 +131,7 @@ impl ID { } if other.is_instance_of::() { let other_id: ID = other.extract()?; - return Ok(if self.location() == *UNKNOWN_LOCATION { + return Ok(if self.location() == NO_LOCATION { other_id._suffix(self.0.identifier()) } else { self._prefix(other_id.0.identifier()) @@ -221,7 +221,10 @@ impl ID { } fn _new(identifier: &str, location: Option) -> Result { - Ok(ID(lib::ID::new(identifier, location.map(|l| l.0))?)) + Ok(ID(lib::ID::new( + identifier, + location.map_or(librapidflux::diagnostics::Location::None, |l| l.0), + )?)) } pub(crate) struct IDError(lib::IDError); @@ -240,7 +243,7 @@ impl From for IDError { pub(crate) fn to_id(obj: &Bound<'_, PyAny>) -> Result { if let Ok(s) = obj.extract::() { - lib::ID::new(&s, None).map_err(IDError) + lib::ID::new(&s, librapidflux::diagnostics::Location::None).map_err(IDError) } else if let Ok(id) = obj.extract::() { Ok(id.0) } else { diff --git a/rapidflux/src/lib.rs b/rapidflux/src/lib.rs index 7fd4dd40c..d26375ecf 100644 --- a/rapidflux/src/lib.rs +++ b/rapidflux/src/lib.rs @@ -21,10 +21,7 @@ mod utils; fn rapidflux(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { // Locations m.add_class::()?; - let _ = m.add( - "UNKNOWN_LOCATION", - diagnostics::Location(librapidflux::diagnostics::UNKNOWN_LOCATION.clone()), - ); + let _ = m.add("NO_LOCATION", diagnostics::NO_LOCATION); // Errors m.add_class::()?; diff --git a/rapidflux/src/ty.rs b/rapidflux/src/ty.rs index 6e9704bc9..7adc086da 100644 --- a/rapidflux/src/ty.rs +++ b/rapidflux/src/ty.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use librapidflux::ty as lib; use crate::{ - diagnostics::{Annotation, Location, RapidFluxError, UNKNOWN_LOCATION}, + diagnostics::{Annotation, Location, RapidFluxError, NO_LOCATION}, identifier::{to_id, ID}, impl_states, register_submodule_declarations, }; @@ -208,7 +208,7 @@ impl Enumeration { id: to_id(identifier)?, literals: literals.iter().map(|l| l.0.clone()).collect(), always_valid, - location: location.map(|l| l.0), + location: location.map_or(NO_LOCATION.0, |l| l.0), })), ) } @@ -265,11 +265,7 @@ impl Enumeration { #[getter] fn location(&self) -> Location { - if let Some(location) = &self.0.location { - Location(location.clone()) - } else { - UNKNOWN_LOCATION.clone() - } + Location(self.0.location.clone()) } fn is_compatible(&self, other: &Bound<'_, PyAny>) -> bool { @@ -373,7 +369,7 @@ impl Integer { Ok(AnyInteger::new().add_subclass(Self(lib::Integer { id: to_id(identifier)?, bounds: bounds.0, - location: location.map(|l| l.0), + location: location.map_or(NO_LOCATION.0, |l| l.0), }))) } @@ -405,11 +401,7 @@ impl Integer { #[getter] fn location(&self) -> Location { - if let Some(location) = &self.0.location { - Location(location.clone()) - } else { - UNKNOWN_LOCATION.clone() - } + Location(self.0.location.clone()) } fn is_compatible(&self, other: &Bound<'_, PyAny>) -> bool { @@ -1068,12 +1060,12 @@ fn common_type(types: &Bound<'_, PyList>, py: Python<'_>) -> PyObject { } #[pyfunction] -#[pyo3(signature = (actual, expected, location = None, description = ""))] +#[pyo3(signature = (actual, expected, location, description = ""))] #[allow(clippy::needless_pass_by_value)] fn check_type( actual: &Bound<'_, PyAny>, expected: &Bound<'_, PyAny>, - location: Option<&Location>, + location: &Location, description: &str, ) -> RapidFluxError { let expected = if let Ok(tuple) = expected.extract::>>() { @@ -1086,17 +1078,17 @@ fn check_type( RapidFluxError(lib::check_type( &to_ty(actual), &expected.iter().map(|e| to_ty(e)).collect::>(), - location.map(|l| &l.0), + &location.0, description, )) } #[pyfunction] -#[pyo3(signature = (actual, expected, location = None, description = "", additional_annotations = None))] +#[pyo3(signature = (actual, expected, location, description = "", additional_annotations = None))] fn check_type_instance( actual: &Bound<'_, PyAny>, expected: &Bound<'_, PyAny>, - location: Option<&Location>, + location: &Location, description: &str, additional_annotations: Option>, py: Python<'_>, @@ -1143,7 +1135,7 @@ fn check_type_instance( Ok(RapidFluxError(lib::check_type_instance( &to_ty(actual), &exp, - location.map(|l| &l.0), + &location.0, description, &additional_annotations .unwrap_or_default() diff --git a/rflx/cli.py b/rflx/cli.py index 941f3c3c6..71e92ef2f 100644 --- a/rflx/cli.py +++ b/rflx/cli.py @@ -27,7 +27,7 @@ from rflx.integration import Integration from rflx.model import AlwaysVerify, Cache, Message, Model, NeverVerify, StateMachine from rflx.pyrflx import PyRFLXError -from rflx.rapidflux import ErrorEntry, RecordFluxError, Severity, logging +from rflx.rapidflux import NO_LOCATION, ErrorEntry, RecordFluxError, Severity, logging from rflx.specification import Parser from rflx.validator import ValidationError, Validator from rflx.version import version @@ -443,6 +443,7 @@ def main( # noqa: PLR0915 ErrorEntry( 'unsafe option "--no-verification" given without "--unsafe"', Severity.ERROR, + NO_LOCATION, ), ], ).print_messages() @@ -531,7 +532,7 @@ def parse( for f in files: if not f.is_file(): - error.push(ErrorEntry(f'file not found: "{f}"', Severity.ERROR)) + error.push(ErrorEntry(f'file not found: "{f}"', Severity.ERROR, NO_LOCATION)) continue present_files.append(Path(f)) @@ -659,10 +660,11 @@ def install(args: argparse.Namespace) -> None: if home_dir is None and xdg_config_home is None: RecordFluxError( [ - ErrorEntry("could not find config directory", Severity.ERROR), + ErrorEntry("could not find config directory", Severity.ERROR, NO_LOCATION), ErrorEntry( "make sure $HOME or $XDG_CONFIG_HOME variable is set", Severity.HELP, + NO_LOCATION, ), ], ).propagate() @@ -680,8 +682,8 @@ def install(args: argparse.Namespace) -> None: if home_dir is None: RecordFluxError( [ - ErrorEntry("could not locate home directory", Severity.ERROR), - ErrorEntry("make sure $HOME variable is set", Severity.HELP), + ErrorEntry("could not locate home directory", Severity.ERROR, NO_LOCATION), + ErrorEntry("make sure $HOME variable is set", Severity.HELP, NO_LOCATION), ], ).propagate() assert home_dir is not None diff --git a/rflx/error.py b/rflx/error.py index 8b0b766e8..678354b5b 100644 --- a/rflx/error.py +++ b/rflx/error.py @@ -3,6 +3,7 @@ from typing import NoReturn from rflx.rapidflux import ( + NO_LOCATION, ErrorEntry, FatalError as FatalError, Location as Location, @@ -16,7 +17,9 @@ def fail( severity: Severity = Severity.ERROR, location: Location | None = None, ) -> NoReturn: - raise RecordFluxError([ErrorEntry(message, severity, location)]) + raise RecordFluxError( + [ErrorEntry(message, severity, NO_LOCATION if location is None else location)], + ) def fatal_fail( @@ -24,18 +27,28 @@ def fatal_fail( severity: Severity = Severity.ERROR, location: Location | None = None, ) -> NoReturn: - raise FatalError(str(RecordFluxError([ErrorEntry(message, severity, location)]))) + raise FatalError( + str( + RecordFluxError( + [ErrorEntry(message, severity, NO_LOCATION if location is None else location)], + ), + ), + ) def warn( message: str, location: Location | None = None, ) -> None: - RecordFluxError([ErrorEntry(message, Severity.WARNING, location)]).print_messages() + RecordFluxError( + [ErrorEntry(message, Severity.WARNING, NO_LOCATION if location is None else location)], + ).print_messages() def info( message: str, location: Location | None = None, ) -> None: - RecordFluxError([ErrorEntry(message, Severity.INFO, location)]).print_messages() + RecordFluxError( + [ErrorEntry(message, Severity.INFO, NO_LOCATION if location is None else location)], + ).print_messages() diff --git a/rflx/expr.py b/rflx/expr.py index d0e15b807..d3554a52d 100644 --- a/rflx/expr.py +++ b/rflx/expr.py @@ -17,7 +17,7 @@ from rflx.common import Base, indent, indent_next, unique from rflx.identifier import ID, StrID from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -97,7 +97,7 @@ def __neg__(self) -> Expr: @property def location(self) -> Location: - return self._location or UNKNOWN_LOCATION + return self._location or NO_LOCATION @abstractmethod def _update_str(self) -> None: @@ -2852,7 +2852,7 @@ def similar_fields( def _similar_field_names( field: ID, fields: Iterable[ID], - location: Location | None, + location: Location, ) -> list[ErrorEntry]: similar_flds = similar_fields(field, fields) if similar_flds: diff --git a/rflx/generator/allocator.py b/rflx/generator/allocator.py index 46d6c2faf..c9b0f9d6e 100644 --- a/rflx/generator/allocator.py +++ b/rflx/generator/allocator.py @@ -44,7 +44,7 @@ from rflx.error import Location from rflx.identifier import ID from rflx.integration import Integration -from rflx.rapidflux import UNKNOWN_LOCATION +from rflx.rapidflux import NO_LOCATION from . import common, const @@ -130,7 +130,7 @@ def get_local_slot_ptrs(self) -> list[ID]: return [self._slot_name(s.slot_id) for s in self._numbered_slots if not s.global_] def get_slot_ptr(self, location: Location) -> ID: - assert location != UNKNOWN_LOCATION + assert location != NO_LOCATION slot_id: int = self._allocation_slots[location] return self._slot_name(slot_id) @@ -138,7 +138,7 @@ def get_size(self, variable: ID | None = None, state: ID | None = None) -> int: return self._integration.get_size(self._state_machine.identifier, variable, state) def is_externally_managed(self, location: Location) -> bool: - assert location != UNKNOWN_LOCATION + assert location != NO_LOCATION return location in self._externally_managed_buffers @staticmethod diff --git a/rflx/generator/generator.py b/rflx/generator/generator.py index 83de078ea..c400dcea9 100644 --- a/rflx/generator/generator.py +++ b/rflx/generator/generator.py @@ -98,7 +98,14 @@ StateMachine, TypeDecl, ) -from rflx.rapidflux import ErrorEntry, FatalError, RecordFluxError, Severity, logging +from rflx.rapidflux import ( + NO_LOCATION, + ErrorEntry, + FatalError, + RecordFluxError, + Severity, + logging, +) from . import common, const, message as message_generator from .allocator import AllocatorGenerator @@ -166,18 +173,18 @@ def _write_files( ErrorEntry( "partial update of generated files", Severity.ERROR, - None, + NO_LOCATION, ), ErrorEntry( "files not generated in the current run could lead to unexpected behavior: " + ", ".join(str(f.name) for f in non_updated_files), Severity.NOTE, - None, + NO_LOCATION, ), ErrorEntry( "remove the affected files or choose another directory and retry", Severity.NOTE, - None, + NO_LOCATION, ), ], ).propagate() diff --git a/rflx/generator/state_machine.py b/rflx/generator/state_machine.py index 4942a553b..f4c27a683 100644 --- a/rflx/generator/state_machine.py +++ b/rflx/generator/state_machine.py @@ -97,7 +97,7 @@ from rflx.error import fail, fatal_fail from rflx.identifier import ID from rflx.integration import Integration -from rflx.rapidflux import UNKNOWN_LOCATION, Location +from rflx.rapidflux import NO_LOCATION, Location from . import common, const from .allocator import AllocatorGenerator @@ -2996,11 +2996,7 @@ def _state_action( ) return [ - *( - [CommentStatement(str(action.location))] - if action.location != UNKNOWN_LOCATION - else [] - ), + *([CommentStatement(str(action.location))] if action.location != NO_LOCATION else []), *result, ] diff --git a/rflx/graph.py b/rflx/graph.py index 269897f07..a98edcec9 100644 --- a/rflx/graph.py +++ b/rflx/graph.py @@ -9,7 +9,7 @@ from rflx.expr import TRUE, UNDEFINED from rflx.identifier import ID from rflx.model import FINAL_STATE, Link, Message, StateMachine -from rflx.rapidflux import ErrorEntry, RecordFluxError, Severity, logging +from rflx.rapidflux import NO_LOCATION, ErrorEntry, RecordFluxError, Severity, logging def _graph_with_defaults(name: str) -> Dot: @@ -50,11 +50,11 @@ def write_graph(graph: Dot, filename: Path, fmt: str = "svg") -> None: except InvocationException as e: RecordFluxError( [ - ErrorEntry(str(e), Severity.ERROR, None), + ErrorEntry(str(e), Severity.ERROR, NO_LOCATION), ErrorEntry( "GraphViz is required for creating graphs", Severity.NOTE, - None, + NO_LOCATION, ), ], ).propagate() diff --git a/rflx/ir.py b/rflx/ir.py index 3e18dc0c3..80cb09784 100644 --- a/rflx/ir.py +++ b/rflx/ir.py @@ -19,7 +19,7 @@ from rflx.const import MAX_SCALAR_SIZE, MP_CONTEXT from rflx.error import info from rflx.identifier import ID, StrID -from rflx.rapidflux import UNKNOWN_LOCATION, Location +from rflx.rapidflux import NO_LOCATION, Location if TYPE_CHECKING: from rflx.model import type_decl @@ -141,7 +141,7 @@ def __str__(self) -> str: @property def location(self) -> Location: - return self.origin.location if self.origin else UNKNOWN_LOCATION + return self.origin.location if self.origin else NO_LOCATION @property @abstractmethod @@ -488,7 +488,7 @@ def origin_str(self) -> str: @property def location(self) -> Location: - return self.origin.location if self.origin else UNKNOWN_LOCATION + return self.origin.location if self.origin else NO_LOCATION @property @abstractmethod @@ -958,7 +958,7 @@ def location(self) -> Location: return self.origin.location if self.left.origin is not None: return self.left.origin.location - return UNKNOWN_LOCATION + return NO_LOCATION def _update_str(self) -> None: self._str = intern(f"{self.left}{self._symbol}{self.right}") @@ -2079,7 +2079,7 @@ def declarations(self) -> list[VarDecl]: return [a for a in self.actions if isinstance(a, VarDecl)] -FINAL_STATE = State("Final", [], None, [], None, UNKNOWN_LOCATION) +FINAL_STATE = State("Final", [], None, [], None, NO_LOCATION) @frozen(init=False) diff --git a/rflx/ls/model.py b/rflx/ls/model.py index e4802572b..6ae7fb4f7 100644 --- a/rflx/ls/model.py +++ b/rflx/ls/model.py @@ -87,7 +87,7 @@ class Symbol: identifier: ID category: SymbolCategory - definition_location: Location | None + definition_location: Location parent: ID | None diff --git a/rflx/ls/server.py b/rflx/ls/server.py index 58fb0c113..8530ab671 100644 --- a/rflx/ls/server.py +++ b/rflx/ls/server.py @@ -68,9 +68,9 @@ } -def to_lsp_location(location: error.Location | None) -> Location | None: +def to_lsp_location(location: error.Location) -> Location | None: if ( - location is None + location == error.NO_LOCATION or location.source is None or location.source.name in [str(BUILTINS_PACKAGE), str(INTERNAL_PACKAGE)] ): diff --git a/rflx/model/cache.py b/rflx/model/cache.py index 380789175..4e6487bc7 100644 --- a/rflx/model/cache.py +++ b/rflx/model/cache.py @@ -14,7 +14,7 @@ from rflx.error import warn from rflx.model.message import Message, Refinement from rflx.model.top_level_declaration import TopLevelDeclaration -from rflx.rapidflux import ErrorEntry, RecordFluxError, Severity +from rflx.rapidflux import NO_LOCATION, ErrorEntry, RecordFluxError, Severity from rflx.version import dependencies DEFAULT_FILE = CACHE_PATH / "verification.json" @@ -52,16 +52,19 @@ def __enter__(self) -> TextIO: f"failed to acquire cache lock" f" after {FileLock.LOCK_TIMEOUT} seconds", Severity.ERROR, + NO_LOCATION, ), ErrorEntry( f"the cache is locked by a process" f' with a PID of "{cache_locked_pid}"', Severity.NOTE, + NO_LOCATION, ), ErrorEntry( f"if the process that owns the lock isn't active anymore, deleting " f'"{self._lock_file}" will solve this issue', Severity.HELP, + NO_LOCATION, ), ], ) from None diff --git a/rflx/model/declaration.py b/rflx/model/declaration.py index 843f12cb3..3dec7ede8 100644 --- a/rflx/model/declaration.py +++ b/rflx/model/declaration.py @@ -10,7 +10,7 @@ from rflx.expr import Expr, Selected, Variable from rflx.identifier import ID, StrID from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -24,7 +24,7 @@ class Declaration(Base): DESCRIPTIVE_NAME: ClassVar[str] - def __init__(self, identifier: StrID, location: Location = UNKNOWN_LOCATION): + def __init__(self, identifier: StrID, location: Location = NO_LOCATION): self.identifier = ID(identifier) self.location = location self._refcount = 0 @@ -62,7 +62,7 @@ def __init__( identifier: StrID, type_identifier: StrID, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): super().__init__(identifier, location) self._type_identifier = ID(type_identifier) @@ -103,7 +103,7 @@ def __init__( type_identifier: StrID, expression: Expr | None = None, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): super().__init__(identifier, type_identifier, type_, location) self.expression = expression @@ -150,7 +150,7 @@ def __init__( type_identifier: StrID, expression: Selected, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): super().__init__(identifier, type_identifier, type_, location) self.expression = expression @@ -251,7 +251,7 @@ def __init__( parameters: Sequence[Parameter], return_type: StrID, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): super().__init__(identifier, return_type, type_, location) self._parameters = parameters @@ -299,7 +299,7 @@ def __init__( identifier: StrID, readable: bool = False, writable: bool = False, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): assert readable or writable super().__init__(identifier, location) diff --git a/rflx/model/message.py b/rflx/model/message.py index 2287dbe8d..867e22494 100644 --- a/rflx/model/message.py +++ b/rflx/model/message.py @@ -17,7 +17,7 @@ from rflx.identifier import ID, StrID from rflx.model.top_level_declaration import TopLevelDeclaration from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -68,7 +68,7 @@ class Link(Base): condition: expr.Expr = expr.TRUE size: expr.Expr = expr.UNDEFINED first: expr.Expr = expr.UNDEFINED - location: Location = dataclass_field(default=UNKNOWN_LOCATION, repr=False) + location: Location = dataclass_field(default=NO_LOCATION, repr=False) def __str__(self) -> str: condition = indent_next( @@ -111,7 +111,7 @@ def __init__( # noqa: PLR0913 types: Mapping[Field, type_decl.TypeDecl], checksums: Mapping[ID, Sequence[expr.Expr]] | None = None, byte_order: ByteOrder | Mapping[Field, ByteOrder] | None = None, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, skip_verification: bool = False, workers: int = 1, ) -> None: @@ -2405,7 +2405,7 @@ def __init__( # noqa: PLR0913 types: Mapping[Field, type_decl.TypeDecl] | None = None, checksums: Mapping[ID, Sequence[expr.Expr]] | None = None, byte_order: ByteOrder | Mapping[Field, ByteOrder] | None = None, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, skip_verification: bool = False, workers: int = 1, ) -> None: @@ -2471,7 +2471,7 @@ def __init__( # noqa: PLR0913 field: Field, sdu: Message, condition: expr.Expr = expr.TRUE, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, skip_verification: bool = False, ) -> None: super().__init__( @@ -2676,7 +2676,7 @@ class UncheckedMessage(type_decl.UncheckedTypeDecl): byte_order: Mapping[Field, ByteOrder] | ByteOrder = dataclass_field( default_factory=dict[Field, ByteOrder], ) - location: Location = dataclass_field(default=UNKNOWN_LOCATION) + location: Location = dataclass_field(default=NO_LOCATION) @property def fields(self) -> list[Field]: @@ -3219,7 +3219,7 @@ def _prune_dangling_fields( class UncheckedDerivedMessage(type_decl.UncheckedTypeDecl): identifier: ID base_identifier: ID - location: Location = dataclass_field(default=UNKNOWN_LOCATION) + location: Location = dataclass_field(default=NO_LOCATION) def checked( self, @@ -3276,7 +3276,7 @@ class UncheckedRefinement(type_decl.UncheckedTypeDecl): field: Field sdu: ID condition: expr.Expr - location: Location = dataclass_field(default=UNKNOWN_LOCATION) + location: Location = dataclass_field(default=NO_LOCATION) def __init__( # noqa: PLR0913 self, @@ -3285,7 +3285,7 @@ def __init__( # noqa: PLR0913 field: Field, sdu: ID, condition: expr.Expr = expr.TRUE, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__( ID(package) * f"__REFINEMENT__{sdu.flat}__{pdu.flat}__{field.name}__", diff --git a/rflx/model/state_machine.py b/rflx/model/state_machine.py index 21e80f5ea..0ce61a3c4 100644 --- a/rflx/model/state_machine.py +++ b/rflx/model/state_machine.py @@ -13,7 +13,7 @@ from rflx.common import Base, indent, indent_next, verbose_repr from rflx.identifier import ID, StrID, id_generator from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -36,7 +36,7 @@ def __init__( target: StrID, condition: expr.Expr = expr.TRUE, description: str | None = None, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): self.target = ID(target) self.condition = condition @@ -76,7 +76,7 @@ def __init__( # noqa: PLR0913 actions: Sequence[stmt.Statement] | None = None, declarations: Sequence[decl.BasicDeclaration] | None = None, description: str | None = None, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): if transitions: assert transitions[-1].condition == expr.TRUE, "missing default transition" @@ -359,7 +359,7 @@ def __init__( # noqa: PLR0913 declarations: Sequence[decl.BasicDeclaration], parameters: Sequence[decl.FormalDeclaration], types: Sequence[type_decl.TypeDecl], - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, workers: int = 1, ): super().__init__(identifier, location) diff --git a/rflx/model/statement.py b/rflx/model/statement.py index dd1f84716..91bf5c7c2 100644 --- a/rflx/model/statement.py +++ b/rflx/model/statement.py @@ -8,7 +8,7 @@ from rflx.expr import Expr, Variable from rflx.identifier import ID, StrID from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -22,7 +22,7 @@ def __init__( self, identifier: StrID, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ): self.identifier = ID(identifier) self.type_ = type_ @@ -53,7 +53,7 @@ def __init__( identifier: StrID, expression: Expr, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, type_, location) self.expression = expression @@ -95,7 +95,7 @@ def __init__( field: StrID, expression: Expr, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(message, expression, type_, location) self.message = ID(message) @@ -169,7 +169,7 @@ def __init__( attribute: str, parameters: list[Expr], type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, type_, location) self.attribute = attribute @@ -199,7 +199,7 @@ def __init__( identifier: StrID, parameter: Expr, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, self.__class__.__name__, [parameter], type_, location) @@ -301,7 +301,7 @@ def __init__( identifier: StrID, associations: Mapping[ID, Expr] | None = None, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, self.__class__.__name__, [], type_, location) self.associations = associations or {} @@ -388,7 +388,7 @@ def __init__( identifier: StrID, parameter: Expr, type_: ty.Type = ty.UNDEFINED, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, self.__class__.__name__, [parameter], type_, location) diff --git a/rflx/model/top_level_declaration.py b/rflx/model/top_level_declaration.py index 1264abf86..4d5935617 100644 --- a/rflx/model/top_level_declaration.py +++ b/rflx/model/top_level_declaration.py @@ -6,13 +6,13 @@ from rflx.common import Base from rflx.identifier import ID, StrID -from rflx.rapidflux import UNKNOWN_LOCATION, ErrorEntry, Location, RecordFluxError, Severity +from rflx.rapidflux import NO_LOCATION, ErrorEntry, Location, RecordFluxError, Severity class TopLevelDeclaration(Base): - def __init__(self, identifier: StrID, location: Location = UNKNOWN_LOCATION) -> None: + def __init__(self, identifier: StrID, location: Location = NO_LOCATION) -> None: self.identifier = ID(identifier) - self.location = location or UNKNOWN_LOCATION + self.location = location self.error = RecordFluxError() self._check_identifier() diff --git a/rflx/model/type_decl.py b/rflx/model/type_decl.py index 1cb6478ed..c8a3f6e84 100644 --- a/rflx/model/type_decl.py +++ b/rflx/model/type_decl.py @@ -12,7 +12,7 @@ from rflx.error import fail from rflx.identifier import ID, StrID from rflx.rapidflux import ( - UNKNOWN_LOCATION, + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -70,7 +70,7 @@ def __init__( self, identifier: StrID, size: expr.Expr, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, location) @@ -110,7 +110,7 @@ def __init__( first: expr.Expr, last: expr.Expr, size: expr.Expr, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, size, location) @@ -307,7 +307,7 @@ def __init__( self, identifier: StrID, size: expr.Expr, - location: Location | None = None, + location: Location = NO_LOCATION, ) -> None: super().__init__( identifier, @@ -332,7 +332,7 @@ def __init__( # noqa: PLR0912 literals: abc.Sequence[tuple[StrID, expr.Number]], size: expr.Expr, always_valid: bool, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, size, location) @@ -531,7 +531,7 @@ def __init__( self, identifier: StrID, element_type: TypeDecl, - location: Location = UNKNOWN_LOCATION, + location: Location = NO_LOCATION, ) -> None: super().__init__(identifier, location) self.element_type = element_type @@ -649,7 +649,7 @@ def dependencies(self) -> list[TypeDecl]: class Opaque(Composite): - def __init__(self, location: Location = UNKNOWN_LOCATION) -> None: + def __init__(self, location: Location = NO_LOCATION) -> None: super().__init__(const.INTERNAL_PACKAGE * "Opaque", location) def __repr__(self) -> str: @@ -702,7 +702,7 @@ def checked( class UncheckedUnsignedInteger(UncheckedTypeDecl): identifier: ID size: expr.Expr - location: Location | None + location: Location def checked( self, diff --git a/rflx/pyrflx/error.py b/rflx/pyrflx/error.py index cccd35ca1..25f5a1612 100644 --- a/rflx/pyrflx/error.py +++ b/rflx/pyrflx/error.py @@ -1,6 +1,6 @@ from __future__ import annotations -from rflx.rapidflux import ErrorEntry, RecordFluxError, Severity +from rflx.rapidflux import NO_LOCATION, ErrorEntry, RecordFluxError, Severity class PyRFLXError(RecordFluxError): @@ -8,4 +8,4 @@ def __init__(self, entries: list[ErrorEntry] | None = None) -> None: super().__init__(entries if entries is not None else []) def push_msg(self, message: str) -> None: - self.push(ErrorEntry(message, Severity.ERROR, None)) + self.push(ErrorEntry(message, Severity.ERROR, NO_LOCATION)) diff --git a/rflx/rapidflux/__init__.pyi b/rflx/rapidflux/__init__.pyi index 8bc33ff6d..20ba08a85 100644 --- a/rflx/rapidflux/__init__.pyi +++ b/rflx/rapidflux/__init__.pyi @@ -5,7 +5,7 @@ from typing import Final from typing_extensions import Self -UNKNOWN_LOCATION: Final[Location] +NO_LOCATION: Final[Location] class ID: def __init__( @@ -49,7 +49,7 @@ class Location: def short(self) -> Location: ... def __lt__(self, other: object) -> bool: ... @staticmethod - def merge(locations: Sequence[Location | None]) -> Location: ... + def merge(locations: Sequence[Location]) -> Location: ... class Severity(Enum): ERROR: Severity @@ -77,7 +77,7 @@ class ErrorEntry: self, message: str, severity: Severity, - location: Location | None = None, + location: Location, annotations: Sequence[Annotation] = [], generate_default_annotation: bool = True, ) -> None: ... @@ -87,7 +87,7 @@ class ErrorEntry: @property def severity(self) -> Severity: ... @property - def location(self) -> Location | None: ... + def location(self) -> Location: ... @property def annotations(self) -> Sequence[Annotation]: ... diff --git a/rflx/rapidflux/ty.pyi b/rflx/rapidflux/ty.pyi index bb580127c..bc55cc3e7 100644 --- a/rflx/rapidflux/ty.pyi +++ b/rflx/rapidflux/ty.pyi @@ -53,11 +53,7 @@ class AnyInteger(Any): def bounds(self) -> Bounds: ... class UniversalInteger(AnyInteger): - def __init__( - self, - bounds: Bounds, - location: Location | None = None, - ) -> None: ... + def __init__(self, bounds: Bounds) -> None: ... class Integer(AnyInteger): def __init__( @@ -164,13 +160,13 @@ def common_type(types: abc.Sequence[Type]) -> Type: ... def check_type( actual: Type, expected: Type | tuple[Type, ...], - location: Location | None, + location: Location, description: str, ) -> RecordFluxError: ... def check_type_instance( actual: Type, expected: type[Type] | tuple[type[Type], ...], - location: Location | None, + location: Location, description: str = "", additional_annotations: abc.Sequence[Annotation] | None = None, ) -> RecordFluxError: ... diff --git a/rflx/specification/parser.py b/rflx/specification/parser.py index 59f17b305..604e86930 100644 --- a/rflx/specification/parser.py +++ b/rflx/specification/parser.py @@ -14,6 +14,7 @@ from rflx.integration import Integration from rflx.model import AlwaysVerify, Cache, declaration as decl, statement as stmt from rflx.rapidflux import ( + NO_LOCATION, Annotation, ErrorEntry, Location, @@ -26,7 +27,11 @@ from . import style -def node_location(node: lang.RFLXNode, filename: Path, end_location: bool = False) -> Location: +def node_location( + node: lang.RFLXNode, + filename: Path | None, + end_location: bool = False, +) -> Location: assert node.token_start assert node.token_end start = node.token_start.sloc_range @@ -48,7 +53,6 @@ def type_location(identifier: ID, node: lang.RFLXNode) -> Location: The location object covers the area from the start of an identifier to the end of a node. """ - assert identifier.location.source is not None return Location( identifier.location.start, identifier.location.source, @@ -1357,7 +1361,11 @@ def extract_then( ErrorEntry( f'undefined field "{then.f_target.text}"', Severity.ERROR, - node_location(then.f_target, filename) if then.f_target else None, + ( + node_location(then.f_target, filename) + if then.f_target + else NO_LOCATION + ), ), ], ) diff --git a/tests/property/strategies.py b/tests/property/strategies.py index 5dcaf835e..1c79882e0 100644 --- a/tests/property/strategies.py +++ b/tests/property/strategies.py @@ -27,7 +27,7 @@ Sequence, TypeDecl, ) -from rflx.rapidflux import ErrorEntry, Location, RecordFluxError, Severity +from rflx.rapidflux import NO_LOCATION, ErrorEntry, Location, RecordFluxError, Severity T = TypeVar("T") @@ -328,7 +328,7 @@ def outgoing(field: Field) -> abc.Sequence[Link]: ErrorEntry( f"incorrectly generated message:\n {message!r}", Severity.INFO, - None, + NO_LOCATION, ), ) raise diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index 3ad135386..964831c77 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -20,7 +20,14 @@ from rflx.error import fail from rflx.ls.server import server from rflx.pyrflx import PyRFLXError -from rflx.rapidflux import ErrorEntry, Location, RecordFluxError, Severity, logging +from rflx.rapidflux import ( + NO_LOCATION, + ErrorEntry, + Location, + RecordFluxError, + Severity, + logging, +) from tests.const import DATA_DIR, GITHUB_TRACKER_REF_PATTERN, GNAT_TRACKER_REF_PATTERN, SPEC_DIR from tests.utils import assert_stderr_regex, raise_fatal_error @@ -44,7 +51,7 @@ def raise_error() -> None: def raise_pyrflx_error() -> None: - raise PyRFLXError([ErrorEntry("TEST", Severity.ERROR)]) + raise PyRFLXError([ErrorEntry("TEST", Severity.ERROR, NO_LOCATION)]) def raise_validation_error() -> None: diff --git a/tests/unit/generator/allocator_test.py b/tests/unit/generator/allocator_test.py index 24644c05a..888437730 100644 --- a/tests/unit/generator/allocator_test.py +++ b/tests/unit/generator/allocator_test.py @@ -8,7 +8,7 @@ from rflx.generator.allocator import AllocatorGenerator from rflx.identifier import ID, id_generator from rflx.integration import Integration, IntegrationFile, StateMachineIntegration -from rflx.rapidflux import UNKNOWN_LOCATION, Location +from rflx.rapidflux import NO_LOCATION, Location from tests.data import models from tests.unit.generator.generator_test import create_integration from tests.unit.generator.state_machine_test import dummy_state_machine @@ -34,7 +34,7 @@ "Final", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], None, @@ -46,7 +46,7 @@ ), ], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], declarations=[ @@ -58,7 +58,7 @@ ], parameters=[], types={t.identifier: t for t in models.universal_model().types}, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, variable_id=id_generator(), ), True, @@ -76,13 +76,13 @@ "Final", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], None, [ir.Read("C", ir.ObjVar("X", ty.Message("T")))], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], declarations=[ @@ -94,7 +94,7 @@ ], parameters=[], types={t.identifier: t for t in models.universal_model().types}, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, variable_id=id_generator(), ), True, @@ -129,13 +129,13 @@ def test_allocator( "Final", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], None, [ir.Read("C", ir.ObjVar("X", ty.Message("T")))], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], declarations=[ @@ -147,7 +147,7 @@ def test_allocator( ], parameters=[], types={}, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, variable_id=id_generator(), ), False, @@ -165,13 +165,13 @@ def test_allocator( "Final", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], None, [ir.Read("C", ir.ObjVar("X", ty.Message("T")))], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], declarations=[ @@ -188,7 +188,7 @@ def test_allocator( ], parameters=[], types={}, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, variable_id=id_generator(), ), True, diff --git a/tests/unit/generator/state_machine_test.py b/tests/unit/generator/state_machine_test.py index da97c8e27..4749807f8 100644 --- a/tests/unit/generator/state_machine_test.py +++ b/tests/unit/generator/state_machine_test.py @@ -21,7 +21,7 @@ ) from rflx.identifier import ID, id_generator from rflx.integration import Integration -from rflx.rapidflux import UNKNOWN_LOCATION, Location, RecordFluxError +from rflx.rapidflux import NO_LOCATION, Location, RecordFluxError from tests.data import models INT_TY = ty.Integer("I", ty.Bounds(1, 100)) @@ -41,19 +41,19 @@ def dummy_state_machine() -> ir.StateMachine: "Final", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], None, [], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), ], declarations=[], parameters=[], types={t.identifier: t for t in models.universal_model().types}, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, variable_id=id_generator(), ) @@ -62,7 +62,7 @@ def dummy_state_machine() -> ir.StateMachine: ("parameter", "expected"), [ ( - ir.FuncDecl("F", [], "T", type_=ty.BOOLEAN, location=UNKNOWN_LOCATION), + ir.FuncDecl("F", [], "T", type_=ty.BOOLEAN, location=NO_LOCATION), [ ada.SubprogramDeclaration( specification=ada.ProcedureSpecification( @@ -91,7 +91,7 @@ def dummy_state_machine() -> ir.StateMachine: ], "T", type_=ty.Message("T", is_definite=True), - location=UNKNOWN_LOCATION, + location=NO_LOCATION, ), [ ada.SubprogramDeclaration( @@ -559,7 +559,7 @@ def test_state_machine_declare_error( type_, lambda _: False, expression=expression, - alloc_id=UNKNOWN_LOCATION, + alloc_id=NO_LOCATION, ) @@ -616,7 +616,7 @@ def test_state_machine_state_action_error( ID("S"), action, ExceptionHandler( - ir.State("State", [], None, [], None, UNKNOWN_LOCATION), + ir.State("State", [], None, [], None, NO_LOCATION), [], lambda: None, ), @@ -1008,11 +1008,11 @@ def test_state_machine_assign_error( "E", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), [], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), [], lambda: None, @@ -1088,11 +1088,11 @@ def test_state_machine_append_error( "E", ir.ComplexExpr([], ir.BoolVal(value=True)), None, - UNKNOWN_LOCATION, + NO_LOCATION, ), [], None, - UNKNOWN_LOCATION, + NO_LOCATION, ), [], lambda: None, diff --git a/tests/unit/identifier_test.py b/tests/unit/identifier_test.py index b2a77dde4..ef7857911 100644 --- a/tests/unit/identifier_test.py +++ b/tests/unit/identifier_test.py @@ -7,7 +7,7 @@ import pytest from rflx.identifier import ID -from rflx.rapidflux import UNKNOWN_LOCATION, FatalError, Location +from rflx.rapidflux import NO_LOCATION, FatalError, Location def test_id_constructor() -> None: @@ -138,7 +138,7 @@ def test_id_parts() -> None: def test_id_location() -> None: - assert ID("A::B").location == UNKNOWN_LOCATION + assert ID("A::B").location == NO_LOCATION assert ID("A::B", Location((1, 2))).location == Location((1, 2)) diff --git a/tests/unit/ir_test.py b/tests/unit/ir_test.py index db1c89f5f..64ce7d7b9 100644 --- a/tests/unit/ir_test.py +++ b/tests/unit/ir_test.py @@ -8,7 +8,7 @@ from rflx import expr, ir, ty from rflx.error import Location from rflx.identifier import ID, id_generator -from rflx.rapidflux import UNKNOWN_LOCATION +from rflx.rapidflux import NO_LOCATION PROOF_MANAGER = ir.ProofManager(2) INT_TY = ty.Integer("I", ty.Bounds(10, 100)) @@ -681,7 +681,7 @@ def test_field_access_attr_z3_expr(attribute: ir.FieldAccessAttr, expected: z3.E ], ) def test_binary_expr_location(binary_expr: type[ir.BinaryExpr]) -> None: - assert binary_expr(ir.IntVar("X", INT_TY), ir.IntVal(1)).location == UNKNOWN_LOCATION + assert binary_expr(ir.IntVar("X", INT_TY), ir.IntVal(1)).location == NO_LOCATION assert binary_expr( ir.IntVar("X", INT_TY), ir.IntVal(1), @@ -720,7 +720,7 @@ def test_binary_expr_origin_str(binary_expr: type[ir.BinaryExpr]) -> None: binary_expr( ir.IntVar("X", INT_TY), ir.IntVal(1), - origin=ir.ConstructedOrigin("Z", UNKNOWN_LOCATION), + origin=ir.ConstructedOrigin("Z", NO_LOCATION), ).origin_str == "Z" ) @@ -754,7 +754,7 @@ def test_binary_expr_accessed_vars(binary_expr: type[ir.BinaryExpr]) -> None: binary_expr( ir.IntVar("X", INT_TY), ir.IntVal(1), - origin=ir.ConstructedOrigin("Z", UNKNOWN_LOCATION), + origin=ir.ConstructedOrigin("Z", NO_LOCATION), ).origin_str == "Z" ) diff --git a/tests/unit/ls/model_test.py b/tests/unit/ls/model_test.py index d835b2645..3c0b2ef94 100644 --- a/tests/unit/ls/model_test.py +++ b/tests/unit/ls/model_test.py @@ -7,7 +7,7 @@ from rflx.identifier import ID from rflx.ls.model import LSModel, Symbol, SymbolCategory from rflx.model import UncheckedEnumeration, UncheckedInteger, UncheckedSequence -from rflx.rapidflux import UNKNOWN_LOCATION +from rflx.rapidflux import NO_LOCATION from rflx.specification.parser import Parser @@ -87,14 +87,14 @@ def test_integer_to_symbols() -> None: expr.Number(0), expr.Number(100), expr.Number(8), - UNKNOWN_LOCATION, + NO_LOCATION, ) symbols = LSModel._to_symbols(integer) # noqa: SLF001 assert len(symbols) == 1 assert symbols[0] == Symbol( ID("Package::Integer_Identifier"), SymbolCategory.NUMERIC, - UNKNOWN_LOCATION, + NO_LOCATION, None, ) @@ -106,20 +106,20 @@ def test_enumeration_to_symbols() -> None: [(ID("Literal"), expr.Number(0))], expr.Number(1), always_valid=True, - location=UNKNOWN_LOCATION, + location=NO_LOCATION, ), ) assert len(symbols) == 2 assert symbols[0] == Symbol( ID("Package::Literal"), SymbolCategory.ENUMERATION_LITERAL, - UNKNOWN_LOCATION, + NO_LOCATION, None, ) assert symbols[1] == Symbol( ID("Package::Enumeration_Identifier"), SymbolCategory.ENUMERATION, - UNKNOWN_LOCATION, + NO_LOCATION, None, ) @@ -129,14 +129,14 @@ def test_sequence_to_symbols() -> None: UncheckedSequence( ID("Package::Sequence_Identifier"), ID("Package::Integer_Identifier"), - UNKNOWN_LOCATION, + NO_LOCATION, ), ) assert len(symbols) == 1 assert symbols[0] == Symbol( ID("Package::Sequence_Identifier"), SymbolCategory.SEQUENCE, - UNKNOWN_LOCATION, + NO_LOCATION, None, ) @@ -146,7 +146,7 @@ def test_to_symbol() -> None: assert symbol == Symbol( ID("Package_Identifier"), SymbolCategory.PACKAGE, - UNKNOWN_LOCATION, + NO_LOCATION, None, ) diff --git a/tests/unit/ls/server_test.py b/tests/unit/ls/server_test.py index 058c9ef80..1aa939673 100644 --- a/tests/unit/ls/server_test.py +++ b/tests/unit/ls/server_test.py @@ -123,7 +123,7 @@ def test_to_lsp_location() -> None: Path("test").absolute().as_uri(), Range(Position(0, 1), Position(0, 1)), ) - assert server.to_lsp_location(None) is None + assert server.to_lsp_location(error.NO_LOCATION) is None @pytest.mark.parametrize( @@ -293,7 +293,7 @@ def test_publish_errors_as_diagnostics(monkeypatch: pytest.MonkeyPatch) -> None: error.ErrorEntry( "bar", error.Severity.ERROR, - None, + error.NO_LOCATION, ), ], ) diff --git a/tests/unit/model/message_test.py b/tests/unit/model/message_test.py index 40b5282d6..442dfa5af 100644 --- a/tests/unit/model/message_test.py +++ b/tests/unit/model/message_test.py @@ -2766,8 +2766,8 @@ def test_no_path_to_final_transitive() -> None: Link(Field("F2"), Field("F3"), Greater(Variable("F1"), Number(100))), Link(Field("F3"), FINAL), Link(Field("F2"), Field("F4"), LessEqual(Variable("F1"), Number(100))), - Link(Field("F4"), Field("F5")), - Link(Field("F5"), Field("F6")), + Link(Field(ID("F4", Location((1, 1)))), Field("F5")), + Link(Field(ID("F5", Location((2, 2)))), Field(ID("F6", Location((3, 3))))), ] types = { @@ -2782,9 +2782,9 @@ def test_no_path_to_final_transitive() -> None: structure, types, r"^" - r':1:1: error: no path to FINAL for field "F4"\n' - r':1:1: error: no path to FINAL for field "F5"\n' - r':1:1: error: no path to FINAL for field "F6"' + r':1:1: error: no path to FINAL for field "F4"\n' + r':2:2: error: no path to FINAL for field "F5"\n' + r':3:3: error: no path to FINAL for field "F6"' r"$", ) diff --git a/tests/unit/rapidflux/__init___test.py b/tests/unit/rapidflux/__init___test.py index b56b12401..d73397c40 100644 --- a/tests/unit/rapidflux/__init___test.py +++ b/tests/unit/rapidflux/__init___test.py @@ -59,10 +59,10 @@ def test_location() -> None: l = Location((1, 2)) assert l.start == (1, 2) assert l.source is None - assert l.end is None + assert l.end == (1, 2) def test_location_repr() -> None: - assert repr(Location((1, 2))) == "Location((1, 2), None, None)" + assert repr(Location((1, 2))) == "Location((1, 2), None, (1, 2))" assert repr(Location((1, 2), None, (3, 4))) == "Location((1, 2), None, (3, 4))" assert repr(Location((1, 2), Path("foo"), (3, 4))) == 'Location((1, 2), "foo", (3, 4))' diff --git a/tools/test_rapidflux_coverage.sh b/tools/test_rapidflux_coverage.sh new file mode 100755 index 000000000..ccbf09298 --- /dev/null +++ b/tools/test_rapidflux_coverage.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# TODO(rust-lang/rust#80549): Unreachable lines cannot yet be ignored in coverage calculation + +UNREACHABLE=$(git grep --line-number --color "unreachable\!()" | cut -f1-2 -d:) +UNREACHABLE_COUNT=$(echo "$UNREACHABLE" | wc -l) + +cargo llvm-cov nextest --package librapidflux --no-fail-fast --fail-uncovered-lines $UNREACHABLE_COUNT --show-missing-lines --skip-functions + +echo -e "Ignored $UNREACHABLE_COUNT unreachable lines:\n$UNREACHABLE" +