RegExp.Result.t inconsistency

Recently I’ve been playing around with regex to consume some info from query parameters and noticed what I think is an inconsistency between what js can return and what ReScript types results as.

Issue
Given this example regex:

let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)") 

As expected, when all the groups match, we get a complete array of strings.

switch (regex->RegExp.exec("applesnpears")) {
  // results is typed as "array<string>"
  | Some(results) => Console.log(results) // logs [ "applesnpears", "applesn", "apples", "pears" ]
  | None => ()
}

However, when not all the groups match (thanks to optional groups), js will return an array of string or undefined.

switch (regex->RegExp.exec("pears")) {
  // results is typed as "array<string>"
  | Some(results) => Console.log(results) // logs [ "pears", undefined, undefined, "pears" ]
  | None => ()
}

If I were to manipulate the string some way (String.split or the like, the app would error out).
ReScript Playground showing this

Current workaround
To work around this, I would have to know that these optionals may be undefined, and use something like a Nullable to get to what I would expect, an option.

type stairs = {
  first: string,
  rest: option<string>
}

let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)") 
let parseQuery = (queryStr) => {
  switch (regex->RegExp.exec("applesnpears")) {
    | Some([_, _, nullableRest, first]) =>
      Some({
        first,
        rest: nullableRest->Nullable.make->Nullable.toOption
      })
    | None
    | [] => None
  }
}

Expected logic
I would expect that RegExp.Result.t was typed to be array<option<string>> such that you had to manage the cases that match and which that don’t.

type stairs = {
  first: string,
  rest: option<string>
}

let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)") 
let parseQuery = (queryStr) => {
  switch (regex->RegExp.exec("applesnpears")) {
    | Some([_, _, rest, Some(first)]) => Some({ first, rest })
    | None
    | [] => None
  }
}

I was just going to open an issue in the rescript-core repo for this. As a workaround you can use Js.String2.match_, which is typed to return an option<array<option<string>>>

3 Likes