Skip to content

Commit 5a31e6d

Browse files
committed
Add core interface signature support
1 parent 412391d commit 5a31e6d

12 files changed

Lines changed: 278 additions & 33 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ Rust source lowering
363363
runtime crate type surface
364364
source map JSON for generated Rust packages
365365
rustc diagnostic remapping through source maps
366+
core `.rssi` interface signatures
366367
```
367368

368369
The first milestone is not a production runtime.
@@ -372,7 +373,7 @@ The first milestone is a strong review-first checker that can validate the langu
372373
Current CLI surface:
373374

374375
```sh
375-
rss check [--json] <file.rss>
376+
rss check [--json] [--interface <file.rssi> ...] <file.rss>
376377
rss check --explain <code>
377378
rss fmt <file.rss>
378379
rss review [--json] --diff <old.rss> <new.rss>

core/collections/map.rssi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
struct Map<K, V>
2+
3+
pub fn Map.insert<K, V>(
4+
map: mut Map<K, V>,
5+
key: read K,
6+
value: read V,
7+
) -> Unit
8+
effects(retains(value))
9+

core/fs/file.rssi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
resource File
2+
3+
pub fn File.open(path: read Path) -> fresh File
4+
5+
pub fn File.open_read(path: read Path) -> fresh File
6+
7+
pub fn File.open_write(path: read Path) -> fresh File
8+
9+
pub fn File.read_all(file: mut File) -> fresh Bytes
10+
11+
pub fn File.write(file: mut File, data: read Bytes) -> Unit
12+

core/image/image.rssi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
struct Image
2+
3+
pub fn Image.load(path: read Path) -> fresh Image
4+
5+
pub fn Image.resize(image: mut Image, width: Int, height: Int) -> Unit
6+
7+
pub fn Image.save(image: read Image, path: read Path) -> Unit
8+
9+
pub fn Image.inspect(image: read Image) -> Unit
10+

core/json/json.rssi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
struct JsonValue
2+
3+
pub fn Json.parse(text: read String) -> Result<fresh JsonValue, JsonError>
4+
5+
pub fn Json.field_string(
6+
value: read JsonValue,
7+
name: read String,
8+
) -> Result<String, JsonError>
9+

core/resource/resource_pool.rssi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
mode: uses-local
2+
3+
struct ResourcePool<T: Resource>
4+
5+
pub fn ResourcePool.new<T: Resource>(
6+
create: read Closure,
7+
max_size: Int,
8+
) -> fresh ResourcePool<T>
9+
10+
pub fn ResourcePool.borrow<T: Resource>(pool: mut ResourcePool<T>) -> T
11+

