Deep property inference

/**
 * List of types with different properties
 */
type Animal =
  | {
      name: "cat";
      hair: "long" | "short";
    }
  | {
      name: "dog";
      hair: "long" | "short";
    }
  | {
      name: "fish";
      scale: "blue" | "grey";
    };

/**
 * Type of animal name
 */
type AnimalName = Animal["name"];

/**
 * Type of animal with the property hair
 */
type AnimalWithHair = Animal extends infer Parent
  ? // biome-ignore lint/suspicious/noExplicitAny: <explanation>
    Parent extends { hair: any }
    ? Parent
    : never
  : never;

/**
 * Hair type for animal with this name
 */
type AnimalHair<
  Name extends AnimalName,
  CurrentAnimal = Animal extends infer Parent
    ? Parent extends { name: Name }
      ? Parent
      : never
    : never,
  Method = CurrentAnimal extends AnimalWithHair ? CurrentAnimal["hair"] : never,
> = Method;

/**
 * Random animal
 */
const animal = [
  { name: "cat", hair: "short" },
  { name: "dog", hair: "short" },
  { name: "fish", scale: "blue" },
][Math.floor(3 * Math.random())];

/**
 * Test if the animal has same properties with Typescript arguments autocompletion
 */
function testAnimal<Name extends AnimalName>(
  name: Name,
  hair?: AnimalHair<Name>,
) {
  return (
    name === animal.name &&
    (hair ? hair === (animal as AnimalWithHair).hair : true)
  );
}

testAnimal("fish");
testAnimal("cat", "long");

Type guard for deep optional type of type