Skip to content

Commit 1a412f4

Browse files
committed
Link package manager design in spec
1 parent f447be1 commit 1a412f4

3 files changed

Lines changed: 93 additions & 6 deletions

File tree

RSScript_v0.5_Spec.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,35 @@ Agent, GPU, HTTP, networking, and model-client packages are use-case libraries,
22692269

22702270
---
22712271

2272+
## 29.4 Package Manager Design
2273+
2274+
RSScript package management is specified separately from the core language syntax.
2275+
2276+
The package-manager direction is defined in:
2277+
2278+
```text
2279+
RSScript_Package_Manager_Design.md
2280+
```
2281+
2282+
See [RSScript Package Manager Design](RSScript_Package_Manager_Design.md).
2283+
2284+
That design is compatible with this v0.5 spec because it preserves the same implementation boundary:
2285+
2286+
```text
2287+
RSScript packages expose reviewable .rssi semantic contracts.
2288+
RSScript source lowers to Rust through the compiler pipeline.
2289+
Rust native wrappers are built by Cargo.
2290+
Cargo remains the Rust build and crate dependency substrate.
2291+
Package review metadata reports semantic risk instead of hiding it.
2292+
Unknown package risk is classified as unknown, not safe.
2293+
```
2294+
2295+
Package features declared in `rsspkg.toml` are package selection features. They are not the same as RSScript file features declared with `features:` at the top of a `.rss` or `.rssi` file.
2296+
2297+
If a package feature enables native code, unsafe code, build scripts, proc macros, linked libraries, or another advanced boundary, package review metadata must report that risk explicitly.
2298+
2299+
---
2300+
22722301
# 30. Native Core and FFI Boundary
22732302

22742303
v0.5 supports controlled native core boundaries.

src/syntax/parser.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ impl Parser<'_> {
5252
if let Some(item) = self.parse_type_decl() {
5353
items.push(Item::Type(item));
5454
}
55-
} else if self.at_ident("pub") || self.at_ident("async") || self.at_ident("fn") {
55+
} else if self.at_ident("pub")
56+
|| self.at_ident("async")
57+
|| self.at_ident("native")
58+
|| self.at_ident("fn")
59+
{
5660
if let Some(item) = self.parse_function_decl() {
5761
items.push(Item::Function(item));
5862
}
@@ -158,13 +162,17 @@ impl Parser<'_> {
158162
let span = self.current()?.span.clone();
159163
let mut is_public = false;
160164
let mut is_async = false;
161-
while self.at_ident("pub") || self.at_ident("async") {
165+
let mut is_native = false;
166+
while self.at_ident("pub") || self.at_ident("async") || self.at_ident("native") {
162167
if self.at_ident("pub") {
163168
is_public = true;
164169
}
165170
if self.at_ident("async") {
166171
is_async = true;
167172
}
173+
if self.at_ident("native") {
174+
is_native = true;
175+
}
168176
self.index += 1;
169177
}
170178
if !self.at_ident("fn") {
@@ -204,6 +212,13 @@ impl Parser<'_> {
204212
effects = parse_effects(self.tokens, open + 1, close);
205213
self.index = close + 1;
206214
}
215+
if is_native
216+
&& !effects
217+
.iter()
218+
.any(|effect| matches!(effect, EffectDecl::Name(name) if name == "native"))
219+
{
220+
effects.push(EffectDecl::Name("native".to_string()));
221+
}
207222

208223
let body = if self.at_symbol("{") {
209224
let open = self.index;
@@ -388,6 +403,7 @@ fn starts_top_level_item(tokens: &[Token], index: usize) -> bool {
388403
|| token.is_ident_text("fn")
389404
|| token.is_ident_text("pub")
390405
|| token.is_ident_text("async")
406+
|| token.is_ident_text("native")
391407
}
392408

393409
fn parse_params(tokens: &[Token], start: usize, end: usize) -> Vec<Param> {

tests/checker.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
33
use std::process::Command;
44
use std::time::{SystemTime, UNIX_EPOCH};
55

6-
use rsscript::syntax::ast::Item;
6+
use rsscript::syntax::ast::{EffectDecl, Item};
77
use rsscript::syntax::parse_source;
88
use rsscript::{
99
ReviewMapClassification, ReviewMapFileRisk, ReviewRisk, analyze_source,
@@ -1000,8 +1000,7 @@ fn copy(path: read Path) -> Result<Unit, FileError> {
10001000
#[test]
10011001
fn rust_lowering_maps_native_call_boundaries() {
10021002
let source = r#"
1003-
fn host_emit(message: read String) -> Unit
1004-
effects(native)
1003+
native fn host_emit(message: read String) -> Unit
10051004
10061005
pub fn run() -> Unit {
10071006
host_emit(message: read "host")
@@ -1016,8 +1015,8 @@ pub fn run() -> Unit {
10161015
.collect::<Vec<_>>();
10171016

10181017
assert_eq!(native_calls.len(), 2);
1018+
assert!(native_calls.iter().any(|entry| entry.source.line == 5));
10191019
assert!(native_calls.iter().any(|entry| entry.source.line == 6));
1020-
assert!(native_calls.iter().any(|entry| entry.source.line == 7));
10211020
}
10221021

10231022
#[test]
@@ -1605,6 +1604,29 @@ fn checksum(data: read Bytes) -> UInt64
16051604
}));
16061605
}
16071606

1607+
#[test]
1608+
fn review_reports_native_fn_as_native_boundary() {
1609+
let old_source = r#"
1610+
fn host_emit(message: read String) -> Unit
1611+
{
1612+
Log.write(message: read message)
1613+
}
1614+
"#;
1615+
let new_source = r#"
1616+
native fn host_emit(message: read String) -> Unit
1617+
"#;
1618+
1619+
let findings = review_sources("old.rss", old_source, "new.rss", new_source);
1620+
let unsafe_finding = findings
1621+
.iter()
1622+
.find(|finding| finding.code == "RSR012")
1623+
.expect("expected native boundary review finding");
1624+
1625+
assert_eq!(unsafe_finding.risk, ReviewRisk::Unsafe);
1626+
assert_eq!(unsafe_finding.before.as_deref(), Some("<none>"));
1627+
assert_eq!(unsafe_finding.after.as_deref(), Some("native"));
1628+
}
1629+
16081630
#[test]
16091631
fn review_reports_removed_guarantees() {
16101632
let old_source = r#"
@@ -1869,6 +1891,26 @@ async fn fetch(url: read Url) -> Result<fresh Bytes, NetworkError>
18691891
assert!(analyze_source("net.rssi", source).is_empty());
18701892
}
18711893

1894+
#[test]
1895+
fn parser_accepts_native_function_declaration() {
1896+
let source = r#"
1897+
native fn Host.emit(message: read String) -> Unit
1898+
"#;
1899+
let program = parse_source("host.rssi", source);
1900+
1901+
let Item::Function(function) = &program.items[0] else {
1902+
panic!("expected native function declaration");
1903+
};
1904+
assert_eq!(function.name, "Host.emit");
1905+
assert!(
1906+
function
1907+
.effects
1908+
.iter()
1909+
.any(|effect| matches!(effect, EffectDecl::Name(name) if name == "native"))
1910+
);
1911+
assert!(analyze_source("host.rssi", source).is_empty());
1912+
}
1913+
18721914
#[test]
18731915
fn checker_rejects_unknown_file_features() {
18741916
let source = r#"

0 commit comments

Comments
 (0)