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

Changes to generate anonymous types inside parent type's namespace #109

Merged
merged 4 commits into from
May 28, 2024

Conversation

desaikd
Copy link
Contributor

@desaikd desaikd commented May 15, 2024

Issue #103:

Description of changes:

This PR works on first portion of #103 issue which is defining a namespace corresponding to parent type for anonymous types. As described in the issue:

  • For Java, this means that anonymous types(e.g. AnonymousType1) will be placed inside its parent class (e.g. Foo) in the same namespace (e.g. Foo.AnonymousType1)
  • For Rust, this means that anonymous types(e.g. AnonymousType1) will be placed inside its parent type module(e.g. foo).

Generated code:

  • Generated code for given ISL test files in Java can be found here
  • Generated code for given ISL test files in Rust can be found here

List of changes:

  • Changes to generate anonymous types as child classes in Java
    • Adds a new template anonymous_Type.templ which has macros to generate
      anonymous types as child classes
    • Modifies class.templ to include anonymous types as child classes
    • Modifies setter methods in class.templ to include base types in the
      arguments instead of AnonymousTypes
    • Modifies tests to remove reference to AnonymousTypes
    • Adds new util structure AnonymousType which will be used to render
      anonymous types as chilsren and modify setter methods to include base
      types
  • Adds changes for generating anonymous types in child modules for Rust
    • Adds a new anonymous_type.templ template to generate a child module
      for anonymous types
    • Modifies struct.templ to use anonymous_type template

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

