From b694bc60967f6b661a2eb42657b8096d8142709e Mon Sep 17 00:00:00 2001 From: Gears Date: Wed, 1 Oct 2025 13:54:23 +0100 Subject: [PATCH] Replace phantom types with internal type system --- src/trick.gleam | 489 +++++++++++++++++++++++++++++------------- test/trick_test.gleam | 144 ++++++++++++- 2 files changed, 470 insertions(+), 163 deletions(-) diff --git a/src/trick.gleam b/src/trick.gleam index b651f6f..67fa80d 100644 --- a/src/trick.gleam +++ b/src/trick.gleam @@ -1,42 +1,173 @@ import glam/doc.{type Document} import gleam/bool +import gleam/dict.{type Dict} import gleam/float import gleam/int import gleam/list import gleam/option.{type Option, None, Some} +import gleam/result import lazy_const import splitter -pub opaque type Expression(type_) { - Expression(document: Document) +pub opaque type Expression { + Expression(compile: fn(State) -> Result(CompiledExpression, Error)) } -pub opaque type Statement(type_) { - Statement(document: Document) +pub type Error { + TypeMismatch(expected: Type, got: Type) +} + +type CompiledExpression { + Compiled(document: Document, type_: Type, state: State) +} + +pub type Type { + Custom(module: String, name: String, generics: List(Type)) + TypeVariable(id: Int) +} + +type State { + State(resolved_variables: Dict(Int, Type), type_variable_id: Int) +} + +const type_int = Custom("gleam", "Int", []) + +const type_float = Custom("gleam", "Float", []) + +const type_string = Custom("gleam", "String", []) + +const type_bool = Custom("gleam", "Bool", []) + +const type_nil = Custom("gleam", "Nil", []) + +fn type_list(element: Type) -> Type { + Custom("gleam", "List", [element]) +} + +fn type_variable(state: State) -> #(State, Type) { + let id = state.type_variable_id + let type_ = TypeVariable(id:) + let state = State(..state, type_variable_id: id + 1) + #(state, type_) +} + +fn compile( + state: State, + expression: Expression, + continue: fn(State, CompiledExpression) -> Result(a, Error), +) -> Result(a, Error) { + result.try(expression.compile(state), fn(compiled) { + continue(compiled.state, compiled) + }) +} + +fn compile_statement( + state: State, + statement: Statement, + continue: fn(State, CompiledExpression) -> Result(a, Error), +) -> Result(a, Error) { + result.try(statement.compile(state), fn(compiled) { + continue(compiled.state, compiled) + }) +} + +fn unify(state: State, a: Type, with b: Type) -> Result(#(State, Type), Error) { + use <- bool.guard(a == b, Ok(#(state, a))) + + case a, b { + TypeVariable(id:), other -> + unify_type_variable(state, id, other, VariableFirst) + other, TypeVariable(id:) -> unify_type_variable(state, id, other, TypeFirst) + _, _ -> Error(TypeMismatch(expected: b, got: a)) + } +} + +type Order { + VariableFirst + TypeFirst +} + +fn unify_type_variable( + state: State, + id: Int, + type_: Type, + order: Order, +) -> Result(#(State, Type), Error) { + case dict.get(state.resolved_variables, id) { + Ok(resolved_type) if order == VariableFirst -> + unify(state, resolved_type, type_) + Ok(resolved_type) -> unify(state, type_, resolved_type) + Error(_) -> { + let state = + State( + ..state, + resolved_variables: dict.insert(state.resolved_variables, id, type_), + ) + Ok(#(state, type_)) + } + } +} + +pub opaque type Statement { + Statement(compile: fn(State) -> Result(CompiledExpression, Error)) } const width = 80 const indent = 2 -pub fn expression_to_string(expression: Expression(any)) -> String { - doc.to_string(expression.document, width) +pub fn expression_to_string(expression: Expression) -> Result(String, Error) { + result.map(expression.compile(new_state()), fn(expression) { + doc.to_string(expression.document, width) + }) +} + +fn new_state() -> State { + State(dict.new(), 0) } -pub fn int(value: Int) -> Expression(Int) { - value |> int.to_string |> doc.from_string |> Expression +pub fn int(value: Int) -> Expression { + use state <- Expression + value + |> int.to_string + |> doc.from_string + |> Compiled(type_int, state) + |> Ok } -pub fn float(value: Float) -> Expression(Float) { - value |> float.to_string |> doc.from_string |> Expression +pub fn float(value: Float) -> Expression { + use state <- Expression + value + |> float.to_string + |> doc.from_string + |> Compiled(type_float, state) + |> Ok } -pub fn string(value: String) -> Expression(String) { - value |> escape_string_literal |> doc.from_string |> Expression +pub fn string(value: String) -> Expression { + use state <- Expression + value + |> escape_string_literal + |> doc.from_string + |> Compiled(type_string, state) + |> Ok } -pub fn bool(value: Bool) -> Expression(Bool) { - value |> bool.to_string |> doc.from_string |> Expression +pub fn bool(value: Bool) -> Expression { + use state <- Expression + value + |> bool.to_string + |> doc.from_string + |> Compiled(type_bool, state) + |> Ok +} + +pub fn nil() -> Expression { + use state <- Expression + "Nil" + |> doc.from_string + |> Compiled(type_nil, state) + |> Ok } fn escape_string_literal(string: String) -> String { @@ -65,164 +196,174 @@ fn escape_string_literal_loop( } fn binary_operator( - left: Expression(a), + left: Expression, + operator: String, + right: Expression, + operand_type: Type, + result_type: Type, +) -> Expression { + Expression(do_binary_operator( + _, + left, + operator, + right, + operand_type, + result_type, + )) +} + +fn do_binary_operator( + state: State, + left: Expression, operator: String, - right: Expression(a), -) -> Expression(b) { + right: Expression, + operand_type: Type, + result_type: Type, +) -> Result(CompiledExpression, Error) { + use state, left <- compile(state, left) + use state, right <- compile(state, right) + use #(state, _) <- result.try(unify(state, left.type_, operand_type)) + use #(state, _) <- result.try(unify(state, right.type_, operand_type)) + [left.document, doc.from_string(" " <> operator <> " "), right.document] |> doc.concat - |> Expression + |> Compiled(result_type, state) + |> Ok } -pub fn add(left: Expression(Int), right: Expression(Int)) -> Expression(Int) { - binary_operator(left, "+", right) +pub fn add(left: Expression, right: Expression) -> Expression { + binary_operator(left, "+", right, type_int, type_int) } -pub fn add_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "+.", right) +pub fn add_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, "+.", right, type_float, type_float) } -pub fn subtract( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, "-", right) +pub fn subtract(left: Expression, right: Expression) -> Expression { + binary_operator(left, "-", right, type_int, type_int) } -pub fn subtract_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "-.", right) +pub fn subtract_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, "-.", right, type_float, type_float) } -pub fn multiply( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, "*", right) +pub fn multiply(left: Expression, right: Expression) -> Expression { + binary_operator(left, "*", right, type_int, type_int) } -pub fn multiply_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "*.", right) +pub fn multiply_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, "*.", right, type_float, type_float) } -pub fn divide(left: Expression(Int), right: Expression(Int)) -> Expression(Int) { - binary_operator(left, "/", right) +pub fn divide(left: Expression, right: Expression) -> Expression { + binary_operator(left, "/", right, type_int, type_int) } -pub fn divide_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "/.", right) +pub fn divide_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, "/.", right, type_float, type_float) } -pub fn remainder( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, "%", right) +pub fn remainder(left: Expression, right: Expression) -> Expression { + binary_operator(left, "%", right, type_int, type_int) } -pub fn concatenate( - left: Expression(String), - right: Expression(String), -) -> Expression(String) { - binary_operator(left, "<>", right) +pub fn concatenate(left: Expression, right: Expression) -> Expression { + binary_operator(left, "<>", right, type_string, type_string) } -pub fn and(left: Expression(Bool), right: Expression(Bool)) -> Expression(Bool) { - binary_operator(left, "&&", right) +pub fn and(left: Expression, right: Expression) -> Expression { + binary_operator(left, "&&", right, type_bool, type_bool) } -pub fn or(left: Expression(Bool), right: Expression(Bool)) -> Expression(Bool) { - binary_operator(left, "||", right) +pub fn or(left: Expression, right: Expression) -> Expression { + binary_operator(left, "||", right, type_bool, type_bool) } -pub fn equal(left: Expression(a), right: Expression(a)) -> Expression(Bool) { - binary_operator(left, "==", right) +pub fn equal(left: Expression, right: Expression) -> Expression { + use state <- Expression + let #(state, type_) = type_variable(state) + do_binary_operator(state, left, "==", right, type_, type_bool) } -pub fn not_equal(left: Expression(a), right: Expression(a)) -> Expression(Bool) { - binary_operator(left, "!=", right) +pub fn not_equal(left: Expression, right: Expression) -> Expression { + use state <- Expression + let #(state, type_) = type_variable(state) + do_binary_operator(state, left, "!=", right, type_, type_bool) } -pub fn less_than( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, "<", right) +pub fn less_than(left: Expression, right: Expression) -> Expression { + binary_operator(left, "<", right, type_int, type_bool) } -pub fn less_than_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "<.", right) +pub fn less_than_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, "<.", right, type_float, type_bool) } -pub fn less_than_or_equal( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, "<=", right) +pub fn less_than_or_equal(left: Expression, right: Expression) -> Expression { + binary_operator(left, "<=", right, type_int, type_bool) } pub fn less_than_or_equal_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, "<=.", right) + left: Expression, + right: Expression, +) -> Expression { + binary_operator(left, "<=.", right, type_float, type_bool) } -pub fn greater_than( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, ">", right) +pub fn greater_than(left: Expression, right: Expression) -> Expression { + binary_operator(left, ">", right, type_int, type_bool) } -pub fn greater_than_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, ">.", right) +pub fn greater_than_float(left: Expression, right: Expression) -> Expression { + binary_operator(left, ">.", right, type_float, type_bool) } -pub fn greater_than_or_equal( - left: Expression(Int), - right: Expression(Int), -) -> Expression(Int) { - binary_operator(left, ">=", right) +pub fn greater_than_or_equal(left: Expression, right: Expression) -> Expression { + binary_operator(left, ">=", right, type_int, type_bool) } pub fn greater_than_or_equal_float( - left: Expression(Float), - right: Expression(Float), -) -> Expression(Float) { - binary_operator(left, ">=.", right) + left: Expression, + right: Expression, +) -> Expression { + binary_operator(left, ">=.", right, type_float, type_bool) } -fn unary_operator(operator: String, value: Expression(a)) -> Expression(a) { - [doc.from_string(operator), value.document] |> doc.concat |> Expression +fn unary_operator( + operator: String, + value: Expression, + type_: Type, +) -> Expression { + use state <- Expression + use state, value <- compile(state, value) + use #(state, type_) <- result.try(unify(state, value.type_, type_)) + [doc.from_string(operator), value.document] + |> doc.concat + |> Compiled(type_, state) + |> Ok } -pub fn negate_int(value: Expression(Int)) -> Expression(Int) { - unary_operator("-", value) +pub fn negate_int(value: Expression) -> Expression { + unary_operator("-", value, type_int) } -pub fn negate_bool(value: Expression(Bool)) -> Expression(Bool) { - unary_operator("!", value) +pub fn negate_bool(value: Expression) -> Expression { + unary_operator("!", value, type_bool) } -pub fn list(values: List(Expression(a))) -> Expression(List(a)) { +pub fn list(values: List(Expression)) -> Expression { + use state <- Expression + use values <- result.try( + list.try_map(values, fn(value) { value.compile(state) }), + ) + + use #(state, element_type) <- result.try( + list.try_fold(values, type_variable(state), fn(acc, value) { + let #(state, type_) = acc + unify(state, value.type_, type_) + }), + ) + values |> list.map(fn(value) { value.document }) |> doc.join(doc.break(", ", ",")) @@ -231,7 +372,8 @@ pub fn list(values: List(Expression(a))) -> Expression(List(a)) { |> doc.append(doc.break("", ", ")) |> doc.append(doc.from_string("]")) |> doc.group - |> Expression + |> Compiled(type_list(element_type), state) + |> Ok } fn add_message(document: Document, message: Document) -> Document { @@ -244,37 +386,59 @@ fn add_message(document: Document, message: Document) -> Document { |> grouped } -pub fn panic_(message: Option(Expression(String))) -> Expression(a) { - Expression(case message { - None -> doc.from_string("panic") - Some(message) -> add_message(doc.from_string("panic"), message.document) +pub fn panic_(message: Option(Expression)) -> Expression { + use state <- Expression + let #(state, type_) = type_variable(state) + use #(state, doc) <- result.map(case message { + None -> Ok(#(state, doc.from_string("panic"))) + Some(message) -> { + use state, message <- compile(state, message) + use #(state, _) <- result.map(unify(state, message.type_, type_string)) + #(state, add_message(doc.from_string("panic"), message.document)) + } }) -} - -pub fn todo_(message: Option(Expression(String))) -> Expression(a) { - Expression(case message { - None -> doc.from_string("todo") - Some(message) -> add_message(doc.from_string("todo"), message.document) + Compiled(doc, type_, state) +} + +pub fn todo_(message: Option(Expression)) -> Expression { + use state <- Expression + let #(state, type_) = type_variable(state) + use #(state, doc) <- result.map(case message { + None -> Ok(#(state, doc.from_string("todo"))) + Some(message) -> { + use state, message <- compile(state, message) + use #(state, _) <- result.map(unify(state, message.type_, type_string)) + #(state, add_message(doc.from_string("todo"), message.document)) + } }) + Compiled(doc, type_, state) } -pub fn echo_( - value: Expression(a), - message: Option(Expression(String)), -) -> Expression(a) { +pub fn echo_(value: Expression, message: Option(Expression)) -> Expression { + use state <- Expression + use state, value <- compile(state, value) + let echo_ = doc.prepend(doc.from_string("echo "), to: value.document) - Expression(case message { - None -> echo_ - Some(message) -> add_message(echo_, message.document) + use #(state, doc) <- result.map(case message { + None -> Ok(#(state, echo_)) + Some(message) -> { + use state, message <- compile(state, message) + use #(state, _) <- result.map(unify(state, message.type_, type_string)) + #(state, add_message(echo_, message.document)) + } }) + Compiled(doc, value.type_, state) } pub fn variable( name: String, - value: Expression(a), - continue: fn(Expression(a)) -> Statement(b), -) -> Statement(b) { + value: Expression, + continue: fn(Expression) -> Statement, +) -> Statement { + use state <- Statement + use state, value <- compile(state, value) + let declaration = [ doc.from_string("let "), @@ -285,30 +449,41 @@ pub fn variable( |> grouped |> doc.append(doc.line) - let variable_expression = Expression(doc.from_string(name)) + let variable_expression = + Expression(fn(state) { + Ok(Compiled(doc.from_string(name), value.type_, state)) + }) let rest = continue(variable_expression) + use rest <- result.map(rest.compile(state)) - Statement(doc.prepend(declaration, to: rest.document)) + Compiled(doc.prepend(declaration, to: rest.document), rest.type_, state) } fn grouped(documents: List(Document)) -> Document { documents |> doc.concat |> doc.nest(indent) |> doc.group } -pub fn expression(expression: Expression(a)) -> Statement(a) { - Statement(expression.document) +pub fn expression(expression: Expression) -> Statement { + Statement(expression.compile) } -pub fn discard( - statement: Statement(a), - continue: fn() -> Statement(b), -) -> Statement(b) { +pub fn discard(statement: Statement, continue: fn() -> Statement) -> Statement { + use state <- Statement + use state, statement <- compile_statement(state, statement) let rest = continue() - Statement(doc.concat([statement.document, doc.line, rest.document])) + use rest <- result.map(rest.compile(state)) + Compiled( + doc.concat([statement.document, doc.line, rest.document]), + rest.type_, + state, + ) } -pub fn block(inner: Statement(a)) -> Expression(a) { +pub fn block(inner: Statement) -> Expression { + use state <- Expression + use inner <- result.map(inner.compile(state)) + [ doc.from_string("{"), doc.line, @@ -318,19 +493,25 @@ pub fn block(inner: Statement(a)) -> Expression(a) { |> doc.nest(indent) |> doc.append(doc.line) |> doc.append(doc.from_string("}")) - |> Expression + |> Compiled(inner.type_, state) } -pub fn assert_( - condition: Expression(Bool), - message: Option(Expression(String)), -) -> Statement(Nil) { +pub fn assert_(condition: Expression, message: Option(Expression)) -> Statement { + use state <- Statement + use state, condition <- compile(state, condition) + use #(state, _) <- result.try(unify(state, condition.type_, type_bool)) + let assert_ = doc.prepend(doc.from_string("assert "), to: condition.document) - Statement(case message { - None -> assert_ - Some(message) -> add_message(assert_, message.document) + use #(state, doc) <- result.map(case message { + None -> Ok(#(state, assert_)) + Some(message) -> { + use state, message <- compile(state, message) + use #(state, _) <- result.map(unify(state, message.type_, type_string)) + #(state, add_message(assert_, message.document)) + } }) + Compiled(doc, type_nil, state) } // TODO: // BitString diff --git a/test/trick_test.gleam b/test/trick_test.gleam index edf1eee..4114af8 100644 --- a/test/trick_test.gleam +++ b/test/trick_test.gleam @@ -8,12 +8,18 @@ pub fn main() -> Nil { gleeunit.main() } +fn unwrap(x: Result(a, b)) -> a { + let assert Ok(x) = x + x +} + pub fn int_literal_test() { - assert 53 |> trick.int |> trick.expression_to_string == "53" + assert 53 |> trick.int |> trick.expression_to_string |> unwrap == "53" } pub fn float_literal_test() { - assert 3.1415 |> trick.float |> trick.expression_to_string == "3.1415" + assert 3.1415 |> trick.float |> trick.expression_to_string |> unwrap + == "3.1415" } pub fn string_literal_test() { @@ -21,131 +27,213 @@ pub fn string_literal_test() { It has multiple lines,\nand even quotes: \". This is how you type a backslash: \\\\" |> trick.string |> trick.expression_to_string + |> unwrap |> birdie.snap("string_literal") } pub fn binary_operator_test() { - assert trick.add(trick.int(10), trick.int(20)) |> trick.expression_to_string + assert trick.add(trick.int(10), trick.int(20)) + |> trick.expression_to_string + |> unwrap == "10 + 20" assert trick.add_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 +. 1.41" assert trick.subtract(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 - 20" assert trick.subtract_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 -. 1.41" assert trick.multiply(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 * 20" assert trick.multiply_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 *. 1.41" assert trick.divide(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 / 20" assert trick.divide_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 /. 1.41" assert trick.remainder(trick.int(10), trick.int(3)) |> trick.expression_to_string + |> unwrap == "10 % 3" assert trick.remainder(trick.int(10), trick.int(3)) |> trick.expression_to_string + |> unwrap == "10 % 3" assert trick.concatenate(trick.string("Hello,"), trick.string(" world!")) |> trick.expression_to_string + |> unwrap == "\"Hello,\" <> \" world!\"" assert trick.and(trick.bool(False), trick.bool(True)) |> trick.expression_to_string + |> unwrap == "False && True" assert trick.or(trick.bool(False), trick.bool(True)) |> trick.expression_to_string + |> unwrap == "False || True" assert trick.less_than(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 < 20" assert trick.less_than_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 <. 1.41" assert trick.less_than_or_equal(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 <= 20" assert trick.less_than_or_equal_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 <=. 1.41" assert trick.greater_than(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 > 20" assert trick.greater_than_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 >. 1.41" assert trick.greater_than_or_equal(trick.int(10), trick.int(20)) |> trick.expression_to_string + |> unwrap == "10 >= 20" assert trick.greater_than_or_equal_float(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 >=. 1.41" assert trick.equal(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 == 1.41" assert trick.equal(trick.int(23), trick.int(89)) |> trick.expression_to_string + |> unwrap == "23 == 89" assert trick.equal(trick.bool(False), trick.bool(False)) |> trick.expression_to_string + |> unwrap == "False == False" assert trick.not_equal(trick.float(3.14), trick.float(1.41)) |> trick.expression_to_string + |> unwrap == "3.14 != 1.41" assert trick.not_equal(trick.int(23), trick.int(89)) |> trick.expression_to_string + |> unwrap == "23 != 89" assert trick.not_equal(trick.bool(False), trick.bool(False)) |> trick.expression_to_string + |> unwrap == "False != False" } +const type_int = trick.Custom("gleam", "Int", []) + +const type_float = trick.Custom("gleam", "Float", []) + +const type_string = trick.Custom("gleam", "String", []) + +const type_bool = trick.Custom("gleam", "Bool", []) + +const type_nil = trick.Custom("gleam", "Nil", []) + +pub fn binary_operator_type_mismatch_test() { + let assert Error(error) = + trick.add(trick.int(10), trick.float(20.0)) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_int, got: type_float) + + let assert Error(error) = + trick.add_float(trick.string("Hello"), trick.int(12)) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_float, got: type_string) + + let assert Error(error) = + trick.equal(trick.string("Hello"), trick.bool(True)) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_string, got: type_bool) + + let assert Error(error) = + trick.not_equal(trick.nil(), trick.int(23)) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_nil, got: type_int) +} + pub fn unary_operator_test() { - assert 10 |> trick.int |> trick.negate_int |> trick.expression_to_string + assert 10 + |> trick.int + |> trick.negate_int + |> trick.expression_to_string + |> unwrap == "-10" - assert False |> trick.bool |> trick.negate_bool |> trick.expression_to_string + assert False + |> trick.bool + |> trick.negate_bool + |> trick.expression_to_string + |> unwrap == "!False" } +pub fn unary_operator_type_mismatch_test() { + let assert Error(error) = + trick.negate_int(trick.float(20.0)) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_int, got: type_float) + + let assert Error(error) = + trick.negate_bool(trick.nil()) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_bool, got: type_nil) +} + pub fn list_test() { assert [1, 2, 3, 4] |> list.map(trick.int) |> trick.list |> trick.expression_to_string + |> unwrap == "[1, 2, 3, 4]" } @@ -158,11 +246,19 @@ pub fn long_list_test() { |> list.map(trick.string) |> trick.list |> trick.expression_to_string + |> unwrap |> birdie.snap("long_list") } +pub fn list_type_mismatch_test() { + let assert Error(error) = + trick.list([trick.int(10), trick.int(20), trick.float(30.0)]) + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_int, got: type_float) +} + pub fn panic_no_message_test() { - assert trick.panic_(None) |> trick.expression_to_string == "panic" + assert trick.panic_(None) |> trick.expression_to_string |> unwrap == "panic" } pub fn panic_with_message_test() { @@ -171,6 +267,7 @@ pub fn panic_with_message_test() { |> Some |> trick.panic_ |> trick.expression_to_string + |> unwrap |> birdie.snap("panic_with_message") } @@ -180,11 +277,12 @@ pub fn panic_with_long_message_test() { |> Some |> trick.panic_ |> trick.expression_to_string + |> unwrap |> birdie.snap("panic_with_long_message") } pub fn todo_no_message_test() { - assert trick.todo_(None) |> trick.expression_to_string == "todo" + assert trick.todo_(None) |> trick.expression_to_string |> unwrap == "todo" } pub fn todo_with_message_test() { @@ -193,6 +291,7 @@ pub fn todo_with_message_test() { |> Some |> trick.todo_ |> trick.expression_to_string + |> unwrap |> birdie.snap("todo_with_message") } @@ -202,11 +301,16 @@ pub fn todo_with_long_message_test() { |> Some |> trick.todo_ |> trick.expression_to_string + |> unwrap |> birdie.snap("todo_with_long_message") } pub fn echo_test() { - assert 15 |> trick.int |> trick.echo_(None) |> trick.expression_to_string + assert 15 + |> trick.int + |> trick.echo_(None) + |> trick.expression_to_string + |> unwrap == "echo 15" } @@ -215,6 +319,7 @@ pub fn echo_with_message_test() { |> trick.float |> trick.echo_(Some(trick.string("This is a pi!"))) |> trick.expression_to_string + |> unwrap |> birdie.snap("echo_with_message") } @@ -227,6 +332,7 @@ pub fn echo_with_long_message_test() { )), ) |> trick.expression_to_string + |> unwrap |> birdie.snap("echo_with_long_message") } @@ -237,7 +343,11 @@ pub fn block_test() { trick.expression(trick.add(a, b)) } - block |> trick.block |> trick.expression_to_string |> birdie.snap("block") + block + |> trick.block + |> trick.expression_to_string + |> unwrap + |> birdie.snap("block") } pub fn block_with_discarded_expression_test() { @@ -251,14 +361,28 @@ pub fn block_with_discarded_expression_test() { block |> trick.block |> trick.expression_to_string + |> unwrap |> birdie.snap("block_with_discarded_expression") } +pub fn unified_variable_cannot_be_changed_test() { + let assert Error(error) = + { + use x <- trick.variable("x", trick.todo_(None)) + use <- trick.discard(trick.expression(trick.add(x, trick.int(1)))) + trick.expression(trick.concatenate(x, trick.string("!"))) + } + |> trick.block + |> trick.expression_to_string + assert error == trick.TypeMismatch(expected: type_string, got: type_int) +} + pub fn assert_test() { trick.equal(trick.int(1), trick.int(2)) |> trick.assert_(None) |> trick.block |> trick.expression_to_string + |> unwrap |> birdie.snap("assert") } @@ -267,6 +391,7 @@ pub fn assert_with_message_test() { |> trick.assert_(Some(trick.string("This is always going to fail"))) |> trick.block |> trick.expression_to_string + |> unwrap |> birdie.snap("assert_with_message") } @@ -279,5 +404,6 @@ pub fn assert_with_long_message_test() { ) |> trick.block |> trick.expression_to_string + |> unwrap |> birdie.snap("assert_with_long_message") } -- knotmirror 0.0.1