diff --git a/Cargo.lock b/Cargo.lock index 8ac7115718..7e99da5cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,7 +2928,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "crossbeam", - "indoc", "inlinable_string", "maplit", "num_cpus", @@ -2950,6 +2949,7 @@ dependencies = [ "roc_types", "roc_unify", "tempfile", + "ven_pretty", ] [[package]] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 2cedcf4ea8..dfec81975e 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -135,8 +135,8 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io Err(LoadingProblem::ParsingFailedReport(report)) => { print!("{}", report); } - Err(LoadingProblem::NoPlatform) => { - print!("The file you ran did not provide a platform\nMaybe you are trying to run an Interface module?"); + Err(LoadingProblem::NoPlatform(report)) => { + print!("{}", report); } Err(other) => { panic!("build_file failed with error:\n{:?}", other); diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 09caec0dec..701b7dd6da 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -19,6 +19,7 @@ roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_reporting = { path = "../reporting" } +ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.2", features = ["collections"] } inlinable_string = "0.1" parking_lot = { version = "0.11", features = ["deadlock_detection"] } @@ -29,6 +30,5 @@ num_cpus = "1" tempfile = "3.1.0" pretty_assertions = "0.5.1" maplit = "1.0.1" -indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 721fa41509..7b0ccdc924 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -616,6 +616,7 @@ struct ModuleHeader<'a> { module_id: ModuleId, module_name: ModuleNameEnum<'a>, module_path: PathBuf, + is_root_module: bool, exposed_ident_ids: IdentIds, deps_by_name: MutMap, ModuleId>, packages: MutMap<&'a str, PackageOrPath<'a>>, @@ -767,6 +768,14 @@ enum Msg<'a> { FailedToParse(ParseProblem<'a, SyntaxError<'a>>), } +#[derive(Debug)] +enum PlatformPath<'a> { + NotSpecified, + Valid(To<'a>), + RootIsInterface, + RootIsPkgConfig, +} + #[derive(Debug)] struct State<'a> { pub root_id: ModuleId, @@ -775,7 +784,7 @@ struct State<'a> { pub stdlib: &'a StdLib, pub exposed_types: SubsByModule, pub output_path: Option<&'a str>, - pub platform_path: Option>, + pub platform_path: PlatformPath<'a>, pub headers_parsed: MutSet, @@ -981,7 +990,7 @@ pub enum LoadingProblem<'a> { ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), UnexpectedHeader(String), /// there is no platform (likely running an Interface module) - NoPlatform, + NoPlatform(String), MsgChannelDied, ErrJoiningWorkerThreads, @@ -1135,6 +1144,7 @@ impl<'a> LoadStart<'a> { load_filename( arena, filename, + true, None, Arc::clone(&arc_modules), Arc::clone(&ident_ids_by_module), @@ -1403,7 +1413,7 @@ where goal_phase, stdlib, output_path: None, - platform_path: None, + platform_path: PlatformPath::NotSpecified, module_cache: ModuleCache::default(), dependencies: Dependencies::default(), procedures: MutMap::default(), @@ -1612,16 +1622,27 @@ fn update<'a>( match header_extra { App { to_platform } => { - debug_assert_eq!(state.platform_path, None); + debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); + debug_assert!(header.is_root_module); - state.platform_path = Some(to_platform.clone()); + state.platform_path = PlatformPath::Valid(to_platform.clone()); } PkgConfig { .. } => { debug_assert_eq!(state.platform_id, None); state.platform_id = Some(header.module_id); + + if header.is_root_module { + debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); + state.platform_path = PlatformPath::RootIsPkgConfig; + } + } + Interface => { + if header.is_root_module { + debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); + state.platform_path = PlatformPath::RootIsInterface; + } } - Interface => {} } // store an ID to name mapping, so we know the file to read when fetching dependencies' headers @@ -2073,21 +2094,89 @@ fn finish_specialization( .. } = module_cache; - let sources = sources + let sources: MutMap)> = sources .into_iter() .map(|(id, (path, src))| (id, (path, src.into()))) .collect(); let path_to_platform = { + use PlatformPath::*; let package_or_path = match platform_path { - Some(To::ExistingPackage(shorthand)) => { + Valid(To::ExistingPackage(shorthand)) => { match (*state.arc_shorthands).lock().get(shorthand) { Some(p_or_p) => p_or_p.clone(), None => unreachable!(), } } - Some(To::NewPackage(p_or_p)) => p_or_p, - None => return Err(LoadingProblem::NoPlatform), + Valid(To::NewPackage(p_or_p)) => p_or_p, + other => { + use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; + use ven_pretty::DocAllocator; + + let module_id = state.root_id; + + let palette = DEFAULT_PALETTE; + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&[], module_id, &interns); + + let report = { + match other { + Valid(_) => unreachable!(), + NotSpecified => { + let doc = alloc.stack(vec![ + alloc.reflow("I could not find a platform based on your input file."), + alloc.reflow(r"Does the module header contain an entry that looks like this:"), + alloc + .parser_suggestion(" packages { base: \"platform\" }") + .indent(4), + alloc.reflow("See also TODO."), + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + RootIsInterface => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + RootIsPkgConfig => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a package config file, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + } + }; + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + return Err(LoadingProblem::NoPlatform(buf)); + } }; match package_or_path { @@ -2295,6 +2384,7 @@ fn load_module<'a>( load_filename( arena, filename, + false, opt_shorthand, module_ids, ident_ids_by_module, @@ -2334,6 +2424,7 @@ fn parse_header<'a>( arena: &'a Bump, read_file_duration: Duration, filename: PathBuf, + is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, ident_ids_by_module: Arc>>, @@ -2359,6 +2450,7 @@ fn parse_header<'a>( value: ModuleNameEnum::Interface(header.name.value), }, filename, + is_root_module, opt_shorthand, &[], header.exposes.into_bump_slice(), @@ -2381,6 +2473,7 @@ fn parse_header<'a>( value: ModuleNameEnum::App(header.name.value), }, filename, + is_root_module, opt_shorthand, packages, header.provides.into_bump_slice(), @@ -2486,6 +2579,7 @@ fn parse_header<'a>( fn load_filename<'a>( arena: &'a Bump, filename: PathBuf, + is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, ident_ids_by_module: Arc>>, @@ -2501,6 +2595,7 @@ fn load_filename<'a>( arena, file_io_duration, filename, + is_root_module, opt_shorthand, module_ids, ident_ids_by_module, @@ -2535,6 +2630,7 @@ fn load_from_str<'a>( arena, file_io_duration, filename, + false, None, module_ids, ident_ids_by_module, @@ -2556,6 +2652,7 @@ enum ModuleNameEnum<'a> { fn send_header<'a>( loc_name: Located>, filename: PathBuf, + is_root_module: bool, opt_shorthand: Option<&'a str>, packages: &'a [Located>], exposes: &'a [Located>], @@ -2742,6 +2839,7 @@ fn send_header<'a>( ModuleHeader { module_id: home, module_path: filename, + is_root_module, exposed_ident_ids: ident_ids, module_name: loc_name.value, packages: package_entries, @@ -2763,6 +2861,7 @@ fn send_header<'a>( fn send_header_two<'a>( arena: &'a Bump, filename: PathBuf, + is_root_module: bool, shorthand: &'a str, app_module_id: ModuleId, packages: &'a [Located>], @@ -2959,6 +3058,7 @@ fn send_header_two<'a>( ModuleHeader { module_id: home, module_path: filename, + is_root_module, exposed_ident_ids: ident_ids, module_name, packages: package_entries, @@ -3102,6 +3202,7 @@ fn fabricate_pkg_config_module<'a>( send_header_two( arena, filename, + false, shorthand, app_module_id, &[],