-
Notifications
You must be signed in to change notification settings - Fork 9
Updates to derive and lift
#1838
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
Conversation
…eneration on lift and derive
…mas from them for and
...s-transformers/test/fixtures/ast-transform/schema-generation-derive-no-generics.expected.tsx
Outdated
Show resolved
Hide resolved
…ixture showing we do correct inference and schema generation now
...s-transformers/test/fixtures/ast-transform/schema-generation-handler-event-only.expected.tsx
Outdated
Show resolved
Hide resolved
| /// <cts-enable /> | ||
| import { handler, JSONSchema } from "commontools"; | ||
| // No type annotations at all - should generate false schemas (unknown type) | ||
| export const genericHandler = handler(false as const satisfies JSONSchema, false as const satisfies JSONSchema, (event, state) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need the as const satisfies JSONSchema when we use true or false. It shouldn't be a problem, but it's more verbose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes sense, thanks. i took this linear issue to come back to this to try to wrangle this PR in a more manageable form :) https://linear.app/common-tools/issue/CT-954/dont-generate-as-const-satisfies-jsonschema-for-truefalse-schemas
…odes in the type registry
| * Uses WeakMap with node identity as key. Node identity is preserved when | ||
| * transformers are applied in sequence via ts.transform(). | ||
| */ | ||
| export class TypeRegistry { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type TypeRegistry = WeakMap<ts.Node, ts.Type> would suffice here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooooh yes great point thank you, done
| return [ | ||
| createModularOpaqueRefTransformer(program), | ||
| createSchemaTransformer(program), | ||
| createModularOpaqueRefTransformer(program, { typeRegistry }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the lifetime of the ts.Nodes in the weak map? I didn't think the Program held a reference to all nodes it's created, and if it doesn't, what persists between these transformation calls that ensures the weak map does not clean its entries?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly don't have a great theoretical answer here - it appears empirically to be the case that Typescript's transformation pipeline passes the updated-in-place SourceFile from one transformer to the next, such that when the later transformers use the SourceFile object (which is the root node of the AST) they receive to walk the AST, they encounter the Nodes that were created/modified by the previous transformers in the pipeline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed that even when using a transformed (new) SourceFile, walking produces the same (instance equality) nodes when it can -- probably stored in the Program
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for checking on that
| // First check if we have a registered Type for this node | ||
| // (from schema-injection when synthetic TypeNodes were created) | ||
| let type: ts.Type; | ||
| if (typeRegistry && typeRegistry.has(node)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these the same Nodes that have registered to the type registry, e.g. the same instance (as opposed to a different instance representing the same underlying syntax fragment)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see the comment that node identity is preserved during transformations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it's the same Node object with the same identity
packages/ts-transformers/src/opaque-ref/rules/schema-injection.ts
Outdated
Show resolved
Hide resolved
| /// <cts-enable /> | ||
| import { derive, h } from "commontools"; | ||
| declare const value: number; | ||
| // This derive is INSIDE JSX and should NOT be transformed with schemas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should transform derive, handler, etc. inside JSX as well + add schemas, even for the ones that are automatically added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for noticing this, i fixed this now
| if (typeof valueSchema === "boolean") { | ||
| // Boolean schemas (true/false) cannot have properties | ||
| // Wrap in an object schema | ||
| return { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than { allOf: [valueSchema], default: defaultValue} here, can we do the { default: defaultValue } / {not: true, default: defaultValue}?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure thing, done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if the inclusion of these three type-inference files was intentional.
They're useful, since other devs will probably want to explore the functionality as well, so reasonable to include.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of leaving them in through the merge to main, then maybe deleting them off main later - that way they'd be in the repo history but not taking up space in the folder... but I'm open to whatever, not sure what's best!
ubik2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
Promisereturn type optionliftandderiveSummary by cubic
Removes async support from derive/lift and adds automatic schema generation using TypeScript type inference when generics are omitted. This simplifies the API and cuts boilerplate by inferring and injecting schemas at build time.
New Features
Migration