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

Try ConversionFrom if ConversionTo returns nil #194

Merged
merged 1 commit into from
Dec 7, 2024

Conversation

Andrew-Morozko
Copy link
Contributor

Currently when converting between two capsule types if ConversionTo returns nil then the conversion fails, even if the target type defines a suitable ConversionFrom.

Example
package main

import (
	"fmt"
	"log"
	"reflect"

	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/convert"
)

func main() {
	myStringType := cty.CapsuleWithOps("MyString", reflect.TypeOf(""), &cty.CapsuleOps{
		ConversionTo: func(dst cty.Type) func(cty.Value, cty.Path) (interface{}, error) {
			// conversions to some types other than myIntType defined here
			// returns nil when asked to convert to the myIntType
			return nil
		},
	})
	myIntType := cty.CapsuleWithOps("MyInt", reflect.TypeOf(0), &cty.CapsuleOps{
		ConversionFrom: func(src cty.Type) func(interface{}, cty.Path) (cty.Value, error) {
			if src.Equals(myStringType) {
				return func(v interface{}, p cty.Path) (cty.Value, error) {
					val := fmt.Sprintf("%d", *(v.(*int)))
					return cty.CapsuleVal(
						myStringType,
						&val,
					), nil
				}
			}
			return nil
		},
	})
	val := 42
	myInt := cty.CapsuleVal(myIntType, &val)
	myString, err := convert.Convert(myInt, myStringType)
	if err != nil {
		log.Fatalf("Cannot convert: %s", err)
	}
	log.Printf("Conversion successful: %#v", *(myString.EncapsulatedValue().(*string)))
}

This PR checks the ConversionFrom if ConversionTo fails

}
}
if in.IsCapsuleType() {
if fn := in.CapsuleOps().ConversionFrom; fn != nil {
return conversionFromCapsule(in, out, fn)
if conv := conversionFromCapsule(in, out, fn); conv != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is strictly speaking unnecessary, but decided to change the conversionFrom case in the same way for uniformity / in case more conversion methods would be added

@Andrew-Morozko
Copy link
Contributor Author

P.S.:
What do you think about including more operations in CapsuleOps? In particular the ability to use GetAttr and/or Index on capsule types. Currently I have to wrap an inconvenient amount of capsule "leaf" values in nested cty.Maps to later traverse them in cty-land, would be nice if Capsule types were traversable on their own. I can work on this PR if you're OK it.

@apparentlymart
Copy link
Collaborator

Thanks for working on this! I can't review it immediately, but I'll try to look at it soon.

In answer to the follow-up question: so far I've been holding the line that CapsuleOps only supports the subset of operations that cty considers to be generic across all types, and that this isn't a general-purpose "operator overloading" sort of mechanism. I'd really rather not have special cases for capsule types sprawled across every single operation, since I expect that would make ongoing maintenance considerably more challenging.

I'm not sure what sort of application you're using cty from, but perhaps you can make your own definition of "get attribute" and "index" at your application layer and thus be able to implement your own rules for how that should work, including possibly supporting your own capsule types. You can find some examples of that strategy in HashiCorp's HCL codebase, which wraps cty but provides its own higher-level definition of both of these operations:

https://github.com/hashicorp/hcl/blob/d20d07fa73707e58954493bdd600c1fb0a0d4c77/ops.go#L26

https://github.com/hashicorp/hcl/blob/d20d07fa73707e58954493bdd600c1fb0a0d4c77/ops.go#L265

HCL admittedly has some different goals in its versions of these functions -- it's primarily motivated by HCL's automatic type conversion rules, rather than by capsule types -- but if HCL did have some special capsule types that ought to support HCL's attribute access and index syntax then those would be plausible places for HCL to implement those extensions.

@Andrew-Morozko
Copy link
Contributor Author

Ok, got it, this seems perfectly reasonable line to hold. For context, I'm passing those maps of maps to HCL so it could be evaluated there, but I've bumped into limitations of HCL often enough to already rewrite a large chunk of it. I guess expression evaluator is next 😉

Thanks for your time and your library!

@apparentlymart
Copy link
Collaborator

Hello again! Sorry for the delay.

I had originally implemented this mechanism primarily as a way to convert from normal types to capsule types and vice-versa, so that it would be easier to use capsule types in a language that doesn't have direct support for them like HashiCorp's HCL.

I hadn't really considered capsule-to-capsule conversions, but I don't see any reason why that shouldn't be allowed, and if they are going to be allowed then it seems very reasonable to support converting "the wrong way" if there's only a conversion in one direction.

Therefore I'm going to merge this. Thanks for working on it!

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.

2 participants