Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
ghcVersions = [
"ghc96"
"ghc98"
"ghc910"
"ghc912"
];
in {
_pkgs = eachSystem (localSystem: makePkgs {inherit localSystem;});
Expand Down
3 changes: 2 additions & 1 deletion src/ghci/error_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tokio::io::BufWriter;
use tracing::instrument;

use super::parse::CompilationResult;
use super::parse::ModulesLoaded;
use super::CompilationLog;

/// Error log writer.
Expand Down Expand Up @@ -40,7 +41,7 @@ impl ErrorLog {
// `ghcid` only writes the headline if there's no errors.
if let CompilationResult::Ok = summary.result {
tracing::debug!(%path, "Writing 'All good'");
let modules_loaded = if summary.modules_loaded != 1 {
let modules_loaded = if summary.modules_loaded != ModulesLoaded::Count(1) {
format!("{} modules", summary.modules_loaded)
} else {
format!("{} module", summary.modules_loaded)
Expand Down
137 changes: 124 additions & 13 deletions src/ghci/parse/ghc_message/compilation_summary.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use winnow::ascii::digit1;
use winnow::combinator::alt;
use winnow::combinator::opt;
Expand All @@ -23,10 +25,31 @@ pub struct CompilationSummary {
/// The compilation result; whether compilation succeeded or failed.
pub result: CompilationResult,
/// The count of modules loaded.
pub modules_loaded: usize,
pub modules_loaded: ModulesLoaded,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModulesLoaded {
/// The count of modules loaded.
Count(usize),
/// All modules were loaded, unknown count.
All,
}

impl Display for ModulesLoaded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModulesLoaded::Count(n) => write!(f, "{n}"),
ModulesLoaded::All => write!(f, "all"),
}
}
}

/// Parse a compilation summary, like `Ok, one module loaded.`.
///
/// NB: This will definitely explode if you have `Opt_ShowLoadedModules` enabled.
///
/// See: <https://gitlab.haskell.org/ghc/ghc/-/blob/6d779c0fab30c39475aef50d39064ed67ce839d7/ghc/GHCi/UI.hs#L2309-L2329>
pub fn compilation_summary(input: &mut &str) -> PResult<CompilationSummary> {
let result = alt((
"Ok".map(|_| CompilationResult::Ok),
Expand All @@ -35,6 +58,33 @@ pub fn compilation_summary(input: &mut &str) -> PResult<CompilationSummary> {
.parse_next(input)?;
let _ = ", ".parse_next(input)?;

let modules_loaded = alt((
compilation_summary_no_modules,
compilation_summary_unloaded_all,
compilation_summary_count,
))
.parse_next(input)?;

let _ = '.'.parse_next(input)?;
let _ = line_ending_or_eof.parse_next(input)?;

Ok(CompilationSummary {
result,
modules_loaded,
})
}

fn compilation_summary_no_modules(input: &mut &str) -> PResult<ModulesLoaded> {
let _ = "no modules to be reloaded".parse_next(input)?;
Ok(ModulesLoaded::Count(0))
}

fn compilation_summary_unloaded_all(input: &mut &str) -> PResult<ModulesLoaded> {
let _ = "unloaded all modules".parse_next(input)?;
Ok(ModulesLoaded::All)
}

fn compilation_summary_count(input: &mut &str) -> PResult<ModulesLoaded> {
// There's special cases for 0-6 modules!
// https://gitlab.haskell.org/ghc/ghc/-/blob/288235bbe5a59b8a1bda80aaacd59e5717417726/ghc/GHCi/UI.hs#L2286-L2287
// https://gitlab.haskell.org/ghc/ghc/-/blob/288235bbe5a59b8a1bda80aaacd59e5717417726/compiler/GHC/Utils/Outputable.hs#L1429-L1453
Expand All @@ -51,13 +101,10 @@ pub fn compilation_summary(input: &mut &str) -> PResult<CompilationSummary> {
.parse_next(input)?;
let _ = " module".parse_next(input)?;
let _ = opt("s").parse_next(input)?;
let _ = " loaded.".parse_next(input)?;
let _ = line_ending_or_eof.parse_next(input)?;
let _ = ' '.parse_next(input)?;
let _ = alt(("loaded", "reloaded", "added", "unadded", "checked")).parse_next(input)?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GHC 9.10 says Ok, 10 modules reloaded instead of Ok, 10 modules loaded. There's some other special cases I've accounted for here, but that was the big one causing problems.


Ok(CompilationSummary {
result,
modules_loaded,
})
Ok(ModulesLoaded::Count(modules_loaded))
}

#[cfg(test)]
Expand All @@ -75,7 +122,7 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: 123,
modules_loaded: ModulesLoaded::Count(123),
}
);

Expand All @@ -85,7 +132,7 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: 0,
modules_loaded: ModulesLoaded::Count(0),
}
);

Expand All @@ -95,7 +142,7 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: 1,
modules_loaded: ModulesLoaded::Count(1),
}
);

Expand All @@ -105,7 +152,7 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: 6,
modules_loaded: ModulesLoaded::Count(6),
}
);

