Skip to content

Commit ebe2eda

Browse files
committed
Reject cast-style implicit conversions
1 parent f5fee09 commit ebe2eda

4 files changed

Lines changed: 76 additions & 0 deletions

File tree

src/checks/forbidden.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,56 @@ use crate::lexer::TokenKind;
44

55
pub(crate) fn check(analyzer: &mut Analyzer<'_>) {
66
check_operator_overload_attempts(analyzer);
7+
check_implicit_conversion_attempts(analyzer);
8+
}
9+
10+
fn check_implicit_conversion_attempts(analyzer: &mut Analyzer<'_>) {
11+
for index in 0..analyzer.tokens.len() {
12+
if !analyzer.tokens[index].is_ident_text("as") || as_belongs_to_with(analyzer, index) {
13+
continue;
14+
}
15+
analyzer.diagnostics.push(
16+
Diagnostic::error(
17+
code::IMPLICIT_CONVERSION_ATTEMPT,
18+
"cast-style conversions are not part of RSScript.",
19+
analyzer.tokens[index].span.clone(),
20+
"implicit conversion attempt",
21+
)
22+
.with_cause("RSScript requires conversions to be explicit named APIs so review tools can see them.")
23+
.with_fix(
24+
"use_named_conversion",
25+
"Use a named conversion such as `Target.from(value: read source)`.",
26+
"manual",
27+
),
28+
);
29+
}
30+
}
31+
32+
fn as_belongs_to_with(analyzer: &Analyzer<'_>, as_index: usize) -> bool {
33+
for token in analyzer.tokens[..as_index].iter().rev() {
34+
if token.is_ident_text("with") {
35+
return true;
36+
}
37+
if token.symbol("{")
38+
|| token.symbol("}")
39+
|| token.is_ident_text("let")
40+
|| token.is_ident_text("local")
41+
|| token.is_ident_text("return")
42+
|| token.is_ident_text("fn")
43+
|| token.is_ident_text("class")
44+
|| token.is_ident_text("struct")
45+
|| token.is_ident_text("resource")
46+
|| token.is_ident_text("if")
47+
|| token.is_ident_text("else")
48+
|| token.is_ident_text("loop")
49+
|| token.is_ident_text("while")
50+
|| token.is_ident_text("break")
51+
|| token.is_ident_text("continue")
52+
{
53+
return false;
54+
}
55+
}
56+
false
757
}
858

959
fn check_operator_overload_attempts(analyzer: &mut Analyzer<'_>) {

src/diagnostic.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod code {
3131
pub const LOCAL_CAPTURED_BY_MANAGED_CLOSURE: &str = "RS0801";
3232
pub const TAKE_HANDLE_FIELD: &str = "RS0901";
3333
pub const OPERATOR_OVERLOAD_ATTEMPT: &str = "RS1001";
34+
pub const IMPLICIT_CONVERSION_ATTEMPT: &str = "RS1002";
3435

3536
pub const REVIEW_MODE_CHANGED: &str = "RSR001";
3637
pub const REVIEW_FUNCTION_REMOVED: &str = "RSR002";
@@ -347,6 +348,11 @@ static DIAGNOSTIC_EXPLANATIONS: &[DiagnosticExplanation] = &[
347348
title: "operator overload attempt",
348349
explanation: "The MVP language surface rejects likely user-defined operator overloads to keep review semantics explicit.",
349350
},
351+
DiagnosticExplanation {
352+
code: code::IMPLICIT_CONVERSION_ATTEMPT,
353+
title: "implicit conversion attempt",
354+
explanation: "RSScript rejects cast-style conversion syntax. Conversions must be visible named APIs such as `Type.from(value: read x)`.",
355+
},
350356
];
351357

352358
pub fn format_diagnostics_json(diagnostics: &[Diagnostic]) -> String {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// expect: RS1002
2+
mode: managed
3+
4+
fn widen(value: Int) -> Int64 {
5+
let widened = value as Int64
6+
return widened
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mode: managed
2+
3+
resource File {
4+
fd: Int
5+
}
6+
7+
fn read_file(path: read Path) -> Result<fresh Bytes, IOError> {
8+
with File.open_read(
9+
path: read path
10+
) as file {
11+
return File.read_all(file: mut file)
12+
}
13+
}

0 commit comments

Comments
 (0)