src/analyzer.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@ pub fn analyze_source(file: &str, source: &str) -> Vec<Diagnostic> {
1313
let tokens = lex(file, source);
1414
let syntax_program = parse_source(file, source);
1515
let hir = Hir::from_syntax(&syntax_program);
16+
analyze_program(tokens, syntax_program, hir)
17+
}
18+
19+
pub fn analyze_source_with_interfaces(
20+
file: &str,
21+
source: &str,
22+
interfaces: &[(&str, &str)],
23+
) -> Vec<Diagnostic> {
24+
let tokens = lex(file, source);
25+
let syntax_program = parse_source(file, source);
26+
let interface_programs = interfaces
27+
.iter()
28+
.map(|(file, source)| parse_source(file, source))
29+
.collect::<Vec<_>>();
30+
let hir = Hir::from_syntax_with_interfaces(&syntax_program, &interface_programs);
31+
analyze_program(tokens, syntax_program, hir)
32+
}
33+
34+
fn analyze_program(
35+
tokens: Vec<Token>,
36+
syntax_program: crate::syntax::ast::Program,
37+
hir: Hir,
38+
) -> Vec<Diagnostic> {
1639
let mut analyzer = Analyzer {
1740
tokens: &tokens,
1841
syntax_program,

src/hir.rs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -316,44 +316,63 @@ pub struct Hir {
316316

317317
impl Hir {
318318
pub fn from_syntax(program: &SyntaxProgram) -> Self {
319+
Self::from_syntax_with_interfaces(program, &[])
320+
}
321+
322+
pub fn from_syntax_with_interfaces(
323+
program: &SyntaxProgram,
324+
interfaces: &[SyntaxProgram],
325+
) -> Self {
319326
let mut hir = Self::default();
320327
hir.insert_builtins();
321-
let mut type_symbols = HashMap::new();
322-
let mut callable_symbols = HashMap::new();
328+
let mut type_symbols: HashMap<String, (DuplicateSymbolKind, Span)> = HashMap::new();
329+
let mut callable_symbols: HashMap<String, (DuplicateSymbolKind, Span)> = HashMap::new();
330+
for interface in interfaces {
331+
hir.collect_item_signatures(interface, &mut type_symbols, &mut callable_symbols);
332+
}
333+
hir.collect_item_signatures(program, &mut type_symbols, &mut callable_symbols);
334+
hir.collect_body_facts(program);
335+
hir
336+
}
337+
338+
fn collect_item_signatures(
339+
&mut self,
340+
program: &SyntaxProgram,
341+
type_symbols: &mut HashMap<String, (DuplicateSymbolKind, Span)>,
342+
callable_symbols: &mut HashMap<String, (DuplicateSymbolKind, Span)>,
343+
) {
323344
for item in &program.items {
324345
match item {
325346
Item::Function(function) => {
326347
record_duplicate_symbol(
327-
&mut hir.duplicate_symbols,
328-
&mut callable_symbols,
348+
&mut self.duplicate_symbols,
349+
callable_symbols,
329350
DuplicateSymbolKind::Function,
330351
&function.name,
331352
&function.span,
332353
);
333-
hir.insert_function(function_sig_from_decl(function));
354+
self.insert_function(function_sig_from_decl(function));
334355
}
335356
Item::Type(type_decl) => {
336-
record_duplicate_fields(&mut hir.duplicate_symbols, type_decl);
357+
record_duplicate_fields(&mut self.duplicate_symbols, type_decl);
337358
record_duplicate_symbol(
338-
&mut hir.duplicate_symbols,
339-
&mut type_symbols,
359+
&mut self.duplicate_symbols,
360+
type_symbols,
340361
DuplicateSymbolKind::Type,
341362
&type_decl.name,
342363
&type_decl.span,
343364
);
344365
record_duplicate_symbol(
345-
&mut hir.duplicate_symbols,
346-
&mut callable_symbols,
366+
&mut self.duplicate_symbols,
367+
callable_symbols,
347368
DuplicateSymbolKind::Constructor,
348369
&type_decl.name,
349370
&type_decl.span,
350371
);
351-
hir.insert_type(type_info_from_decl(type_decl));
372+
self.insert_type(type_info_from_decl(type_decl));
352373
}
353374
}
354375
}
355-
hir.collect_body_facts(program);
356-
hir
357376
}
358377

359378
pub fn resolve_function(&self, namespace: Option<&str>, name: &str) -> Option<&FunctionSig> {
@@ -1301,9 +1320,10 @@ fn callee_display(callee: &Callee) -> String {
13011320
}
13021321

13031322
fn function_sig_from_decl(function: &FunctionDecl) -> FunctionSig {
1323+
let (namespace, name) = split_function_name(&function.name);
13041324
FunctionSig {
1305-
namespace: None,
1306-
name: function.name.clone(),
1325+
namespace,
1326+
name,
13071327
is_async: function.is_async,
13081328
params: function.params.iter().map(param_sig_from_decl).collect(),
13091329
return_type: function.return_ty.as_ref().map(type_ref_name),
@@ -1320,6 +1340,14 @@ fn function_sig_from_decl(function: &FunctionDecl) -> FunctionSig {
13201340
}
13211341
}
13221342

1343+
fn split_function_name(name: &str) -> (Option<String>, String) {
1344+
if let Some((namespace, name)) = name.rsplit_once('.') {
1345+
(Some(namespace.to_string()), name.to_string())
1346+
} else {
1347+
(None, name.to_string())
1348+
}
1349+
}
1350+
13231351
fn type_ref_name(ty: &TypeRef) -> String {
13241352
if ty.args.is_empty() {
13251353
return ty.name.clone();

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod review;
77
mod rust_lower;
88
pub mod syntax;
99

10-
pub use analyzer::analyze_source;
10+
pub use analyzer::{analyze_source, analyze_source_with_interfaces};
1111
pub use diagnostic::{
1212
Diagnostic, DiagnosticExplanation, Severity, explain_diagnostic_code,
1313
format_diagnostic_explanation, format_diagnostics_human, format_diagnostics_json,

src/main.rs

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use std::process::ExitCode;
55
use std::time::{SystemTime, UNIX_EPOCH};
66

77
use rsscript::{
8-
Diagnostic, analyze_source, check_generated_rust_package, explain_diagnostic_code,
9-
format_diagnostic_explanation, format_diagnostics_human, format_diagnostics_json,
10-
format_review_human, format_review_json, format_review_map_human, format_review_map_json,
11-
lower_source_to_rust, lower_source_to_rust_package, parse_source_map_json,
12-
remap_rustc_diagnostic_json_lines, review_map_sources, review_sources,
8+
Diagnostic, analyze_source, analyze_source_with_interfaces, check_generated_rust_package,
9+
explain_diagnostic_code, format_diagnostic_explanation, format_diagnostics_human,
10+
format_diagnostics_json, format_review_human, format_review_json, format_review_map_human,
11+
format_review_map_json, lower_source_to_rust, lower_source_to_rust_package,
12+
parse_source_map_json, remap_rustc_diagnostic_json_lines, review_map_sources, review_sources,
1313
write_generated_rust_package,
1414
};
1515

@@ -44,8 +44,8 @@ fn run_check(args: &[String]) -> ExitCode {
4444
return ExitCode::SUCCESS;
4545
}
4646

47-
let (json, path) = parse_path_args(args);
48-
let Some(path) = path else {
47+
let options = parse_check_args(args);
48+
let Some(path) = options.path else {
4949
print_usage();
5050
return ExitCode::from(2);
5151
};
@@ -58,8 +58,23 @@ fn run_check(args: &[String]) -> ExitCode {
5858
}
5959
};
6060

61-
let diagnostics = analyze_source(path, &source);
62-
if json {
61+
let interfaces = match read_interface_sources(&options.interfaces) {
62+
Ok(interfaces) => interfaces,
63+
Err(error) => {
64+
eprintln!("{error}");
65+
return ExitCode::from(2);
66+
}
67+
};
68+
let interface_refs = interfaces
69+
.iter()
70+
.map(|interface| (interface.path.as_str(), interface.contents.as_str()))
71+
.collect::<Vec<_>>();
72+
let diagnostics = if interface_refs.is_empty() {
73+
analyze_source(path, &source)
74+
} else {
75+
analyze_source_with_interfaces(path, &source, &interface_refs)
76+
};
77+
if options.json {
6378
println!("{}", format_diagnostics_json(&diagnostics));
6479
} else if diagnostics.is_empty() {
6580
println!("{path}: ok");
@@ -376,6 +391,39 @@ struct LowerOptions<'a> {
376391
out_dir: Option<&'a str>,
377392
}
378393

394+
struct CheckOptions<'a> {
395+
json: bool,
396+
path: Option<&'a str>,
397+
interfaces: Vec<&'a str>,
398+
}
399+
400+
fn parse_check_args(args: &[String]) -> CheckOptions<'_> {
401+
let mut json = false;
402+
let mut path = None;
403+
let mut interfaces = Vec::new();
404+
let mut index = 0;
405+
406+
while let Some(arg) = args.get(index) {
407+
if arg == "--json" {
408+
json = true;
409+
} else if arg == "--interface" {
410+
index += 1;
411+
if let Some(interface) = args.get(index) {
412+
interfaces.push(interface.as_str());
413+
}
414+
} else if path.is_none() {
415+
path = Some(arg.as_str());
416+
}
417+
index += 1;
418+
}
419+
420+
CheckOptions {
421+
json,
422+
path,
423+
interfaces,
424+
}
425+
}
426+
379427
fn parse_lower_args(args: &[String]) -> LowerOptions<'_> {
380428
let mut emit_rust = false;
381429
let mut path = None;
@@ -401,6 +449,25 @@ fn parse_lower_args(args: &[String]) -> LowerOptions<'_> {
401449
}
402450
}
403451

452+
struct InterfaceSource {
453+
path: String,
454+
contents: String,
455+
}
456+
457+
fn read_interface_sources(paths: &[&str]) -> Result<Vec<InterfaceSource>, String> {
458+
paths
459+
.iter()
460+
.map(|path| {
461+
fs::read_to_string(path)
462+
.map(|contents| InterfaceSource {
463+
path: (*path).to_string(),
464+
contents,
465+
})
466+
.map_err(|error| format!("failed to read interface {path}: {error}"))
467+
})
468+
.collect()
469+
}
470+
404471
fn default_runtime_path() -> Result<PathBuf, String> {
405472
let path = env::current_dir()
406473
.map_err(|error| format!("failed to read current directory: {error}"))?
@@ -600,7 +667,7 @@ fn read_review_map_file(path: &Path) -> Result<ReviewMapSource, String> {
600667

601668
fn print_usage() {
602669
eprintln!("usage:");
603-
eprintln!(" rsscript check [--json] <file.rss>");
670+
eprintln!(" rsscript check [--json] [--interface <file.rssi> ...] <file.rss>");
604671
eprintln!(" rsscript check --explain <code>");
605672
eprintln!(" rsscript fmt <file.rss>");
606673
eprintln!(" rsscript lower --rust <file.rss>");

0 commit comments

Comments
 (0)