Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions crates/psrs-driver/src/tests/data_tuple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! The library's `Data.Tuple` surface.
//!
//! `Tuple a b` is the closed record `{ _1 :: a, _2 :: b }` that FE-06 already
//! lowers a tuple to, not an algebraic product. These tests check that the
//! synonym, the tuple syntax, and a record literal are one value, and that the
//! projection functions execute under Wasmtime.

use super::*;

#[test]
fn a_tuple_is_the_closed_record_and_executes_under_wasmtime() {
let source = r#"
module Main where

import Prelude
import Data.Tuple (Tuple, curry, fst, snd, swap, uncurry)

fromSyntax :: Tuple Int Int
fromSyntax = (40, 2)

fromRecord :: Tuple Int Int
fromRecord = { _1: 40, _2: 2 }

passed :: Boolean -> Int
passed flag = if flag then 1 else 0

main :: Int
main =
passed (uncurry (\a b -> a + b) fromSyntax == 42)
+ passed (uncurry (\a b -> a + b) fromRecord == 42)
+ passed (fst fromSyntax == 40)
+ passed (snd fromRecord == 2)
+ passed (fst (swap fromSyntax) == 2)
+ passed (snd (swap fromRecord) == 40)
+ passed (curry (\pair -> fst pair + snd pair) 20 22 == 42)
"#;
let Some(output) = run_with_wasmtime(source) else {
eprintln!("skipping: wasmtime is not installed");
return;
};
assert_eq!(
output.status.code(),
Some(7),
"tuple syntax and the record form must be the same value: {output:?}"
);
}
56 changes: 56 additions & 0 deletions crates/psrs-driver/src/tests/foldable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! The library's `Monoid` and `Foldable` classes.
//!
//! `foldMap` and `fold` are constrained by `Monoid`, so the two classes land
//! together. These tests run the instances, not a second traversal: `Array`
//! walks the compiler's index primitives, and `Maybe` and `Either` fold by
//! cases.

use super::*;

#[test]
fn monoid_identities_and_folds_run_under_wasmtime() {
let source = r#"
module Main where

import Prelude
import Effect.Console (log)
import Data.Monoid (mempty)
import Data.Foldable (class Foldable, foldl, foldr, foldMap)
import Data.Maybe (Maybe(..))
import Data.Either (Either(..))

data One a = One a

instance foldableOne :: Foldable One where
foldr f z (One x) = f x z
foldl f z (One x) = f z x
foldMap f (One x) = f x

checks :: Effect Unit
checks = do
log (foldr (\x acc -> x <> acc) ":" ["a", "b"])
log (foldl (\acc x -> acc <> x) ":" ["a", "b"])
log (foldMap (\x -> x <> "!") ["a", "b"])
log (if arrayLength (mempty :: Array Int) == 0 then "empty-array" else "empty-array-wrong")
log (if (mempty :: Unit) == unit then "empty-unit" else "empty-unit-wrong")
log mempty
log (foldr (\x acc -> x <> acc) "z" (Right "r" :: Either Int String))
log (foldr (\x acc -> x <> acc) "z" (Left 1 :: Either Int String))
log (foldl (\acc x -> acc <> x) "z" (Just "m"))
log (foldMap (\x -> x) (Nothing :: Maybe String))
log (foldMap (\x -> x) (One "one"))
pure unit

main = let ignored = runEffect checks in 0
"#;
let Some(output) = run_with_wasmtime(source) else {
eprintln!("skipping: wasmtime is not installed");
return;
};
assert_eq!(output.status.code(), Some(0), "{output:?}");
assert_eq!(
output.stdout,
"ab:\n:ab\na!b!\nempty-array\nempty-unit\n\nrz\nz\nzm\n\none\n".as_bytes(),
"monoid identities and folds must match the class instances: {output:?}"
);
}
3 changes: 3 additions & 0 deletions crates/psrs-driver/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ static WASM_ARTIFACT_COUNTER: AtomicU32 = AtomicU32::new(0);
mod assertions;
mod coercion;
mod data_function;
mod data_tuple;
mod deriving;
mod effect_arity;
mod effects;
mod foldable;
mod guard_coverage;
mod operators;
mod partial_application;
mod scalars;
mod semigroup;
mod show;

fn lower_source_to_mir(source: &str) -> psrs_backend::mir::Module {
let core = lower_source_to_core("Main.purs", source).expect("source should lower to Core");
Expand Down
8 changes: 8 additions & 0 deletions crates/psrs-driver/src/tests/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,18 @@ fn loads_the_standard_library_from_disk_in_trusted_order() {
"Prelude",
"Data.Function",
"Data.Semigroup",
"Data.Monoid",
"Data.Eq",
"Data.Ord",
"Data.Semiring",
"Data.Show",
"Effect",
"Effect.Console",
"Test.Assert",
"Data.Maybe",
"Data.Either",
"Data.Tuple",
"Data.Foldable",
"WASI.Resource",
"WASI.IO",
"WASI.Clock",
Expand All @@ -128,14 +132,18 @@ fn loads_the_standard_library_from_disk_in_trusted_order() {
path.ends_with("lib/Prelude.purs")
|| path.ends_with("lib/Data/Function.purs")
|| path.ends_with("lib/Data/Semigroup.purs")
|| path.ends_with("lib/Data/Monoid.purs")
|| path.ends_with("lib/Data/Eq.purs")
|| path.ends_with("lib/Data/Ord.purs")
|| path.ends_with("lib/Data/Semiring.purs")
|| path.ends_with("lib/Data/Show.purs")
|| path.ends_with("lib/Effect.purs")
|| path.ends_with("lib/Effect/Console.purs")
|| path.ends_with("lib/Test/Assert.purs")
|| path.ends_with("lib/Data/Maybe.purs")
|| path.ends_with("lib/Data/Either.purs")
|| path.ends_with("lib/Data/Tuple.purs")
|| path.ends_with("lib/Data/Foldable.purs")
|| path.ends_with("lib/WASI/Resource.purs")
|| path.ends_with("lib/WASI/IO.purs")
|| path.ends_with("lib/WASI/Clock.purs")
Expand Down
112 changes: 112 additions & 0 deletions crates/psrs-driver/src/tests/show.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! The library's `Show` class.
//!
//! These tests execute under Wasmtime. The strings are the official `Show`
//! spellings: `true`/`false`, decimal `Int`, `unit`, quoted `Char` and
//! `String` (with the official escapes), arrays without spaces, and `Number`
//! with a trailing `.0` on an integer token.

use super::*;

#[test]
fn show_renders_the_instances_the_corpus_prints() {
let source = r#"
module Main where

import Prelude
import Effect.Console (log)

checks :: Effect Unit
checks = do
log (show true)
log (show false)
log (show 0)
log (show 42)
log (show (0 - 7))
log (show ((0 - 2147483647) - 1))
log (show unit)
log (show "hi")
log (show "a\"b")
log (show "line\n")
log (show 'a')
log (show '\n')
log (show '\'')
log (show [1, 2, 3])
log (show [1.0, 2.0])
log (show 0.0)
log (show 1.0)
log (show (numberNeg 2.0))
log (show 1.5)
log (show 0.5)
log (show 0.25)
log (show 10000000000.0)
log (show 1.0e21)
log (show 1.0e-5)
log (show "é")
log (show "cafés")
pure unit

main = let ignored = runEffect checks in 0
"#;
let Some(output) = run_with_wasmtime(source) else {
eprintln!("skipping: wasmtime is not installed");
return;
};
assert_eq!(output.status.code(), Some(0), "{output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let expected = concat!(
"true\n",
"false\n",
"0\n",
"42\n",
"-7\n",
"-2147483648\n",
"unit\n",
"\"hi\"\n",
"\"a\\\"b\"\n",
"\"line\\n\"\n",
"'a'\n",
"'\\n'\n",
"'\\''\n",
"[1,2,3]\n",
"[1.0,2.0]\n",
"0.0\n",
"1.0\n",
"-2.0\n",
"1.5\n",
"0.5\n",
"0.25\n",
"10000000000.0\n",
"1e+21\n",
"0.00001\n",
"\"é\"\n",
"\"cafés\"\n",
);
assert_eq!(
stdout.as_ref(),
expected,
"stdout:\n{stdout}\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}

#[test]
fn a_type_without_show_is_rejected() {
let source = r#"
module Main where

import Prelude

data Box = Box

main :: String
main = show Box
"#;
let errors = check_program_types_lenient_with_prelude(&[("Main.purs", source)])
.expect_err("Box is not a Show");
assert!(
errors
.iter()
.any(|error| error.diagnostic.code == Some("NoInstanceFound")),
"expected NoInstanceFound, got {errors:?}"
);
}
Loading
Loading