switchupcb / copygen

Go generator to copy values from type to type and fields from struct to struct (copier without reflection). Generate any code based on types.
https://switchupcb.com/copygen-license-exception/
GNU General Public License v3.0
348 stars 22 forks source link

Copygen v0.5 #34

Open switchupcb opened 1 year ago

switchupcb commented 1 year ago

Version 0.5 will require the following changes.

The following changes are dependent on a third-party.

Status Started on 3/24/23. Paused on 3/25/23. Resumed on 3/26/23. Paused on 3/27/23. Resumed on 3/29/23. Paused on 3/29/23 (Housing Issues).

ETA This target is under development.

switchupcb commented 1 year ago

Cast Option

Here is the specification for the upcoming /cast option.

Purpose

The purpose of the cast option is to streamline type conversion and assertion in field-to-field copies.

Concepts

There are three main features in the Go Programming Language Specification that Copygen uses to assign variables to another.

Functionality

Typecasting is not a feature in the Go language. Instead, there are type conversions and type assertions. That said, the main domain of Copygen is to handle field-to-field assignment. This involves assignability, type conversion, and type assertion to assign variables of one type to a variable of another type.

Assignability

The main Copygen program already handles assignability of fields with identical field names AND types. Copygen alternatives that focus on using solely field names to match are prone to mismatching types, and alternatives that focus on using solely type names to match miss the entire point of using Copygen (or the manual map option). So while Go has six main rules to check for valid type assignment, Copygen only cares about the case for the end-user (where both field names and type definitions match).

Here is an example of a case that maintains assignable fields (via the go/types AssignableTo() function), but shouldn't be assigned in Copygen.

// Placeholder represents a basic type.
type Placeholder bool

// Copygen defines the functions that will be generated.
type Copygen interface {
    NoMatchBasicAlias(B bool) (B Placeholder)
}

In this example, the B bool field is assignable to the aliased B Placeholder field. Each field maintains the same name, but since the types are not identical, it's assumed that the end user does NOT want these fields assigned. If the user did, they would have either used equivalent type definitions or a map and convert option to type convert the field (i.e Placeholder(B).

If this assumption wasn't implemented, then cases such as byte assignment to string would occur while automatching, which is not always intended.

All this to say that Copygen handles assignability and is only missing direct conversion and assertion options.

Conversion

Type conversion can be fulfilled in Copygen using a map option with a convert option (function). However, it may be preferable to allow automatching of type convertable fields. This functionality is what the cast option planned to address.

Assertability

Type assertion can be used on interfaces to assert an interface into an object of another type. This is another way to transform a field of one type to another when applicable. The reverse — assigning an object to an interface — does not require assertion, but the behavior is still not supported in Copygen without the use of convert functions.

Quality of Life

There are other functionalities involving type conversion, type assertion, or direct conversion functions that the end user may want to use without requiring a map and convert option. This includes but is not limited to custom type to string assignments using the type's String() function or ToOtherType() function equivalent.

Features

Instead of creating a separate option for each of these functionalities, each one can be addressed in a single cast option. This name works out because — as a reminder — Go doesn't feature "typecasting". So users can't technically confuse the option with a specific feature of the Go language. In other words, the cast option of Copygen lets end users perform direct type conversion, assertion, functions (i.e type.String()) with the automatcher.

Here is a list of features the cast option covers:

Matcher

The cast option effects matcher behavior: There must be a way to specify how casting works with matched options. This presents multiple approaches to the option's semantics.

  1. The cast option operates as a matching modifier (i.e /cast from to [optional | .String() | .Property]). When two fields with similar names, but different types match the cast option rules, the matcher will attempt to "cast" the from-field to the to-field using an automatic resolver or the provided (optional) resolver. Thus, the cast option is usable with any matcher, and does NOT disable the automatcher by default.
  2. The cast option operates as a matching operation (similar to map). The cast option functions like map or the automatcher, but attempts to "cast" the from-field to the to-field using an automatic resolver or the provided (optional) resolver. Thus, the cast option is NOT usable with any matcher, and DISABLES the automatcher.

