Old Fuzzer, new Sampler

This commit is contained in:
Riley-Kilgore 2024-12-17 06:01:19 -08:00 committed by Riley
parent c0fabcd26a
commit 84a0abeb0f
14 changed files with 164 additions and 127 deletions

View File

@ -258,7 +258,7 @@ fn str_to_keyword(word: &str) -> Option<Token> {
"or" => Some(Token::Or), "or" => Some(Token::Or),
"validator" => Some(Token::Validator), "validator" => Some(Token::Validator),
"via" => Some(Token::Via), "via" => Some(Token::Via),
"benchmark" => Some(Token::Benchmark), "bench" => Some(Token::Benchmark),
_ => None, _ => None,
} }
} }

View File

@ -8,7 +8,8 @@ pub const BOOL: &str = "Bool";
pub const BOOL_CONSTRUCTORS: &[&str] = &["False", "True"]; pub const BOOL_CONSTRUCTORS: &[&str] = &["False", "True"];
pub const BYTE_ARRAY: &str = "ByteArray"; pub const BYTE_ARRAY: &str = "ByteArray";
pub const DATA: &str = "Data"; pub const DATA: &str = "Data";
pub const GENERATOR: &str = "Generator"; pub const FUZZER: &str = "Fuzzer";
pub const SAMPLER: &str = "Sampler";
pub const G1_ELEMENT: &str = "G1Element"; pub const G1_ELEMENT: &str = "G1Element";
pub const G2_ELEMENT: &str = "G2Element"; pub const G2_ELEMENT: &str = "G2Element";
pub const INT: &str = "Int"; pub const INT: &str = "Int";
@ -179,7 +180,7 @@ impl Type {
}) })
} }
pub fn generator(c: Rc<Type>, a: Rc<Type>) -> Rc<Type> { pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
let prng_annotation = Annotation::Constructor { let prng_annotation = Annotation::Constructor {
location: Span::empty(), location: Span::empty(),
module: None, module: None,
@ -188,15 +189,15 @@ impl Type {
}; };
Rc::new(Type::Fn { Rc::new(Type::Fn {
args: vec![c, Type::prng()], args: vec![Type::prng()],
ret: Type::option(Type::tuple(vec![Type::prng(), a])), ret: Type::option(Type::tuple(vec![Type::prng(), a])),
alias: Some( alias: Some(
TypeAliasAnnotation { TypeAliasAnnotation {
alias: GENERATOR.to_string(), alias: FUZZER.to_string(),
parameters: vec!["c".to_string(), "a".to_string()], parameters: vec!["a".to_string()],
annotation: Annotation::Fn { annotation: Annotation::Fn {
location: Span::empty(), location: Span::empty(),
arguments: vec![Annotation::data(Span::empty()), prng_annotation.clone()], arguments: vec![prng_annotation.clone()],
ret: Annotation::Constructor { ret: Annotation::Constructor {
location: Span::empty(), location: Span::empty(),
module: None, module: None,
@ -220,6 +221,53 @@ impl Type {
}) })
} }
pub fn sampler(a: Rc<Type>) -> Rc<Type> {
let prng_annotation = Annotation::Constructor {
location: Span::empty(),
module: None,
name: PRNG.to_string(),
arguments: vec![],
};
Rc::new(Type::Fn {
args: vec![Type::int()],
ret: Type::fuzzer(a),
alias: Some(
TypeAliasAnnotation {
alias: SAMPLER.to_string(),
parameters: vec!["a".to_string()],
annotation: Annotation::Fn {
location: Span::empty(),
arguments: vec![Annotation::int(Span::empty())],
ret: Annotation::Fn {
location: Span::empty(),
arguments: vec![prng_annotation.clone()],
ret: Annotation::Constructor {
location: Span::empty(),
module: None,
name: OPTION.to_string(),
arguments: vec![Annotation::Tuple {
location: Span::empty(),
elems: vec![
prng_annotation,
Annotation::Var {
location: Span::empty(),
name: "a".to_string(),
},
],
}],
}
.into(),
}
.into(),
}
.into(),
}
.into(),
),
})
}
pub fn map(k: Rc<Type>, v: Rc<Type>) -> Rc<Type> { pub fn map(k: Rc<Type>, v: Rc<Type>) -> Rc<Type> {
Rc::new(Type::App { Rc::new(Type::App {
public: true, public: true,

View File

@ -477,23 +477,38 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {
), ),
); );
// Generator // Fuzzer
// //
// pub type Generator<c, a> = // pub type Fuzzer<a> =
// fn(Data, PRNG) -> Option<(PRNG, a)> // fn(PRNG) -> Option<(PRNG, a)>
let generator_context = Type::generic_var(id_gen.next()); let fuzzer_generic = Type::generic_var(id_gen.next());
let generator_value = Type::generic_var(id_gen.next());
prelude.types.insert( prelude.types.insert(
well_known::GENERATOR.to_string(), well_known::FUZZER.to_string(),
TypeConstructor { TypeConstructor {
location: Span::empty(), location: Span::empty(),
parameters: vec![generator_context.clone(), generator_value.clone()], parameters: vec![fuzzer_generic.clone()],
tipo: Type::generator(generator_context, generator_value), tipo: Type::fuzzer(fuzzer_generic),
module: "".to_string(), module: "".to_string(),
public: true, public: true,
}, },
); );
// Sampler
//
// pub type Sampler<a> =
// fn(Int) -> Fuzzer<a>
let sampler_generic = Type::generic_var(id_gen.next());
prelude.types.insert(
well_known::SAMPLER.to_string(),
TypeConstructor {
location: Span::empty(),
parameters: vec![sampler_generic.clone()],
tipo: Type::sampler(sampler_generic),
module: "".to_string(),
public: true,
}
);
prelude prelude
} }

View File

@ -135,7 +135,7 @@ mod tests {
fn def_benchmark() { fn def_benchmark() {
assert_definition!( assert_definition!(
r#" r#"
benchmark foo(x via fuzz.any_int) { bench foo(x via fuzz.any_int) {
True True
} }
"# "#
@ -146,7 +146,7 @@ mod tests {
fn def_invalid_benchmark() { fn def_invalid_benchmark() {
assert_definition!( assert_definition!(
r#" r#"
benchmark foo(x via f, y via g) { bench foo(x via f, y via g) {
True True
} }
"# "#
@ -157,7 +157,7 @@ mod tests {
fn def_benchmark_annotated_fuzzer() { fn def_benchmark_annotated_fuzzer() {
assert_definition!( assert_definition!(
r#" r#"
benchmark foo(x: Int via foo()) { bench foo(x: Int via foo()) {
True True
} }
"# "#

View File

@ -1,7 +1,7 @@
--- ---
source: crates/aiken-lang/src/parser/definition/benchmark.rs source: crates/aiken-lang/src/parser/definition/benchmark.rs
assertion_line: 136 assertion_line: 136
description: "Code:\n\nbenchmark foo(x via fuzz.any_int) {\n True\n}\n" description: "Code:\n\nbench foo(x via fuzz.any_int) {\n True\n}\n"
snapshot_kind: text snapshot_kind: text
--- ---
Benchmark( Benchmark(
@ -13,35 +13,35 @@ Benchmark(
Named { Named {
name: "x", name: "x",
label: "x", label: "x",
location: 14..15, location: 10..11,
}, },
), ),
location: 14..15, location: 10..11,
annotation: None, annotation: None,
doc: None, doc: None,
is_validator_param: false, is_validator_param: false,
}, },
via: FieldAccess { via: FieldAccess {
location: 20..32, location: 16..28,
label: "any_int", label: "any_int",
container: Var { container: Var {
location: 20..24, location: 16..20,
name: "fuzz", name: "fuzz",
}, },
}, },
}, },
], ],
body: Var { body: Var {
location: 40..44, location: 36..40,
name: "True", name: "True",
}, },
doc: None, doc: None,
location: 0..33, location: 0..29,
name: "foo", name: "foo",
public: false, public: false,
return_annotation: None, return_annotation: None,
return_type: (), return_type: (),
end_position: 45, end_position: 41,
on_test_failure: FailImmediately, on_test_failure: FailImmediately,
}, },
) )

View File

@ -1,7 +1,7 @@
--- ---
source: crates/aiken-lang/src/parser/definition/benchmark.rs source: crates/aiken-lang/src/parser/definition/benchmark.rs
assertion_line: 158 assertion_line: 158
description: "Code:\n\nbenchmark foo(x: Int via foo()) {\n True\n}\n" description: "Code:\n\nbench foo(x: Int via foo()) {\n True\n}\n"
snapshot_kind: text snapshot_kind: text
--- ---
Benchmark( Benchmark(
@ -13,13 +13,13 @@ Benchmark(
Named { Named {
name: "x", name: "x",
label: "x", label: "x",
location: 14..15, location: 10..11,
}, },
), ),
location: 14..20, location: 10..16,
annotation: Some( annotation: Some(
Constructor { Constructor {
location: 17..20, location: 13..16,
module: None, module: None,
name: "Int", name: "Int",
arguments: [], arguments: [],
@ -31,24 +31,24 @@ Benchmark(
via: Call { via: Call {
arguments: [], arguments: [],
fun: Var { fun: Var {
location: 25..28, location: 21..24,
name: "foo", name: "foo",
}, },
location: 25..30, location: 21..26,
}, },
}, },
], ],
body: Var { body: Var {
location: 38..42, location: 34..38,
name: "True", name: "True",
}, },
doc: None, doc: None,
location: 0..31, location: 0..27,
name: "foo", name: "foo",
public: false, public: false,
return_annotation: None, return_annotation: None,
return_type: (), return_type: (),
end_position: 43, end_position: 39,
on_test_failure: FailImmediately, on_test_failure: FailImmediately,
}, },
) )

View File

@ -1,7 +1,7 @@
--- ---
source: crates/aiken-lang/src/parser/definition/benchmark.rs source: crates/aiken-lang/src/parser/definition/benchmark.rs
assertion_line: 147 assertion_line: 147
description: "Code:\n\nbenchmark foo(x via f, y via g) {\n True\n}\n" description: "Code:\n\nbench foo(x via f, y via g) {\n True\n}\n"
snapshot_kind: text snapshot_kind: text
--- ---
Benchmark( Benchmark(
@ -13,16 +13,16 @@ Benchmark(
Named { Named {
name: "x", name: "x",
label: "x", label: "x",
location: 14..15, location: 10..11,
}, },
), ),
location: 14..15, location: 10..11,
annotation: None, annotation: None,
doc: None, doc: None,
is_validator_param: false, is_validator_param: false,
}, },
via: Var { via: Var {
location: 20..21, location: 16..17,
name: "f", name: "f",
}, },
}, },
@ -32,31 +32,31 @@ Benchmark(
Named { Named {
name: "y", name: "y",
label: "y", label: "y",
location: 23..24, location: 19..20,
}, },
), ),
location: 23..24, location: 19..20,
annotation: None, annotation: None,
doc: None, doc: None,
is_validator_param: false, is_validator_param: false,
}, },
via: Var { via: Var {
location: 29..30, location: 25..26,
name: "g", name: "g",
}, },
}, },
], ],
body: Var { body: Var {
location: 38..42, location: 34..38,
name: "True", name: "True",
}, },
doc: None, doc: None,
location: 0..31, location: 0..27,
name: "foo", name: "foo",
public: false, public: false,
return_annotation: None, return_annotation: None,
return_type: (), return_type: (),
end_position: 43, end_position: 39,
on_test_failure: FailImmediately, on_test_failure: FailImmediately,
}, },
) )

