This is related to a plugin I am building for the @nexus/schema library (type-safe GraphQL), but it is purely a Typescript typing issue.
I have a rules system where all my rules are derived form this interface:
interface Rule<Type extends string, Field extends string> {
  resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>): boolean;
}
Note: The RootValue and ArgsValue are types used to fetch the "real" generated type or return any, this is a trick nexus uses to type everything without the use needing to explicitly specify the type.
See this link for the source code.
The two most basic are:
type Options = { cache?: boolean }
type RuleFunc<Type extends string, Field extends string> =
  (root: RootValue<Type>, args: ArgsValue<Type, Field>) => boolean;
class BaseRule<Type extends string, Field extends string> implements Rule<Type, Field> {
  constructor(private options: Options, private func: RuleFunc<Type, Field>) {}
  resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>) {
    // Do stuff with the options
    const result = this.func(root, args)
    return result
  }
}
class AndRule<Type extends string, Field extends string> implements Rule<Type, Field> {
  constructor(private rules: Rule<Type, Field>[]) { }
  resolve(root: RootValue<Type>, args: ArgsValue<Type, Field>) {
    return this.rules
      .map(r => r.resolve(root, args))
      .reduce((acc, val) => acc && val)
  }
}
I then define helpers:
const rule = (options?: Options) =>
  <Type extends string, Field extends string>(func: RuleFunc<Type, Field>): Rule<Type, Field> => {
    options = options || {};
    return new BaseRule<Type, Field>(options, func);
  };
const and = <Type extends string, Field extends string>(...rules: Rule<Type, Field>[]): Rule<Type, Field> => {
  return new AndRule(rules)
}
My problem is that I need to be able to support generic rules that apply to all types/fields and specific rules only for one type/field. But if I combine a generic rule with a specific rule, the resulting rule is a Rule<any, any> which then allows bad rules to be accepted.
const genericRule = rule()<any, any>((root, args) => { return true; })
const myBadRule = rule()<"OtherType", "OtherField">((root, args) => {
  return true;
})
const myRule: Rule<"Test", "prop"> = and(
  rule()((root, args) => {
    return false
  }),
  genericRule,
  myBadRule // THIS SHOULD BE AN ERROR
)
I am guessing that has to do in part with the lack of existential typing in Typescript that basically forces me to use a any in the first place, but is there a workaround that I could use to prevent the type the any from overriding my type. One workaround I found is to explicitly type the and, but that is not nice from a usability perspective.
EDIT 2: I created a playground with a simplified version so it easier to view the problem.
EDIT 3: As pointed out in the comments, never works with the previous example. I thus created this example for which never does not work. Also I reworked the issue so that all information is inside the issue for posterity. I also found that the reason never cannot be used is because of the ArgsValue type.
Thanks a lot!
EDIT 1:
I found a workaround, though it requires a change in the interface:
export interface FullRule<
  Type extends string,
  Field extends string
> {
  resolve(
    root: RootValue<Type>,
    args: ArgsValue<Type, Field>,
  ): boolean;
}
export interface PartialRule<Type extends string>
  extends FullRule<Type, any> {}
export interface GenericRule extends FullRule<any, any> {}
export type Rule<Type extends string, Field extends string> =
  | FullRule<TypeName, FieldName>
  | PartialRule<TypeName>
  | GenericRule;
With and becoming:
export const and = <Type extends string, Field extends string>(
  ...rules: Rule<Type, Field>[]
): FullRule<Type, Field> => {
  return new RuleAnd<Type, Field>(rules);
};
The and returns a properly typed FullRule<'MyType','MyField'> and will thus reject the badRule. But it does require that I add new methods to create partial and generic rules.