Skip to content

Commit fef4ab4

Browse files
author
ffonion
committed
fix(compiler): preserve declared resource schemas across branches
1 parent 173c7ce commit fef4ab4

3 files changed

Lines changed: 102 additions & 3 deletions

File tree

src/compiler/typing/state.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,35 @@ impl LocalTypeState {
361361
if merged != BoundType::Unknown {
362362
self.by_slot.insert(slot, merged);
363363
}
364-
if lhs.schema(slot) == rhs.schema(slot)
365-
&& let Some(schema) = lhs.schema(slot).cloned()
364+
let lhs_schema = lhs.schema(slot);
365+
let rhs_schema = rhs.schema(slot);
366+
let one_sided_declared_resource =
367+
match (lhs.has_declared_schema(slot), rhs.has_declared_schema(slot)) {
368+
(true, false) => lhs_schema
369+
.filter(|schema| schema.contains_resource())
370+
.cloned(),
371+
(false, true) => rhs_schema
372+
.filter(|schema| schema.contains_resource())
373+
.cloned(),
374+
_ => None,
375+
};
376+
let declared_schema = if lhs.has_declared_schema(slot)
377+
&& rhs.has_declared_schema(slot)
378+
&& lhs_schema == rhs_schema
366379
{
380+
lhs_schema.cloned()
381+
} else {
382+
one_sided_declared_resource
383+
};
384+
let merged_schema = if lhs_schema == rhs_schema {
385+
lhs_schema.cloned()
386+
} else {
387+
declared_schema.clone()
388+
};
389+
if let Some(schema) = merged_schema {
367390
self.schemas.insert(slot, schema);
368391
}
369-
if lhs.has_declared_schema(slot) && rhs.has_declared_schema(slot) {
392+
if declared_schema.is_some() {
370393
self.declared_schema_slots.insert(slot);
371394
}
372395
if lhs.is_optional(slot) || rhs.is_optional(slot) {
@@ -443,3 +466,25 @@ pub(crate) struct HostCallableSignature {
443466
#[cfg_attr(not(feature = "runtime"), allow(dead_code))]
444467
pub(crate) runtime_builtin: bool,
445468
}
469+
470+
#[cfg(test)]
471+
mod tests {
472+
use crate::host_api::ResourceTypeKey;
473+
474+
use super::*;
475+
476+
#[test]
477+
fn branch_merge_preserves_one_sided_declared_resource_schema() {
478+
let slot = 7;
479+
let schema = TypeSchema::Resource(ResourceTypeKey::new("sqlite.connection").unwrap());
480+
let mut declared_branch = LocalTypeState::default();
481+
declared_branch.set_with_schema_origin(slot, BoundType::Int, Some(schema.clone()), true);
482+
let empty_branch = LocalTypeState::default();
483+
484+
let mut merged = LocalTypeState::default();
485+
merged.merge_from_branches(&declared_branch, &empty_branch);
486+
487+
assert_eq!(merged.schema(slot), Some(&schema));
488+
assert!(merged.has_declared_schema(slot));
489+
}
490+
}

tests/host_resource_type_inference_tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,10 @@ use sqlite;
695695
fn query(db: resource<sqlite.connection>) -> int {
696696
sqlite::query(&db, "SELECT 1")
697697
}
698+
fn forwarded(db: resource<sqlite.connection>) -> int {
699+
query(&db);
700+
query(&db)
701+
}
698702
"#,
699703
CompileSourceFileOptions::default().with_host_api_catalog(test_catalog()),
700704
)

tests/semantic_model_exact_tests.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,56 @@ fn analyze_modules(root: &str, overrides: &[(&str, &str)]) -> SemanticModel {
116116
model
117117
}
118118

119+
#[test]
120+
fn declared_resource_schema_survives_cross_module_parameter_forwarding() {
121+
let root = r#"
122+
use io;
123+
use outer;
124+
let db = io::open("db");
125+
outer::run(db, true);
126+
"#;
127+
let outer = r#"
128+
use inner;
129+
pub fn run(db: resource<db.session>, use_first: bool) -> string {
130+
let mut failed: bool = false;
131+
if use_first {
132+
if !inner::session_exists(&db) {
133+
failed = true;
134+
}
135+
}
136+
if failed == false {
137+
if use_first && !inner::run_exists(&db) {
138+
failed = true;
139+
}
140+
}
141+
if failed == false {
142+
inner::read(&db)
143+
} else {
144+
"missing"
145+
}
146+
}
147+
"#;
148+
let inner = r#"
149+
use io;
150+
pub fn session_exists(db: resource<db.session>) -> bool {
151+
io::read(&db) != ""
152+
}
153+
pub fn run_exists(db: resource<db.session>) -> bool {
154+
io::read(&db) != ""
155+
}
156+
pub fn read(db: resource<db.session>) -> string {
157+
io::read(&db)
158+
}
159+
"#;
160+
161+
let model = analyze_modules(root, &[("outer", outer), ("inner", inner)]);
162+
assert!(
163+
model.diagnostics().is_empty(),
164+
"resource schema must survive module linking and forwarding: {:?}",
165+
model.diagnostics()
166+
);
167+
}
168+
119169
/// The completion labels at a position in the given source, in order.
120170
fn labels(model: &SemanticModel, offset: usize) -> Vec<String> {
121171
model

0 commit comments

Comments
 (0)