Expand All @@ -115,7 +162,7 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Err,
modules_loaded: 7,
modules_loaded: ModulesLoaded::Count(7)
}
);

Expand All @@ -125,7 +172,71 @@ mod tests {
.unwrap(),
CompilationSummary {
result: CompilationResult::Err,
modules_loaded: 1,
modules_loaded: ModulesLoaded::Count(1),
}
);

// Other verbs.
assert_eq!(
compilation_summary
.parse("Ok, 10 modules reloaded.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::Count(10),
}
);

assert_eq!(
compilation_summary
.parse("Ok, 10 modules added.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::Count(10),
}
);

assert_eq!(
compilation_summary
.parse("Ok, 10 modules unadded.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::Count(10),
}
);

assert_eq!(
compilation_summary
.parse("Ok, 10 modules checked.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::Count(10),
}
);

// Special cases!
assert_eq!(
compilation_summary
.parse("Ok, no modules to be reloaded.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::Count(0),
}
);

// Literally just for the 'unloaded' message. You can definitely reload all modules too,
// but whatever.
assert_eq!(
compilation_summary
.parse("Ok, unloaded all modules.\n")
.unwrap(),
CompilationSummary {
result: CompilationResult::Ok,
modules_loaded: ModulesLoaded::All,
}
);

Expand Down
61 changes: 60 additions & 1 deletion src/ghci/parse/ghc_message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod message_body;
mod compilation_summary;
use compilation_summary::compilation_summary;
pub use compilation_summary::CompilationSummary;
pub use compilation_summary::ModulesLoaded;

mod loaded_configuration;
use loaded_configuration::loaded_configuration;
Expand Down Expand Up @@ -267,9 +268,67 @@ mod tests {
}),
GhcMessage::Summary(CompilationSummary {
result: CompilationResult::Err,
modules_loaded: 1,
modules_loaded: ModulesLoaded::Count(1),
}),
]
);
}

#[test]
fn test_parse_messages_ghc910() {
assert_eq!(
parse_ghc_messages(indoc!(
r#"
[2 of 3] Compiling MyModule ( src/MyModule.hs, interpreted ) [Source file changed]

src/MyModule.hs:4:11: error: [GHC-83865]
• Couldn't match type ‘[Char]’ with ‘()’
Expected: ()
Actual: String
• In the expression: "example"
In an equation for ‘example’: example = "example"
|
4 | example = "example"
| ^^^^^^^^^
Failed, two modules loaded.
"#
))
.unwrap(),
vec![
GhcMessage::Compiling(
CompilingModule {
name: "MyModule".into(),
path: "src/MyModule.hs".into(),
},
),
GhcMessage::Diagnostic(
GhcDiagnostic {
severity: Severity::Error,
path: Some(
"src/MyModule.hs".into(),
),
span: PositionRange::new(4, 11, 4, 11),
message: [
"[GHC-83865]",
" • Couldn't match type ‘[Char]’ with ‘()’",
" Expected: ()",
" Actual: String",
" • In the expression: \"example\"",
" In an equation for ‘example’: example = \"example\"",
" |",
"4 | example = \"example\"",
" | ^^^^^^^^^",
""
].join("\n"),
},
),
GhcMessage::Summary(
CompilationSummary {
result: CompilationResult::Err,
modules_loaded: ModulesLoaded::Count(2),
},
),
]
);
}
}
1 change: 1 addition & 0 deletions src/ghci/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub use ghc_message::CompilationResult;
pub use ghc_message::CompilationSummary;
pub use ghc_message::GhcDiagnostic;
pub use ghc_message::GhcMessage;
pub use ghc_message::ModulesLoaded;
pub use ghc_message::Severity;
pub use module_and_files::CompilingModule;
pub use show_paths::parse_show_paths;
Expand Down
23 changes: 14 additions & 9 deletions test-harness/src/ghc_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ impl Display for FullGhcVersion {
/// Variants of this enum will correspond to `ghcVersions` in `../../flake.nix`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GhcVersion {
/// GHC 9.0
Ghc90,
/// GHC 9.2
Ghc92,
/// GHC 9.4
Ghc94,
/// GHC 9.6
Ghc96,
/// GHC 9.8
Ghc98,
/// GHC 9.10
Ghc910,
/// GHC 9.12
Ghc912,
}

fn ghc_version_re() -> &'static Regex {
Expand All @@ -61,13 +61,18 @@ impl FromStr for GhcVersion {
let (_full, [major, minor, _patch]) = captures.extract();

match (major, minor) {
("9", "0") => Ok(Self::Ghc90),
("9", "2") => Ok(Self::Ghc92),
("9", "4") => Ok(Self::Ghc94),
("9", "6") => Ok(Self::Ghc96),
("9", "8") => Ok(Self::Ghc98),
("9", "10") => Ok(Self::Ghc910),
("9", "12") => Ok(Self::Ghc912),
(_, _) => Err(miette!(
"Only GHC versions 9.0, 9.2, 9.4, 9.6, and 9.8 are supported"
"Only the following GHC versions are supported:\n\
- 9.4\n\
- 9.6\n\
- 9.8\n\
- 9.10\n\
- 9.12"
)),
}
}
Expand All @@ -79,10 +84,10 @@ mod tests {

#[test]
fn test_parse_ghc_version() {
assert_eq!("9.0.2".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc90);
assert_eq!("9.2.4".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc92);
assert_eq!("9.4.8".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc94);
assert_eq!("9.6.1".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc96);
assert_eq!("9.10.1".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc910);
assert_eq!("9.12.1".parse::<GhcVersion>().unwrap(), GhcVersion::Ghc910);

"9.6.1rc1"
.parse::<GhcVersion>()
Expand Down
4 changes: 2 additions & 2 deletions tests/error_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async fn can_write_error_log_compilation_errors() {
.expect("ghciwatch writes ghcid.txt");

let expected = match session.ghc_version() {
Ghc90 | Ghc92 | Ghc94 => expect![[r#"
Ghc94 => expect![[r#"
src/My/Module.hs:3:11: error:
* Couldn't match type `[Char]' with `()'
Expected: ()
Expand All @@ -96,7 +96,7 @@ async fn can_write_error_log_compilation_errors() {
3 | myIdent = "Uh oh!"
| ^^^^^^^^
"#]],
Ghc96 | Ghc98 => expect![[r#"
Ghc96 | Ghc98 | Ghc910 | Ghc912 => expect![[r#"
src/My/Module.hs:3:11: error: [GHC-83865]
* Couldn't match type `[Char]' with `()'
Expected: ()
Expand Down