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

Allow registered serializable types to be pointers. #457

Merged
merged 1 commit into from
Jul 18, 2023

Conversation

ghemawat
Copy link
Collaborator

Remove type constraint from RegisterSerializable[T] and instead check at runtime that either T or *T implements AutoMarshal.

@ghemawat ghemawat requested a review from mwhittaker July 13, 2023 21:37
Copy link
Member

@mwhittaker mwhittaker left a comment

Choose a reason for hiding this comment

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

Awesome!

}

// canEncodeInterface returns true if the concrete type of value is registered
// as a serializable type and can therefore be sent using Encoder.Interface.
func canEncodeInterface(value any) bool {
if _, ok := value.(AutoMarshal); !ok {
Copy link
Member

Choose a reason for hiding this comment

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

If I'm understanding correctly, if value is of type T, T implements error, but *T implements AutoMarshal instead of T, then we return false here? Is it possible for us to encode value by taking a pointer to it?

I noticed that values of type codegen.customTestError return false here and end up being decoded as codegen.decodedError, but I would have expected them be decoded into a codegen.customTestError.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Fixed.

// We allow either value, or a pointer to value to implement AutoMarshal.
am, ok := value.(AutoMarshal)
if !ok {
// Try using a pointer to value.
Copy link
Member

Choose a reason for hiding this comment

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

Can we use the trick you used in automarshal.go here?

if !ok {
    value = &value
    am, ok = value.(AutoMarshal)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That was a good idea. Until I realized that the "a = &value" trick was only working because value had the generic type T. Here, value has type any, and so "&value" does not have the type we want.

// (since AutoMarshal methods have pointer receivers, but T itself may be a
// struct).
func RegisterSerializable[T any, PT autoMarshalPointer[T]]() {
// REQUIRES: T can be serialized (either T or a pointer to T must implement
Copy link
Member

Choose a reason for hiding this comment

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

I believe right now, we always generate AutoMarshal methods on *T, and I don't think users are supposed to implement AutoMarshal methods manually. Does it simplify things if we always require *T to implement AutoMarshal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good point. Maybe I should abandon this change.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, maybe I misunderstood, but I thought most of the changes here were still needed to support errors where the Error method has a pointer receiver?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are correct. I was wondering if we need to support Error() with a pointer receiver.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I'm in favor of supporting it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ack. Just to clarify, yes the AutoMarshal methods are always defined on a pointer to a struct S.

The code here allows T to be either S or *S. This way, the user is free to place their Error() method on either S or *S.

The type T passed to RegisterSerializable[T] is now always
AutoMarshal. However the error interface may be implemented by either
T or *T.

There are now two encoding tags for custom error values: one tag if
the error itself is AutoMarshal, another tag if a pointer to the error
is AutoMarshal.

errors.Is etc. do not handle cyclic errors well, so make a separate
test for cyclic errors that just verifies that we can encode and
decode cyclic errors.
@ghemawat
Copy link
Collaborator Author

PTAL

Copy link
Member

@mwhittaker mwhittaker left a comment

Choose a reason for hiding this comment

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

LGTM!

@ghemawat ghemawat merged commit 9f4eaa9 into ServiceWeaver:main Jul 18, 2023
7 checks passed
@ghemawat ghemawat deleted the errors branch July 18, 2023 21:19
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.

None yet

2 participants