Skip to content
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

Adapt default generation approach to deal with optional structs, lists #32

Open
sdboyer opened this issue Sep 12, 2021 · 2 comments
Open
Assignees
Labels
kind/bug Something isn't working kind/feature New feature or request

Comments

@sdboyer
Copy link
Contributor

sdboyer commented Sep 12, 2021

Problem

Our current approach to generating Typescript defaults is to create a typed constant, and leave it to the caller to figure out how to conditionally apply that constant to the type in order to replace absent values with the CUE-specified default. This approach has two, not-unrelated problems:

  1. Leaving the mechanism for default application to the user leaves room for user error
  2. It is impossible (barring some TS type system magic i'm unable to imagine) to express defaults in a const for values within optional nested structures

The latter case could be considered a bug, as it violates cuetsy's goal of having the same semantics in Typescript as in CUE. Specifically, this is the problem:

Outer: {
  top: string | *"foo"
  inner?: {
    req: string
    value: string | *"bar"
  }
} @cuetsy(kind="interface")

We have two basic choices - either we generate a default const that contains inner, or we don't:

export interface Outer {
  top: string
  inner?: {
    value: string
  }
}

// Option 1 - exclude the inner structure
export const outerDefault: Outer = {
  top: "foo"
}

// Option 2 - include the inner structure
export const outerDefault: Outer = {
  top: "foo"
  inner: {
    value: "bar"
  }
}

Both of these options are critically flawed. Option 1 simply omits default information entirely, flagrantly violating any notion of semantic equivalence. Option 2 unconditionally includes the nested inner value, which effectively conflates the absence of inner with it being present for any object to which this default is applied.

This is especially problematic if inner contains another field that is required and lacks a default, like inner.req. outerDefault becomes completely impossible to use just for its outermost field default (top) due to a property of one of its optional nested structures.

Solution?

i don't know exactly what the best solution here looks like, but i'm reasonably sure that it will entail switching to functions for applying defaults, as it's the only way to achieve the necessary conditionality. At least, i think it's the only way - perhaps some type magic would allow us to get around it. However, given that we also have the first problem - leaving application a problem for the user - it seems preferable to just embrace functions, as it allows us to target a single pattern for all defaults: a function that returns T, with a parameter list that is basically a Pick<T, (the list of required fields that lack a default)>. Each level of structure would probably end up getting its own function, at which point...idk, something like uncurrying to roll everything up to the parent? I haven't thought it fully through.

cc @ryantxu

@sdboyer
Copy link
Contributor Author

sdboyer commented Apr 25, 2022

i think we can do something less extreme for lists, at least for now.

For starters - and going back on something i'd said earlier - i think it makes the most sense to treat empty lists and structs as semantically equivalent to absent ones. This makes me itch a bit, as we don't do the same with scalar values.

I can't put words to a precise explanation of why we should treat them differently. But lists and structs are grouping constructs. Ultimately, it just seems more right to acknowledge this by treating them differently in the context of absence/default questions than the various scalar types.

@frossano-grafana
Copy link

not sure this is G10, to review when we discuss what we actually can include in G10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working kind/feature New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants