Warn on the presence of unnecessary wildcards in output positions

This commit is contained in:
Ayaz Hafiz 2022-10-26 10:49:19 -05:00
parent d55dbbf0ae
commit cfe7c8e5ef
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
17 changed files with 151 additions and 106 deletions

View file

@ -991,7 +991,7 @@ mod cli_run {
This #UserApp.main value is a:
Task.Task {} * [Write [Stdout]*]* ?
Task.Task {} * [Write [Stdout]a]b ?
But the type annotation on main says it should be:
@ -1012,7 +1012,7 @@ mod cli_run {
This #UserApp.main value is a:
Task.Task {} * [Write [Stdout]*]* ?
Task.Task {} * [Write [Stdout]a]b ?
But toEffect needs its 1st argument to be:

View file

@ -1,7 +1,7 @@
interface Base64 exposes [fromBytes, fromStr, toBytes, toStr] imports [Base64.Decode, Base64.Encode]
# base 64 encoding from a sequence of bytes
fromBytes : List U8 -> Result Str [InvalidInput]*
fromBytes : List U8 -> Result Str [InvalidInput]
fromBytes = \bytes ->
when Base64.Decode.fromBytes bytes is
Ok v ->
@ -11,16 +11,16 @@ fromBytes = \bytes ->
Err InvalidInput
# base 64 encoding from a string
fromStr : Str -> Result Str [InvalidInput]*
fromStr : Str -> Result Str [InvalidInput]
fromStr = \str ->
fromBytes (Str.toUtf8 str)
# base64-encode bytes to the original
toBytes : Str -> Result (List U8) [InvalidInput]*
toBytes : Str -> Result (List U8) [InvalidInput]
toBytes = \str ->
Ok (Base64.Encode.toBytes str)
toStr : Str -> Result Str [InvalidInput]*
toStr : Str -> Result Str [InvalidInput]
toStr = \str ->
when toBytes str is
Ok bytes ->

View file

@ -36,7 +36,7 @@ nestHelp = \{ s, f, m, x } ->
Expr : [Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr]
divmod : I64, I64 -> Result { div : I64, mod : I64 } [DivByZero]*
divmod : I64, I64 -> Result { div : I64, mod : I64 } [DivByZero]
divmod = \l, r ->
when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is
Pair (Ok div) (Ok mod) -> Ok { div, mod }

View file

@ -105,7 +105,7 @@ withCapacity = \n -> @Dict (List.withCapacity n)
##
## expect Dict.get dictionary 1 == Ok "Apple"
## expect Dict.get dictionary 2000 == Err KeyNotFound
get : Dict k v, k -> Result v [KeyNotFound]* | k has Eq
get : Dict k v, k -> Result v [KeyNotFound] | k has Eq
get = \@Dict list, needle ->
when List.findFirst list (\Pair key _ -> key == needle) is
Ok (Pair _ v) ->

View file

@ -219,7 +219,7 @@ isEmpty = \list ->
# but will cause a reference count increment on the value it got out of the list
getUnsafe : List a, Nat -> a
get : List a, Nat -> Result a [OutOfBounds]*
get : List a, Nat -> Result a [OutOfBounds]
get = \list, index ->
if index < List.len list then
Ok (List.getUnsafe list index)
@ -298,7 +298,7 @@ reserve : List a, Nat -> List a
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List a -> Result a [ListWasEmpty]*
last : List a -> Result a [ListWasEmpty]
last = \list ->
when List.get list (Num.subSaturated (List.len list) 1) is
Ok v -> Ok v
@ -683,7 +683,7 @@ sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ListWasEmpty]*
first : List a -> Result a [ListWasEmpty]
first = \list ->
when List.get list 0 is
Ok v -> Ok v
@ -776,7 +776,7 @@ drop = \list, n ->
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
min : List (Num a) -> Result (Num a) [ListWasEmpty]*
min : List (Num a) -> Result (Num a) [ListWasEmpty]
min = \list ->
when List.first list is
Ok initial ->
@ -793,7 +793,7 @@ minHelp = \list, initial ->
else
bestSoFar
max : List (Num a) -> Result (Num a) [ListWasEmpty]*
max : List (Num a) -> Result (Num a) [ListWasEmpty]
max = \list ->
when List.first list is
Ok initial ->
@ -820,7 +820,7 @@ joinMap = \list, mapper ->
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
findFirst : List elem, (elem -> Bool) -> Result elem [NotFound]*
findFirst : List elem, (elem -> Bool) -> Result elem [NotFound]
findFirst = \list, pred ->
callback = \_, elem ->
if pred elem then
@ -834,7 +834,7 @@ findFirst = \list, pred ->
## Returns the last element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
findLast : List elem, (elem -> Bool) -> Result elem [NotFound]*
findLast : List elem, (elem -> Bool) -> Result elem [NotFound]
findLast = \list, pred ->
callback = \_, elem ->
if pred elem then
@ -849,7 +849,7 @@ findLast = \list, pred ->
## Returns the index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findFirstIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]*
findFirstIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findFirstIndex = \list, matcher ->
foundIndex = List.iterate list 0 \index, elem ->
if matcher elem then
@ -864,7 +864,7 @@ findFirstIndex = \list, matcher ->
## Returns the last index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findLastIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]*
findLastIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]
findLastIndex = \list, matches ->
foundIndex = List.iterateBackwards list (List.len list) \prevIndex, elem ->
if matches elem then
@ -962,7 +962,7 @@ split = \elements, userSplitIndex ->
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is
Ok index ->
@ -977,7 +977,7 @@ splitFirst = \list, delimiter ->
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is
Ok index ->

