Feat: add -c flag to skip entry-point check

`fluxc -c <file>` 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 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 15:36:43 +01:00
parent bb5e9e42d9
commit 9aa6b9694b
4 changed files with 34 additions and 24 deletions

View File

@@ -166,7 +166,7 @@ fn value_struct_name(ty: &Ty) -> Option<&str> {
// ── Entry point ──────────────────────────────────────────────────────────────── // ── Entry point ────────────────────────────────────────────────────────────────
pub fn check(program: &ast::Program<Parsed>) -> Vec<Diagnostic> { pub fn check(program: &ast::Program<Parsed>, no_main: bool) -> Vec<Diagnostic> {
let mut checker = Checker::new(); let mut checker = Checker::new();
// ── Pass 1: collect struct names + function signatures ──────────────────── // ── Pass 1: collect struct names + function signatures ────────────────────
@@ -259,29 +259,31 @@ pub fn check(program: &ast::Program<Parsed>) -> Vec<Diagnostic> {
} }
// ── Pass 4: verify entry point ──────────────────────────────────────────── // ── Pass 4: verify entry point ────────────────────────────────────────────
let main_info = checker if !no_main {
.phi let main_info = checker
.get("main") .phi
.map(|e| (e.name_span, e.params.len(), e.ret.clone())); .get("main")
match main_info { .map(|e| (e.name_span, e.params.len(), e.ret.clone()));
None => { match main_info {
checker.emit( None => {
Diagnostic::error("program has no `main` function")
.with_label(Label::primary(program.span)),
);
}
Some((name_span, param_count, ret)) => {
if param_count != 0 {
checker.emit( checker.emit(
Diagnostic::error("`main` must take no parameters") Diagnostic::error("program has no `main` function")
.with_label(Label::primary(name_span)), .with_label(Label::primary(program.span)),
); );
} }
if ret != Ty::Unit && ret != Ty::I32 && !ret.is_error() { Some((name_span, param_count, ret)) => {
checker.emit( if param_count != 0 {
Diagnostic::error("`main` must return `()` or `i32`") checker.emit(
.with_label(Label::primary(name_span)), 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)),
);
}
} }
} }
} }

View File

@@ -19,7 +19,7 @@ mod tests {
"parse errors: {:?}", "parse errors: {:?}",
parser.errors parser.errors
); );
checker::check(&program) checker::check(&program, false)
.into_iter() .into_iter()
.map(|d| d.message) .map(|d| d.message)
.collect() .collect()

View File

@@ -23,6 +23,10 @@ pub fn print_help() {
"-V".bold(), "-V".bold(),
"--version".bold(), "--version".bold(),
); );
println!(
" {} Compile without requiring a `main` entry point",
"-c".bold(),
);
println!(); println!();
println!("{}", "ARGS:".bold().yellow()); println!("{}", "ARGS:".bold().yellow());
println!( println!(
@@ -57,10 +61,13 @@ pub fn io_error(path: &str, err: std::io::Error) -> ! {
pub struct Opts { pub struct Opts {
pub files: Vec<String>, pub files: Vec<String>,
/// `-c`: compile without requiring a `main` entry point.
pub no_main: bool,
} }
pub fn parse_args() -> Opts { pub fn parse_args() -> Opts {
let mut files = Vec::new(); let mut files = Vec::new();
let mut no_main = false;
for arg in std::env::args().skip(1) { for arg in std::env::args().skip(1) {
match arg.as_str() { match arg.as_str() {
@@ -72,6 +79,7 @@ pub fn parse_args() -> Opts {
print_version(); print_version();
process::exit(0); process::exit(0);
} }
"-c" => no_main = true,
flag if flag.starts_with('-') => { flag if flag.starts_with('-') => {
fatal(&format!("unknown option `{flag}`")); 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"); fatal("no input files — at least one source file is required");
} }
Opts { files } Opts { files, no_main }
} }

View File

@@ -26,7 +26,7 @@ fn main() {
} }
if parser.errors.is_empty() { 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 { for diag in &sema_errors {
eprint!("{}", diag.render(&content, path)); eprint!("{}", diag.render(&content, path));
had_errors = true; had_errors = true;