Implementation "2" entails implementing an automatcher with casting enabled, or a manual matcher with casting enabled, or both. This would warrant a question on "why there are multiple casting matcher implementations (for each type of matcher)?" That's because casting is a matching modifier (implementation "1") and not a standalone matching operation. In other words, cast must work with other matching options (i.e map, tag, automatch) to modify matched fields.

map should remain a standalone option for a similar reason: It's a matching operation, not a modifier.

Implementation

Matcher

As a matching modifier, it's expected that cast is applied when fields are matched. By default, casting is disabled. So using the cast option must enable the matcher to resolve assignment using automatic or provided casting. In other words, casting controls the assignment semantics of a match.

QOL

Without further implementation to the above behavior, users will be expected to include the cast option modifier for each function to enable automatic casting. This becomes tedious when you must deal with hundreds of functions. Furthermore, usage of the default automatcher implies that a user does NOT want to include automatch -cast declarations for each function.

The following implementation requires clarification of match semantics when ANY matching option is provided: Usage of ANY match option (map, tag, automatch) disables the automatcher for that function.

How can you specify to use the automatcher with automatic casting by default? One option is to allow comments above the Copygen interface. These options would operate at the same level that setup file options do, but with the benefit of being pinned to the respective Copygen file. This implementation lacks in standardization with other configuration standards, so it won't be implemented.

Configuration

The other alternative is to implement these options in the setup configuration: Copygen currently supports the .yml standard.

# Define how the matcher operates.
matcher:
  skip: false # replaces -xm which disables (skips) the matcher step (default: disabled).
  cast:
    enabled: true # Determines whether automatic casting is applied to matched fields (default: disabled).
    depth: 1 # Determines the level at which a type is cast (default: 1).

This configuration specifies that any matched fields will perform assignment using automatic "typecasting" at a depth level of one.

Modification

When a user needs to specify a manual cast, they could do so by applying the option modifier at the function level.

/cast from to [optional | .String() | .Property | depth int]

This implementation lets the user to enable casting and modify it. The user can disable casting by specifying a depth-level of 0. Since it's common for the cast option to be applied with matcher options, there can also exist a modifier definition - used to define cast within the same comment.

These modification symbols allow the end user to specify how casting is performed for each function, but do NOT allow the user to specify special rules at the generator level. In other words, the user may enable or disable casting at the generator level, but still want the option applied in special condition for EVERY FUNCTION. How do we address these concerns?

In the current implementation, we won't: The user is required to specify special conditions at the function-level. This behavior is consistent with how other options — that invoke special rules to a function's output — defined. This design decision is justified by an expectation of collaboration: Any person should be able to look at a Copygen setup file and predict its output.

Declaring special rules at the function-level removes confusion from an output's result.

Depth

Depth works in a similar manner to the depth feature for the automatcher, but with type conversion steps instead of field depth. For example,

type A []string // when cast is disabled, only matches with other A fields
type B A

type Copygen interface {
    ZeroDepth([]string) ([]string) // []string = []string
    OneDepth([]string) (A) // A([]string) = A
    TwoDepth([]string) B // B(A([]string)) = B
   ...
}

This will be implemented if possible with the go/types ConvertibleTo() function or go/types AssertableTo function or Field.Underlying field.

switchupcb commented 1 year ago

Additional Configuration

Additional configuration may be added to allow the user to specify feature flags.

matcher:
  cast:
    enabled: true # Determines whether automatic casting is applied to matched fields (default: disabled).
    disabled:
      assignObjectInterface: false # Disables assignment of object to interfaces (default: false).
      asertInterfaceObject: false # Disables assertion of interface to object (default: false).
      convert: false # Disables type conversion (default: false).

Must use disable as a default due to zero value semantics.

listepo commented 1 year ago

Hello, any news?

switchupcb commented 1 year ago

@listepo I'm technically homeless, but working on Open Source Software every Monday. This week I plan to work multiple days on Copygen to implement this feature with an incoming updated revision (and any incoming issues).

If this is an urgent feature for you, consider sponsoring me and I will make this a first priority over all other work. Otherwise, this feature can be worked around using the convert option as demonstrated in your discussion.

listepo commented 1 year ago

Thank you