View File

@ -243,7 +243,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
"when" => Token::When, "when" => Token::When,
"validator" => Token::Validator, "validator" => Token::Validator,
"via" => Token::Via, "via" => Token::Via,
"benchmark" => Token::Benchmark, "bench" => Token::Benchmark,
_ => { _ => {
if s.chars().next().map_or(false, |c| c.is_uppercase()) { if s.chars().next().map_or(false, |c| c.is_uppercase()) {
Token::UpName { Token::UpName {

View File

@ -183,7 +183,7 @@ impl fmt::Display for Token {
Token::Once => "once", Token::Once => "once",
Token::Validator => "validator", Token::Validator => "validator",
Token::Via => "via", Token::Via => "via",
Token::Benchmark => "benchmark", Token::Benchmark => "bench",
}; };
write!(f, "{s}") write!(f, "{s}")
} }

View File

@ -332,14 +332,12 @@ impl PropertyTest {
) -> Result<Option<Counterexample<'a>>, FuzzerError> { ) -> Result<Option<Counterexample<'a>>, FuzzerError> {
let mut prng = initial_prng; let mut prng = initial_prng;
let mut counterexample = None; let mut counterexample = None;
let mut iteration = 0;
while *remaining > 0 && counterexample.is_none() { while *remaining > 0 && counterexample.is_none() {
let (next_prng, cex) = self.run_once(prng, labels, plutus_version, iteration)?; let (next_prng, cex) = self.run_once(prng, labels, plutus_version)?;
prng = next_prng; prng = next_prng;
counterexample = cex; counterexample = cex;
*remaining -= 1; *remaining -= 1;
iteration += 1;
} }
Ok(counterexample) Ok(counterexample)
@ -350,12 +348,11 @@ impl PropertyTest {
prng: Prng, prng: Prng,
labels: &mut BTreeMap<String, usize>, labels: &mut BTreeMap<String, usize>,
plutus_version: &'a PlutusVersion, plutus_version: &'a PlutusVersion,
iteration: usize,
) -> Result<(Prng, Option<Counterexample<'a>>), FuzzerError> { ) -> Result<(Prng, Option<Counterexample<'a>>), FuzzerError> {
use OnTestFailure::*; use OnTestFailure::*;
let (next_prng, value) = prng let (next_prng, value) = prng
.sample(&self.fuzzer.program, iteration)? .sample(&self.fuzzer.program)?
.expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors."); .expect("A seeded PRNG returned 'None' which indicates a fuzzer is ill-formed and implemented wrongly; please contact library's authors.");
let mut result = self.eval(&value, plutus_version); let mut result = self.eval(&value, plutus_version);
@ -386,8 +383,8 @@ impl PropertyTest {
value, value,
choices: next_prng.choices(), choices: next_prng.choices(),
cache: Cache::new(move |choices| { cache: Cache::new(move |choices| {
match Prng::from_choices(choices, iteration) match Prng::from_choices(choices)
.sample(&self.fuzzer.program, iteration) .sample(&self.fuzzer.program)
{ {
Err(..) => Status::Invalid, Err(..) => Status::Invalid,
Ok(None) => Status::Invalid, Ok(None) => Status::Invalid,
@ -454,7 +451,7 @@ pub struct Benchmark {
pub name: String, pub name: String,
pub on_test_failure: OnTestFailure, pub on_test_failure: OnTestFailure,
pub program: Program<Name>, pub program: Program<Name>,
pub fuzzer: Fuzzer<Name>, pub sampler: Fuzzer<Name>,
} }
unsafe impl Send for Benchmark {} unsafe impl Send for Benchmark {}
@ -467,11 +464,12 @@ impl Benchmark {
plutus_version: &PlutusVersion, plutus_version: &PlutusVersion,
) -> Vec<BenchmarkResult> { ) -> Vec<BenchmarkResult> {
let mut results = Vec::with_capacity(n); let mut results = Vec::with_capacity(n);
let mut remaining = n; let mut iteration = 0;
let mut prng = Prng::from_seed(seed); let mut prng = Prng::from_seed(seed);
while remaining > 0 { while n > iteration {
match prng.sample(&self.fuzzer.program, n - remaining) { let fuzzer = self.sampler.program.apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64)));
match prng.sample(&fuzzer) {
Ok(Some((new_prng, value))) => { Ok(Some((new_prng, value))) => {
prng = new_prng; prng = new_prng;
let mut eval_result = self.eval(&value, plutus_version); let mut eval_result = self.eval(&value, plutus_version);
@ -496,7 +494,7 @@ impl Benchmark {
break; break;
} }
} }
remaining -= 1; iteration += 1;
} }
results results
@ -534,12 +532,10 @@ pub enum Prng {
Seeded { Seeded {
choices: Vec<u8>, choices: Vec<u8>,
uplc: PlutusData, uplc: PlutusData,
iteration: usize,
}, },
Replayed { Replayed {
choices: Vec<u8>, choices: Vec<u8>,
uplc: PlutusData, uplc: PlutusData,
iteration: usize,
}, },
} }
@ -588,12 +584,11 @@ impl Prng {
Data::bytestring(vec![]), // Random choices Data::bytestring(vec![]), // Random choices
], ],
), ),
iteration: 0,
} }
} }
/// Construct a Pseudo-random number generator from a pre-defined list of choices. /// Construct a Pseudo-random number generator from a pre-defined list of choices.
pub fn from_choices(choices: &[u8], iteration: usize) -> Prng { pub fn from_choices(choices: &[u8]) -> Prng {
Prng::Replayed { Prng::Replayed {
uplc: Data::constr( uplc: Data::constr(
Prng::REPLAYED, Prng::REPLAYED,
@ -603,7 +598,6 @@ impl Prng {
], ],
), ),
choices: choices.to_vec(), choices: choices.to_vec(),
iteration,
} }
} }
@ -611,11 +605,10 @@ impl Prng {
pub fn sample( pub fn sample(
&self, &self,
fuzzer: &Program<Name>, fuzzer: &Program<Name>,
iteration: usize, // iteration: usize,
) -> Result<Option<(Prng, PlutusData)>, FuzzerError> { ) -> Result<Option<(Prng, PlutusData)>, FuzzerError> {
let program = Program::<NamedDeBruijn>::try_from( let program = Program::<NamedDeBruijn>::try_from(
fuzzer fuzzer
.apply_data(Data::integer(num_bigint::BigInt::from(iteration as i64)))
.apply_data(self.uplc())).unwrap(); .apply_data(self.uplc())).unwrap();
let mut result = program.eval(ExBudget::max()); let mut result = program.eval(ExBudget::max());
result result
@ -624,7 +617,7 @@ impl Prng {
traces: result.logs(), traces: result.logs(),
uplc_error, uplc_error,
}) })
.map(|term| Prng::from_result(term, iteration)) .map(|term| Prng::from_result(term))
} }
/// Obtain a Prng back from a fuzzer execution. As a reminder, fuzzers have the following /// Obtain a Prng back from a fuzzer execution. As a reminder, fuzzers have the following
@ -639,10 +632,9 @@ impl Prng {
/// aborted altogether with 'None'. /// aborted altogether with 'None'.
pub fn from_result( pub fn from_result(
result: Term<NamedDeBruijn>, result: Term<NamedDeBruijn>,
iteration: usize,
) -> Option<(Self, PlutusData)> { ) -> Option<(Self, PlutusData)> {
/// Interpret the given 'PlutusData' as one of two Prng constructors. /// Interpret the given 'PlutusData' as one of two Prng constructors.
fn as_prng(cst: &PlutusData, iteration: usize) -> Prng { fn as_prng(cst: &PlutusData) -> Prng {
if let PlutusData::Constr(Constr { tag, fields, .. }) = cst { if let PlutusData::Constr(Constr { tag, fields, .. }) = cst {
if *tag == 121 + Prng::SEEDED { if *tag == 121 + Prng::SEEDED {
if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] = if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] =
@ -659,7 +651,6 @@ impl Prng {
PlutusData::BoundedBytes(vec![].into()), PlutusData::BoundedBytes(vec![].into()),
], ],
), ),
iteration,
}; };
} }
} }
@ -670,7 +661,6 @@ impl Prng {
return Prng::Replayed { return Prng::Replayed {
choices: choices.to_vec(), choices: choices.to_vec(),
uplc: cst.clone(), uplc: cst.clone(),
iteration,
}; };
} }
} }
@ -684,7 +674,7 @@ impl Prng {
if *tag == 121 + Prng::SOME { if *tag == 121 + Prng::SOME {
if let [PlutusData::Array(elems)] = &fields[..] { if let [PlutusData::Array(elems)] = &fields[..] {
if let [new_seed, value] = &elems[..] { if let [new_seed, value] = &elems[..] {
return Some((as_prng(new_seed, iteration), value.clone())); return Some((as_prng(new_seed), value.clone()));
} }
} }
} }

View File

@ -1773,17 +1773,27 @@ fn pipe_wrong_arity_fully_saturated_return_fn() {
#[test] #[test]
fn fuzzer_ok_basic() { fn fuzzer_ok_basic() {
let source_code = r#" let source_code = r#"
fn int() -> Generator<Void, Int> { todo } fn int() -> Fuzzer<Int> { todo }
test prop(n via int()) { True } test prop(n via int()) { True }
"#; "#;
assert!(check(parse(source_code)).is_ok()); assert!(check(parse(source_code)).is_ok());
} }
#[test]
fn sampler_ok_basic() {
let source_code = r#"
fn int() -> Sampler<Int> { todo }
bench prop(n via int()) { True }
"#;
assert!(check(parse(source_code)).is_ok());
}
#[test] #[test]
fn fuzzer_ok_explicit() { fn fuzzer_ok_explicit() {
let source_code = r#" let source_code = r#"
fn int(void: Void, prng: PRNG) -> Option<(PRNG, Int)> { todo } fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo }
test prop(n via int) { Void } test prop(n via int) { Void }
"#; "#;
@ -1793,8 +1803,8 @@ fn fuzzer_ok_explicit() {
#[test] #[test]
fn fuzzer_ok_list() { fn fuzzer_ok_list() {
let source_code = r#" let source_code = r#"
fn int() -> Generator<Void, Int> { todo } fn int() -> Fuzzer<Int> { todo }
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo } fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
test prop(xs via list(int())) { True } test prop(xs via list(int())) { True }
"#; "#;
@ -1805,8 +1815,8 @@ fn fuzzer_ok_list() {
#[test] #[test]
fn fuzzer_err_unbound() { fn fuzzer_err_unbound() {
let source_code = r#" let source_code = r#"
fn any() -> Generator<Void, a> { todo } fn any() -> Fuzzer<a> { todo }
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo } fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
test prop(xs via list(any())) { todo } test prop(xs via list(any())) { todo }
"#; "#;
@ -1838,7 +1848,7 @@ fn fuzzer_err_unify_1() {
#[test] #[test]
fn fuzzer_err_unify_2() { fn fuzzer_err_unify_2() {
let source_code = r#" let source_code = r#"
fn any() -> Generator<Void, a> { todo } fn any() -> Fuzzer<a> { todo }
test prop(xs via any) { todo } test prop(xs via any) { todo }
"#; "#;
@ -1857,8 +1867,8 @@ fn fuzzer_err_unify_2() {
#[test] #[test]
fn fuzzer_err_unify_3() { fn fuzzer_err_unify_3() {
let source_code = r#" let source_code = r#"
fn list(a: Generator<Void, a>) -> Generator<Void, List<a>> { todo } fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
fn int() -> Generator<Void, Int> { todo } fn int() -> Fuzzer<Int> { todo }
test prop(xs: Int via list(int())) { todo } test prop(xs: Int via list(int())) { todo }
"#; "#;

View File

@ -838,8 +838,7 @@ fn infer_fuzzer(
) -> Result<(Annotation, Rc<Type>), Error> { ) -> Result<(Annotation, Rc<Type>), Error> {
let could_not_unify = || Error::CouldNotUnify { let could_not_unify = || Error::CouldNotUnify {
location: *location, location: *location,
expected: Type::generator( expected: Type::fuzzer(
Type::void(),
expected_inner_type expected_inner_type
.clone() .clone()
.unwrap_or_else(|| Type::generic_var(0)), .unwrap_or_else(|| Type::generic_var(0)),
@ -863,7 +862,7 @@ fn infer_fuzzer(
contains_opaque: _, contains_opaque: _,
alias: _, alias: _,
} if module.is_empty() && name == "Option" && args.len() == 1 => { } if module.is_empty() && name == "Option" && args.len() == 1 => {
match args.first().expect("args.len() == 2 && args[0].is_void()").borrow() { match args.first().expect("args.len() == 1").borrow() {
Type::Tuple { elems, .. } if elems.len() == 2 => { Type::Tuple { elems, .. } if elems.len() == 2 => {
let wrapped = elems.get(1).expect("Tuple has two elements"); let wrapped = elems.get(1).expect("Tuple has two elements");
@ -878,7 +877,7 @@ fn infer_fuzzer(
// `unify` now that we have figured out the type carried by the fuzzer. // `unify` now that we have figured out the type carried by the fuzzer.
environment.unify( environment.unify(
tipo.clone(), tipo.clone(),
Type::generator(Type::void(), wrapped.clone()), Type::fuzzer(wrapped.clone()),
*location, *location,
false, false,
)?; )?;
@ -916,8 +915,7 @@ fn infer_sampler(
) -> Result<(Annotation, Rc<Type>), Error> { ) -> Result<(Annotation, Rc<Type>), Error> {
let could_not_unify = || Error::CouldNotUnify { let could_not_unify = || Error::CouldNotUnify {
location: *location, location: *location,
expected: Type::generator( expected: Type::sampler(
Type::int(),
expected_inner_type expected_inner_type
.clone() .clone()
.unwrap_or_else(|| Type::generic_var(0)), .unwrap_or_else(|| Type::generic_var(0)),
@ -930,34 +928,12 @@ fn infer_sampler(
match tipo.borrow() { match tipo.borrow() {
Type::Fn { Type::Fn {
ret, ret,
args: _,
alias: _,
} => match ret.borrow() {
Type::App {
module,
name,
args, args,
public: _,
contains_opaque: _,
alias: _, alias: _,
} if module.is_empty() && name == "Option" && args.len() == 1 => { } => if args.len() == 1 && args[0].is_int() {
match args.first().expect("args.len() == 2 && args[0].is_int()").borrow() { infer_fuzzer(environment, expected_inner_type, ret, &Span::empty())
Type::Tuple { elems, .. } if elems.len() == 2 => { } else {
let wrapped = elems.get(1).expect("Tuple has two elements"); Err(could_not_unify())
environment.unify(
tipo.clone(),
Type::generator(Type::int(), wrapped.clone()),
*location,
false,
)?;
Ok((annotate_fuzzer(wrapped, location)?, wrapped.clone()))
}
_ => Err(could_not_unify()),
}
}
_ => Err(could_not_unify()),
}, },
Type::Var { tipo, alias } => match &*tipo.deref().borrow() { Type::Var { tipo, alias } => match &*tipo.deref().borrow() {

View File

@ -193,7 +193,7 @@ impl Error {
test.input_path.to_path_buf(), test.input_path.to_path_buf(),
test.program.to_pretty(), test.program.to_pretty(),
), ),
TestResult::Benchmark(_) => ("benchmark".to_string(), PathBuf::new(), String::new()), // todo TestResult::Benchmark(_) => ("bench".to_string(), PathBuf::new(), String::new()), // todo
}; };
Error::TestFailure { Error::TestFailure {

View File

@ -112,10 +112,8 @@ mod test {
const max_int: Int = 255 const max_int: Int = 255
pub type Fuzzer<a> = Generator<Void, a>
pub fn int() -> Fuzzer<Int> { pub fn int() -> Fuzzer<Int> {
fn(v: Void, prng: PRNG) -> Option<(PRNG, Int)> { fn(prng: PRNG) -> Option<(PRNG, Int)> {
when prng is { when prng is {
Seeded { seed, choices } -> { Seeded { seed, choices } -> {
let choice = let choice =
@ -163,21 +161,21 @@ mod test {
} }
pub fn constant(a: a) -> Fuzzer<a> { pub fn constant(a: a) -> Fuzzer<a> {
fn(v, s0) { Some((s0, a)) } fn(s0) { Some((s0, a)) }
} }
pub fn and_then(fuzz_a: Fuzzer<a>, f: fn(a) -> Fuzzer<b>) -> Fuzzer<b> { pub fn and_then(fuzz_a: Fuzzer<a>, f: fn(a) -> Fuzzer<b>) -> Fuzzer<b> {
fn(v, s0) { fn(s0) {
when fuzz_a(v, s0) is { when fuzz_a(s0) is {
Some((s1, a)) -> f(a)(v, s1) Some((s1, a)) -> f(a)(s1)
None -> None None -> None
} }
} }
} }
pub fn map(fuzz_a: Fuzzer<a>, f: fn(a) -> b) -> Fuzzer<b> { pub fn map(fuzz_a: Fuzzer<a>, f: fn(a) -> b) -> Fuzzer<b> {
fn(v, s0) { fn(s0) {
when fuzz_a(v, s0) is { when fuzz_a(s0) is {
Some((s1, a)) -> Some((s1, f(a))) Some((s1, a)) -> Some((s1, f(a)))
None -> None None -> None
} }
@ -185,10 +183,10 @@ mod test {
} }
pub fn map2(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<b>, f: fn(a, b) -> c) -> Fuzzer<c> { pub fn map2(fuzz_a: Fuzzer<a>, fuzz_b: Fuzzer<b>, f: fn(a, b) -> c) -> Fuzzer<c> {
fn(v, s0) { fn(s0) {
when fuzz_a(v, s0) is { when fuzz_a(s0) is {
Some((s1, a)) -> Some((s1, a)) ->
when fuzz_b(Void, s1) is { when fuzz_b(s1) is {
Some((s2, b)) -> Some((s2, f(a, b))) Some((s2, b)) -> Some((s2, f(a, b)))
None -> None None -> None
} }