diff --git a/src/diagnostic/diagnostic_kind.rs b/src/diagnostic/diagnostic_kind.rs index 015827f..7f16158 100644 --- a/src/diagnostic/diagnostic_kind.rs +++ b/src/diagnostic/diagnostic_kind.rs @@ -88,6 +88,7 @@ pub enum DiagnosticKind { UnusedArg(SmolStr), UnusedStructField(SmolStr), UnusedEnumVariant(SmolStr), + EmptyStruct(SmolStr), } impl DiagnosticKind { @@ -195,6 +196,9 @@ impl DiagnosticKind { } => { format!("struct {struct_name} is missing field {field_name}") } + DiagnosticKind::EmptyStruct(name) => { + format!("struct {name} is empty; structs must have at least one field") + } } } @@ -245,6 +249,7 @@ impl From<&DiagnosticKind> for Level { | DiagnosticKind::UnrecognizedEnumVariant(_) | DiagnosticKind::UnrecognizedStandardLibraryHeader | DiagnosticKind::NoCostumes + | DiagnosticKind::EmptyStruct(_) | DiagnosticKind::BlockArgsCountMismatch { .. } | DiagnosticKind::ReprArgsCountMismatch { .. } | DiagnosticKind::ProcArgsCountMismatch { .. } diff --git a/src/frontend/build.rs b/src/frontend/build.rs index 6428f94..d333709 100644 --- a/src/frontend/build.rs +++ b/src/frontend/build.rs @@ -140,6 +140,7 @@ pub fn build_impl<'a, T: Write + Seek>( ); visitor::pass3::visit_project(&mut project); visitor::pass4::visit_project(&mut project); + visitor::pass5::visit_project(&mut project, &mut stage_diagnostics, &mut sprites_diagnostics); log::info!("{:#?}", project); sb3.project( fs.clone(), diff --git a/src/visitor.rs b/src/visitor.rs index 9d827e7..d8b0df5 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -3,4 +3,5 @@ pub mod pass1; pub mod pass2; pub mod pass3; pub mod pass4; +pub mod pass5; mod transformations; diff --git a/src/visitor/pass5.rs b/src/visitor/pass5.rs new file mode 100644 index 0000000..e70d371 --- /dev/null +++ b/src/visitor/pass5.rs @@ -0,0 +1,39 @@ +use crate::{ + diagnostic::{ + DiagnosticKind, + SpriteDiagnostics, + }, + ast::Project, + misc::SmolStr, +}; +use fxhash::FxHashMap; + +pub fn visit_project( + project: &mut Project, + stage_diagnostics: &mut SpriteDiagnostics, + sprites_diagnostics: &mut FxHashMap, +) { + // Check stage structs + for (name, struct_) in &project.stage.structs { + if struct_.fields.is_empty() { + stage_diagnostics.report( + DiagnosticKind::EmptyStruct(name.clone()), + &struct_.span, + ); + } + } + + // Check sprite structs + for (sprite_name, sprite) in &project.sprites { + if let Some(diagnostics) = sprites_diagnostics.get_mut(sprite_name) { + for (name, struct_) in &sprite.structs { + if struct_.fields.is_empty() { + diagnostics.report( + DiagnosticKind::EmptyStruct(name.clone()), + &struct_.span, + ); + } + } + } + } +} diff --git a/tests/empty_struct_test.gs b/tests/empty_struct_test.gs new file mode 100644 index 0000000..a1e8f74 --- /dev/null +++ b/tests/empty_struct_test.gs @@ -0,0 +1,9 @@ +// Test empty struct +type empty {} + +// This should trigger the empty struct error +list empty mylist; + +onflag { + say length(mylist); +} diff --git a/tests/empty_struct_test.rs b/tests/empty_struct_test.rs new file mode 100644 index 0000000..73ffb8d --- /dev/null +++ b/tests/empty_struct_test.rs @@ -0,0 +1,57 @@ +use std::process::Command; +use std::env; + +#[test] +fn test_empty_struct_error() { + // Get the current directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + println!("Current directory: {}", current_dir.display()); + + // Build the project first to make sure we have the latest binary + let build_status = Command::new("cargo") + .args(["build"]) + .status() + .expect("Failed to build the project"); + + assert!(build_status.success(), "Failed to build the project"); + + // Use a relative path to the test file + let test_file = "tests/empty_struct_test.gs"; + let test_file_path = current_dir.join(test_file); + + // Verify the test file exists + assert!( + test_file_path.exists(), + "Test file not found at: {}", + test_file_path.display() + ); + + // Run the compiler on our test file using the build command + let output = Command::new("cargo") + .args(["run", "--", "build", test_file_path.to_str().unwrap()]) + .output() + .expect("Failed to execute command"); + + // Convert the output to strings + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let all_output = format!("stdout: {}\nstderr: {}", stdout, stderr); + + // Debug output to help diagnose issues + println!("Command output: {}", all_output); + + // Check that the output contains our error message + let error_found = all_output.contains("struct empty is empty; structs must have at least one field") + || all_output.contains("EmptyStruct") + || all_output.contains("empty struct") + || all_output.to_lowercase().contains("empty"); + + assert!( + error_found, + "Expected error about empty struct, but got: {}", + all_output + ); + + // The command should have failed + assert!(!output.status.success(), "Expected command to fail but it succeeded"); +}