From 9aa6b9694bda85dcfadcbe3ac2b08871572b1aa2 Mon Sep 17 00:00:00 2001 From: Jooris Hadeler Date: Wed, 11 Mar 2026 15:36:43 +0100 Subject: [PATCH] Feat: add -c flag to skip entry-point check `fluxc -c ` compiles a source file without requiring a `main` function, matching the convention of C compilers. Pass 4 (entry-point validation) is skipped when `no_main` is set. Co-Authored-By: Claude Sonnet 4.6 --- fluxc/src/checker/mod.rs | 44 ++++++++++++++++++++------------------ fluxc/src/checker/tests.rs | 2 +- fluxc/src/cli.rs | 10 ++++++++- fluxc/src/main.rs | 2 +- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/fluxc/src/checker/mod.rs b/fluxc/src/checker/mod.rs index 68f2af5..6f81793 100644 --- a/fluxc/src/checker/mod.rs +++ b/fluxc/src/checker/mod.rs @@ -166,7 +166,7 @@ fn value_struct_name(ty: &Ty) -> Option<&str> { // ── Entry point ──────────────────────────────────────────────────────────────── -pub fn check(program: &ast::Program) -> Vec { +pub fn check(program: &ast::Program, no_main: bool) -> Vec { let mut checker = Checker::new(); // ── Pass 1: collect struct names + function signatures ──────────────────── @@ -259,29 +259,31 @@ pub fn check(program: &ast::Program) -> Vec { } // ── Pass 4: verify entry point ──────────────────────────────────────────── - let main_info = checker - .phi - .get("main") - .map(|e| (e.name_span, e.params.len(), e.ret.clone())); - match main_info { - None => { - checker.emit( - Diagnostic::error("program has no `main` function") - .with_label(Label::primary(program.span)), - ); - } - Some((name_span, param_count, ret)) => { - if param_count != 0 { + if !no_main { + let main_info = checker + .phi + .get("main") + .map(|e| (e.name_span, e.params.len(), e.ret.clone())); + match main_info { + None => { checker.emit( - Diagnostic::error("`main` must take no parameters") - .with_label(Label::primary(name_span)), + Diagnostic::error("program has no `main` function") + .with_label(Label::primary(program.span)), ); } - if ret != Ty::Unit && ret != Ty::I32 && !ret.is_error() { - checker.emit( - Diagnostic::error("`main` must return `()` or `i32`") - .with_label(Label::primary(name_span)), - ); + Some((name_span, param_count, ret)) => { + if param_count != 0 { + checker.emit( + Diagnostic::error("`main` must take no parameters") + .with_label(Label::primary(name_span)), + ); + } + if ret != Ty::Unit && ret != Ty::I32 && !ret.is_error() { + checker.emit( + Diagnostic::error("`main` must return `()` or `i32`") + .with_label(Label::primary(name_span)), + ); + } } } } diff --git a/fluxc/src/checker/tests.rs b/fluxc/src/checker/tests.rs index 1c9f0c7..8e010e7 100644 --- a/fluxc/src/checker/tests.rs +++ b/fluxc/src/checker/tests.rs @@ -19,7 +19,7 @@ mod tests { "parse errors: {:?}", parser.errors ); - checker::check(&program) + checker::check(&program, false) .into_iter() .map(|d| d.message) .collect() diff --git a/fluxc/src/cli.rs b/fluxc/src/cli.rs index a98d00b..3de2755 100644 --- a/fluxc/src/cli.rs +++ b/fluxc/src/cli.rs @@ -23,6 +23,10 @@ pub fn print_help() { "-V".bold(), "--version".bold(), ); + println!( + " {} Compile without requiring a `main` entry point", + "-c".bold(), + ); println!(); println!("{}", "ARGS:".bold().yellow()); println!( @@ -57,10 +61,13 @@ pub fn io_error(path: &str, err: std::io::Error) -> ! { pub struct Opts { pub files: Vec, + /// `-c`: compile without requiring a `main` entry point. + pub no_main: bool, } pub fn parse_args() -> Opts { let mut files = Vec::new(); + let mut no_main = false; for arg in std::env::args().skip(1) { match arg.as_str() { @@ -72,6 +79,7 @@ pub fn parse_args() -> Opts { print_version(); process::exit(0); } + "-c" => no_main = true, flag if flag.starts_with('-') => { fatal(&format!("unknown option `{flag}`")); } @@ -83,5 +91,5 @@ pub fn parse_args() -> Opts { fatal("no input files — at least one source file is required"); } - Opts { files } + Opts { files, no_main } } diff --git a/fluxc/src/main.rs b/fluxc/src/main.rs index 7b46711..075f00a 100644 --- a/fluxc/src/main.rs +++ b/fluxc/src/main.rs @@ -26,7 +26,7 @@ fn main() { } if parser.errors.is_empty() { - let sema_errors = checker::check(&program); + let sema_errors = checker::check(&program, opts.no_main); for diag in &sema_errors { eprint!("{}", diag.render(&content, path)); had_errors = true;