Skip to content

feat(compiler): Add validation for empty structs #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions src/diagnostic/diagnostic_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub enum DiagnosticKind {
UnusedArg(SmolStr),
UnusedStructField(SmolStr),
UnusedEnumVariant(SmolStr),
EmptyStruct(SmolStr),
}

impl DiagnosticKind {
Expand Down Expand Up @@ -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")
}
Copy link
Owner

Choose a reason for hiding this comment

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

I'm being pedantic here, but annoys me that this is below the "catch-all" match arm.

}
}

Expand Down Expand Up @@ -245,6 +249,7 @@ impl From<&DiagnosticKind> for Level {
| DiagnosticKind::UnrecognizedEnumVariant(_)
| DiagnosticKind::UnrecognizedStandardLibraryHeader
| DiagnosticKind::NoCostumes
| DiagnosticKind::EmptyStruct(_)
| DiagnosticKind::BlockArgsCountMismatch { .. }
| DiagnosticKind::ReprArgsCountMismatch { .. }
| DiagnosticKind::ProcArgsCountMismatch { .. }
Expand Down
1 change: 1 addition & 0 deletions src/frontend/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod pass1;
pub mod pass2;
pub mod pass3;
pub mod pass4;
pub mod pass5;
mod transformations;
39 changes: 39 additions & 0 deletions src/visitor/pass5.rs
Copy link
Owner

Choose a reason for hiding this comment

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

This is fine, just move it inside one of the existing passes, preferably somewhere that already has relevant error reporting.

Original file line number Diff line number Diff line change
@@ -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<SmolStr, SpriteDiagnostics>,
) {
// 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,
);
}
}
}
}
}
9 changes: 9 additions & 0 deletions tests/empty_struct_test.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Test empty struct
type empty {}
Copy link
Owner

Choose a reason for hiding this comment

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

This is not valid goboscript syntax, did you even read the documentation? Again, the tests will not pass. Compiling single-files is not supported.


// This should trigger the empty struct error
list empty mylist;

onflag {
say length(mylist);
Copy link
Owner

Choose a reason for hiding this comment

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

This will still cause a panic. The original issue in question is not even fixed.

}
57 changes: 57 additions & 0 deletions tests/empty_struct_test.rs
Copy link
Owner

Choose a reason for hiding this comment

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

This test does not even work. Did you even run tests before committing?

Original file line number Diff line number Diff line change
@@ -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");
}
Loading