Skip to content

Conversation

@CarsonF
Copy link
Collaborator

@CarsonF CarsonF commented Apr 21, 2025

Fixes #1242.

scalarCastableFrom/scalarAssignableBy

This changes the scalarCastableFrom/scalarAssignableBy types to just pick the type based on the given type's FQN.
This is much easier for TS, esp with generated enums, as their members are statically written as well and TS was duck checking all of these.

- export type scalarAssignableBy<T extends $.ScalarType> =
- T extends _std.number ? _std.number :
- T extends _std.int64 ? _std.int64 :
- ...
+ export interface ScalarAssignableByMap {
+   "std::number": _std.$number;
+   "std::int64": _std.$int64;
+   ...
+ }

getSharedParentScalar

This also changes getSharedParentScalar to compare against the types' FQN.
This should be much more performant and still safe since these are scalar types with unique names.
We can also skip enumerating every case if A & B are the same.
This saves having to declare mapping for 80% of the scalars that can't be cast into another.

This removes 79 branches with my generated client, leaving only 6 specific scalar branches.
Before:

type getSharedParentScalar<A, B> =
  A extends _cal.$local_date ?
    B extends _cal.$local_datetime ?
      B
    :
    B extends _cal.$local_date ?
      B
    :
    never
  :
  A extends _std.$number ?
    B extends _std.$number ?
      B
    :
    never
  :
  A extends _sys.$VersionStage ?
    B extends _sys.$VersionStage ?
      B
    :
    never
  :
  ...

After:

type getSharedParentScalar<A, B> =
  AType extends $.ScalarType<infer AName> ?
  BType extends $.ScalarType<infer BName> ?
    AName extends "cal::local_date" ?
      BName extends "cal::local_datetime" ?
        B
      :
      BName extends "cal::local_date" ?
        B
      :
      never
    :
    ...
    AName extends BName ? A : never

Having scalars castable into others is the edge case.
Optimize this type by checking if the two types are equal
before checking the edge cases.
This saves having to declare mapping for 80% of the scalars
that can't be cast into another.
@CarsonF CarsonF force-pushed the optimize-cast-maps-shared-parent branch from 4c9de16 to 49f975e Compare April 21, 2025 15:15

const returnTypes = new Set<string>();

const uncastables = new Set(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like uncrustables but saltier.
Open to different names.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is the perfect name 👨‍🍳


const outerCastableTo = casting(outer.id);
staticMap.writeln([t` A extends ${getRef(outer.name)} ?`]);
if (!uncastables.has(outer)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these ifs feel dirty, but I understand wanting to keep the runtime logic next to compile time. But I'm not sure now if we should deviate since the compile time is changing its logic somewhat.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm perfectly happy to make this division more clear here vs in the generated code: we should prioritize the maintenance of this code over the understandability of the generated code. No change needed, but I'm 100% with you on wanting to branch harder here instead of littering if statements everywhere.

@CarsonF
Copy link
Collaborator Author

CarsonF commented Apr 21, 2025

This is to prevent an excessive depth error, and it appears that the current TS benchmarks do not measure these depth calculations.

@CarsonF CarsonF requested review from Copilot and scotttrinh April 21, 2025 17:14
@CarsonF CarsonF marked this pull request as ready for review April 21, 2025 17:14
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes the getSharedParentScalar type generation for the common "80%" case by short-circuiting comparisons when the two scalar types are equal and by excluding uncastable scalars.

  • Moves the equality check (IsEqual) to the top of the generated type definition.
  • Introduces an uncastables set to conditionally generate type branches and reduces the overall branch count.

@CarsonF
Copy link
Collaborator Author

CarsonF commented Apr 21, 2025

Just tried v6 on my codebase again with this. This does fix the depth problem with this type alias, but assignableBy & castableFrom are still a problem.

Copy link
Collaborator

@scotttrinh scotttrinh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👑


const returnTypes = new Set<string>();

const uncastables = new Set(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is the perfect name 👨‍🍳


const outerCastableTo = casting(outer.id);
staticMap.writeln([t` A extends ${getRef(outer.name)} ?`]);
if (!uncastables.has(outer)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm perfectly happy to make this division more clear here vs in the generated code: we should prioritize the maintenance of this code over the understandability of the generated code. No change needed, but I'm 100% with you on wanting to branch harder here instead of littering if statements everywhere.

@CarsonF
Copy link
Collaborator Author

CarsonF commented Apr 21, 2025

Thanks for the review @scotttrinh.
I think the remaining issues for me with assignableBy & castableFrom come from the need for TS look at each member of all my enums.

export type scalarCastableFrom<T extends $.ScalarType> =
  T extends _Project.$Status ? _Project.$Status :
  ...;

export type $Status = {
  "InDevelopment": $.$expr_Literal<$Status>;
  "Active": $.$expr_Literal<$Status>;
  "Terminated": $.$expr_Literal<$Status>;
  "Completed": $.$expr_Literal<$Status>;
  "DidNotDevelop": $.$expr_Literal<$Status>;
} & $.EnumType<"Project::Status", ["InDevelopment", "Active", "Terminated", "Completed", "DidNotDevelop"]>;

What do you think about only checking the FQNs instead?

export type scalarCastableFrom<T extends $.ScalarType> =
  T extends $.ScalarType<infer N> ?
    N extends "Project::Status" ? _Project.$Status :
    ...
  : never;

Actually following this train of thought we could just use a mapping instead.
Thoughts?
Is there a use case I'm not thinking of where only checking the name is insufficient?

This is `O(1)` compared to the previous `O(N)`.

This is easier on TS esp for enums, as enums have all of their members declared statically and the previous impl was having TS
check all of these members for duck compatibility.
@CarsonF CarsonF changed the title Optimize getSharedParentScalar for the "80%" case [QB] Optimize scalar cast maps Apr 21, 2025
@CarsonF CarsonF requested a review from scotttrinh April 21, 2025 19:00
@CarsonF
Copy link
Collaborator Author

CarsonF commented Apr 21, 2025

Alright well if this truly is the direction, I should probably change getSharedParentScalar to just check names as well.

@CarsonF CarsonF force-pushed the optimize-cast-maps-shared-parent branch 2 times, most recently from 0a34451 to dbddbfa Compare April 21, 2025 19:56
@CarsonF CarsonF force-pushed the optimize-cast-maps-shared-parent branch from dbddbfa to 50d5652 Compare April 21, 2025 20:05
@CarsonF
Copy link
Collaborator Author

CarsonF commented Apr 21, 2025

Ok I'm done here. These changes do fix all related problems in my codebase, blocking my v6 upgrade.
Perhaps a future PR could untangle what's here more.

@scotttrinh scotttrinh merged commit b43cdb8 into geldata:master Apr 21, 2025
10 checks passed
@CarsonF CarsonF deleted the optimize-cast-maps-shared-parent branch April 21, 2025 20:47
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

Successfully merging this pull request may close these issues.

QB getSharedParentScalar excessive type depth

2 participants