* Adds a new template anonymous_Type.templ which has macros to generate
anonymous types as child classes
* Modifies class.templ to include anonymous types as child classes
* Modifies setter methods in class.templ to include base types in the
arguments instead of `AnonymousType`s
* Modifies tests to remove reference to `AnonymousType`s
* Adds new util structure `AnonymousType` which will be used to render
anonymous types as chilsren and modify setter methods to include base
types
* Adds a new `anonymous_type.templ` template to generate a child module
for anonymous types
* Modifies `struct.templ` to use anonymous_type template
@desaikd desaikd requested review from popematt and zslayton May 15, 2024 23:50
@@ -0,0 +1,209 @@
{# following macro defines an anonymous type as children class for its parent type definition #}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This file adds 3 new macros for generating anonymous types.

  • anonymous_type: Generates a new child class for given anonymous type based on its properties (e.g. target_kind_name, abstract_data_type).
  • initialize_anonymous_type: This is helper macro to initialize anonymous types inside setter methods.
  • define_params_for_anonymous_type: This is helper macro to define arguments to setter methods. (This macrohelps get the base type as argument instead of anonymous type itself, check this code snippet to see how this is used)

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just get rid of the anonymous types when it's a List<Something>? (Is that going to be done in a different PR?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did you mean to get rid of creating a nested class which contains List<Something>? I have created a separate class for anonymous type with ISL like {type: list, element: int} because there could be more constraints added to this anonymous type which might require adding more variables/APIs to this anonymous type class with List<Something>.
But I can add a check to see if there are no more constraints then the type and element then I can skip generating anonymous type for it.

@@ -0,0 +1,135 @@
{# following macro defines an anonymous type as children class for its parent type definition #}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This file adds a new macros for generating anonymous types. This macro generates a new child class for given anonymous type based on its properties (e.g. target_kind_name, abstract_data_type).

{# Includes the macros for anonymous types that will be added as child classes #}
{% import "anonymous_type.templ" as macros %}
use {{ target_kind_name | snake }}::{{ target_kind_name }};
pub mod {{ target_kind_name | snake }} {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This change adds a new module with the name top level type definition. This module is where all the intermediate/anonymous types will be generated.

@@ -159,4 +173,8 @@ public final class {{ target_kind_name }} {
writer.stepOut();
{% endif %}
}

{% for inline_type in anonymous_types -%}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This is where all the anonymous types will be generated for given top level type definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

Will this approach also work for nested types within other nested types? E.g.:

type::{
  name: Foo
  fields: {
    position: {
      x: {
        fields: { 
          real_part: float, 
          imaginary_part: float 
        }
      },
      y: {
        fields: { 
          real_part: float, 
          imaginary_part: float 
        }
      }
    }
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it does because the template defined for anonymous types also has statement to check for its anonymous types and generate it as a child class.
This gist has generated code for your example ISL.
Here's the statement in anonymous type template that generated nested types within other nested types: https://github.com/amazon-ion/ion-cli/pull/109/files#diff-4316040659b5b43183dc6dbee67e1f131e4942f950cd2e017b7c24b352b2e51dR197

}

{% for inline_type in anonymous_types -%}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This is where all the anonymous types will be generated for given top level type definition.

Comment on lines 36 to 41
pub struct AnonymousType {
pub(crate) target_kind_name: String,
pub(crate) fields: Vec<Field>,
pub(crate) abstract_data_type: AbstractDataType,
pub(crate) anonymous_types: Vec<AnonymousType>,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(🗺️ PR tour) This is a utility that is used to store anonymous types and use it in the templates to render as child classes/structs for a type definition.

{{ field.name | snake }},
{% endfor %}
}
#[derive(Debug, Clone, Default)]
Copy link
Contributor Author

@desaikd desaikd May 16, 2024

Choose a reason for hiding this comment

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

(🗺️ PR tour) Most of the following changes are due to indentation of having struct inside a module. The only change to consider is here.

Copy link
Contributor

@zslayton zslayton left a comment

Choose a reason for hiding this comment

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

This looks good to me, though I must admit I'm not versed enough in Tera template syntax to feel like I reviewed that part thoroughly.

Do the generated unit tests exercise this new functionality?

src/bin/ion/commands/beta/generate/generator.rs Outdated Show resolved Hide resolved
src/bin/ion/commands/beta/generate/generator.rs Outdated Show resolved Hide resolved
Comment on lines 123 to 128
/**
* Writes a {{ target_kind_name }} as Ion from an {@link IonWriter}.
*
* This method does not close the writer after writing is complete.
* The caller is responsible for closing the stream associated with the writer.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like that this generates javadoc 👍

Comment on lines -42 to 49
s.setC(new AnonymousType3(new ArrayList<String>()));
s.setC(new ArrayList<String>());
assertEquals(true, s.getC().getValue().isEmpty(), "s.getC().isEmpty() should return `true`");
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to make a similar change to the getter, or else it will just lead to user confusion. If setC(List<String> c) accepts a list, then getC() should return a list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't change the getters because for an inline type that is for example a struct it would require to return the struct(e.g. check this test case of nested struct hat has a field c which is also a struct ). This will be simplified once we have $codegen_name or similar which will return class C instead of AnonymousTypeX.
Other option is to instead let all the getX() methods of the nested struct be part of the parent class.

@@ -0,0 +1,209 @@
{# following macro defines an anonymous type as children class for its parent type definition #}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just get rid of the anonymous types when it's a List<Something>? (Is that going to be done in a different PR?)

@@ -159,4 +173,8 @@ public final class {{ target_kind_name }} {
writer.stepOut();
{% endif %}
}

{% for inline_type in anonymous_types -%}
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this approach also work for nested types within other nested types? E.g.:

type::{
  name: Foo
  fields: {
    position: {
      x: {
        fields: { 
          real_part: float, 
          imaginary_part: float 
        }
      },
      y: {
        fields: { 
          real_part: float, 
          imaginary_part: float 
        }
      }
    }
  }
}

src/bin/ion/commands/beta/generate/utils.rs Outdated Show resolved Hide resolved
@desaikd
Copy link
Contributor Author

desaikd commented May 17, 2024

Do the generated unit tests exercise this new functionality?

Yes, it does. The nested_struct.isl file contains a 2-level anonymous type/nested type for which the code generates, In Java the anonymous class into the parent class and for Rust the anonymous struct into parent struct's module.

@desaikd
Copy link
Contributor Author

desaikd commented May 23, 2024

As per the conversation in this PR and offline, it would be simpler to express sequence and scalar nested types as is instead of creating another nested class/struct for it. This will be implemented in a separate PR.
Example ISL:

type::{
   name: foo,
   fields: {
      sequence_type: { type: list, element: int },
      scalar_type: { type: string }
   }
}

Output Java:

class Foo {
   private sequence_type: ArrayList<Integer>; // current impl would treat it as `NestedTypeX`
   private scalar_type: String; // current impl would treat it as `NestedTypeY`
   ...
}

@desaikd desaikd merged commit b2cfecd into amazon-ion:master May 28, 2024
4 checks passed
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.

3 participants