siefkenj / unified-latex

Utilities for parsing and manipulating LaTeX ASTs with the Unified.js framework
MIT License
85 stars 20 forks source link

type guard is not working when visitinfo is provided in test function #30

Closed mooncaker816 closed 1 year ago

mooncaker816 commented 1 year ago

It looks like the signature of test function is not consistent.

there is a second argument info in the test function of VisitOptions:

type VisitOptions = {
    startingContext?: VisitorContext;
    /**
     * Type guard for types that are passed to the `visitor` function.
     */
    test?: (node: Ast.Ast, info: VisitInfo) => boolean;
    /**
     * Whether arrays will be sent to the `visitor` function. If falsy,
     * only nodes will be past to `visitor`.
     */
    includeArrays?: boolean;
};

but currently only one argument is in the signature of the test function constraint, so PossibleTypes will always be inferred as the type of node when the test function has two arguments.

type GetGuard<T> = T extends (x: any) => x is infer R ? R : never;
/**
 * Gets the type that a type-guard function is guarding. If
 * the guard type cannot be determined, the input type is returned.
 */
type GuardTypeOf<T extends (x: any) => boolean> = GetGuard<T> extends never
    ? T extends (x: infer A) => any
        ? A
        : never
    : GetGuard<T>;

/**
 * Extracts the guard type from the `test` function provided in a
 * `VisitOptions` argument.
 */
type GuardFromOptions<
    Opts extends VisitOptions,
    PossibleTypes = Ast.Ast
> = Opts extends {
    test: infer R;
}
    ? R extends (x: any) => boolean
        ? // A guard like `typeof Array.isArray` will return `any[]` as the type.
          // This type cannot be narrowed, so instead we use it to pick from
          // the set of all possible types.
          Extract<PossibleTypes, GuardTypeOf<R>>
        : PossibleTypes
    : PossibleTypes;

Maybe the signature should be changed to (node: Ast.Ast, info?: VisitInfo) and (x: any, y?: any)?

siefkenj commented 1 year ago

Do you have some example code that doesn't type check correctly?

mooncaker816 commented 1 year ago

Yes, this is a simple example shows that the type guard works differently when the VisitInfo is provided or not. https://codesandbox.io/s/typescript-playground-export-forked-0qlgow?file=/index.ts

Basically i want to extract the group contents under some specific macro (e.g \abc{{1}{2}{3}{4}}) with VisitInfo:

const groupMatcher1 = (node: Ast.Ast, info: VisitInfo): node is Ast.Group =>
  match.group(node) && match.createMacroMatcher(["abc"])(info.parents[1]);

const extractGroups1 = (root: Ast.Root): string[] => {
  let groups: string[] = [];
  visit(
    root,
    (group) => {
      // group is typed as "Ast.Node | Ast.Argument" not "Ast.Group"
      groups.push(printRaw(group.content));
    },
    {
      test: groupMatcher1
    }
  );
  return groups;
};

without VisitInfo

const extractGroups2 = (root: Ast.Root): string[] => {
  let groups: string[] = [];
  visit(
    root,
    (group) => {
      // group is typed as "Ast.Group"
      groups.push(printRaw(group.content));
    },
    {
      test: match.group
    }
  );
  return groups;
};
siefkenj commented 1 year ago

Thanks! PR #31 should fix this and hopefully be robust to any future changes.

mooncaker816 commented 1 year ago

Thank you for this great lib.

siefkenj commented 1 year ago

Should be fixed in 1.3.2 :-D