View file

@ -743,7 +743,7 @@ atan : Frac a -> Frac a
## >>> Num.sqrt -4.0f64
sqrt : Frac a -> Frac a
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]*
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]
sqrtChecked = \x ->
if x < 0.0 then
Err SqrtOfNegative
@ -752,7 +752,7 @@ sqrtChecked = \x ->
log : Frac a -> Frac a
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]*
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
logChecked = \x ->
if x <= 0.0 then
Err LogNeedsPositive
@ -791,7 +791,7 @@ logChecked = \x ->
## >>> |> Num.div 2.0
div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]*
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]
divChecked = \a, b ->
if Num.isZero b then
Err DivByZero
@ -800,7 +800,7 @@ divChecked = \a, b ->
divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]
divCeilChecked = \a, b ->
if Num.isZero b then
Err DivByZero
@ -825,7 +825,7 @@ divCeilChecked = \a, b ->
##
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
divTruncChecked = \a, b ->
if Num.isZero b then
Err DivByZero
@ -845,7 +845,7 @@ divTruncChecked = \a, b ->
## >>> Num.rem -8 -3
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero]*
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
remChecked = \a, b ->
if Num.isZero b then
Err DivByZero
@ -944,7 +944,7 @@ addSaturated : Num a, Num a -> Num a
##
## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
addChecked : Num a, Num a -> Result (Num a) [Overflow]*
addChecked : Num a, Num a -> Result (Num a) [Overflow]
addChecked = \a, b ->
result = addCheckedLowlevel a b
@ -970,7 +970,7 @@ subSaturated : Num a, Num a -> Num a
##
## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
subChecked : Num a, Num a -> Result (Num a) [Overflow]*
subChecked : Num a, Num a -> Result (Num a) [Overflow]
subChecked = \a, b ->
result = subCheckedLowlevel a b
@ -994,7 +994,7 @@ mulSaturated : Num a, Num a -> Num a
##
## This is the same as [Num.mul] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
mulChecked : Num a, Num a -> Result (Num a) [Overflow]*
mulChecked : Num a, Num a -> Result (Num a) [Overflow]
mulChecked = \a, b ->
result = mulCheckedLowlevel a b
@ -1250,19 +1250,19 @@ toF64 : Num * -> F64
## Converts a [Int] to an [I8].
## If the given integer can't be precisely represented in an [I8], returns
## `Err OutOfBounds`.
toI8Checked : Int * -> Result I8 [OutOfBounds]*
toI16Checked : Int * -> Result I16 [OutOfBounds]*
toI32Checked : Int * -> Result I32 [OutOfBounds]*
toI64Checked : Int * -> Result I64 [OutOfBounds]*
toI128Checked : Int * -> Result I128 [OutOfBounds]*
toU8Checked : Int * -> Result U8 [OutOfBounds]*
toU16Checked : Int * -> Result U16 [OutOfBounds]*
toU32Checked : Int * -> Result U32 [OutOfBounds]*
toU64Checked : Int * -> Result U64 [OutOfBounds]*
toU128Checked : Int * -> Result U128 [OutOfBounds]*
toNatChecked : Int * -> Result Nat [OutOfBounds]*
toF32Checked : Num * -> Result F32 [OutOfBounds]*
toF64Checked : Num * -> Result F64 [OutOfBounds]*
toI8Checked : Int * -> Result I8 [OutOfBounds]
toI16Checked : Int * -> Result I16 [OutOfBounds]
toI32Checked : Int * -> Result I32 [OutOfBounds]
toI64Checked : Int * -> Result I64 [OutOfBounds]
toI128Checked : Int * -> Result I128 [OutOfBounds]
toU8Checked : Int * -> Result U8 [OutOfBounds]
toU16Checked : Int * -> Result U16 [OutOfBounds]
toU32Checked : Int * -> Result U32 [OutOfBounds]
toU64Checked : Int * -> Result U64 [OutOfBounds]
toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]
# Special Floating-Point operations
## When given a [F64] or [F32] value, returns `Bool.false` if that value is

