Skip to content
Merged
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
14 changes: 7 additions & 7 deletions compatibility/compiler-expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,10 +425,10 @@
"oracle:real-world/6v6-adjustments/oracle.json",
"provenance:real-world/6v6-adjustments/fixture.json"
],
"owner": "opy-rs#157",
"note": "The full real-world project now resolves included-file settings, #!mainFile, and #!defineMember directives; it remains outside the compiler baseline at the next macro declaration syntax boundary.",
"owner": "opy-rs#167",
"note": "The source frontend reaches the pinned semantic unknown-member frontier after resolving the demonstrated macro constants, settings expressions, multiline comprehensions, and catalog-backed receiver members; canonical lowering remains outside this issue.",
"failureClass": "frontend",
"diagnosticCode": "parse-error"
"diagnosticCode": "unknown-member"
},
{
"fixture": "real-world/overpy-broken-weapons",
Expand Down Expand Up @@ -720,12 +720,12 @@
"evidence": [
"oracle:synthetic/issue-29-invalid/oracle.json",
"provenance:synthetic/issue-29-invalid/fixture.json",
"test:opy-rs::compiler::integration_tests::issue_93_frontend::frontend_diagnostics_are_public_and_source_attributed"
"test:opy-rs::compiler::integration_tests::issue_167_source::issue_29_reaches_the_duplicate_rule_name_frontier"
],
"owner": "opy-rs#93",
"note": "The public compiler reports the invalid annotation as a source-attributed parse diagnostic.",
"owner": "opy-rs#167",
"note": "An ordinary rule @Name annotation reaches the pinned duplicate-rule-name semantic frontier with source attribution.",
"failureClass": "frontend",
"diagnosticCode": "parse-error"
"diagnosticCode": "duplicate-rule-name"
},
{
"fixture": "synthetic/issue-31-negative",
Expand Down
2 changes: 2 additions & 0 deletions compatibility/conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"do-while-placement": "semantic",
"invalid-range-binder": "semantic",
"four-dimensional-assignment": "semantic",
"duplicate-rule-name": "semantic",
"unknown-member": "semantic",
}
FRONTIER_CONSTRUCTS = {
"lambda-context": "parse-error",
Expand Down
4 changes: 2 additions & 2 deletions compatibility/differential-expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{"fixture": "synthetic/expressions-values", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/expressions-values/oracle.json", "implementation-invariant:source implementation-resolves-expressions"], "note": "Expressions, arrays, strings, vectors, calls, and format expressions resolve."},
{"fixture": "synthetic/preprocessing", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/preprocessing/oracle.json", "implementation-invariant:preprocessor-include-define-undef"], "note": "Include, object-like and function-like defines, and undef are preserved through preprocessing."},
{"fixture": "synthetic/issue-29-directives", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-29-directives/oracle.json", "implementation-invariant:issue-29-preprocessing-state"], "note": "Advanced directive state and source annotations resolve without executing Workshop optimizer, localization, or emission effects."},
{"fixture": "synthetic/issue-29-invalid", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-29-invalid/oracle.json", "implementation-invariant:issue-29-structured-directive-diagnostics"], "note": "Malformed preprocessing and annotation forms remain source-located structured diagnostics."},
{"fixture": "synthetic/issue-29-invalid", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-29-invalid/oracle.json", "implementation-invariant:issue-167-duplicate-rule-name-frontier"], "note": "The ordinary-rule @Name form reaches the pinned duplicate-rule-name semantic frontier with source attribution."},
{"fixture": "synthetic/issue-29-main-file", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-29-main-file/oracle.json", "implementation-invariant:issue-29-main-file-scope"], "note": "mainFile redirects the source implementation entry point and preserves child-include prefix state without copying catalog data."},
{"fixture": "synthetic/issue-129-included-main-file", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-129-included-main-file/oracle.json", "provenance:synthetic/issue-129-included-main-file/fixture.json"], "note": "The pinned oracle accepts a leading included-file #!mainFile directive, while native compilation preserves the declared root entry point and both rule outputs."},
{"fixture": "synthetic/issue-31-positive", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-31-positive/oracle.json", "implementation-invariant:issue-31-directive-surfaces"], "note": "Pinned positive probe covers global rulePrefixTemplate, include prefix restoration, AST macro/enum redeclaration, and translation normalization."},
Expand All @@ -47,7 +47,7 @@
{"fixture": "real-world/overpy-meipocalypse", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/overpy-meipocalypse/oracle.json", "provenance:real-world/overpy-meipocalypse/fixture.json"], "note": "The reference rejects the missing script hook; native rejection is retained with a different earlier diagnostic."},
{"fixture": "real-world/overpy-zencopter", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/overpy-zencopter/oracle.json", "provenance:real-world/overpy-zencopter/fixture.json"], "note": "Both sides reject the pinned upstream example; native diagnostic wording is not used as semantic evidence."},
{"fixture": "real-world/ow1-emulator", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/ow1-emulator/oracle.json", "provenance:real-world/ow1-emulator/fixture.json"], "note": "The full project now resolves included-file #!mainFile, #!defineMember directives, continued directive lines, and duplicate includes; the failure case remains at the next parser boundary."},
{"fixture": "real-world/6v6-adjustments", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/6v6-adjustments/oracle.json", "provenance:real-world/6v6-adjustments/fixture.json"], "note": "The full project now resolves included-file #!mainFile, #!defineMember directives, and included settings; the failure case remains at the next parser boundary in the included constants."},
{"fixture": "real-world/6v6-adjustments", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/6v6-adjustments/oracle.json", "provenance:real-world/6v6-adjustments/fixture.json", "implementation-invariant:issue-167-source-frontier"], "note": "The full project reaches the pinned semantic unknown-member frontier after the demonstrated source constructs are parsed and lowered."},
{"fixture": "synthetic/issue-35-integration", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-35-integration/oracle.json", "implementation-invariant:opy-rs::compiler-vertical-slice"], "note": "The OPY source implementation resolves the source fixture; the compiler test independently lowers it through canonical WIR validation and deterministic workshop-rs emission."},
{"fixture": "synthetic/issue-40-structural", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-40-structural/oracle.json", "implementation-invariant:opy-rs::compiler-structural-lowering"], "note": "The pinned oracle records subroutine source identity, deterministic explicit/implicit variable allocation, and player event filters; the compiler test independently asserts those structures in canonical WIR."},
{"fixture": "synthetic/issue-46-primitives", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-46-primitives/oracle.json", "implementation-invariant:opy-rs::compiler-primitive-lowering", "test:opy-rs::compiler-issue-46-native-wir-equivalence"], "note": "The pinned oracle records assignment and modification lowering (including **= and single-level indexed forms), value expressions, array indexing (including firstOf index-0 normalization), not-comparison negation, implicit default variables at fixed slots, and non-null variable initializers; #145 additionally covers null-default omission, float-zero initialization, and nested indexed writes. The compiler test compares native lowered WIR directly with the parsed oracle WIR and asserts structural equivalence."},
Expand Down
2 changes: 2 additions & 0 deletions crates/opy-rs/src/compiler/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ mod issue_145_lowering;
mod issue_161_preprocessing;
#[path = "tests/issue_162_semantic.rs"]
mod issue_162_semantic;
#[path = "tests/issue_167_source.rs"]
mod issue_167_source;
#[path = "tests/issue_42_oracle.rs"]
mod issue_42_oracle;
#[path = "tests/issue_46_oracle.rs"]
Expand Down
5 changes: 5 additions & 0 deletions crates/opy-rs/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,11 @@ fn convert_settings_node(node: crate::hir::SettingsNode) -> workshop_rs::setting
value,
span: span.map(convert_settings_span),
},
SourceNode::Raw { name, value, span } => TargetNode::Raw {
name,
value,
span: span.map(convert_settings_span),
},
SourceNode::List {
name,
elements,
Expand Down
87 changes: 87 additions & 0 deletions crates/opy-rs/src/compiler/tests/issue_167_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Pinned source failure-frontier coverage for issue #167.

use std::path::{Path, PathBuf};

use crate::hir::SettingsNode;
use crate::{CompileFailureClass, CompileStatus, Compiler};
use workshop_rs::catalog::Locale;

fn fixture_dir(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../compatibility/fixtures")
.join(name)
}

#[test]
fn issue_29_reaches_the_duplicate_rule_name_frontier() {
let dir = fixture_dir("synthetic/issue-29-invalid");
let source = std::fs::read_to_string(dir.join("source.opy")).unwrap();
let report = Compiler::new().unwrap().compile_source_report_with_locale(
&source,
"source.opy",
&dir,
&Locale::new("en-US"),
);

assert_eq!(report.compile.status, CompileStatus::Failure);
assert_eq!(
report.compile.failure_class,
Some(CompileFailureClass::Frontend)
);
let diagnostic = report.compile.diagnostics.first().unwrap();
assert_eq!(diagnostic.code, "duplicate-rule-name");
assert!(
diagnostic
.message
.contains("Rule name was already declared")
);
let span = diagnostic.span.as_ref().unwrap();
assert_eq!(span.path, "source.opy");
assert_eq!(span.start.line, 5);
}

#[test]
fn six_v_six_reaches_the_pinned_unknown_member_frontier() {
let dir = fixture_dir("real-world/6v6-adjustments");
let source = std::fs::read_to_string(dir.join("main.opy")).unwrap();
let report = Compiler::new().unwrap().compile_source_report_with_locale(
&source,
"main.opy",
&dir,
&Locale::new("en-US"),
);

assert_eq!(report.compile.status, CompileStatus::Failure);
assert_eq!(
report.compile.failure_class,
Some(CompileFailureClass::Frontend)
);
let diagnostic = report.compile.diagnostics.first().unwrap();
assert_eq!(diagnostic.code, "unknown-member");
assert!(diagnostic.message.contains("unknown member"));
let span = diagnostic.span.as_ref().unwrap();
assert_eq!(span.path, "utilities/custom_hp.opy");
assert_eq!(span.start.line, 31);
}

#[test]
fn expression_valued_setting_stays_raw_through_compiler() {
let source = "settings {\n \"lobby\": {\n \"modeName\": GAMEMODE_NAME\" \"GAMEMODE_VERSION,\n },\n \"gamemodes\": {}\n}\nrule \"r\":\n @Event global\n pass\n";
let hir = crate::compile(source, "source.opy", Path::new(".")).unwrap();
let lobby = match &hir.settings.as_ref().unwrap().children[0] {
SettingsNode::Group { children, .. } => children,
other => panic!("expected lobby group, got {other:?}"),
};
assert!(matches!(
&lobby[0],
SettingsNode::Raw { value, .. }
if value == "GAMEMODE_NAME\" \"GAMEMODE_VERSION"
));

let artifact = Compiler::new().unwrap().compile_hir(&hir).unwrap();
assert!(
artifact
.emitted
.contains("modeName: GAMEMODE_NAME\" \"GAMEMODE_VERSION")
);
}
2 changes: 1 addition & 1 deletion crates/opy-rs/src/compiler/tests/issue_93_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn frontend_diagnostics_are_public_and_source_attributed() {
(
"issue-29-invalid.opy",
"globalvar value\n\nrule \"invalid directive\":\n @Event global\n @Name \"wrong\"\n value = 1\n",
"parse-error",
"duplicate-rule-name",
),
(
"issue-31-negative.opy",
Expand Down
12 changes: 12 additions & 0 deletions crates/opy-rs/src/cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ pub enum SettingsNode {
value: String,
span: Span,
},
/// An unquoted settings expression preserved without assigning it a type.
Raw {
name: String,
value: String,
span: Span,
},
List {
name: String,
elements: Vec<SettingsListElement>,
Expand Down Expand Up @@ -104,6 +110,12 @@ pub enum Decl {
members: Vec<(String, Span)>,
span: Span,
},
/// A source-level constant declared with `macro name = expression`.
Constant {
name: String,
value: Expr,
span: Span,
},
/// A `macro` declaration with parameterized statement body.
Macro {
name: String,
Expand Down
9 changes: 9 additions & 0 deletions crates/opy-rs/src/hir/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,15 @@ fn dump_settings_node(node: &SettingsNode, out: &mut String, level: usize) {
span_suffix(span.as_ref())
));
}
SettingsNode::Raw { name, value, span } => {
out.push_str(&format!(
"{}raw {} {:?}{}\n",
indent(level),
name,
value,
span_suffix(span.as_ref())
));
}
SettingsNode::List {
name,
elements,
Expand Down
8 changes: 8 additions & 0 deletions crates/opy-rs/src/hir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,13 @@ pub enum SettingsNode {
#[serde(skip_serializing_if = "Option::is_none")]
span: Option<Span>,
},
/// A settings expression preserved without assigning it a literal type.
Raw {
name: String,
value: String,
#[serde(skip_serializing_if = "Option::is_none")]
span: Option<Span>,
},
List {
name: String,
#[serde(default)]
Expand All @@ -265,6 +272,7 @@ impl SettingsNode {
| SettingsNode::Number { span, .. }
| SettingsNode::Bool { span, .. }
| SettingsNode::String { span, .. }
| SettingsNode::Raw { span, .. }
| SettingsNode::List { span, .. } => span.as_ref(),
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/opy-rs/src/hir/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const EXPR_KINDS: &[&str] = &[
"format",
];
/// Settings node `kind` values understood by this consumer.
const SETTINGS_NODE_KINDS: &[&str] = &["group", "number", "bool", "string", "list"];
const SETTINGS_NODE_KINDS: &[&str] = &["group", "number", "bool", "string", "raw", "list"];

/// Declared names, collected before reference validation.
struct NameTables<'a> {
Expand Down Expand Up @@ -338,6 +338,7 @@ fn node_name(node: &SettingsNode) -> &str {
| SettingsNode::Number { name, .. }
| SettingsNode::Bool { name, .. }
| SettingsNode::String { name, .. }
| SettingsNode::Raw { name, .. }
| SettingsNode::List { name, .. } => name,
}
}
Expand Down
Loading