Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,156 @@
|
||||
use super::checked;
|
||||
use dever::compiler::{check, model::Program};
|
||||
use dever::syntax::{diagnostic::Diagnostic, source::Source};
|
||||
|
||||
pub(super) fn packages(files: &[(&str, &str)]) -> Result<Program, Diagnostic> {
|
||||
let mut modules = Vec::new();
|
||||
let mut identities = Vec::new();
|
||||
for (index, (identity, text)) in files.iter().enumerate() {
|
||||
let source = Source::new(index, format!("{identity}.dever"), (*text).into());
|
||||
modules.push(dever::syntax::parse(&source)?);
|
||||
identities.push((*identity).into());
|
||||
}
|
||||
check::check(modules, &identities)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_signatures_cannot_publish_private_types() {
|
||||
for declaration in [
|
||||
"public echo(value: Hidden) (answer: Hidden) { answer = value }",
|
||||
"public echo(value: List<Hidden?>) (answer: List<Hidden?>) { answer = value }",
|
||||
"public echo(value: Map<Text, List<Hidden>>) (answer: Map<Text, List<Hidden>>) { answer = value }",
|
||||
"public echo(value: MapEntry<Text, Hidden>) (answer: MapEntry<Text, Hidden>) { answer = value }",
|
||||
"public apply(action: handler(value: Hidden) (answer: Hidden)) () {}",
|
||||
"public type Visible { nested: Hidden }",
|
||||
"public type Visible { Wrapped(value: Hidden) }",
|
||||
] {
|
||||
let text = format!("type Hidden {{ value: Int }}\n{declaration}");
|
||||
assert_eq!(checked(&text).unwrap_err().code, "C006", "{text}");
|
||||
}
|
||||
assert!(checked("public type Visible { value: Int }\npublic echo(value: List<Visible?>) (answer: List<Visible?>) { answer = value }").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_failures_cannot_leak_private_error_identity() {
|
||||
for call in ["fail(Hidden.Failed(\"private\"))", "hidden()"] {
|
||||
let text = format!(
|
||||
"type Hidden {{ error Failed(message: Text) }}\nhidden() () {{ fail(Hidden.Failed(\"private\")) }}\npublic exposed() () {{ {call} }}"
|
||||
);
|
||||
let error = checked(&text).unwrap_err();
|
||||
assert_eq!(error.code, "C006", "{error:?}");
|
||||
}
|
||||
assert!(checked("public type Problem { error Failed(message: Text) }\nprivateFailure() () { fail(Problem.Failed(\"public\")) }\npublic exposed() () { privateFailure() }").is_ok());
|
||||
assert_eq!(
|
||||
checked("public apply(action: handler(value: Map<Float, Int>) ()) () {}")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C005"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_cycles_are_rejected_without_rejecting_static_callbacks() {
|
||||
for files in [
|
||||
[
|
||||
(
|
||||
"left",
|
||||
"public type A { value: Int }\npublic make() () { local: right.B? = null }",
|
||||
),
|
||||
(
|
||||
"right",
|
||||
"public type B { value: Int }\npublic make() () { local: left.A? = null }",
|
||||
),
|
||||
],
|
||||
[
|
||||
(
|
||||
"left",
|
||||
"public first() (answer: Int) { answer = right.second() }\npublic last() (answer: Int) { answer = 1 }",
|
||||
),
|
||||
(
|
||||
"right",
|
||||
"public second() (answer: Int) { answer = left.last() }",
|
||||
),
|
||||
],
|
||||
[
|
||||
(
|
||||
"left",
|
||||
"public type A { value: Int }\npublic fromRight(value: right.B) () {}",
|
||||
),
|
||||
(
|
||||
"right",
|
||||
"public type B { value: Int }\npublic fromLeft(value: left.A) () {}",
|
||||
),
|
||||
],
|
||||
[
|
||||
(
|
||||
"left",
|
||||
"public type A { value: Int }\npublic make() () { local = right.B { value = 1 } }",
|
||||
),
|
||||
(
|
||||
"right",
|
||||
"public type B { value: Int }\npublic make() () { local = left.A { value = 1 } }",
|
||||
),
|
||||
],
|
||||
] {
|
||||
assert_eq!(packages(&files).unwrap_err().code, "C007");
|
||||
}
|
||||
let files = [
|
||||
(
|
||||
"client",
|
||||
"privateDouble(value: Int) (answer: Int) { answer = value * 2 }\nmain() (answer: Int) { answer = helper.apply(privateDouble, 4) }",
|
||||
),
|
||||
(
|
||||
"helper",
|
||||
"public apply(action: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = action(value) }",
|
||||
),
|
||||
];
|
||||
assert!(packages(&files).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_package_names_and_callbacks_obey_visibility() {
|
||||
let helper = "type Hidden { value: Int }\nprivateRead(value: Int) (answer: Int) { answer = value }\npublic type Visible { value: Int }\npublic read(value: Int) (answer: Int) { answer = privateRead(value) }";
|
||||
for caller in [
|
||||
"main() (answer: Int) { answer = helper.privateRead(1) }",
|
||||
"main() (answer: List<Int>) { answer = each(helper.privateRead, [1]) }",
|
||||
"main(value: helper.Hidden) () {}",
|
||||
"main() () { value = helper.Hidden { value = 1 } }",
|
||||
] {
|
||||
assert_eq!(
|
||||
packages(&[("main", caller), ("helper", helper)])
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C006"
|
||||
);
|
||||
}
|
||||
assert!(packages(&[("main", "main() (answer: helper.Visible) { answer = helper.Visible { value = helper.read(3) } }"), ("helper", helper)]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_fields_reject_foreign_reads_writes_and_construction() {
|
||||
let vault = "public type Session { private key: Text\nlabel: Text }\npublic type Envelope { value: Session }";
|
||||
for caller in [
|
||||
"read(value: vault.Session) (answer: Text) { answer = value.key }",
|
||||
"write(value: vault.Session) () { value.key = \"changed\" }",
|
||||
"make() (answer: vault.Session) { answer = vault.Session { key = \"hidden\", label = \"name\" } }",
|
||||
"make() (answer: vault.Session) { answer = vault.Session { label = \"name\" } }",
|
||||
"read(value: vault.Envelope) (answer: Text) { answer = value.value.key }",
|
||||
"write(value: vault.Envelope) () { value.value.key = \"changed\" }",
|
||||
] {
|
||||
let error = packages(&[("main", caller), ("vault", vault)]).unwrap_err();
|
||||
assert_eq!(error.code, "C006", "{caller}: {error:?}");
|
||||
assert!(error.message.contains("field is private"), "{error:?}");
|
||||
}
|
||||
assert!(packages(&[("main", "rename(value: vault.Session) (answer: vault.Session) { value.label = \"new\"\nanswer = value }"), ("vault", vault)]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_record_fields_can_hide_types_but_choice_payloads_cannot() {
|
||||
assert!(checked("type Hidden { value: Int }\npublic type Visible { private hidden: Hidden\nlabel: Text }\npublic make() (answer: Visible) { answer = Visible { hidden = Hidden { value = 7 }, label = \"name\" } }\npublic read(value: Visible) (answer: Int) { answer = value.hidden.value }").is_ok());
|
||||
assert_eq!(
|
||||
checked("public type Choice { Ready(private value: Int) }")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C005"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user