Improve selectors' diagnostics

This commit is contained in:
dkanus 2026-05-01 18:43:55 +07:00
commit e29ffb2a9c
14 changed files with 1473 additions and 195 deletions

View file

@ -8,6 +8,7 @@ use rottlib::parser::Parser;
mod block_items;
mod control_flow_expressions;
mod primary_expressions;
mod selector_expressions;
#[derive(Debug)]
pub(super) struct ExpectedLabel {

View file

@ -35,6 +35,10 @@ pub(super) const P0002_FIXTURES: &[Fixture] = &[
label: "files/P0002_04.uc",
source: "a * * *",
},
Fixture {
label: "files/P0002_05.uc",
source: "new(Outer, Name, 7 +) SomeClass"
}
];
pub(super) const P0003_FIXTURES: &[Fixture] = &[
@ -322,6 +326,7 @@ fn check_p0002_fixtures() {
assert_eq!(runs.get("files/P0002_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0002_03.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0002_04.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0002_05.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0002_01.uc"),
@ -404,6 +409,25 @@ fn check_p0002_fixtures() {
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0002_05.uc"),
&ExpectedDiagnostic {
headline: "expected expression after `+`, found `)`",
severity: Severity::Error,
code: Some("P0002"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(11),
end: TokenPosition(11),
},
message: "unexpected `)`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
}
#[test]
@ -665,7 +689,7 @@ fn check_p0005_fixtures() {
code: Some("P0005"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(9),
start: TokenPosition(14),
end: TokenPosition(14),
},
message: "unexpected `>`",
@ -699,7 +723,7 @@ fn check_p0005_fixtures() {
code: Some("P0005"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(5),
start: TokenPosition(10),
end: TokenPosition(10),
},
message: "unexpected `>`",

View file

@ -0,0 +1,778 @@
use super::*;
pub(super) const P0028_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0028_01.uc",
source: "{\n local Actor A;\n A.\n}\n",
},
Fixture {
label: "files/P0028_02.uc",
source: "{\n local Actor A;\n A.;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0028_03.uc",
source: "{\n local Actor A;\n A.\n ;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0028_04.uc",
source: "{\n local Actor A;\n Log(A.\n );\n Log(\"after\");\n}\n",
},
];
pub(super) const P0029_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0029_01.uc",
source: "{\n local array<int> Values;\n Values[];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_02.uc",
source: "{\n local array<int> Values;\n Values[\n ];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_03.uc",
source: "{\n local array<int> Values;\n Values[, 1];\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0029_04.uc",
source: "{\n local array<int> Values;\n Log(Values[\n ]);\n Log(\"after\");\n}\n",
},
];
pub(super) const P0030_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0030_01.uc",
source: "{\n local array<int> Values;\n Values[0;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_02.uc",
source: "{\n local array<int> Values;\n Values[\n 0\n ;\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_03.uc",
source: "{\n local array<int> Values;\n Log(Values[0));\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0030_04.uc",
source: "{\n local array<int> Values;\n Values[GetIndex()\n Values[1] = 7;\n}\n",
},
];
#[test]
fn check_p0028_fixtures() {
let runs = run_fixtures(P0028_FIXTURES);
assert_eq!(runs.get("files/P0028_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0028_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0028_03.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0028_04.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0028_01.uc"),
&ExpectedDiagnostic {
headline: "expected member name after `.`, found `}`",
severity: Severity::Error,
code: Some("P0028"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(14),
end: TokenPosition(14),
},
message: "unexpected `}`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(12),
end: TokenPosition(12),
},
message: "after this `.`, a member name was expected",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0028_02.uc"),
&ExpectedDiagnostic {
headline: "expected member name after `.`, found `;`",
severity: Severity::Error,
code: Some("P0028"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(13),
end: TokenPosition(13),
},
message: "unexpected `;`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0028_03.uc"),
&ExpectedDiagnostic {
headline: "expected member name after `.`, found `;`",
severity: Severity::Error,
code: Some("P0028"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(15),
},
message: "unexpected `;`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(12),
end: TokenPosition(12),
},
message: "after this `.`, a member name was expected",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0028_04.uc"),
&ExpectedDiagnostic {
headline: "expected member name after `.`, found `)`",
severity: Severity::Error,
code: Some("P0028"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(17),
end: TokenPosition(17),
},
message: "unexpected `)`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(14),
end: TokenPosition(14),
},
message: "after this `.`, a member name was expected",
}],
help: None,
notes: &[],
},
);
}
#[test]
fn check_p0029_fixtures() {
let runs = run_fixtures(P0029_FIXTURES);
assert_eq!(runs.get("files/P0029_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0029_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0029_03.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0029_04.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0029_01.uc"),
&ExpectedDiagnostic {
headline: "expected index expression after `[`, found `]`",
severity: Severity::Error,
code: Some("P0029"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(16),
end: TokenPosition(16),
},
message: "expected expression before `]`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0029_02.uc"),
&ExpectedDiagnostic {
headline: "expected index expression after `[`, found `]`",
severity: Severity::Error,
code: Some("P0029"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(18),
},
message: "expected expression before `]`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(15),
},
message: "after this `[`, an index expression was expected",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0029_03.uc"),
&ExpectedDiagnostic {
headline: "expected index expression after `[`, found `,`",
severity: Severity::Error,
code: Some("P0029"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(16),
end: TokenPosition(16),
},
message: "unexpected `,`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0029_04.uc"),
&ExpectedDiagnostic {
headline: "expected index expression after `[`, found `]`",
severity: Severity::Error,
code: Some("P0029"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(17),
end: TokenPosition(20),
},
message: "expected expression before `]`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(17),
end: TokenPosition(17),
},
message: "after this `[`, an index expression was expected",
}],
help: None,
notes: &[],
},
);
}
#[test]
fn check_p0030_fixtures() {
let runs = run_fixtures(P0030_FIXTURES);
assert_eq!(runs.get("files/P0030_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0030_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0030_04.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0030_01.uc"),
&ExpectedDiagnostic {
headline: "missing `]` to close index selector",
severity: Severity::Error,
code: Some("P0030"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(17),
end: TokenPosition(17),
},
message: "expected `]` before `;`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0030_02.uc"),
&ExpectedDiagnostic {
headline: "missing `]` to close index selector",
severity: Severity::Error,
code: Some("P0030"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(21),
},
message: "expected `]` before `;`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(15),
},
message: "index selector starts here",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_by_code("files/P0030_03.uc", "P0030"),
&ExpectedDiagnostic {
headline: "missing `]` to close index selector",
severity: Severity::Error,
code: Some("P0030"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(19),
end: TokenPosition(19),
},
message: "expected `]` before `)`",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0030_04.uc"),
&ExpectedDiagnostic {
headline: "missing `]` to close index selector",
severity: Severity::Error,
code: Some("P0030"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(21),
},
message: "expected `]` before `Values`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(15),
end: TokenPosition(15),
},
message: "index selector starts here",
}],
help: None,
notes: &[],
},
);
}
pub(super) const P0031_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0031_01.uc",
source: "{\n Func(A B);\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0031_02.uc",
source: "{\n Func\n (A 123);\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0031_03.uc",
source: "{\n Func(\n A\n new SomeClass\n );\n Log(\"after\");\n}\n",
},
];
pub(super) const P0032_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0032_01.uc",
source: "Func(",
},
Fixture {
label: "files/P0032_02.uc",
source: "Func\n(\n A,",
},
Fixture {
label: "files/P0032_03.uc",
source: "Func(A,\n B,",
},
Fixture {
label: "files/P0032_04.uc",
source: "{\n Func\n (\n A,\n B,\n",
},
];
pub(super) const P0033_FIXTURES: &[Fixture] = &[
Fixture {
label: "files/P0033_01.uc",
source: "{\n Func(A #, B);\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0033_02.uc",
source: "{\n Func\n (A\n :,\n B);\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0033_03.uc",
source: "{\n Func(\n A ?\n , B\n );\n Log(\"after\");\n}\n",
},
Fixture {
label: "files/P0033_04.uc",
source: "{\n Func\n (\n A\n #,\n B\n );\n Log(\"after\");\n}\n",
},
];
#[test]
fn check_p0031_fixtures() {
let runs = run_fixtures(P0031_FIXTURES);
assert_eq!(runs.get("files/P0031_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0031_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0031_03.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0031_01.uc"),
&ExpectedDiagnostic {
headline: "missing `,` between function call arguments",
severity: Severity::Error,
code: Some("P0031"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "expected `,` before `B`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(5),
end: TokenPosition(5),
},
message: "previous argument ends here",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0031_02.uc"),
&ExpectedDiagnostic {
headline: "missing `,` between function call arguments",
severity: Severity::Error,
code: Some("P0031"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(9),
end: TokenPosition(9),
},
message: "expected `,` before `123`",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(3),
end: TokenPosition(3),
},
message: "function called here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "previous argument ends here",
},
],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0031_03.uc"),
&ExpectedDiagnostic {
headline: "missing `,` between function call arguments",
severity: Severity::Error,
code: Some("P0031"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(10),
end: TokenPosition(10),
},
message: "expected `,` before `new`",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(4),
end: TokenPosition(4),
},
message: "function call argument list starts here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "previous argument ends here",
},
],
help: None,
notes: &[],
},
);
}
#[test]
fn check_p0032_fixtures() {
let runs = run_fixtures(P0032_FIXTURES);
assert_eq!(runs.get("files/P0032_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0032_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0032_03.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0032_04.uc").unwrap().len(), 2);
assert_diagnostic(
&runs.get_any("files/P0032_01.uc"),
&ExpectedDiagnostic {
headline: "missing `)` to close function call argument list",
severity: Severity::Error,
code: Some("P0032"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(2),
end: TokenPosition(2),
},
message: "expected `)` before end of file",
}),
secondary_labels: &[],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0032_02.uc"),
&ExpectedDiagnostic {
headline: "missing `)` to close function call argument list",
severity: Severity::Error,
code: Some("P0032"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(2),
end: TokenPosition(7),
},
message: "expected `)` before end of file",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(0),
end: TokenPosition(0),
},
message: "function called here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(2),
end: TokenPosition(2),
},
message: "function call argument list starts here",
},
],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0032_03.uc"),
&ExpectedDiagnostic {
headline: "missing `)` to close function call argument list",
severity: Severity::Error,
code: Some("P0032"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(1),
end: TokenPosition(8),
},
message: "expected `)` before end of file",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(1),
end: TokenPosition(1),
},
message: "function call argument list starts here",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_by_code("files/P0032_04.uc", "P0032"),
&ExpectedDiagnostic {
headline: "missing `)` to close function call argument list",
severity: Severity::Error,
code: Some("P0032"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(6),
end: TokenPosition(16),
},
message: "expected `)` before end of file",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(3),
end: TokenPosition(3),
},
message: "function called here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(6),
end: TokenPosition(6),
},
message: "function call argument list starts here",
},
],
help: None,
notes: &[],
},
);
}
#[test]
fn check_p0033_fixtures() {
let runs = run_fixtures(P0033_FIXTURES);
assert_eq!(runs.get("files/P0033_01.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0033_02.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0033_03.uc").unwrap().len(), 1);
assert_eq!(runs.get("files/P0033_04.uc").unwrap().len(), 1);
assert_diagnostic(
&runs.get_any("files/P0033_01.uc"),
&ExpectedDiagnostic {
headline: "expected `,` or `)` after argument, found `#`",
severity: Severity::Error,
code: Some("P0033"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "unexpected `#`",
}),
secondary_labels: &[ExpectedLabel {
span: TokenSpan {
start: TokenPosition(5),
end: TokenPosition(5),
},
message: "argument ends here",
}],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0033_02.uc"),
&ExpectedDiagnostic {
headline: "expected `,` or `)` after argument, found `:`",
severity: Severity::Error,
code: Some("P0033"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(10),
end: TokenPosition(10),
},
message: "unexpected `:`",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(3),
end: TokenPosition(3),
},
message: "function called here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "argument ends here",
},
],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0033_03.uc"),
&ExpectedDiagnostic {
headline: "expected `,` or `)` after argument, found `?`",
severity: Severity::Error,
code: Some("P0033"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(9),
end: TokenPosition(9),
},
message: "unexpected `?`",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(4),
end: TokenPosition(4),
},
message: "function call argument list starts here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(7),
end: TokenPosition(7),
},
message: "argument ends here",
},
],
help: None,
notes: &[],
},
);
assert_diagnostic(
&runs.get_any("files/P0033_04.uc"),
&ExpectedDiagnostic {
headline: "expected `,` or `)` after argument, found `#`",
severity: Severity::Error,
code: Some("P0033"),
primary_label: Some(ExpectedLabel {
span: TokenSpan {
start: TokenPosition(12),
end: TokenPosition(12),
},
message: "unexpected `#`",
}),
secondary_labels: &[
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(3),
end: TokenPosition(3),
},
message: "function called here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(6),
end: TokenPosition(6),
},
message: "function call argument list starts here",
},
ExpectedLabel {
span: TokenSpan {
start: TokenPosition(9),
end: TokenPosition(9),
},
message: "argument ends here",
},
],
help: None,
notes: &[],
},
);
}