View file

@ -220,7 +220,7 @@ toUtf8 : Str -> List U8
##
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
## expect Str.fromUtf8 [0xb0] == Err (BadUtf8 InvalidStartByte 0)
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]*
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
@ -233,7 +233,7 @@ fromUtf8 = \bytes ->
## into a [Str]
##
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]*
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config ->
if config.start + config.count <= List.len bytes then
result = fromUtf8RangeLowlevel bytes config.start config.count
@ -288,7 +288,7 @@ trimRight : Str -> Str
## expect Str.toDec "10" == Ok 10dec
## expect Str.toDec "-0.25" == Ok -0.25dec
## expect Str.toDec "not a number" == Err InvalidNumStr
toDec : Str -> Result Dec [InvalidNumStr]*
toDec : Str -> Result Dec [InvalidNumStr]
toDec = \string -> strToNumHelp string
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
@ -297,7 +297,7 @@ toDec = \string -> strToNumHelp string
##
## expect Str.toF64 "0.10" == Ok 0.10f64
## expect Str.toF64 "not a number" == Err InvalidNumStr
toF64 : Str -> Result F64 [InvalidNumStr]*
toF64 : Str -> Result F64 [InvalidNumStr]
toF64 = \string -> strToNumHelp string
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
@ -306,7 +306,7 @@ toF64 = \string -> strToNumHelp string
##
## expect Str.toF32 "0.10" == Ok 0.10f32
## expect Str.toF32 "not a number" == Err InvalidNumStr
toF32 : Str -> Result F32 [InvalidNumStr]*
toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string
## Convert a [Str] to a [Nat]. If the given number doesn't fit in [Nat], it will be [truncated](https://www.ualberta.ca/computing-science/media-library/teaching-resources/java/truncation-rounding.html).
@ -324,7 +324,7 @@ toF32 = \string -> strToNumHelp string
##
## expect Str.toNat "9_000_000_000" == Ok 9000000000
## expect Str.toNat "not a number" == Err InvalidNumStr
toNat : Str -> Result Nat [InvalidNumStr]*
toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
@ -335,7 +335,7 @@ toNat = \string -> strToNumHelp string
## expect Str.toU128 "0.1" == Err InvalidNumStr
## expect Str.toU128 "-1" == Err InvalidNumStr
## expect Str.toU128 "not a number" == Err InvalidNumStr
toU128 : Str -> Result U128 [InvalidNumStr]*
toU128 : Str -> Result U128 [InvalidNumStr]
toU128 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I128] integer. A [I128] value can hold numbers
@ -347,7 +347,7 @@ toU128 = \string -> strToNumHelp string
## expect Str.toI128 "-1" == Ok -1i128
## expect Str.toI128 "0.1" == Err InvalidNumStr
## expect Str.toI128 "not a number" == Err InvalidNumStr
toI128 : Str -> Result I128 [InvalidNumStr]*
toI128 : Str -> Result I128 [InvalidNumStr]
toI128 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
@ -358,7 +358,7 @@ toI128 = \string -> strToNumHelp string
## expect Str.toU64 "0.1" == Err InvalidNumStr
## expect Str.toU64 "-1" == Err InvalidNumStr
## expect Str.toU64 "not a number" == Err InvalidNumStr
toU64 : Str -> Result U64 [InvalidNumStr]*
toU64 : Str -> Result U64 [InvalidNumStr]
toU64 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
@ -369,7 +369,7 @@ toU64 = \string -> strToNumHelp string
## expect Str.toI64 "-1" == Ok -1i64
## expect Str.toI64 "0.1" == Err InvalidNumStr
## expect Str.toI64 "not a number" == Err InvalidNumStr
toI64 : Str -> Result I64 [InvalidNumStr]*
toI64 : Str -> Result I64 [InvalidNumStr]
toI64 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
@ -380,7 +380,7 @@ toI64 = \string -> strToNumHelp string
## expect Str.toU32 "0.1" == Err InvalidNumStr
## expect Str.toU32 "-1" == Err InvalidNumStr
## expect Str.toU32 "not a number" == Err InvalidNumStr
toU32 : Str -> Result U32 [InvalidNumStr]*
toU32 : Str -> Result U32 [InvalidNumStr]
toU32 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
@ -391,7 +391,7 @@ toU32 = \string -> strToNumHelp string
## expect Str.toI32 "-1" == Ok -1i32
## expect Str.toI32 "0.1" == Err InvalidNumStr
## expect Str.toI32 "not a number" == Err InvalidNumStr
toI32 : Str -> Result I32 [InvalidNumStr]*
toI32 : Str -> Result I32 [InvalidNumStr]
toI32 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
@ -401,7 +401,7 @@ toI32 = \string -> strToNumHelp string
## expect Str.toU16 "0.1" == Err InvalidNumStr
## expect Str.toU16 "-1" == Err InvalidNumStr
## expect Str.toU16 "not a number" == Err InvalidNumStr
toU16 : Str -> Result U16 [InvalidNumStr]*
toU16 : Str -> Result U16 [InvalidNumStr]
toU16 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
@ -412,7 +412,7 @@ toU16 = \string -> strToNumHelp string
## expect Str.toI16 "-1" == Ok -1i16
## expect Str.toI16 "0.1" == Err InvalidNumStr
## expect Str.toI16 "not a number" == Err InvalidNumStr
toI16 : Str -> Result I16 [InvalidNumStr]*
toI16 : Str -> Result I16 [InvalidNumStr]
toI16 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
@ -422,7 +422,7 @@ toI16 = \string -> strToNumHelp string
## expect Str.toU8 "-0.1" == Err InvalidNumStr
## expect Str.toU8 "not a number" == Err InvalidNumStr
## expect Str.toU8 "1500" == Err InvalidNumStr
toU8 : Str -> Result U8 [InvalidNumStr]*
toU8 : Str -> Result U8 [InvalidNumStr]
toU8 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
@ -432,7 +432,7 @@ toU8 = \string -> strToNumHelp string
## expect Str.toI8 "-15" == Ok -15i8
## expect Str.toI8 "150.00" == Err InvalidNumStr
## expect Str.toI8 "not a number" == Err InvalidNumStr
toI8 : Str -> Result I8 [InvalidNumStr]*
toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string
## Get the byte at the given index, without performing a bounds check.
@ -451,7 +451,7 @@ substringUnsafe : Str, Nat, Nat -> Str
##
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
replaceEach : Str, Str, Str -> Result Str [NotFound]*
replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
@ -483,7 +483,7 @@ expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
##
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
replaceFirst : Str, Str, Str -> Result Str [NotFound]*
replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
@ -498,7 +498,7 @@ expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
##
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
replaceLast : Str, Str, Str -> Result Str [NotFound]*
replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
Ok { before, after } ->
@ -514,7 +514,7 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
##
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]*
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitFirst = \haystack, needle ->
when firstMatch haystack needle is
Some index ->
@ -567,7 +567,7 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
##
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
## expect Str.splitLast "no slashes here" "/" == Err NotFound
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]*
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitLast = \haystack, needle ->
when lastMatch haystack needle is
Some index ->
@ -684,7 +684,7 @@ appendScalarUnsafe : Str, U32 -> Str
##
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
appendScalar : Str, U32 -> Result Str [InvalidScalar]*
appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar ->
if isValidScalar scalar then
Ok (appendScalarUnsafe string scalar)
@ -749,7 +749,7 @@ walkScalarsUntilHelp = \string, state, step, index, length ->
strToNum : Str -> { berrorcode : U8, aresult : Num * }
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]*
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]
strToNumHelp = \string ->
result : { berrorcode : U8, aresult : Num a }
result = strToNum string

