diff --git a/TestModels/OrphanedShapes/runtimes/go/ImplementationFromDafny-go/go.mod b/TestModels/OrphanedShapes/runtimes/go/ImplementationFromDafny-go/go.mod index 7b5f8db98..0b329403c 100644 --- a/TestModels/OrphanedShapes/runtimes/go/ImplementationFromDafny-go/go.mod +++ b/TestModels/OrphanedShapes/runtimes/go/ImplementationFromDafny-go/go.mod @@ -3,8 +3,8 @@ module github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes go 1.23.0 require ( - github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1 - github.com/dafny-lang/DafnyStandardLibGo v0.0.0 + github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.2 + github.com/aws/aws-cryptographic-material-providers-library/releases/go/smithy-dafny-standard-library v0.0.0 ) -replace github.com/dafny-lang/DafnyStandardLibGo => ../../../../dafny-dependencies/StandardLibrary/runtimes/go/ImplementationFromDafny-go/ +replace github.com/aws/aws-cryptographic-material-providers-library/releases/go/smithy-dafny-standard-library => ../../../../dafny-dependencies/StandardLibrary/runtimes/go/ImplementationFromDafny-go/ diff --git a/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/ExternDefinitions/extern.go b/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/ExternDefinitions/extern.go index 6b1a3bbda..e2028a060 100644 --- a/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/ExternDefinitions/extern.go +++ b/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/ExternDefinitions/extern.go @@ -1,9 +1,7 @@ package ExternDefinitions import ( - "fmt" - - Wrappers "github.com/dafny-lang/DafnyStandardLibGo/Wrappers" + Wrappers "github.com/aws/aws-cryptographic-material-providers-library/releases/go/smithy-dafny-standard-library/Wrappers" OrphanedResource "github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes/OrphanedResource" SimpleOrphanedTypes "github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes/SimpleOrphanedTypes" simpleorphanedsmithygenerated "github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes/simpleorphanedsmithygenerated" @@ -12,12 +10,16 @@ import ( var _ Wrappers.Dummy__ -// TODO: Finish implementation. -// This is missing structure converter. +// TODO: Remove this once Dafny bug is fixed. +// Dafny bug: https://t.corp.amazon.com/9a9028fd-2711-4843-b8f0-09965f7e61a7/communication#f03694be-7410-47f9-866d-e43093b018f9 +var m_ExternDefinitions = CompanionStruct_Default___{} func (CompanionStruct_Default___) InitializeOrphanedStructure(input SimpleOrphanedTypes.OrphanedStructure) SimpleOrphanedTypes.OrphanedStructure { - // Missing Structure converter - return input + nativeInput := simpleorphanedsmithygenerated.OrphanedStructure_FromDafny(input) + stringToReplace := "the extern MUST use Smithy-generated conversions to set this value in the native structure" + nativeInput.StringValue = &stringToReplace + dafnyInitializedStructure := simpleorphanedsmithygenerated.OrphanedStructure_ToDafny(nativeInput) + return dafnyInitializedStructure } func (CompanionStruct_Default___) CallNativeOrphanedResource(input *OrphanedResource.OrphanedResource) Wrappers.Result { @@ -32,7 +34,7 @@ func (CompanionStruct_Default___) CallNativeOrphanedResource(input *OrphanedReso } func (CompanionStruct_Default___) CallNativeOrphanedError(input SimpleOrphanedTypes.Error) SimpleOrphanedTypes.Error { - native_error := simpleorphanedsmithygenerated.Error_FromDafny(input) + native_error := simpleorphanedsmithygenerated.Error_FromDafny(input).(simpleorphanedsmithygeneratedtypes.OrphanedError) native_error.Message = "the extern MUST set this string using the catch-all error converter, NOT the orphaned error-specific converter" dafny_error_again := simpleorphanedsmithygenerated.Error_ToDafny(native_error) return dafny_error_again diff --git a/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/go.mod b/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/go.mod index 349df89cc..92c300db6 100644 --- a/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/go.mod +++ b/TestModels/OrphanedShapes/runtimes/go/TestsFromDafny-go/go.mod @@ -2,13 +2,12 @@ module github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes/test go 1.23.0 -require github.com/dafny-lang/DafnyStandardLibGo v0.0.0 - require ( - github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.1 + github.com/aws/aws-cryptographic-material-providers-library/releases/go/smithy-dafny-standard-library v0.0.0 + github.com/dafny-lang/DafnyRuntimeGo/v4 v4.9.2 github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes v0.0.0 ) replace github.com/smithy-lang/smithy-dafny/TestModels/OrphanedShapes v0.0.0 => ../ImplementationFromDafny-go -replace github.com/dafny-lang/DafnyStandardLibGo => ../../../../dafny-dependencies/StandardLibrary/runtimes/go/ImplementationFromDafny-go/ +replace github.com/aws/aws-cryptographic-material-providers-library/releases/go/smithy-dafny-standard-library => ../../../../dafny-dependencies/StandardLibrary/runtimes/go/ImplementationFromDafny-go/ diff --git a/codegen/smithy-dafny-codegen-test/src/test/java/software/amazon/polymorph/smithygo/GoTestModels.java b/codegen/smithy-dafny-codegen-test/src/test/java/software/amazon/polymorph/smithygo/GoTestModels.java index 47bb0fecd..2bb352aa8 100644 --- a/codegen/smithy-dafny-codegen-test/src/test/java/software/amazon/polymorph/smithygo/GoTestModels.java +++ b/codegen/smithy-dafny-codegen-test/src/test/java/software/amazon/polymorph/smithygo/GoTestModels.java @@ -21,8 +21,6 @@ class GoTestModels extends TestModelTest { DISABLED_TESTS.add("AggregateReferences"); DISABLED_TESTS.add("Documentation"); DISABLED_TESTS.add("LanguageSpecificLogic"); - // Needs work to generate some missing orphaned shape conversion methods - DISABLED_TESTS.add("OrphanedShapes"); DISABLED_TESTS.add("SimpleTypes/BigDecimal"); DISABLED_TESTS.add("SimpleTypes/BigInteger"); DISABLED_TESTS.add("SimpleTypes/SimpleByte"); diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java index e470c92ee..f04f970af 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceDirectedCodegen.java @@ -213,7 +213,11 @@ protected void generateOrphanedShapesForService( ShapeType.INTEGER, ShapeType.UNION, ShapeType.STRING, - ShapeType.LONG + ShapeType.LONG, + ShapeType.MAP, + ShapeType.LIST, + ShapeType.BOOLEAN, + ShapeType.BLOB ); for (final var shapeToGenerate : orderedShapes) { diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java index df365be41..fa4660f9b 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/localservice/DafnyLocalServiceTypeConversionProtocol.java @@ -214,6 +214,7 @@ public void generateSerializers(final GenerationContext context) { final var resource = refResource .expectTrait(ReferenceTrait.class) .getReferentId(); + alreadyVisited.add(refResource.toShapeId()); if (!refResource.expectTrait(ReferenceTrait.class).isService()) { final var resourceShape = model.expectShape( resource, @@ -418,13 +419,131 @@ public void generateSerializers(final GenerationContext context) { }); } } - generateErrorSerializer(context); + generateErrorSerializer(context, alreadyVisited); if (serviceShape.hasTrait(LocalServiceTrait.class)) { - generateConfigSerializer(context); + generateConfigSerializer(context, alreadyVisited); + } + final var orphanShapes = + ModelUtils.getTopologicallyOrderedOrphanedShapesForService( + serviceShape, + model + ); + // Loop through each Shape in orphanShapes + for (Shape shape : orphanShapes) { + generateOrphanShapeSerializer( + context, + shape, + alreadyVisited, + serviceShape + ); } generateSerializerFunctions(context, alreadyVisited); } + public void generateOrphanShapeSerializer( + final GenerationContext context, + final Shape shape, + final Set alreadyVisited, + ServiceShape serviceShape + ) { + if ( + GoCodegenUtils.shapeShouldHaveConversionFunction(shape) == false || + alreadyVisited.contains(shape.toShapeId()) + ) { + return; + } + if (shape.hasTrait(UnitTypeTrait.class)) { + return; + } + final var inputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName( + serviceShape, + shape, + "" + ); + final var writerDelegator = context.writerDelegator(); + String outputType; + final var curSymbol = context.symbolProvider().toSymbol(shape); + final String inputType; + alreadyVisited.add(shape.toShapeId()); + if (shape.hasTrait(ReferenceTrait.class)) { + final var referenceTrait = shape.expectTrait(ReferenceTrait.class); + final var resourceOrService = context + .model() + .expectShape(referenceTrait.getReferentId()); + if (resourceOrService.isResourceShape()) { + throw new IllegalStateException( + "Reference to resource shapes are already handled in generateSerializers function." + ); + } + if (resourceOrService.hasTrait(ServiceTrait.class)) { + outputType = + DafnyNameResolver.getDafnyInterfaceClient( + resourceOrService.asServiceShape().get(), + resourceOrService.getTrait(ServiceTrait.class).get() + ); + inputType = + GoCodegenUtils.getType( + context.symbolProvider().toSymbol(resourceOrService), + resourceOrService, + true + ); + } else { + outputType = + DafnyNameResolver.getDafnyInterfaceClient(resourceOrService); + inputType = + SmithyNameResolver + .shapeNamespace(resourceOrService) + .concat(".") + .concat(context.symbolProvider().toSymbol(serviceShape).getName()); + } + } else { + inputType = GoCodegenUtils.getType(curSymbol, shape, true); + outputType = DafnyNameResolver.getDafnyType(shape, curSymbol); + } + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_DAFNY + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(shape) + ); + writer.write( + """ + func $L(nativeInput $L)($L) { + ${C|} + }""", + inputToDafnyMethodName, + inputType, + outputType, + writer.consumer(w -> { + final String shapeVisitorOutput = shape.accept( + new SmithyToDafnyShapeVisitor( + context, + "nativeInput", + writer, + false, + false, + false + ) + ); + writer.write( + """ + return $L + """, + shapeVisitorOutput + ); + }) + ); + } + ); + } + @Override public void generateDeserializers(final GenerationContext context) { final Set alreadyVisited = new HashSet<>(); @@ -596,7 +715,7 @@ public void generateDeserializers(final GenerationContext context) { final var resource = refResource .expectTrait(ReferenceTrait.class) .getReferentId(); - + alreadyVisited.add(refResource.toShapeId()); if (!refResource.expectTrait(ReferenceTrait.class).isService()) { final var resourceShape = context .model() @@ -805,13 +924,116 @@ public void generateDeserializers(final GenerationContext context) { }); } } - generateErrorDeserializer(context); + generateErrorDeserializer(context, alreadyVisited); if (serviceShape.hasTrait(LocalServiceTrait.class)) { - generateConfigDeserializer(context); + generateConfigDeserializer(context, alreadyVisited); + } + final var orphanShapes = + ModelUtils.getTopologicallyOrderedOrphanedShapesForService( + serviceShape, + context.model() + ); + // Loop through each Shape in orphanShapes + for (Shape shape : orphanShapes) { + generateOrphanShapeDeserializer( + context, + shape, + alreadyVisited, + serviceShape + ); } generateDeserializerFunctions(context, alreadyVisited); } + public void generateOrphanShapeDeserializer( + final GenerationContext context, + final Shape shape, + final Set alreadyVisited, + ServiceShape serviceShape + ) { + if ( + GoCodegenUtils.shapeShouldHaveConversionFunction(shape) == false || + alreadyVisited.contains(shape.toShapeId()) + ) { + return; + } + if (shape.hasTrait(UnitTypeTrait.class)) { + return; + } + final var writerDelegator = context.writerDelegator(); + final String outputType; + final var inputFromDafnyMethodName = + SmithyNameResolver.getFromDafnyMethodName(serviceShape, shape, ""); + if (shape.hasTrait(ReferenceTrait.class)) { + final var referenceTrait = shape.expectTrait(ReferenceTrait.class); + final var resourceOrService = context + .model() + .expectShape(referenceTrait.getReferentId()); + if (resourceOrService.isResourceShape()) { + throw new IllegalStateException( + "Reference to resource shapes are already handled in generateDeserializers function." + ); + } + if (resourceOrService.hasTrait(ServiceTrait.class)) { + outputType = + SmithyNameResolver.getAwsServiceClient( + resourceOrService.expectTrait(ServiceTrait.class) + ); + } else { + final var namespace = SmithyNameResolver + .shapeNamespace(resourceOrService) + .concat("."); + outputType = + "*".concat( + namespace.concat( + context.symbolProvider().toSymbol(resourceOrService).getName() + ) + ); + } + } else { + outputType = + GoCodegenUtils.getType( + context.symbolProvider().toSymbol(shape), + shape, + true + ); + } + writerDelegator.useFileWriter( + "%s/%s".formatted( + SmithyNameResolver.shapeNamespace(serviceShape), + TO_NATIVE + ), + SmithyNameResolver.shapeNamespace(serviceShape), + writer -> { + writer.addImportFromModule( + SmithyNameResolver.getGoModuleNameForSmithyNamespace( + shape.toShapeId().getNamespace() + ), + SmithyNameResolver.smithyTypesNamespace(shape) + ); + writer.write( + """ + func $L(input interface{})($L) { + ${C|} + }""", + inputFromDafnyMethodName, + outputType, + writer.consumer(w -> { + final var shapeVisitorOutput = shape.accept( + new DafnyToSmithyShapeVisitor(context, "input", writer, false) + ); + writer.write( + """ + $L + """, + shapeVisitorOutput + ); + }) + ); + } + ); + } + private void generateRequestSerializer( final GenerationContext context, final OperationShape operation, @@ -934,7 +1156,10 @@ private void generateResponseDeserializer( ); } - private void generateConfigSerializer(final GenerationContext context) { + private void generateConfigSerializer( + final GenerationContext context, + final Set alreadyVisited + ) { final var service = context.settings().getService(context.model()); final var localServiceTrait = service.expectTrait(LocalServiceTrait.class); final var configShape = context @@ -942,7 +1167,15 @@ private void generateConfigSerializer(final GenerationContext context) { .expectShape(localServiceTrait.getConfigId(), StructureShape.class); final var getInputToDafnyMethodName = SmithyNameResolver.getToDafnyMethodName(service, configShape, ""); - + if ( + !configShape + .toShapeId() + .getNamespace() + .equals(service.toShapeId().getNamespace()) + ) { + return; + } + alreadyVisited.add(configShape.getId()); context .writerDelegator() .useFileWriter( @@ -986,8 +1219,10 @@ private void generateConfigSerializer(final GenerationContext context) { ); } - private void generateErrorSerializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); + private void generateErrorSerializer( + final GenerationContext context, + final Set alreadyVisited + ) { final var serviceShape = context.settings().getService(context.model()); final var errorShapes = context .model() @@ -1285,7 +1520,10 @@ private void handleDepErrorSerializer( } } - private void generateConfigDeserializer(final GenerationContext context) { + private void generateConfigDeserializer( + final GenerationContext context, + final Set alreadyVisited + ) { final var serviceShape = context.settings().getService(context.model()); final var localServiceTrait = serviceShape.expectTrait( LocalServiceTrait.class @@ -1293,9 +1531,17 @@ private void generateConfigDeserializer(final GenerationContext context) { final var configShape = context .model() .expectShape(localServiceTrait.getConfigId(), StructureShape.class); + if ( + !configShape + .toShapeId() + .getNamespace() + .equals(serviceShape.toShapeId().getNamespace()) + ) { + return; + } final var getOutputFromDafnyMethodName = SmithyNameResolver.getFromDafnyMethodName(serviceShape, configShape, ""); - + alreadyVisited.add(configShape.getId()); context .writerDelegator() .useFileWriter( @@ -1346,8 +1592,10 @@ private void generateConfigDeserializer(final GenerationContext context) { ); } - private void generateErrorDeserializer(final GenerationContext context) { - final Set alreadyVisited = new HashSet<>(); + private void generateErrorDeserializer( + final GenerationContext context, + final Set alreadyVisited + ) { final var serviceShape = context.settings().getService(context.model()); final var errorShapes = context .model() diff --git a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java index 00fc41ffa..8715cbf9b 100644 --- a/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java +++ b/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithygo/utils/GoCodegenUtils.java @@ -6,6 +6,7 @@ import software.amazon.polymorph.smithygo.codegen.SymbolUtils; import software.amazon.polymorph.smithygo.localservice.nameresolver.DafnyNameResolver; import software.amazon.polymorph.smithygo.localservice.nameresolver.SmithyNameResolver; +import software.amazon.polymorph.smithypython.awssdk.nameresolver.AwsSdkNameResolver; import software.amazon.polymorph.traits.PositionalTrait; import software.amazon.polymorph.traits.ReferenceTrait; import software.amazon.smithy.aws.traits.ServiceTrait; @@ -20,6 +21,7 @@ import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; import software.amazon.smithy.model.traits.EnumTrait; +import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; public class GoCodegenUtils { @@ -188,4 +190,34 @@ private static String getInputOrOutputName( ); } } + + /** + * Returns true if a conversion function should be written for the shape, false otherwise. + * Conversion functions are only written for "complex" shapes: + * - StructureShapes ("complex" because StructureShapes can be recursive) + * - except for non-AWS SDK StructureShapes with ErrorTrait; these aren't "complex" + * - UnionShapes ("complex" because the conversion is not a one-liner) + * - EnumShapes or StringShapes with EnumTrait ("complex" because the conversion is not a one-liner) + * @param shape + * @return + */ + public static boolean shapeShouldHaveConversionFunction(Shape shape) { + if (shape.isStructureShape()) { + if ( + !SmithyNameResolver.isShapeFromAWSSDK(shape) && + shape.hasTrait(ErrorTrait.class) + ) { + return false; + } + return true; + } else if (shape.isUnionShape()) { + return true; + } else if ( + (shape.isStringShape() && shape.hasTrait(EnumTrait.class)) || + shape.isEnumShape() + ) { + return true; + } + return false; + } }