Open switchupcb opened 1 year ago
Here is the specification for the upcoming /cast
option.
The purpose of the cast option is to streamline type conversion and assertion in field-to-field copies.
There are three main features in the Go Programming Language Specification that Copygen uses to assign variables to another.
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.
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.
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.
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.
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.
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:
.String()
, .Property
).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.
/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.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.
cast from to [optional | .String() | .Property | depth int]
match-operation from to -cast [optional]
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.
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.
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.
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.
match-operation from to -cast [optional]
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 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.
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.
Hello, any news?
@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.
Thank you
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.