View file

@ -334,7 +334,7 @@ pub(crate) fn canonicalize_annotation(
}
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum CanPolarity {
/// Polarity should be disregarded for now; relevant in aliaes + opaques.
Disregard,
@ -1081,6 +1081,17 @@ fn can_extension_type<'a>(
references,
);
if valid_extension_type(shallow_dealias_with_scope(scope, &ext_type)) {
if matches!(loc_ann.extract_spaces().item, TypeAnnotation::Wildcard)
&& matches!(ext_problem_kind, ExtensionTypeKind::TagUnion)
&& pol == CanPolarity::Pos
{
// Wildcards are redundant in positive positions, since they will always be
// inferred as necessary there!
env.problem(roc_problem::can::Problem::UnnecessaryOutputWildcard {
region: loc_ann.region,
})
}
ext_type
} else {
// Report an error but mark the extension variable to be inferred

View file

@ -182,6 +182,9 @@ pub enum Problem {
original_opaque: Symbol,
ability_member: Symbol,
},
UnnecessaryOutputWildcard {
region: Region,
},
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -502,7 +502,7 @@ mod solve_expr {
Str.fromUtf8
"#
),
"List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]*",
"List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]",
);
}
@ -3330,7 +3330,7 @@ mod solve_expr {
List.get ["a"] 0
"#
),
"Result Str [OutOfBounds]*",
"Result Str [OutOfBounds]",
);
}
@ -3432,7 +3432,7 @@ mod solve_expr {
List.get [10, 9, 8, 7] 1
"#
),
"Result (Num *) [OutOfBounds]*",
"Result (Num *) [OutOfBounds]",
);
infer_eq_without_problem(
@ -3441,7 +3441,7 @@ mod solve_expr {
List.get
"#
),
"List a, Nat -> Result a [OutOfBounds]*",
"List a, Nat -> Result a [OutOfBounds]",
);
}
@ -3543,7 +3543,7 @@ mod solve_expr {
Num.divChecked
"#
),
"Float a, Float a -> Result (Float a) [DivByZero]*",
"Float a, Float a -> Result (Float a) [DivByZero]",
)
}
@ -3567,7 +3567,7 @@ mod solve_expr {
Num.divCeilChecked
"#
),
"Int a, Int a -> Result (Int a) [DivByZero]*",
"Int a, Int a -> Result (Int a) [DivByZero]",
);
}
@ -3591,7 +3591,7 @@ mod solve_expr {
Num.divTruncChecked
"#
),
"Int a, Int a -> Result (Int a) [DivByZero]*",
"Int a, Int a -> Result (Int a) [DivByZero]",
);
}
@ -3779,7 +3779,7 @@ mod solve_expr {
Model position : { openSet : Set position }
cheapestOpen : Model position -> Result position [KeyNotFound]* | position has Eq
cheapestOpen : Model position -> Result position [KeyNotFound] | position has Eq
cheapestOpen = \model ->
folder = \resSmallestSoFar, position ->
@ -3794,14 +3794,14 @@ mod solve_expr {
Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder
|> Result.map (\x -> x.position)
astar : Model position -> Result position [KeyNotFound]* | position has Eq
astar : Model position -> Result position [KeyNotFound] | position has Eq
astar = \model -> cheapestOpen model
main =
astar
"#
),
"Model position -> Result position [KeyNotFound]* | position has Eq",
"Model position -> Result position [KeyNotFound] | position has Eq",
);
}
@ -7498,7 +7498,7 @@ mod solve_expr {
r#"
OList := [Nil, Cons {} OList]
lst : [Cons {} OList]*
lst : [Cons {} OList]
olist : OList
olist = (\l -> @OList l) lst
@ -7517,7 +7517,7 @@ mod solve_expr {
r#"
OList := [Nil, Cons {} OList]
lst : [Cons {} OList]*
lst : [Cons {} OList]
olist : OList
olist = @OList lst

View file

@ -1531,7 +1531,7 @@ fn polymorphic_tag() {
assert_evals_to!(
indoc!(
r#"
x : [Y U8]*
x : [Y U8]
x = Y 3
x
"#
@ -1695,7 +1695,7 @@ fn instantiate_annotated_as_recursive_alias_toplevel() {
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo : [Nil]
foo = Nil
it : Value
@ -1723,7 +1723,7 @@ fn instantiate_annotated_as_recursive_alias_polymorphic_expr() {
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo : [Nil]
foo = Nil
it : Value
@ -1750,7 +1750,7 @@ fn instantiate_annotated_as_recursive_alias_multiple_polymorphic_expr() {
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo : [Nil]
foo = Nil
v1 : Value

View file

@ -70,7 +70,7 @@ fn num_floor_division() {
fn num_floor_checked_division_success() {
expect_success(
"Num.divTruncChecked 4 3",
"Ok 1 : Result (Int *) [DivByZero]*",
"Ok 1 : Result (Int *) [DivByZero]",
);
}
@ -79,7 +79,7 @@ fn num_floor_checked_division_success() {
fn num_floor_checked_division_divby_zero() {
expect_success(
"Num.divTruncChecked 4 0",
"Err DivByZero : Result (Int *) [DivByZero]*",
"Err DivByZero : Result (Int *) [DivByZero]",
);
}
@ -94,7 +94,7 @@ fn num_ceil_division() {
fn num_ceil_checked_division_success() {
expect_success(
"Num.divCeilChecked 4 3",
"Ok 2 : Result (Int *) [DivByZero]*",
"Ok 2 : Result (Int *) [DivByZero]",
)
}
@ -380,30 +380,30 @@ fn num_mul_saturated() {
#[cfg(not(feature = "wasm"))]
#[test]
fn num_add_checked() {
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [Overflow]*");
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [Overflow]");
expect_success(
"Num.addChecked Num.maxI64 1",
"Err Overflow : Result I64 [Overflow]*",
"Err Overflow : Result I64 [Overflow]",
);
}
#[cfg(not(feature = "wasm"))]
#[test]
fn num_sub_checked() {
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [Overflow]*");
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [Overflow]");
expect_success(
"Num.subChecked Num.minI64 1",
"Err Overflow : Result I64 [Overflow]*",
"Err Overflow : Result I64 [Overflow]",
);
}
#[cfg(not(feature = "wasm"))]
#[test]
fn num_mul_checked() {
expect_success("Num.mulChecked 20 2", "Ok 40 : Result (Num *) [Overflow]*");
expect_success("Num.mulChecked 20 2", "Ok 40 : Result (Num *) [Overflow]");
expect_success(
"Num.mulChecked Num.maxI64 2",
"Err Overflow : Result I64 [Overflow]*",
"Err Overflow : Result I64 [Overflow]",
);
}
@ -437,11 +437,11 @@ fn list_sum() {
fn list_first() {
expect_success(
"List.first [12, 9, 6, 3]",
"Ok 12 : Result (Num *) [ListWasEmpty]*",
"Ok 12 : Result (Num *) [ListWasEmpty]",
);
expect_success(
"List.first []",
"Err ListWasEmpty : Result a [ListWasEmpty]*",
"Err ListWasEmpty : Result a [ListWasEmpty]",
);
}
@ -450,12 +450,12 @@ fn list_first() {
fn list_last() {
expect_success(
"List.last [12, 9, 6, 3]",
"Ok 3 : Result (Num *) [ListWasEmpty]*",
"Ok 3 : Result (Num *) [ListWasEmpty]",
);
expect_success(
"List.last []",
"Err ListWasEmpty : Result a [ListWasEmpty]*",
"Err ListWasEmpty : Result a [ListWasEmpty]",
);
}
@ -654,18 +654,18 @@ fn type_problem() {
#[test]
fn issue_2149() {
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [InvalidNumStr]*");
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [InvalidNumStr]");
expect_success(
r#"Str.toI8 "128""#,
"Err InvalidNumStr : Result I8 [InvalidNumStr]*",
"Err InvalidNumStr : Result I8 [InvalidNumStr]",
);
expect_success(
r#"Str.toI16 "32767""#,
"Ok 32767 : Result I16 [InvalidNumStr]*",
"Ok 32767 : Result I16 [InvalidNumStr]",
);
expect_success(
r#"Str.toI16 "32768""#,
"Err InvalidNumStr : Result I16 [InvalidNumStr]*",
"Err InvalidNumStr : Result I16 [InvalidNumStr]",
);
}
@ -1155,7 +1155,7 @@ fn box_box_type_alias() {
fn issue_2582_specialize_result_value() {
expect_success(
r#"\x, list -> if x > 0 then List.first list else Ok """#,
r"<function> : Num *, List Str -> Result Str [ListWasEmpty]*",
r"<function> : Num *, List Str -> Result Str [ListWasEmpty]",
)
}
@ -1236,7 +1236,7 @@ fn dict_get_single() {
Dict.single 0 {a: 1, c: 2} |> Dict.get 0
"#
),
r#"Ok { a: 1, c: 2 } : Result { a : Num *, c : Num * } [KeyNotFound]*"#,
r#"Ok { a: 1, c: 2 } : Result { a : Num *, c : Num * } [KeyNotFound]"#,
)
}

View file

@ -1015,6 +1015,15 @@ pub fn can_problem<'b>(
title = "OVERLOADED SPECIALIZATION".to_string();
severity = Severity::Warning;
}
Problem::UnnecessaryOutputWildcard { region } => {
doc = alloc.stack([
alloc.reflow("I see you annotated a wildcard in a place where it's not needed:"),
alloc.region(lines.convert_region(region)),
alloc.reflow("Tag unions that are constants, or the return values of functions, are always inferred to be open by default! You can remove this annotation safely."),
]);
title = "UNNECESSARY WILDCARD".to_string();
severity = Severity::Warning;
}
};
Report {

View file

@ -11731,4 +11731,26 @@ All branches in an `if` must have the same type!
denoted with .. - is that what you meant?
"###
);
test_report!(
unnecessary_extension_variable,
indoc!(
r#"
f : {} -> [A, B]*
f
"#
),
@r###"
UNNECESSARY WILDCARD /code/proj/Main.roc
I see you annotated a wildcard in a place where it's not needed:
4 f : {} -> [A, B]*
^
Tag unions that are constants, or the return values of functions, are
always inferred to be open by default! You can remove this annotation
safely.
"###
);
}

View file

@ -147,7 +147,7 @@ toHelpHelper = \@Parser parser, configs ->
List.append configs (Positional config)
|> Config
findOneArg : Str, Str, MarkedArgs -> Result { val : Str, newlyTaken : Taken } [NotFound]*
findOneArg : Str, Str, MarkedArgs -> Result { val : Str, newlyTaken : Taken } [NotFound]
findOneArg = \long, short, { args, taken } ->
argMatches = \{ index, found: _ }, arg ->
if Set.contains taken index || Set.contains taken (index + 1) then

View file

@ -2,13 +2,13 @@ interface Stdout
exposes [line, write]
imports [Effect, Task.{ Task }, InternalTask]
line : Str -> Task {} * [Write [Stdout]*]*
line : Str -> Task {} * [Write [Stdout]]
line = \str ->
Effect.stdoutLine str
|> Effect.map (\_ -> Ok {})
|> InternalTask.fromEffect
write : Str -> Task {} * [Write [Stdout]*]*
write : Str -> Task {} * [Write [Stdout]]
write = \str ->
Effect.stdoutWrite str
|> Effect.map (\_ -> Ok {})

View file

@ -19,7 +19,7 @@ pushStack = \ctx, data ->
# I think an open tag union should just work here.
# Instead at a call sites, I need to match on the error and then return the same error.
# Otherwise it hits unreachable code in ir.rs
popStack : Context -> Result [T Context Data] [EmptyStack]*
popStack : Context -> Result [T Context Data] [EmptyStack]
popStack = \ctx ->
when List.last ctx.stack is
Ok val ->
@ -66,7 +66,7 @@ with = \path, callback ->
callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount }
# I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is.
getChar : Context -> Task [T U8 Context] [EndOfData, NoScope]*
getChar : Context -> Task [T U8 Context] [EndOfData, NoScope]
getChar = \ctx ->
when List.last ctx.scopes is
Ok scope ->
@ -76,7 +76,7 @@ getChar = \ctx ->
Err ListWasEmpty ->
Task.fail NoScope
getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope]*
getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope]
getCharScope = \scope ->
when List.get scope.buf scope.index is
Ok val ->