Skip to content

Anonymous types must have locally-defined names in order to be stable #103

Description

@popematt

Suppose I have the following schema:

$ion_schema_2_0

type::{
  name: Foo,
  fields: {
    a: { fields: { x: int, y: int } },
    b: int,
  }
}

type::{
  name: Bar,
  type: list,
  element: { fields: { u: float, v: float } }
}

As it is currently implemented, the code generator might generate something like this. (I'm using Kotlin, rather than Java, for conciseness here, but the principle will still apply.)

class Foo(val a: AnonymousType1, val b: Int)

class AnonymousType1(val x: Int, val y: Int)

class Bar(val value: List<AnonymousType2>)

class AnonymousType2(val u: Double, val v: Double)

Now, suppose I add a new field to Foo, like this:

type::{
  name: Foo,
  fields: {
    a: { fields: { x: int, y: int } },
    b: int,
    c: { fields: { id: string, value: int },
  }
}

The code generator has no way of knowing how to keep the generated code stable, and would likely end up generating these classes:

class Foo(val a: AnonymousType1, val b: Int, val c: AnonymousType2)

class AnonymousType1(val x: Int, val y: Int)
class AnonymousType2(val id: String, val value: Int)

class Bar(val value: List<AnonymousType3>)

class AnonymousType3(val u: Double, val v: Double)

In this scenario, AnonymousType2 and Bar have been redefined in a way that will break the code of anyone who is using either one of these classes in any non-trivial way. This cannot be solved by sorting the anonymous types because the only sort attribute that would not result in types being moved/redefined would be the timestamp that the inline type was added to the schema for the first time.


There are a few things that I think we must do in order to fix this problem, but I'm certainly open to hearing other solutions.

  1. All inline types must be placed inside a namespace corresponding to the parent type. In Java, that would mean e.g. org.example.AnonymousType1 would instead be org.example.Foo.AnonymousType1. In Rust, it's a little trickier because you could end up with naming clashes where you create a module called foo to hold children of Foo when there's already another module you're generating called foo, but this is solvable.
  2. Inline types, if they have a $code_gen_name field, should use that name; otherwise...
  • Inline types in a fields constraint should be named after their respective fields. I.e.AnonymousType1 would be named Foo.A in Java/Kotlin.
  • Inline types in an element constraint should be named Element. I.e. AnonymousType2 would be named Bar.Element in Java/Kotlin.
  • Inline types in type or all_of should have their constraints flattened into the containing type.
  • Inline types in one_of constraint have two possible solutions
    • They should either be required to have a code-gen name (so that we can add a variant-specific annotation) OR
    • They should be named Variant0..VariantN, and every one_of constraint must have its own distinct counter (i.e. not a globally incremented counter). E.g. Car.Variant0, Car.Variant1, etc.
  • Inline types in ordered_elements should be named Element0..ElementN, and every ordered_elements constraint must have its own distinct counter.

I might be missing a case somewhere, but I think that mostly covers them all.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

code generationImprovements for code generation subcommand `generate`

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions