Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

between and sepBy return unknown type #109

Open
devurandom opened this issue Oct 14, 2023 · 4 comments
Open

between and sepBy return unknown type #109

devurandom opened this issue Oct 14, 2023 · 4 comments

Comments

@devurandom
Copy link

devurandom commented Oct 14, 2023

Following code has type errors with TypeScript 5.1.3:

import {
    between,
    char,
    regex,
    sepBy,
    sequenceOf,
    whitespace
} from "arcsecond";

const word = regex(/^\w+/);

const quotedString = between(char('"'))(char('"'))(regex(/^[^"]+/));

const attribute = sequenceOf([word, char("="), quotedString]).map(
    ([name, _, value]): [string, string] => [name, value]
);

const attributeList = sepBy(whitespace)(attribute).map(
    (values): [string, string][] => values
);

export const parse = (input: string) => attributeList.run(input);
❯ pnpm build

> arcsecond-repro@0.1.0 build [REDACTED]/arcsecond-repro
> tsc -p .

src/parser.ts:15:52 - error TS2322: Type 'unknown' is not assignable to type 'string'.

15     ([name, _, value]): [string, string] => [name, value]
                                                      ~~~~~

src/parser.ts:19:37 - error TS2322: Type 'unknown[]' is not assignable to type '[string, string][]'.
  Type 'unknown' is not assignable to type '[string, string]'.

19     (values): [string, string][] => values
                                       ~~~~~~


Found 2 errors in the same file, starting at: src/parser.ts:15

 ELIFECYCLE  Command failed with exit code 2.

I would expect:

  • quotedString to contain type string, but TypeScript detects it as unknown.
    • Generally: I would expect between(_)(_)(T) to have type T instead of unknown.
  • attributeList to contain type [string, string][], but TypeScript detects it as unknown[].
    • Generally: I would expect sepBy(_)(T) to have type T[] instead of unknown[].

Please find a complete example in https://github.com/devurandom/arcsecond-issue-109-repro.

@devurandom devurandom changed the title between and sepBy return unknown type between and sepBy return unknown type Oct 14, 2023
@francisrstokes
Copy link
Owner

Hey @devurandom,
Thanks for reporting the issue, and thanks for taking the time to set up a proper repro environment! This is unfortunately a known issue with TypeScript, and how it resolves types in curried functions:

const f = (a: unknown) => (b: unknown) => (c: string): [typeof a, typeof b, typeof c] => {
    return [a, b, c];
}

const g = f(1)(true)("hello"); // -> const g: [unknown, unknown, string]

A workaround for this is to use as Parser<x, y, z> to ensure that your parsers are typed correctly.

@devurandom
Copy link
Author

devurandom commented Oct 14, 2023

@devurandom
Copy link
Author

Hey @devurandom, Thanks for reporting the issue, and thanks for taking the time to set up a proper repro environment! This is unfortunately a known issue with TypeScript, and how it resolves types in curried functions:

const f = (a: unknown) => (b: unknown) => (c: string): [typeof a, typeof b, typeof c] => {
    return [a, b, c];
}

const g = f(1)(true)("hello"); // -> const g: [unknown, unknown, string]

Could you explain this a bit more, please? The return type of g in your example seems quite expected to me.

What I have with between seems more like:

const f = (a: unknown) => (b: unknown) => (c: string): [typeof a, typeof b, typeof c] => {
    return [a, b, c];
}

const g = f(1)(true)("hello"); // -> const g: [unknown, unknown, unknown] //<< The third invocation / curry argument has the wrong type.

And confusingly the type seems semi-correct, getting the "array aspect" correctly:

const f = (a: unknown) => (b: unknown) => (c: string[]): [typeof a, typeof b, typeof c] => {
    return [a, b, c];
}

const g = f(1)(true)(["hello","world"]); // -> const g: [unknown, unknown, unknown[]] //<< TypeScript noticed the third invocation returns an array, but it got the type of the elements wrong.

@devurandom
Copy link
Author

  • Would it be possible to add a non-curried version of sepBy and between (and any other to which this applies), to avoid the TypeScript issue?
    • I have in the past often created functions like (x) => sepBy(_, x), but sepBy(_)(x) as Parser<X> wouldn't be the first thing I would try.

What I did as a temporary countermeasure:

import {
    between as _between,
    Parser,
    sepBy as _sepBy,
} from "arcsecond";

const between = <L, R, T>(
    left: Parser<L>,
    right: Parser<R>,
    value: Parser<T>
) => _between(left)(right)(value) as Parser<T>;

const sepBy = <S, T, E, D>(sep: Parser<S, E, D>, value: Parser<T, E, D>) =>
    _sepBy(sep)(value) as Parser<T[], E, D>;

A bit surprising was that between could not be defined as:

const between = <L, R, T, E, D>(
    left: Parser<L, E, D>,
    right: Parser<R, E, D>,
    value: Parser<T, E, D>
) => _between(left)(right)(value) as Parser<T, E, D>;

because TypeScript would report an error:

TS2345: Argument of type  Parser<L, E, D>  is not assignable to parameter of type  Parser<L, string, any> 
  Type  E  is not assignable to type  string 

probably because between is:

const between =
    <L, T, R>(leftParser: Parser<L>) =>
    (rightParser: Parser<R>) =>
    (parser: Parser<T>): Parser<T> =>
        sequenceOf([leftParser, parser, rightParser]).map(([_l, x, _r]) => x);

and not:

const _between =
    <L, T, R, E, D>(leftParser: Parser<L, E, D>) =>
    (rightParser: Parser<R, E, D>) =>
    (parser: Parser<T, E, D>): Parser<T, E, D> =>
        sequenceOf([leftParser, parser, rightParser]).map(([_l, x, _r]) => x);

which in turn is an effect of sequenceOf not taking E and D template arguments.

sepBy did not have this problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants