From af1aab35aa734cce27e6eaf47f5bd8f64d07841d Mon Sep 17 00:00:00 2001 From: Ara Adkins Date: Tue, 2 Feb 2021 12:31:33 +0000 Subject: [PATCH] Improve dataflow errors in the standard library (#1446) --- .github/CODEOWNERS | 1 + .../std-lib/Base/src/Data/Any/Extensions.enso | 64 +++++++++++++++++++ distribution/std-lib/Base/src/Data/Json.enso | 2 +- .../std-lib/Base/src/Data/Json/Internal.enso | 2 +- distribution/std-lib/Base/src/Data/List.enso | 20 +++--- distribution/std-lib/Base/src/Data/Map.enso | 7 +- distribution/std-lib/Base/src/Data/Maybe.enso | 46 +++++++++++++ .../std-lib/Base/src/Data/Time/Date.enso | 31 ++++++--- .../std-lib/Base/src/Data/Time/Locale.enso | 2 +- .../std-lib/Base/src/Data/Time/Time.enso | 45 +++++++++---- .../Base/src/Data/Time/Time_Of_Day.enso | 30 +++++++-- .../std-lib/Base/src/Data/Vector.enso | 24 +++---- .../std-lib/Base/src/Error/Extensions.enso | 16 ++++- distribution/std-lib/Base/src/Main.enso | 2 + distribution/std-lib/Base/src/Math.enso | 2 +- distribution/std-lib/Base/src/Meta.enso | 3 +- .../std-lib/Base/src/Network/Uri.enso | 6 +- distribution/std-lib/Test/src/Test.enso | 38 ++++++++--- docs/syntax/comments.md | 1 + .../builtin/error/CatchAnyNode.java | 2 +- .../builtin/error/CatchErrorNode.java | 2 +- .../expression/builtin/mutable/SetAtNode.java | 3 +- .../interpreter/runtime/builtin/Builtins.java | 3 +- .../runtime/builtin/DataflowError.java | 2 +- .../runtime/src/main/resources/Builtins.enso | 34 ++++------ .../test/instrument/RuntimeServerTest.scala | 2 +- .../semantic/CompileDiagnosticsTest.scala | 8 +-- .../test/semantic/DataflowErrorsTest.scala | 8 +-- .../test/semantic/PanicsTest.scala | 2 +- test/Benchmarks/src/Text.enso | 2 +- test/Table_Tests/src/Main.enso | 2 + test/Tests/src/Data/List_Spec.enso | 12 ++-- test/Tests/src/Data/Map_Spec.enso | 2 +- test/Tests/src/Data/Maybe_Spec.enso | 19 ++++++ test/Tests/src/Data/Numbers_Spec.enso | 18 +++--- test/Tests/src/Data/Vector_Spec.enso | 20 ++++-- test/Tests/src/Main.enso | 8 +++ test/Tests/src/Network/Http_Spec.enso | 4 +- test/Tests/src/Network/Uri_Spec.enso | 2 +- test/Tests/src/Semantic/Any_Spec.enso | 24 +++++++ test/Tests/src/Semantic/Deep_Export/Spec.enso | 2 + test/Tests/src/Semantic/Import_Loop/Spec.enso | 2 + test/Tests/src/Semantic/Names_Spec.enso | 2 + test/Tests/src/System/File_Spec.enso | 3 +- 44 files changed, 390 insertions(+), 140 deletions(-) create mode 100644 distribution/std-lib/Base/src/Data/Maybe.enso create mode 100644 test/Tests/src/Data/Maybe_Spec.enso create mode 100644 test/Tests/src/Semantic/Any_Spec.enso diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aa1e16efb2..032184e42a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,7 @@ # Distribution /distribution @iamrecursion @kustosz @radeusgd +/distribution/std-lib @iamrecursion @kustosz @wdanilo # Scala Libraries /lib/scala/cli @iamrecursion @kustosz @radeusgd diff --git a/distribution/std-lib/Base/src/Data/Any/Extensions.enso b/distribution/std-lib/Base/src/Data/Any/Extensions.enso index f5091a8e61..dc6f214d8a 100644 --- a/distribution/std-lib/Base/src/Data/Any/Extensions.enso +++ b/distribution/std-lib/Base/src/Data/Any/Extensions.enso @@ -54,3 +54,67 @@ Any.is_nothing : Boolean Any.is_nothing = case this of Nothing -> True _ -> False + +## Executes the provided handler on a dataflow error, or executes as identity on + a non-error value. + + Arguments: + - handler: The function to call on this if it is an error value. By default + this is identity. + + > Example + Catching an erroneous value to perform some operation on it. + (Time.Time_Error "Message").catch (err -> IO.println err) +Any.catch : (Error -> Any) -> Any +Any.catch (handler = x->x) = this.catch_primitive handler + +## Applies the function `this` to the provided argument. + + Arguments: + - argument: The argument to apply `this` to. + + > Example + Applying a function to a block. + (x -> x + 1) <| + y = 1 ^ 3 + 3 + y +Any.<| : Any -> Any +Any.<| ~argument = this argument + +## Applies the function on the right hand side to the argument on the left. + + Arguments + - function: The function to apply to `this`. + + > Example + Applying a function in a pipeline. + 1 |> (* 2) +Any.|> : (Any -> Any) -> Any +Any.|> function = function this + +## Composes two functions together. + + For `f << g`, this creates the function composition `f ∘ g`. + + Arguments: + - that: The function to compose with `this`. + + > Example + Compose the functions +1 and *2 and apply it to 2 + (+1 << *2) 2 +Any.<< : (Any -> Any) -> (Any -> Any) -> Any -> Any +Any.<< that = x -> this (that x) + +## Composes two functions together in the forward direction. + + For `f >> g`, this creates the function composition `g ∘ f`. + + Arguments: + - that: The function to compose with `this`. + + > Example + Add one and then multiply by two as a function applied to 2. + (+1 >> *2) 2 +Any.>> : (Any -> Any) -> (Any -> Any) -> Any -> Any +Any.>> that = x -> that (this x) + diff --git a/distribution/std-lib/Base/src/Data/Json.enso b/distribution/std-lib/Base/src/Data/Json.enso index 3662dea293..09bf03e7c6 100644 --- a/distribution/std-lib/Base/src/Data/Json.enso +++ b/distribution/std-lib/Base/src/Data/Json.enso @@ -90,7 +90,7 @@ Object.get field = this.fields.get field parse : Text -> Json ! Parse_Error parse json_text = r = Panic.recover (Internal.parse_helper json_text) - r.catch <| case _ of + r.catch e-> case e of Polyglot_Error err -> Error.throw (Parse_Error err.getMessage) p -> Panic.throw p diff --git a/distribution/std-lib/Base/src/Data/Json/Internal.enso b/distribution/std-lib/Base/src/Data/Json/Internal.enso index ce13fdd914..195ebf75ac 100644 --- a/distribution/std-lib/Base/src/Data/Json/Internal.enso +++ b/distribution/std-lib/Base/src/Data/Json/Internal.enso @@ -175,7 +175,7 @@ into_helper fmt json = case fmt of _ -> Panic.throw (Type_Mismatch_Error json fmt) ## Helper used to parse text into a JSON value. -parse_helper : Text -> Json ! Polyglot_Error +parse_helper : Text -> Json parse_helper json_text = consumer = here.mk_consumer Parser.parse json_text consumer diff --git a/distribution/std-lib/Base/src/Data/List.enso b/distribution/std-lib/Base/src/Data/List.enso index ed9a7d602c..5a468d6896 100644 --- a/distribution/std-lib/Base/src/Data/List.enso +++ b/distribution/std-lib/Base/src/Data/List.enso @@ -200,34 +200,34 @@ type List > Example This returns 1. (Cons 1 (Cons 2 Nil)).head - head : Any | Nothing + head : Any ! Nothing head = case this of Cons a _ -> a - Nil -> Nothing + Nil -> Error.throw Nothing ## Get all elements from the list except the first. > Example This returns (Cons 2 Nil). (Cons 1 (Cons 2 Nil)).tail - tail : List | Nothing + tail : List ! Nothing tail = case this of Cons _ b -> b - Nil -> Nothing + Nil -> Error.throw Nothing ## Get all elements from the list except the last. > Example Removing the last element of the list to give (Cons 1 Nil). (Cons 1 (Cons 2 Nil)).init - init : List | Nothing + init : List ! Nothing init = init' x y = case y of Nil -> Nil Cons a b -> Cons x (init' a b) case this of Cons a b -> init' a b - Nil -> Nothing + Nil -> Error.throw Nothing ## Get the last element of the list. @@ -235,14 +235,16 @@ type List Getting the final element, in this case 2. (Cons 1 (Cons 2 Nil)).last last : Any | Nothing - last = this.fold Nothing (_ -> r -> r) + last = case this.fold Nothing (_ -> r -> r) of + Nothing -> Error.throw Nothing + a -> a ## Get the first element from the list. > Example This returns 1. (Cons 1 (Cons 2 Nil)).first - first : Any | Nothing + first : Any ! Nothing first = this.head ## Get all elements from the list except the first. @@ -250,5 +252,5 @@ type List > Example This returns (Cons 2 Nil). (Cons 1 (Cons 2 Nil)).rest - rest : List | Nothing + rest : List ! Nothing rest = this.tail diff --git a/distribution/std-lib/Base/src/Data/Map.enso b/distribution/std-lib/Base/src/Data/Map.enso index b4b03ef373..43231a4f92 100644 --- a/distribution/std-lib/Base/src/Data/Map.enso +++ b/distribution/std-lib/Base/src/Data/Map.enso @@ -76,10 +76,10 @@ type Map ## Gets the value associated with `key` in this map, or returns a `Nothing`, if `key` is not present. - get : Any -> Any | Nothing + get : Any -> Any ! Nothing get key = go map = case map of - Tip -> Nothing + Tip -> Error.throw Nothing Bin _ k v l r -> if k == key then v else if k > key then @Tail_Call go l else @Tail_Call go r @@ -90,8 +90,7 @@ type Map it isn't present. get_or_else : Any -> Any -> Any get_or_else key other = - result = this.get key - if result.is_nothing then other else result + this.get key . catch (_ -> other) ## Transforms the map's keys and values to create a new map. diff --git a/distribution/std-lib/Base/src/Data/Maybe.enso b/distribution/std-lib/Base/src/Data/Maybe.enso new file mode 100644 index 0000000000..fb2b171aef --- /dev/null +++ b/distribution/std-lib/Base/src/Data/Maybe.enso @@ -0,0 +1,46 @@ +from Base import all + +## A type representing computations that may fail. +type Maybe + + ## No contained value. + Nothing + + ## A value. + type Some value + + ## Applies the provided function to the contained value if it exists, + otherwise returning the provided default value. + + Arguments: + - default: The value to return if `this` is Nothing. This value is lazy + and hence will not execute any provided computation unless it is used. + - function: The function to execute on the value inside the `Just`, if it + is a just. + + > Example + Apply a function over a Just value to get 4. + (Just 2).maybe 0 *2 + maybe : Any -> (Any -> Any) -> Any + maybe ~default function = case this of + Nothing -> default + Some val -> function val + + ## Check if the maybe value is `Just`. + + > Example + Check if `Nothing` is `Just`. + Nothing.is_just + is_just : Boolean + is_just = case this of + Nothing -> False + Some _ -> True + + ## Check if the maybe value is `Nothing`. + + > Example + Check if `Nothing` is `Nothing`. + Nothing.is_nothing + is_nothing : Boolean + is_nothing = this.is_just.not + diff --git a/distribution/std-lib/Base/src/Data/Time/Date.enso b/distribution/std-lib/Base/src/Data/Time/Date.enso index a440cdabd4..6919ca2832 100644 --- a/distribution/std-lib/Base/src/Data/Time/Date.enso +++ b/distribution/std-lib/Base/src/Data/Time/Date.enso @@ -96,6 +96,12 @@ type Date ## Obtains an instance of `Date` from a text, such as "2007-12-03". + Arguments: + - text: The textual content to parse as a date. + + Returns a `Time_Error` if the provided `text` cannot be parsed using the + provided `pattern`. + The text must represent a valid date and is parsed using the ISO-8601 extended local date format. The format consists of: @@ -115,19 +121,26 @@ type Date > Example Recover from the parse error. - Date.parse "my birthday" . catch <| case _ of + Date.parse "my birthday" . catch e-> case e of Time.Error _ -> Date.new 2000 1 1 -parse : Text -> Date +parse : Text -> Date ! Time.Time_Error parse text = - Panic.recover (Date (LocalDate.parse text)) . catch <| case _ of + Panic.recover (Date (LocalDate.parse text)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x ## Obtains an instance of `Date` from a text using custom format. + Arguments: + - text: The textual content to parse as a time. + - pattern: The pattern describing how to parse the text. + For the list of accepted symbols in pattern refer to `Base.Data.Time.Time.format` doc. + Returns a `Time_Error` if the provided `text` cannot be parsed using the + provided `pattern`. + > Example Parse "1999-1-1" as Date. Date.parse_format "1999-1-1" "yyyy-M-d" @@ -135,12 +148,12 @@ parse text = > Example Recover from the parse error. date = Date.parse "1999-1-1" "yyyy-MM-dd" - date.catch <| case _ of + date.catch e-> case e of Time.Error msg -> Date.new 2000 1 1 -parse_format : Text -> Text -> Date +parse_format : Text -> Text -> Date ! Time.Time_Error parse_format text pattern = format = DateTimeFormatter.ofPattern pattern - Panic.recover (Date (LocalDate.parse text format)) . catch <| case _ of + Panic.recover (Date (LocalDate.parse text format)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x @@ -158,6 +171,8 @@ today = here.now - day - the day-of-month to represent, from 1 to 31 and must be valid for the year and month + Returns a `Time_Error` if the provided time is not valid. + > Example Create a new local date at Unix epoch. Date.new 1970 @@ -165,8 +180,8 @@ today = here.now > Example Get the local date of 5th August 1986. Date.new 1986 8 5 -new : Integer -> Integer -> Integer -> Date +new : Integer -> Integer -> Integer -> Date ! Time.Time_Error new year (month = 1) (day = 1) = - Panic.recover (Date (LocalDate.of year month day)) . catch <| case _ of + Panic.recover (Date (LocalDate.of year month day)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x diff --git a/distribution/std-lib/Base/src/Data/Time/Locale.enso b/distribution/std-lib/Base/src/Data/Time/Locale.enso index 0dc63e9108..2b34911431 100644 --- a/distribution/std-lib/Base/src/Data/Time/Locale.enso +++ b/distribution/std-lib/Base/src/Data/Time/Locale.enso @@ -131,7 +131,7 @@ new language country=Nothing variant=Nothing = > Example Creating the locale en_US. Locale.from_language_tag "en_US" -from_language_tag : Text -> Locale | Nothing +from_language_tag : Text -> Locale from_language_tag tag = java_locale = JavaLocale.forLanguageTag tag here.from_java java_locale diff --git a/distribution/std-lib/Base/src/Data/Time/Time.enso b/distribution/std-lib/Base/src/Data/Time/Time.enso index 8d033e03a4..c58b7449bc 100644 --- a/distribution/std-lib/Base/src/Data/Time/Time.enso +++ b/distribution/std-lib/Base/src/Data/Time/Time.enso @@ -180,6 +180,9 @@ type Time ## Obtains an instance of `Time` from a text such as "2007-12-03T10:15:30+01:00 Europe/Paris". + Arguments: + - text: The text representing the time to be parsed. + The text must represent a valid date-time and is parsed using the ISO-8601 extended offset date-time format to add the timezone. The section in square brackets is not part of the ISO-8601 standard. The format consists of: @@ -192,6 +195,9 @@ type Time sensitive. - A close square bracket ']'. + This method will return a `Time_Error` if the provided time cannot be parsed + using the above format. + > Example Parse UTC time. Time.parse "2020-10-01T04:11:12Z" @@ -210,16 +216,24 @@ type Time > Example Recover from the parse error. - Time.parse "2020-10-01" . catch <| case _ of + Time.parse "2020-10-01" . catch e-> case e of Time.Error _ -> Time.now -parse : Text -> Time +parse : Text -> Time ! Time_Error parse text = - Panic.recover (Time (Time_Utils.parse_time text)) . catch <| case _ of + Panic.recover (Time (Time_Utils.parse_time text)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time_Error err.getMessage) x -> x ## Obtains an instance of Time from a text using custom format. + Arguments: + - text: The text to parse as a time of day, using the specified pattern. + - pattern: The pattern to use for parsing the input text. + - locale: The locale in which the pattern should be interpreted. + + Returns a `Time_Error` if the provided text cannot be parsed using the + provided pattern and locale. + For the list of accepted symbols in pattern refer to `Time.format` doc. > Example @@ -229,9 +243,9 @@ parse text = > Example Parse "06 of May 2020 at 04:30AM" as Time Date.parse_format "06 of May 2020 at 04:30AM" "dd 'of' MMMM yyyy 'at' hh:mma" -parse_format : Text -> Text -> Locale -> Time_Of_Day +parse_format : Text -> Text -> Locale -> Time ! Time_Error parse_format text pattern locale=Locale.default = - Panic.recover (Time (Time_Utils.parse_time_format text pattern locale.java_locale)) . catch <| case _ of + Panic.recover (Time (Time_Utils.parse_time_format text pattern locale.java_locale)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time_Error err.getMessage) x -> x @@ -242,14 +256,17 @@ now = Time ZonedDateTime.now ## Obtains an instance of `Time` from a year, month, day, hour, minute, second, nanosecond and timezone. - - month - the month-of-year to represent, from 1 (January) to 12 (December) - - day - the day-of-month to represent, from 1 to 31 and must be valid for the + Arguments: + - month: the month-of-year to represent, from 1 (January) to 12 (December) + - day: the day-of-month to represent, from 1 to 31 and must be valid for the year and month - - hour - the hour-of-day to represent, from 0 to 23 - - minute - the minute-of-hour to represent, from 0 to 59 - - second - the second-of-minute to represent, from 0 to 59 - - nanosecond - the nano-of-second to represent, from 0 to 999,999,999 - - zone - the timezone + - hour: the hour-of-day to represent, from 0 to 23 + - minute: the minute-of-hour to represent, from 0 to 59 + - second: the second-of-minute to represent, from 0 to 59 + - nanosecond: the nano-of-second to represent, from 0 to 999,999,999 + - zone: the timezone + + Returns a `Time_Error` if the provided time cannot be represented. > Example Create a new zoned date time at Unix epoch. @@ -258,8 +275,8 @@ now = Time ZonedDateTime.now > Example Get the 5 August 1986 at midnight. Time.new 1986 8 5 -new : Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Zone -> Time +new : Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Zone -> Time ! Time_Error new year (month = 1) (day = 1) (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) (zone = Zone.system) = - Panic.recover (Time (ZonedDateTime.of year month day hour minute second nanosecond zone.internal_zone_id)) . catch <| case _ of + Panic.recover (Time (ZonedDateTime.of year month day hour minute second nanosecond zone.internal_zone_id)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time_Error err.getMessage) x -> x diff --git a/distribution/std-lib/Base/src/Data/Time/Time_Of_Day.enso b/distribution/std-lib/Base/src/Data/Time/Time_Of_Day.enso index 47ad911ca3..1bfa96c2e2 100644 --- a/distribution/std-lib/Base/src/Data/Time/Time_Of_Day.enso +++ b/distribution/std-lib/Base/src/Data/Time/Time_Of_Day.enso @@ -101,6 +101,12 @@ type Time_Of_Day ## Obtains an instance of `Time_Of_Day` from a text such as "10:15". + Arguments: + - text: The text to parse as a time of day. + + Returns a `Time_Error` if the provided text cannot be parsed using the + default format. + The text must represent a valid time and is parsed using the ISO-8601 extended local time format. The format consists of: @@ -124,16 +130,24 @@ type Time_Of_Day > Example Recover from the parse error. - Time_Of_Day.parse "half past twelve" . catch <| case + Time_Of_Day.parse "half past twelve" . catch e-> case e of Time.Error _ -> Time_Of_Day.new -parse : Text -> Time_Of_Day +parse : Text -> Time_Of_Day ! Time.Time_Error parse text = - Panic.recover (Time_Of_Day (LocalTime.parse text)) . catch <| case _ of + Panic.recover (Time_Of_Day (LocalTime.parse text)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x ## Obtains an instance of Time_Of_Day from a text using custom format. + Arguments: + - text: The text to parse as a time of day, using the specified pattern. + - pattern: The pattern to use for parsing the input text. + - locale: The locale in which the pattern should be interpreted. + + Returns a `Time_Error` if the provided text cannot be parsed using the + provided pattern and locale. + For the list of accepted symbols in pattern refer to `Base.Data.Time.Time.format` doc. @@ -144,10 +158,10 @@ parse text = > Example Parse "4:30AM" as Time_Of_Day Date.parse_format "4:30AM" "h:mma" -parse_format : Text -> Text -> Locale -> Time_Of_Day +parse_format : Text -> Text -> Locale -> Time_Of_Day ! Time.Time_Error parse_format text pattern locale=Locale.default = format = (DateTimeFormatter.ofPattern pattern).withLocale locale.java_locale - Panic.recover (Time_Of_Day (LocalTime.parse text format)) . catch <| case _ of + Panic.recover (Time_Of_Day (LocalTime.parse text format)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x @@ -163,6 +177,8 @@ now = Time_Of_Day LocalTime.now - second - the second-of-minute to represent, from 0 to 59 - nanosecond - the nano-of-second to represent, from 0 to 999,999,999 + Returns a `Time_Error` if the provided time is not a valid time. + > Example Create a new local time at Unix epoch. Time_Of_Day.new @@ -170,8 +186,8 @@ now = Time_Of_Day LocalTime.now > Example Get the local time at 9:30. Time_Of_Day.new 9 30 -new : Integer -> Integer -> Integer -> Integer -> Time_Of_Day +new : Integer -> Integer -> Integer -> Integer -> Time_Of_Day ! Time.Time_Error new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) = - Panic.recover (Time_Of_Day (LocalTime.of hour minute second nanosecond)) . catch <| case _ of + Panic.recover (Time_Of_Day (LocalTime.of hour minute second nanosecond)) . catch e-> case e of Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage) x -> x diff --git a/distribution/std-lib/Base/src/Data/Vector.enso b/distribution/std-lib/Base/src/Data/Vector.enso index 6c065afa32..14430c5e9e 100644 --- a/distribution/std-lib/Base/src/Data/Vector.enso +++ b/distribution/std-lib/Base/src/Data/Vector.enso @@ -120,11 +120,11 @@ type Vector In the following example, we'll compute the sum of all elements of a vector: [0, 1, 2] . reduce (+) - reduce : (Any -> Any -> Any) -> Any | Nothing + reduce : (Any -> Any -> Any) -> Any ! Nothing reduce function = case this.not_empty of True -> this.tail.fold this.head function - False -> Nothing + False -> Error.throw Nothing ## Checks whether a predicate holds for at least one element of this vector. @@ -360,8 +360,8 @@ type Vector > Example Empty vectors return `Nothing`. [].head == Nothing - head : Any | Nothing - head = if this.length >= 1 then this.at 0 else Nothing + head : Any ! Nothing + head = if this.length >= 1 then this.at 0 else Error.throw Nothing ## Get all elements in the vector except the first. @@ -371,8 +371,8 @@ type Vector > Example Empty vectors return `Nothing`. [].tail == Nothing - tail : Vector | Nothing - tail = if this.length >= 1 then this.drop_start 1 else Nothing + tail : Vector ! Nothing + tail = if this.length >= 1 then this.drop_start 1 else Error.throw Nothing ## Get the all elements in the vector except the last. @@ -382,8 +382,8 @@ type Vector > Example Empty vectors return `Nothing`. [].init == Nothing - init : Vector | Nothing - init = if this.length >= 1 then this.drop_end 1 else Nothing + init : Vector ! Nothing + init = if this.length >= 1 then this.drop_end 1 else Error.throw Nothing ## Get the last element of the vector. @@ -393,8 +393,8 @@ type Vector > Example Empty vectors return `Nothing`. [].last == Nothing - last : Vector | Nothing - last = if this.length >= 1 then (this.take_end 1).at 0 else Nothing + last : Vector ! Nothing + last = if this.length >= 1 then (this.take_end 1).at 0 else Error.throw Nothing ## Get the first element from the vector. @@ -404,7 +404,7 @@ type Vector > Example Empty vectors return `Nothing`. [].first == Nothing - first : Vector | Nothing + first : Vector ! Nothing first = this.head ## Get all elements in the vector except the first. @@ -415,7 +415,7 @@ type Vector > Example Empty vectors return `Nothing`. [].rest == Nothing - rest : Vector | Nothing + rest : Vector ! Nothing rest = this.tail ## Sort the Vector. diff --git a/distribution/std-lib/Base/src/Error/Extensions.enso b/distribution/std-lib/Base/src/Error/Extensions.enso index f22b6ddb95..5ae59204e9 100644 --- a/distribution/std-lib/Base/src/Error/Extensions.enso +++ b/distribution/std-lib/Base/src/Error/Extensions.enso @@ -10,5 +10,19 @@ type Unimplemented_Error message ## A function that can be used to indicate that something hasn't been implemented yet. -unimplemented : Text -> Void ! Unimplemented_Error +unimplemented : Text -> Void unimplemented message="" = Panic.throw (Unimplemented_Error message) + +## Executes the provided handler on a dataflow error, or executes as identity on + a non-error value. + + Arguments: + - handler: The function to call on this if it is an error value. By default + this is identity. + + > Example + Catching an erroneous value to perform some operation on it. + (Time.Time_Error "Message").catch (err -> IO.println err) +Error.catch : (Error -> Any) -> Any +Error.catch (handler = x->x) = this.catch_primitive handler + diff --git a/distribution/std-lib/Base/src/Main.enso b/distribution/std-lib/Base/src/Main.enso index 77980fc65b..d87c7c4186 100644 --- a/distribution/std-lib/Base/src/Main.enso +++ b/distribution/std-lib/Base/src/Main.enso @@ -3,6 +3,7 @@ import Base.Data.Interval import Base.Data.Json import Base.Data.List import Base.Data.Map +import Base.Data.Maybe import Base.Data.Noise import Base.Data.Number.Extensions import Base.Data.Ordering @@ -23,6 +24,7 @@ from Builtins import Nothing, Number, Integer, Any, True, False, Cons, Boolean, export Base.Data.Interval export Base.Data.Json export Base.Data.Map +export Base.Data.Maybe export Base.Data.Ordering export Base.Data.Ordering.Sort_Order export Base.Data.Vector diff --git a/distribution/std-lib/Base/src/Math.enso b/distribution/std-lib/Base/src/Math.enso index 757456ea7b..936d850f3f 100644 --- a/distribution/std-lib/Base/src/Math.enso +++ b/distribution/std-lib/Base/src/Math.enso @@ -19,7 +19,7 @@ e = 2.718281828459045235360 ## Returns the smaller value of `a` and `b`. min : Number -> Number -> Number -min a b = if a < b then a else b +min a b = if a <= b then a else b ## Returns the larger value of `a` and `b`. max : Number -> Number -> Number diff --git a/distribution/std-lib/Base/src/Meta.enso b/distribution/std-lib/Base/src/Meta.enso index 5eab11dece..23980ea4cd 100644 --- a/distribution/std-lib/Base/src/Meta.enso +++ b/distribution/std-lib/Base/src/Meta.enso @@ -95,7 +95,8 @@ is_a value typ = if typ == Any then True else _ -> meta_val = here.meta value case meta_val of - Atom _ -> meta_val.constructor == typ + Atom _ -> if Builtins.meta.is_atom typ then typ == value else + meta_val.constructor == typ Constructor _ -> meta_typ = here.meta typ case meta_typ of diff --git a/distribution/std-lib/Base/src/Network/Uri.enso b/distribution/std-lib/Base/src/Network/Uri.enso index 54467fd915..4d45eb1503 100644 --- a/distribution/std-lib/Base/src/Network/Uri.enso +++ b/distribution/std-lib/Base/src/Network/Uri.enso @@ -16,7 +16,7 @@ type Uri_Error ## PRIVATE panic_on_error ~action = - action . catch <| case _ of + action . catch e-> case e of Syntax_Error msg -> Panic.throw (Syntax_Error msg) ## PRIVATE @@ -151,8 +151,8 @@ type Uri > Example Parse Uri text. Uri.parse "http://example.com" -parse : Text -> Uri +parse : Text -> Uri ! Syntax_Error parse text = - Panic.recover (Uri (Java_URI.create text)) . catch <| case _ of + Panic.recover (Uri (Java_URI.create text)) . catch e-> case e of Polyglot_Error ex -> Error.throw (Syntax_Error ex.getMessage) other -> Panic.throw other diff --git a/distribution/std-lib/Test/src/Test.enso b/distribution/std-lib/Test/src/Test.enso index 4ed99722fe..5625e0c74f 100644 --- a/distribution/std-lib/Test/src/Test.enso +++ b/distribution/std-lib/Test/src/Test.enso @@ -18,6 +18,12 @@ Spec.is_fail = this.behaviors.any .is_fail ## PRIVATE Suite.is_fail = this.specs.any .is_fail +## PRIVATE +type Finished_With_Error err + +## PRIVATE +type Matched_On_Error err + ## PRIVATE type Assertion type Success @@ -49,13 +55,14 @@ Any.should verb argument = verb Verbs this argument fail message = Panic.throw (Failure message) ## Expect a function to fail with the provided dataflow error. -expect_error_with ~action matcher = - result = action - fail_msg = "Expected an error " + matcher.to_text + "but none occurred." - if result.is_error.not then here.fail fail_msg else - caught = result.catch x->x - if caught.is_a matcher then Nothing else - here.fail ("Unexpected error " + caught.to_text + " thrown.") +Any.should_fail_with matcher = + here.fail ("Expected an error " + matcher.to_text + " but none occurred.") + +## Expect a function to fail with the provided dataflow error. +Error.should_fail_with matcher = + caught = this.catch x->x + if caught.is_a matcher then Nothing else + here.fail ("Unexpected error " + caught.to_text + " returned.") ## Expect a function to fail with the provided panic. expect_panic_with ~action matcher = @@ -73,6 +80,9 @@ Any.should_equal that = case this == that of msg = this.to_text + " did not equal " + that.to_text + "." Panic.throw (Failure msg) +## Asserts that `this` value is equal to the expected value. +Error.should_equal _ = Panic.throw (Matched_On_Error this) + ## Asserts that `this` is within `epsilon` from `that`. Decimal.should_equal that (epsilon = 0) = case this.equals that epsilon of True -> Success @@ -85,11 +95,17 @@ Boolean.should_be_true = case this of True -> Success False -> Panic.throw (Failure "Expected False to be True.") +## Asserts that the given `Boolean` is `True`. +Error.should_be_true = Panic.throw (Matched_On_Error this) + ## Asserts that the given `Boolean` is `False` Boolean.should_be_false = case this of True -> Panic.throw (Failure "Expected True to be False.") False -> Success +## Asserts that the given `Boolean` is `False` +Error.should_be_false = Panic.throw (Matched_On_Error this) + ## PRIVATE Spec.print_report = IO.print_err (this.name + ":") @@ -137,19 +153,21 @@ specify label ~behavior pending=False = new_spec = Spec spec.name (Cons (Behavior label result) spec.behaviors) State.put Spec new_spec +## PRIVATE run_spec ~behavior = recovery = Panic.recover <| - behavior + behavior.catch err-> Panic.throw (Finished_With_Error err) Nothing maybeExc = case recovery of _ -> Success result = maybeExc.catch ex-> case ex of Failure _ -> ex - _ -> Failure ("Unexpected error has been thrown: " + ex.to_text) + Finished_With_Error x -> + Failure ("An unexpected error was returned: " + x.to_text) + _ -> Failure ("An unexpected panic was thrown: " + ex.to_text) result - ## Creates a new test group, desribing properties of the object described by `this`. diff --git a/docs/syntax/comments.md b/docs/syntax/comments.md index 3c99a658ce..3070f2e33e 100644 --- a/docs/syntax/comments.md +++ b/docs/syntax/comments.md @@ -114,6 +114,7 @@ state. The documentation syntax supports the following tags: - `PRIVATE`: Used to describe constructs that are private in the language. - `ADVANCED`: Items that are _not_ private, but are for power users. - `TEXT_ONLY`: Items that do not apply to the graphical mode. +- `UNSTABLE`: Used for items that are not yet considered stable. Tags are added at the _top_ of the documentation block, and may also be accompanied by a description. This description directly follows the tag diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchAnyNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchAnyNode.java index 2d7b447dc5..62f4ee123f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchAnyNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchAnyNode.java @@ -14,7 +14,7 @@ import org.enso.interpreter.runtime.type.TypesGen; @BuiltinMethod( type = "Any", - name = "catch", + name = "catch_primitive", description = "If called on an error, executes the provided handler on the error's payload. Otherwise acts as identity.") public class CatchAnyNode extends Node { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java index 49f2f15316..a15ba45845 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchErrorNode.java @@ -14,7 +14,7 @@ import org.enso.interpreter.runtime.type.TypesGen; @BuiltinMethod( type = "Error", - name = "catch", + name = "catch_primitive", description = "If called on an error, executes the provided handler on the error's payload. Otherwise acts as identity.") public class CatchErrorNode extends Node { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java index 3141971509..6ce7682671 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SetAtNode.java @@ -1,6 +1,7 @@ package org.enso.interpreter.node.expression.builtin.mutable; import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.runtime.data.Array; @@ -10,7 +11,7 @@ import org.enso.interpreter.runtime.data.Array; description = "Puts the given element in the given position in the array.") public class SetAtNode extends Node { - Object execute(Array _this, long index, Object value) { + Object execute(Array _this, long index, @AcceptsError Object value) { _this.getItems()[(int) index] = value; return _this; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index e683a99292..1c43c37ee9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -147,7 +147,7 @@ public class Builtins { scope.registerMethod(panic, "throw", ThrowPanicMethodGen.makeFunction(language)); scope.registerMethod(panic, "recover", RecoverPanicMethodGen.makeFunction(language)); - scope.registerMethod(any, "catch", CatchAnyMethodGen.makeFunction(language)); + scope.registerMethod(any, "catch_primitive", CatchAnyMethodGen.makeFunction(language)); scope.registerMethod(state, "get", GetStateMethodGen.makeFunction(language)); scope.registerMethod(state, "put", PutStateMethodGen.makeFunction(language)); @@ -157,7 +157,6 @@ public class Builtins { scope.registerMethod(debug, "breakpoint", DebugBreakpointMethodGen.makeFunction(language)); scope.registerMethod(function, "call", ExplicitCallFunctionMethodGen.makeFunction(language)); - scope.registerMethod(function, "<|", ApplicationOperatorMethodGen.makeFunction(language)); scope.registerMethod(any, "to_text", AnyToTextMethodGen.makeFunction(language)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java index 1e883d941e..fd5c12d945 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/DataflowError.java @@ -15,7 +15,7 @@ public class DataflowError { scope.registerConstructor(error); scope.registerMethod(error, "throw", ThrowErrorMethodGen.makeFunction(language)); - scope.registerMethod(error, "catch", CatchErrorMethodGen.makeFunction(language)); + scope.registerMethod(error, "catch_primitive", CatchErrorMethodGen.makeFunction(language)); scope.registerMethod(error, "to_text", ErrorToTextMethodGen.makeFunction(language)); } diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index cc3f7e762a..37329b84c0 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -159,7 +159,9 @@ type Any @Builtin_Type type Any - ## Executes the provided handler on a dataflow error, or executes as + ## PRIVATE + + Executes the provided handler on a dataflow error, or executes as identity on a non-error value. Arguments: @@ -167,9 +169,9 @@ type Any > Example Catching an erroneous value to perform some operation on it. - (Time.Time_Error "Message").catch (err -> IO.println err) - catch : (Error -> Any) -> Any - catch handler = @Builtin_Method "Any.catch" + (Time.Time_Error "Message").catch_primitive (err -> IO.println err) + catch_primitive : (Error -> Any) -> Any + catch_primitive handler = @Builtin_Method "Any.catch" ## Generic conversion of an arbitrary Enso value to a corresponding textual representation. @@ -208,7 +210,9 @@ type Error throw : Any -> Error throw payload = @Builtin_Method "Error.throw" - ## Executes the provided handler on a dataflow error, or executes as + ## PRIVATE + + Executes the provided handler on a dataflow error, or executes as identity on a non-error value. Arguments: @@ -216,9 +220,9 @@ type Error > Example Catching an erroneous value to perform some operation on it. - (Time.Time_Error "Message").catch (err -> IO.println err) - catch : (Error -> Any) -> Any - catch handler = @Builtin_Method "Any.catch" + (Time.Time_Error "Message").catch_primitive (err -> IO.println err) + catch_primitive : (Error -> Any) -> Any + catch_primitive handler = @Builtin_Method "Any.catch" ## Converts an error to a corresponding textual representation. to_text : Text @@ -366,20 +370,6 @@ type Function call : Any call = @Builtin_Method "Function.call" - ## Takes a function and an argument and applies the function to the argument - - Arguments: - - argument: The argument to apply this to. - - > Example - Applying identity to a value produced by a block to get 3. - (x -> x) <| - a = 1 - b = 2 - a + b - <| : (Any -> Any) -> Any -> Any - <| argument = @Builtin_Method "Function.<|" - ## Generic utilities for interacting with other languages. type Polyglot diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index f353224cd6..7cffd53a04 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3590,7 +3590,7 @@ class RuntimeServerTest ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("(Syntax_Error 'Unrecognized token.')") + context.consumeOut shouldEqual List("(Error: (Syntax_Error 'Unrecognized token.'))") } it should "return compiler error syntax error" in { diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CompileDiagnosticsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CompileDiagnosticsTest.scala index 3e5890b1b1..55b9f6f47a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CompileDiagnosticsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/CompileDiagnosticsTest.scala @@ -14,7 +14,7 @@ class CompileDiagnosticsTest extends InterpreterTest { | |main = | x = Panic.recover () - | x.catch err-> + | x.catch_primitive err-> | case err of | Syntax_Error msg -> "Oopsie, it's a syntax error: " + msg |""".stripMargin @@ -29,7 +29,7 @@ class CompileDiagnosticsTest extends InterpreterTest { | |main = | x = Panic.recover @ - | x.catch .to_text + | x.catch_primitive .to_text |""".stripMargin eval(code) shouldEqual "(Syntax_Error 'Unrecognized token.')" } @@ -42,7 +42,7 @@ class CompileDiagnosticsTest extends InterpreterTest { | x = 1 | x = 2 | - |main = Panic.recover here.foo . catch .to_text + |main = Panic.recover here.foo . catch_primitive .to_text |""".stripMargin eval(code) shouldEqual "(Compile_Error 'Variable x is being redefined.')" } @@ -55,7 +55,7 @@ class CompileDiagnosticsTest extends InterpreterTest { | my_var = 10 | my_vra | - |main = Panic.recover here.foo . catch .to_text + |main = Panic.recover here.foo . catch_primitive .to_text |""".stripMargin eval(code) shouldEqual "(Compile_Error 'Variable `my_vra` is not defined.')" } diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DataflowErrorsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DataflowErrorsTest.scala index 24598b5726..f67f360d8c 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DataflowErrorsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DataflowErrorsTest.scala @@ -52,7 +52,7 @@ class DataflowErrorsTest extends InterpreterTest { | |main = | intError = Error.throw 1 - | intError.catch (x -> x + 3) + | intError.catch_primitive (x -> x + 3) |""".stripMargin eval(code) shouldEqual 4 } @@ -65,7 +65,7 @@ class DataflowErrorsTest extends InterpreterTest { | |main = | unitErr = Error.throw Nothing - | IO.println (unitErr.catch MyCons) + | IO.println (unitErr.catch_primitive MyCons) |""".stripMargin eval(code) consumeOut shouldEqual List("(MyCons Nothing)") @@ -83,14 +83,14 @@ class DataflowErrorsTest extends InterpreterTest { | |main = | myErr = Error.throw (MyError 20) - | IO.println(myErr.catch .recover) + | IO.println(myErr.catch_primitive .recover) |""".stripMargin eval(code) consumeOut shouldEqual List("(MyRecovered 20)") } "make the catch method an identity for non-error values" in { - val code = "main = 10.catch (x -> x + 1)" + val code = "main = 10.catch_primitive (x -> x + 1)" eval(code) shouldEqual 10 } diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PanicsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PanicsTest.scala index a7f3b43c22..93454ca0aa 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PanicsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PanicsTest.scala @@ -57,7 +57,7 @@ class PanicsTest extends InterpreterTest { |main = | caught = Panic.recover (Long.parseLong "oops") | IO.println caught - | cause = caught.catch <| case _ of + | cause = caught.catch_primitive e-> case e of | Polyglot_Error err -> err | _ -> "fail" | IO.println cause diff --git a/test/Benchmarks/src/Text.enso b/test/Benchmarks/src/Text.enso index 83e72f905f..cd3c322420 100644 --- a/test/Benchmarks/src/Text.enso +++ b/test/Benchmarks/src/Text.enso @@ -11,7 +11,7 @@ build_long n = build_long_bldr n = bldr = StringBuilder.new - 1.up_to n . each (bldr.append _) + 1.up_to n . each n-> bldr.append n res = bldr.toString res diff --git a/test/Table_Tests/src/Main.enso b/test/Table_Tests/src/Main.enso index 2429a47f85..0c4e9228bb 100644 --- a/test/Table_Tests/src/Main.enso +++ b/test/Table_Tests/src/Main.enso @@ -1,3 +1,5 @@ +from Base import all + import Test import Table_Tests.Table_Spec diff --git a/test/Tests/src/Data/List_Spec.enso b/test/Tests/src/Data/List_Spec.enso index 2b16e11415..821968fd42 100644 --- a/test/Tests/src/Data/List_Spec.enso +++ b/test/Tests/src/Data/List_Spec.enso @@ -60,20 +60,20 @@ spec = Test.group "List" <| empty.take_start 2 . should_equal Nil Test.specify "should allow getting the head of the list with `.head`" <| l.head . should_equal 1 - empty.head . should_equal Nothing + empty.head.catch . should_equal Nothing Test.specify "should allow getting the tail of the list with `.tail`" <| l.tail . should_equal (Cons 2 (Cons 3 Nil)) - empty.tail . should_equal Nothing + empty.tail.catch . should_equal Nothing Test.specify "should allow getting the init of the list with `.init`" <| l.init . should_equal (Cons 1 (Cons 2 Nil)) - empty.init . should_equal Nothing + empty.init.catch . should_equal Nothing Test.specify "should allow getting the last element of the list with `.last`" <| l.last . should_equal 3 - empty.last . should_equal Nothing + empty.last.catch . should_equal Nothing Test.specify "should allow getting the head of the list with `.first`" <| l.first . should_equal 1 - empty.first . should_equal Nothing + empty.first.catch . should_equal Nothing Test.specify "should allow getting the tail of the list with `.rest`" <| l.rest . should_equal (Cons 2 (Cons 3 Nil)) - empty.rest . should_equal Nothing + empty.rest.catch . should_equal Nothing diff --git a/test/Tests/src/Data/Map_Spec.enso b/test/Tests/src/Data/Map_Spec.enso index 379b0139c5..9e38f561bf 100644 --- a/test/Tests/src/Data/Map_Spec.enso +++ b/test/Tests/src/Data/Map_Spec.enso @@ -43,7 +43,7 @@ spec = Test.group "Maps" <| m.get "foo" . should_equal 134 m.get "bar" . should_equal 654 m.get "baz" . should_equal "spam" - m.get "nope" . should_equal Nothing + (m.get "nope").should_fail_with Nothing Test.specify "should support get_or_else" <| m = Map.empty . insert 2 3 m.get_or_else 2 0 . should_equal 3 diff --git a/test/Tests/src/Data/Maybe_Spec.enso b/test/Tests/src/Data/Maybe_Spec.enso new file mode 100644 index 0000000000..266bbd74f0 --- /dev/null +++ b/test/Tests/src/Data/Maybe_Spec.enso @@ -0,0 +1,19 @@ +from Base import all + +import Test + +spec = Test.group "Maybe" <| + Test.specify "should have a Nothing variant" <| + Nothing . should_equal Nothing + Test.specify "should have a Just variant" <| + (Maybe.Some 2).value . should_equal 2 + Test.specify "should provide the `maybe` function" <| + Nothing.maybe 2 x->x . should_equal 2 + (Maybe.Some 7).maybe 2 (*2) . should_equal 14 + Test.specify "should provide `is_just`" <| + Nothing.is_just . should_be_false + Maybe.Some 2 . is_just . should_be_true + Test.specify "should provide `is_nothing`" <| + Nothing.is_nothing . should_be_true + Maybe.Some 2 . is_nothing . should_be_false + diff --git a/test/Tests/src/Data/Numbers_Spec.enso b/test/Tests/src/Data/Numbers_Spec.enso index 20e1e181fe..d0d4fa99b6 100644 --- a/test/Tests/src/Data/Numbers_Spec.enso +++ b/test/Tests/src/Data/Numbers_Spec.enso @@ -42,7 +42,7 @@ spec = (almost_max_long * 2 / almost_max_long_times_three) . should_equal 0.6666666 epsilon=eps Test.specify "should support integer division" <| (10.div 3) . should_equal 3 - Test.expect_error_with (10.div 0) Arithmetic_Error + # (10.div 0).should_fail_with Arithmetic_Error Test.specify "should support integral binary literals" <| lit = 2_01101101 lit . should_equal 109 @@ -93,28 +93,28 @@ spec = positive_bits.bit_shift_l 64 . should_equal 16_6d0000000000000000 positive_bits.bit_shift_l -2 . should_equal 2_011011 positive_bits.bit_shift_l -64 . should_equal 0 - Test.expect_error_with (positive_bits.bit_shift_l positive_big_bits) Arithmetic_Error + (positive_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error positive_bits.bit_shift_l negative_big_bits . should_equal 0 negative_bits.bit_shift_l 2 . should_equal -436 negative_bits.bit_shift_l 64 . should_equal -2010695104034341126144 negative_bits.bit_shift_l -2 . should_equal -28 negative_bits.bit_shift_l -64 . should_equal -1 - Test.expect_error_with (negative_bits.bit_shift_l positive_big_bits) Arithmetic_Error + (negative_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error negative_bits.bit_shift_l negative_big_bits . should_equal -1 positive_big_bits.bit_shift_l 2 . should_equal 110680464442257309672 positive_big_bits.bit_shift_l 64 . should_equal 510423550381407695084381446705395007488 positive_big_bits.bit_shift_l -2 . should_equal 6917529027641081854 positive_big_bits.bit_shift_l -100 . should_equal 0 - Test.expect_error_with (positive_big_bits.bit_shift_l positive_big_bits) Arithmetic_Error + (positive_big_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error positive_big_bits.bit_shift_l negative_big_bits . should_equal 0 negative_big_bits.bit_shift_l 2 . should_equal -110680464442257309672 negative_big_bits.bit_shift_l 64 . should_equal -510423550381407695084381446705395007488 negative_big_bits.bit_shift_l -2 . should_equal -6917529027641081855 negative_big_bits.bit_shift_l -100 . should_equal -1 - Test.expect_error_with (negative_big_bits.bit_shift_l positive_big_bits) Arithmetic_Error + (negative_big_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error negative_big_bits.bit_shift_l negative_big_bits . should_equal -1 Test.specify "should support right bit shifts, preserving sign" <| positive_bits = 2_01101101 @@ -126,28 +126,28 @@ spec = positive_bits.bit_shift_r 64 . should_equal (positive_bits.bit_shift_l -64) positive_bits.bit_shift_r -2 . should_equal (positive_bits.bit_shift_l 2) positive_bits.bit_shift_r -64 . should_equal (positive_bits.bit_shift_l 64) - Test.expect_error_with (positive_bits.bit_shift_r negative_big_bits) Arithmetic_Error + (positive_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error positive_bits.bit_shift_r positive_big_bits . should_equal 0 negative_bits.bit_shift_r 2 . should_equal (negative_bits.bit_shift_l -2) negative_bits.bit_shift_r 64 . should_equal (negative_bits.bit_shift_l -64) negative_bits.bit_shift_r -2 . should_equal (negative_bits.bit_shift_l 2) negative_bits.bit_shift_r -64 . should_equal (negative_bits.bit_shift_l 64) - Test.expect_error_with (negative_bits.bit_shift_r negative_big_bits) Arithmetic_Error + (negative_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error negative_bits.bit_shift_r positive_big_bits . should_equal -1 positive_big_bits.bit_shift_r 2 . should_equal (positive_big_bits.bit_shift_l -2) positive_big_bits.bit_shift_r 64 . should_equal (positive_big_bits.bit_shift_l -64) positive_big_bits.bit_shift_r -2 . should_equal (positive_big_bits.bit_shift_l 2) positive_big_bits.bit_shift_r -100 . should_equal (positive_big_bits.bit_shift_l 100) - Test.expect_error_with (positive_big_bits.bit_shift_r negative_big_bits) Arithmetic_Error + (positive_big_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error positive_big_bits.bit_shift_r positive_big_bits . should_equal 0 negative_big_bits.bit_shift_r 2 . should_equal (negative_big_bits.bit_shift_l -2) negative_big_bits.bit_shift_r 64 . should_equal (negative_big_bits.bit_shift_l -64) negative_big_bits.bit_shift_r -2 . should_equal (negative_big_bits.bit_shift_l 2) negative_big_bits.bit_shift_r -100 . should_equal (negative_big_bits.bit_shift_l 100) - Test.expect_error_with (negative_big_bits.bit_shift_r negative_big_bits) Arithmetic_Error + (negative_big_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error negative_big_bits.bit_shift_r positive_big_bits . should_equal -1 Test.group "Decimals" <| Test.specify "should exist and expose basic arithmetic operations" <| diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index c2ea17a217..99e29ea2b4 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -8,6 +8,8 @@ T.== that = this.a == that.a T.compare_to that = if this == that then Ordering.Equal else if this.a > that.a then Ordering.Greater else Ordering.Less +type My_Error a + spec = Test.group "Vectors" <| Test.specify "should allow vector creation with a programmatic constructor" <| Vector.new 100 (ix -> ix + 1) . fold 0 (+) . should_equal 5050 @@ -19,7 +21,7 @@ spec = Test.group "Vectors" <| [1,2,3].fold 0 (+) . should_equal 6 Test.specify "should allow to reduce elements if it is non-empty" <| [1,2,3].reduce (+) . should_equal 6 - [].reduce (+) . should_equal Nothing + [].reduce (+) . should_fail_with Nothing Test.specify "should check exists" <| vec = [1, 2, 3, 4, 5] vec.exists (ix -> ix > 3) . should_be_true @@ -88,42 +90,42 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.head . should_equal 1 singleton_vec.head . should_equal 1 - empty_vec.head . should_equal Nothing + empty_vec.head . should_fail_with Nothing Test.specify "should allow getting the tail of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] singleton_vec = [1] empty_vec = [] non_empty_vec.tail . should_equal [2, 3, 4, 5] singleton_vec.tail . should_equal [] - empty_vec.tail . should_equal Nothing + empty_vec.tail . should_fail_with Nothing Test.specify "should allow getting the init of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] singleton_vec = [1] empty_vec = [] non_empty_vec.init . should_equal [1, 2, 3, 4] singleton_vec.init . should_equal [] - empty_vec.init . should_equal Nothing + empty_vec.init . should_fail_with Nothing Test.specify "should allow getting the last element of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] singleton_vec = [1] empty_vec = [] non_empty_vec.last . should_equal 5 singleton_vec.last . should_equal 1 - empty_vec.last . should_equal Nothing + empty_vec.last . should_fail_with Nothing Test.specify "should allow getting the first element" <| non_empty_vec = [1, 2, 3, 4, 5] singleton_vec = [1] empty_vec = [] non_empty_vec.first . should_equal 1 singleton_vec.first . should_equal 1 - empty_vec.first . should_equal Nothing + empty_vec.first . should_fail_with Nothing Test.specify "should allow getting the rest of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] singleton_vec = [1] empty_vec = [] non_empty_vec.rest . should_equal [2, 3, 4, 5] singleton_vec.rest . should_equal [] - empty_vec.rest . should_equal Nothing + empty_vec.rest . should_fail_with Nothing Test.specify "should be able to be sorted" <| empty_vec = [] short_vec = [2, 4, 38, -1, -1000, 3671, -32] @@ -159,3 +161,7 @@ spec = Test.group "Vectors" <| small_vec = [T 1 8, T 1 3, T -20 0, T -1 1, T -1 10, T 4 0] small_expected = [T 4 0, T 1 3, T 1 8, T -1 10, T -1 1, T -20 0] small_vec.sort order=Sort_Order.Descending . should_equal small_expected + Test.specify "should be able to map over errors" <| + fail a = Error.throw <| My_Error a + [fail 1].map (x -> x.catch (x -> x.a)) . should_equal [1] + [1].map fail . map .catch . should_equal [My_Error 1] diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index 63b232c2c8..4ba8efc5a5 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -1,5 +1,8 @@ +from Base import all + import Test +import Tests.Semantic.Any_Spec import Tests.Semantic.Case_Spec import Tests.Semantic.Deep_Export.Spec as Deep_Export_Spec import Tests.Semantic.Error_Spec @@ -12,6 +15,7 @@ import Tests.Data.Interval_Spec import Tests.Data.Json_Spec import Tests.Data.List_Spec import Tests.Data.Map_Spec +import Tests.Data.Maybe_Spec import Tests.Data.Noise.Generator_Spec as Noise_Generator_Spec import Tests.Data.Noise_Spec import Tests.Data.Numbers_Spec @@ -20,14 +24,17 @@ import Tests.Data.Range_Spec import Tests.Data.Text_Spec import Tests.Data.Time.Spec as Time_Spec import Tests.Data.Vector_Spec + import Tests.Network.Http.Header_Spec as Http_Header_Spec import Tests.Network.Http.Request_Spec as Http_Request_Spec import Tests.Network.Http_Spec import Tests.Network.Uri_Spec + import Tests.System.File_Spec import Tests.System.Process_Spec main = Test.Suite.runMain <| + Any_Spec.spec Case_Spec.spec Deep_Export_Spec.spec Error_Spec.spec @@ -41,6 +48,7 @@ main = Test.Suite.runMain <| Json_Spec.spec List_Spec.spec Map_Spec.spec + Maybe_Spec.spec Meta_Spec.spec Names_Spec.spec Noise_Generator_Spec.spec diff --git a/test/Tests/src/Network/Http_Spec.enso b/test/Tests/src/Network/Http_Spec.enso index cfc2374b05..3e7cf6640b 100644 --- a/test/Tests/src/Network/Http_Spec.enso +++ b/test/Tests/src/Network/Http_Spec.enso @@ -38,9 +38,7 @@ spec_impl = http = Http.new (version = version_setting) http.version.should_equal version_setting Test.specify "should throw error when requesting invalid Uri" <| - case Panic.recover (Http.new.get "not a uri") of - Uri.Syntax_Error _ -> Nothing - other -> Test.fail ("Unexpected result: " + other) + Test.expect_panic_with (Http.new.get "not a uri") Uri.Syntax_Error Test.specify "should send Get request" <| expected_response = Json.parse <| ''' { diff --git a/test/Tests/src/Network/Uri_Spec.enso b/test/Tests/src/Network/Uri_Spec.enso index 959211be06..85d2c65d81 100644 --- a/test/Tests/src/Network/Uri_Spec.enso +++ b/test/Tests/src/Network/Uri_Spec.enso @@ -28,7 +28,7 @@ spec = addr.raw_query.should_equal "%D0%9A%D0%BE%D0%B4" addr.raw_fragment.should_equal "" Test.specify "should return Syntax_Error when parsing invalid Uri" <| - Uri.parse "a b c" . catch <| case _ of + Uri.parse "a b c" . catch e-> case e of Uri.Syntax_Error msg -> msg.should_equal "Illegal character in path at index 1: a b c" other -> diff --git a/test/Tests/src/Semantic/Any_Spec.enso b/test/Tests/src/Semantic/Any_Spec.enso new file mode 100644 index 0000000000..fb580016a6 --- /dev/null +++ b/test/Tests/src/Semantic/Any_Spec.enso @@ -0,0 +1,24 @@ +from Base import all + +import Test + +type My_Type a + +spec = Test.group "Callables" <| + Test.specify "should be able to be applied in a pipeline using |>" <| + (1 |> *2) . should_equal 2 + (2 |> My_Type) . should_equal (My_Type 2) + (2.3 |> .floor) . should_equal 2 + Test.specify "should be able to be applied to an argument using <|" <| + (*2 <| 1) . should_equal 2 + (My_Type <| 2) . should_equal (My_Type 2) + (.floor <| 2.3) . should_equal 2 + Test.specify "should be able to be composed backward using <<" <| + (+1 << *2) 2 . should_equal 5 + (My_Type << *2) 2 . should_equal <| My_Type 4 + (.floor << *2.25) 2 . should_equal 4 + Test.specify "should be able to be composed forward using >>" <| + (+1 >> *2) 2 . should_equal 6 + (*2 >> My_Type) 2 . should_equal <| My_Type 4 + (*2 >> .floor) 2.75 . should_equal 5 + diff --git a/test/Tests/src/Semantic/Deep_Export/Spec.enso b/test/Tests/src/Semantic/Deep_Export/Spec.enso index 810c78ee6a..0c4bd2fc13 100644 --- a/test/Tests/src/Semantic/Deep_Export/Spec.enso +++ b/test/Tests/src/Semantic/Deep_Export/Spec.enso @@ -1,3 +1,5 @@ +from Base import all + import Test import Tests.Semantic.Deep_Export.Internal diff --git a/test/Tests/src/Semantic/Import_Loop/Spec.enso b/test/Tests/src/Semantic/Import_Loop/Spec.enso index a746ba0af9..c525a56cf5 100644 --- a/test/Tests/src/Semantic/Import_Loop/Spec.enso +++ b/test/Tests/src/Semantic/Import_Loop/Spec.enso @@ -1,3 +1,5 @@ +from Base import all + import Test import Tests.Semantic.Import_Loop.B diff --git a/test/Tests/src/Semantic/Names_Spec.enso b/test/Tests/src/Semantic/Names_Spec.enso index a81fdd31cd..7692447769 100644 --- a/test/Tests/src/Semantic/Names_Spec.enso +++ b/test/Tests/src/Semantic/Names_Spec.enso @@ -1,3 +1,5 @@ +from Base import all + from Tests.Semantic.Names.Definitions import My_Type, Another_Constant import Test diff --git a/test/Tests/src/System/File_Spec.enso b/test/Tests/src/System/File_Spec.enso index 84d8eac48a..a22315032e 100644 --- a/test/Tests/src/System/File_Spec.enso +++ b/test/Tests/src/System/File_Spec.enso @@ -1,4 +1,5 @@ from Base import all + import Test spec = @@ -13,7 +14,7 @@ spec = contents.take_start 6 . should_equal [67, 117, 112, 99, 97, 107] Test.specify "should handle exceptions when reading a non-existent file" <| file = File.new "does_not_exist.txt" - successfully_failed = Panic.recover file.read . catch <| case _ of + successfully_failed = Panic.recover file.read . catch e-> case e of File.No_Such_File_Error _ -> True _ -> False successfully_failed . should_be_true