From b52d69dbce0faf4710f65939a0b79247caa8cea7 Mon Sep 17 00:00:00 2001 From: "peaceful-conductor[bot]" <285126712+peaceful-conductor[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:14:51 +0000 Subject: [PATCH 1/4] Release 0.5.0-preview.1 Promote 0.5.0-preview.1 to public. See CHANGELOG.md for consumer-facing changes. --- .editorconfig | 3 + .pre-commit-config.yaml | 5 +- CONTEXT.md | 89 +- Daml.Codegen.CSharp.slnx | 1 + Directory.Build.props | 18 +- Directory.Packages.props | 18 +- conformance/README.md | 91 +- conformance/contractkeys/ContractKeys.daml | 87 ++ conformance/contractkeys/KeyBuilders.daml | 20 + conformance/contractkeys/contractkeys.dar | Bin 0 -> 400687 bytes conformance/contractkeys/daml.yaml | 11 + .../crossmodulecollision/CollisionA.daml | 15 + .../crossmodulecollision/CollisionB.daml | 17 + .../crossmodulecollision.dar | Bin 0 -> 257010 bytes conformance/crossmodulecollision/daml.yaml | 12 + conformance/defaulttarget/DefaultTarget.daml | 11 + conformance/defaulttarget/daml.yaml | 9 + conformance/defaulttarget/defaulttarget.dar | Bin 0 -> 359335 bytes conformance/richtypes/RichTypes.daml | 94 +- conformance/richtypes/daml.yaml | 5 +- conformance/richtypes/richtypes.dar | Bin 261695 -> 297197 bytes jvm-helper/README.md | 4 +- jvm-helper/build.sbt | 13 +- jvm-helper/project/build.properties | 3 + jvm-helper/project/plugins.sbt | 2 + .../peaceful/daml/codegen/helper/Decode.scala | 51 +- .../helper/PartyExpressionAnalyzer.scala | 151 +-- .../test/resources/blank-build-version.txt | 2 + .../daml/codegen/helper/DecodeSpec.scala | 57 +- .../codegen/helper/PartyAnalysesSpec.scala | 4 +- .../helper/PartyExpressionAnalyzerSpec.scala | 123 ++- proto/intermediate_dar.proto | 10 + .../Generated/.daml-langversion | 0 .../Generated/Quickstart/Iou.Transfer.cs | 4 +- .../Generated/Quickstart/Iou.cs | 147 ++- samples/TokenStandardV2/NuGet.config | 3 + samples/TokenStandardV2/Program.cs | 6 +- samples/TokenStandardV2/README.md | 15 +- scripts/bundle-entrypoint/dpm-codegen-cs | 2 +- scripts/bundle-entrypoint/dpm-codegen-cs.cmd | 2 +- scripts/check-license-headers.sh | 365 ++++++- scripts/check-license-headers.test.sh | 283 ++++++ scripts/codegen-pipeline.sh | 2 +- scripts/ensure-jvm-helper-jar.sh | 18 +- scripts/refresh-snapshot.sh | 211 +++- scripts/verify-consumer-build.sh | 12 +- .../Daml.Codegen.CSharp.Cli.csproj | 2 +- src/Daml.Codegen.CSharp.Cli/Program.cs | 95 +- .../CSharpCodeGenerator.FileScaffold.cs | 2 +- .../CodeGen/CSharpCodeGenerator.cs | 97 +- .../CodeGen/ChoiceArgumentInfo.cs | 46 + .../CodeGen/ChoiceCreatedSlots.cs | 60 +- ...iceEmitter.ContractIdExerciserOverloads.cs | 244 +++++ .../ChoiceEmitter.ContractIdExercisers.cs | 683 +------------ .../ChoiceEmitter.InterfaceExtensions.cs | 77 +- .../ChoiceEmitter.NonContractExercisers.cs | 158 +-- .../CodeGen/ChoiceEmitter.ResultStructs.cs | 319 ++++++ .../ChoiceEmitter.SubmissionParameters.cs | 23 + .../CodeGen/ChoiceEmitter.cs | 295 ++++-- .../CodeGen/ChoiceSubmitterParameter.cs | 19 + .../CodeGen/CodeGenOptions.cs | 16 + .../CodeGen/DamlTypeMapper.cs | 108 ++- .../CodeGen/DarCrossPackageResolver.cs | 34 +- .../CodeGen/EmittedUsings.cs | 35 + .../CodeGen/EmitterHelpers.cs | 23 +- .../CodeGen/EnumEmitter.cs | 2 +- .../CodeGen/ICrossPackageResolver.cs | 2 +- .../CodeGen/Identifiers.cs | 35 +- .../CodeGen/IndentWriter.cs | 15 +- .../CodeGen/InterfaceEmitter.cs | 85 +- .../CodeGen/OptionalRepresentation.cs | 268 +++++ .../CodeGen/PackageEmitContext.cs | 214 +++- .../CodeGen/PackageQualifiedResolver.cs | 35 + .../CodeGen/PartyAnalysis.cs | 6 +- .../CodeGen/ProjectFileGenerator.cs | 159 +-- .../CodeGen/RecordEmitter.cs | 148 +-- .../CodeGen/RecordSerializationEmitter.cs | 33 +- .../CodeGen/RuntimeTypeNames.cs | 17 +- .../CodeGen/StdlibPackages.cs | 33 +- .../CodeGen/SubmissionExtensionsEmitter.cs | 83 +- .../CodeGen/TemplateEmitter.cs | 260 ++++- .../CodeGen/TypeReferenceQualifier.cs | 14 +- .../CodeGen/VariantEmitter.cs | 7 +- src/Daml.Codegen.CSharp/CodegenException.cs | 30 + src/Daml.Codegen.CSharp/ConsoleLogger.cs | 45 - .../Daml.Codegen.CSharp.csproj | 9 +- src/Daml.Codegen.CSharp/ICodegenLogger.cs | 31 - .../IntermediateDarReader.cs | 13 +- src/Daml.Codegen.CSharp/README.md | 25 +- .../VerbosityConsoleLoggerProvider.cs | 76 ++ .../FourPartPackageVersion.cs | 4 +- .../Versioning/NuGetVersionResolver.cs | 2 - .../Daml.Codegen.Intermediate.csproj | 23 + .../Model/DamlDataType.cs | 2 +- .../Model/DamlPackage.cs | 2 +- .../Model/DamlTemplate.cs | 18 +- .../Model/DamlType.cs | 55 +- .../Model/DarModel.cs | 10 +- .../Model/IDarSource.cs | 16 +- .../Model/PackageVersionParser.cs | 2 +- src/Daml.Codegen.Intermediate/README.md | 35 + .../ConformanceCorpus.cs | 54 +- .../Daml.Codegen.Testing.Conformance.csproj | 3 +- .../Generated/.daml-langversion | 0 .../Generated/Contractkeys/Account.Credit.cs | 32 + .../Contractkeys/Account.CurrentBalance.cs | 26 + .../Generated/Contractkeys/Account.cs | 609 ++++++++++++ .../Generated/Contractkeys/AccountKey.cs | 32 + .../Generated/Contractkeys/Calendar.cs | 34 + .../Contractkeys/ContractIdentifiers.cs | 42 + .../Generated/Contractkeys/Holiday.Extend.cs | 32 + .../Generated/Contractkeys/Holiday.cs | 500 ++++++++++ .../Generated/Contractkeys/HolidayKey.cs | 32 + .../Contractkeys/Schedule.Reschedule.cs | 32 + .../Generated/Contractkeys/Schedule.cs | 387 ++++++++ .../Generated/Contractkeys/ScheduleKey.cs | 32 + .../Generated/Contractkeys/ScheduleView.cs | 32 + .../Generated/Contractkeys/Steward.Revise.cs | 32 + .../Generated/Contractkeys/Steward.cs | 498 ++++++++++ .../Defaulttarget/ContractIdentifiers.cs | 24 + .../Generated/Defaulttarget/Note.cs | 196 ++++ .../Generated/Richtypes/Asset.cs | 44 +- .../Generated/Richtypes/Box.cs | 33 + .../Generated/Richtypes/Branch.cs | 34 + .../{ => Richtypes}/ContractIdentifiers.cs | 6 + .../Generated/Richtypes/Crate.cs | 33 + .../Generated/Richtypes/Describe.cs | 29 + .../Generated/Richtypes/Holding.cs | 60 -- .../Generated/Richtypes/HoldingView.cs | 4 +- .../Generated/Richtypes/IHolding.cs | 136 ++- .../Generated/Richtypes/Marker.cs | 43 +- .../Generated/Richtypes/Outcome_Win.cs | 5 +- .../Generated/Richtypes/Profile.cs | 5 +- .../Generated/Richtypes/Reissue.cs | 29 + .../Generated/Richtypes/RichRecord.Relabel.cs | 4 +- .../Generated/Richtypes/RichRecord.cs | 147 ++- .../Generated/Richtypes/Slot.cs | 56 ++ .../Generated/Richtypes/TypeCorners.Rebox.cs | 32 + .../Generated/Richtypes/TypeCorners.cs | 481 +++++++++ .../README.md | 36 +- .../CommandIdConformanceFixture.cs | 57 ++ ...er.Abstractions.Testing.Conformance.csproj | 1 - .../LedgerClientConformanceTests.cs | 151 ++- .../README.md | 38 +- .../Daml.Ledger.Abstractions.csproj | 3 +- .../Extensions/CreateByExercise.cs | 137 +-- .../Extensions/PartyOverloads.cs | 107 -- .../Extensions/SingleCommandExtensions.cs | 71 ++ .../Extensions/StreamerSnapshot.cs | 229 +++++ .../Extensions/ThrowingExercise.cs | 93 +- .../Extensions/WriterExtensionHelpers.cs | 32 +- src/Daml.Ledger.Abstractions/ILedgerClient.cs | 2 +- src/Daml.Ledger.Abstractions/ILedgerReader.cs | 2 +- .../ILedgerStreamer.cs | 222 ++++- src/Daml.Ledger.Abstractions/ILedgerWriter.cs | 48 +- .../LedgerNamespaces.cs | 3 + .../LedgerOperationException.cs | 28 +- src/Daml.Ledger.Abstractions/README.md | 54 +- src/Daml.Runtime/Commands/CommandId.cs | 22 + .../Commands/CommandsSubmission.cs | 48 +- .../Commands/DisclosedContract.cs | 15 + src/Daml.Runtime/Commands/MinLedgerTime.cs | 117 +++ src/Daml.Runtime/Commands/SubmitterInfo.cs | 19 +- src/Daml.Runtime/Contracts/Contract.cs | 74 +- src/Daml.Runtime/Contracts/ContractEvent.cs | 271 +++++- src/Daml.Runtime/Contracts/ContractId.cs | 13 +- .../Contracts/EventCollections.cs | 58 ++ src/Daml.Runtime/Contracts/KeyDescriptor.cs | 34 + src/Daml.Runtime/Contracts/Template.cs | 21 +- .../Contracts/TransactionResult.cs | 246 ++++- src/Daml.Runtime/Contracts/TransactionTree.cs | 114 ++- .../Contracts/TransactionTreeExtensions.cs | 44 +- src/Daml.Runtime/Contracts/ViewDescriptor.cs | 23 + src/Daml.Runtime/Daml.Runtime.csproj | 1 - src/Daml.Runtime/Data/DamlCollections.cs | 20 + src/Daml.Runtime/Data/DamlPrimitives.cs | 81 +- src/Daml.Runtime/Data/DamlRecord.cs | 16 + src/Daml.Runtime/Data/DamlValue.cs | 21 + src/Daml.Runtime/Data/DamlValueExtensions.cs | 28 +- src/Daml.Runtime/Data/DamlVariant.cs | 4 + src/Daml.Runtime/Data/SynchronizerId.cs | 76 +- src/Daml.Runtime/Outcomes/ExerciseOutcome.cs | 8 +- .../Outcomes/ExerciseOutcomeProjection.cs | 2 +- src/Daml.Runtime/README.md | 102 ++ .../Serialization/ContractIdJsonConverter.cs | 151 +++ .../Serialization/DamlJsonConverters.cs | 74 ++ .../Serialization/DamlJsonSerializer.cs | 46 +- .../Serialization/DamlLfJsonReader.cs | 832 ++++++++++++++++ .../OpaqueStringIdJsonConverter.cs | 80 ++ src/Daml.Runtime/Stdlib/Either.cs | 6 + src/Daml.Runtime/Stdlib/Map.cs | 40 + src/Daml.Runtime/Stdlib/NonEmpty.cs | 43 + src/Daml.Runtime/Stdlib/Optional.cs | 192 ++++ src/Daml.Runtime/Stdlib/RelTime.cs | 2 +- src/Daml.Runtime/Stdlib/Set.cs | 35 +- src/Daml.Runtime/Stdlib/Tuple.cs | 21 + src/Daml.Runtime/Streams/AcsSnapshotEntry.cs | 112 ++- .../Streams/AcsSnapshotEntryExtensions.cs | 47 + .../Streams/ContractStreamEvent.cs | 135 ++- .../Streams/InterfaceAcsSnapshotEntry.cs | 167 ++++ .../Streams/InterfaceStreamEvent.cs | 315 ++++++ .../CliErrorReportingTests.cs | 6 +- .../CliExitCodeTests.cs | 12 + .../CliReleaseCountersTests.cs | 59 +- ...ConsoleRedirectionCollectionDefinition.cs} | 2 +- .../ChoiceAsyncExerciserTests.cs | 25 +- .../ChoiceCodeGenTests.cs | 28 +- .../ChoiceCreatedSlotsTests.cs | 30 +- .../ChoiceEmitterArchiveChoiceTests.cs | 74 +- .../ChoiceEmitterByKeyCommandTests.cs | 220 +++++ .../ChoiceEmitterContractIdExerciserTests.cs | 65 +- .../ChoiceEmitterContractIdFilteringTests.cs | 9 +- .../ChoiceEmitterCrossPackageChoiceTests.cs | 19 +- .../ChoiceEmitterDescriptorTests.cs | 73 +- .../ChoiceEmitterInterfaceExtensionTests.cs | 45 +- .../ChoiceEmitterNonContractExerciserTests.cs | 26 +- .../ChoiceEmitterUnitReturnExerciserTests.cs | 11 +- .../ChoiceEmitterValueReturnExerciserTests.cs | 61 +- .../ChoiceResultStructTests.cs | 45 +- .../CliExitCodeTests.cs | 67 -- .../CodeGenEdgeCaseTests.Choices.cs | 80 +- .../CodeGenEdgeCaseTests.Conversions.cs | 20 +- .../CodeGenEdgeCaseTests.ForeignPackages.cs | 7 +- ...EdgeCaseTests.InterfaceShadowedRecords.cs} | 146 +-- .../CodeGenEdgeCaseTests.Serialization.cs | 4 +- .../CodeGenEdgeCaseTests.StdlibRouting.cs | 527 ++++------ .../CodeGenEdgeCaseTests.cs | 2 +- .../CodeGenOptionsTests.cs | 3 +- .../CodegenLoggingTests.cs | 98 ++ .../ConformancePackageContentTests.cs | 29 - .../ContractIdentifiersTests.cs | 201 +++- .../Daml.Codegen.CSharp.Tests.csproj | 1 - .../DamlFieldAttributeEmitterTests.cs | 13 +- .../DamlModelTests.cs | 9 +- .../DamlTypeMapperTests.cs | 459 +++++++-- .../DamlTypeTests.cs | 28 +- .../DarCrossPackageResolverTests.cs | 105 +- .../DataTypeCodeGenTests.cs | 4 +- .../DependencyTests.cs | 36 +- .../DriftDetectionTests.cs | 132 ++- .../EmittedCodeCompilesTestHelpers.cs | 62 +- .../EmittedContractKeySlotCompilesTests.cs | 175 ++++ .../EmittedCrossPackageCompilesTests.cs | 22 +- .../EmittedFacetDeclarationCompilesTests.cs | 81 ++ ...InterfaceChoiceExerciserInvocationTests.cs | 172 ++++ ...ttedKeywordNamedIdentifierCompilesTests.cs | 4 +- .../EmittedMangledTypeNamesCompileTests.cs | 2 +- ...llisionGlobalQualificationCompilesTests.cs | 17 +- ...ceCollisionRuntimeTypeNameCompilesTests.cs | 34 +- ...amespaceCollisionShadowingCompilesTests.cs | 66 +- ...yCollidingWithTemplateNameCompilesTests.cs | 141 +++ .../EmittedRecordFieldCompilesTests.cs | 102 +- .../EmittedRecordReferenceCompilesTests.cs | 18 +- .../EmittedSharedViewCompilesTests.cs | 74 ++ ...EmittedSubmitterOverloadResolutionTests.cs | 295 ++++++ .../EmittedTemplateChoiceCompilesTests.cs | 67 +- ...emplateKeyAndChoiceWrapperCompilesTests.cs | 210 ++-- .../EmittedVariantCompilesTests.cs | 17 +- .../EmittedViewFieldCollisionCompilesTests.cs | 176 ++++ .../EmitterMachineryApiSurfaceTests.cs | 2 +- .../EnumEmitterTests.cs | 14 +- .../FourPartPackageVersionTests.cs | 2 +- .../GenericTypeSerializationRoundTripTests.cs | 35 +- .../ICodegenLoggerTests.cs | 43 - .../IdentifiersTests.cs | 3 + ...erfaceChoiceResultCs0117RegressionTests.cs | 44 +- .../InterfaceEmitterTests.cs | 159 ++- .../IntermediateDarReaderBasicTests.cs | 27 +- .../IntermediateDarReaderDataTypesTests.cs | 26 +- .../IntermediateDarReaderEntitiesTests.cs | 33 +- .../IntermediateDarReaderPartiesTests.cs | 10 +- .../MirrorPromotionGateTests.cs | 10 +- .../NamedSubmitterTests.cs | 39 +- .../NewFeaturesCodeGenTests.cs | 344 ++----- .../NonContractChoiceProjectorTests.cs | 18 +- ...ntractChoiceUnitExerciserRoundTripTests.cs | 65 +- .../NuGetPackIntegrationTests.cs | 9 +- .../NuGetVersionResolverTests.cs | 7 +- .../OptionalRepresentationTests.cs | 493 ++++++++++ .../PackageEmitContextTests.cs | 330 ++++++- .../PackageVersionParserTests.cs | 4 +- .../PartyAnalysisTests.cs | 44 +- .../ProjectFileGeneratorTests.Metadata.cs | 2 +- .../ProjectFileGeneratorTests.Output.cs | 109 +-- .../ProjectFileGeneratorTests.References.cs | 126 ++- .../ProjectFileGeneratorTests.Versioning.cs | 2 +- .../ProjectFileGrpcReferenceTests.cs | 4 +- .../QuickstartSample/README.md | 2 +- .../QuickstartSample/intermediate.binpb | Bin 10998 -> 11022 bytes .../QuickstartSampleDriftTests.cs | 10 +- .../RecordEmitterTests.cs | 239 ++--- .../ReleaseCounterStoreTests.cs | 52 +- .../RuntimeTypeNameDriftGuardTests.cs | 6 +- .../cross-module-collision/README.md | 35 +- .../cross-module-collision.dar | Bin 0 -> 257010 bytes .../expected/.daml-langversion | 0 .../Crossmodulecollision/Agreement.Retag.cs | 4 +- .../Crossmodulecollision/Agreement.cs | 133 +-- .../Crossmodulecollision/Offer.Retag.cs | 5 +- .../expected/Crossmodulecollision/Offer.cs | 128 ++- .../cross-module-collision/intermediate.binpb | Bin 11446 -> 11482 bytes .../splice-amulet-name-service/README.md | 25 +- .../expected/.daml-langversion | 0 ...d.AmuletConversionRateFeed_ArchiveAsDso.cs | 4 +- ...ateFeed.AmuletConversionRateFeed_Update.cs | 7 +- .../Name/Service/AmuletConversionRateFeed.cs | 94 +- .../AmuletConversionRateFeed_UpdateResult.cs | 4 +- .../Name/Service/AnsEntry.AnsEntry_Expire.cs | 4 +- .../Name/Service/AnsEntry.AnsEntry_Renew.cs | 4 +- .../Splice/Amulet/Name/Service/AnsEntry.cs | 66 +- ...EntryContext_CollectEntryRenewalPayment.cs | 7 +- ...EntryContext_CollectInitialEntryPayment.cs | 6 +- ...sEntryContext_RejectEntryInitialPayment.cs | 6 +- ...sEntryContext.AnsEntryContext_Terminate.cs | 5 +- .../Amulet/Name/Service/AnsEntryContext.cs | 100 +- ...ontext_CollectEntryRenewalPaymentResult.cs | 5 +- ...ontext_CollectInitialEntryPaymentResult.cs | 5 +- ...Context_RejectEntryInitialPaymentResult.cs | 4 +- .../AnsEntryContext_TerminateResult.cs | 2 +- .../Name/Service/AnsEntry_ExpireResult.cs | 2 +- .../Name/Service/AnsEntry_RenewResult.cs | 4 +- ...les.AnsRules_CollectEntryRenewalPayment.cs | 8 +- ...les.AnsRules_CollectInitialEntryPayment.cs | 7 +- ...ules.AnsRules_RejectEntryInitialPayment.cs | 5 +- .../Service/AnsRules.AnsRules_RequestEntry.cs | 7 +- .../Splice/Amulet/Name/Service/AnsRules.cs | 108 +-- .../Amulet/Name/Service/AnsRulesConfig.cs | 7 +- ...sRules_CollectEntryRenewalPaymentResult.cs | 5 +- ...sRules_CollectInitialEntryPaymentResult.cs | 5 +- ...nsRules_RejectEntryInitialPaymentResult.cs | 4 +- .../Service/AnsRules_RequestEntryResult.cs | 5 +- .../Name/Service/ExpectedEntryContext.cs | 6 +- .../Amulet/Name/Service/ExpectedPayment.cs | 5 +- .../Amulet/Name/Service/MarkerContext.cs | 5 +- .../intermediate.binpb | Bin 215934 -> 216312 bytes .../README.md | 23 +- .../expected/.daml-langversion | 0 .../Instruction/V2/AllocationFactory.cs | 60 -- .../Instruction/V2/AllocationFactoryView.cs | 5 +- .../V2/AllocationFactory_Allocate.cs | 9 +- .../V2/AllocationFactory_PublicFetch.cs | 4 +- .../Instruction/V2/AllocationInstruction.cs | 60 -- .../AllocationInstructionAction_AIA_Custom.cs | 4 +- .../V2/AllocationInstructionResult.cs | 6 +- ...t_AllocationInstructionResult_Completed.cs | 4 +- ...put_AllocationInstructionResult_Pending.cs | 4 +- .../V2/AllocationInstructionView.cs | 11 +- .../V2/AllocationInstruction_Accept.cs | 5 +- .../V2/AllocationInstruction_Withdraw.cs | 5 +- .../Instruction/V2/IAllocationFactory.cs | 91 +- .../Instruction/V2/IAllocationInstruction.cs | 111 ++- .../README.md | 23 +- .../expected/.daml-langversion | 0 .../Request/V2/AllocationRequest.cs | 60 -- .../V2/AllocationRequestAction_ARA_Custom.cs | 4 +- .../Request/V2/AllocationRequestView.cs | 10 +- .../Request/V2/AllocationRequest_Accept.cs | 5 +- .../V2/AllocationRequest_AcceptResult.cs | 4 +- .../Request/V2/AllocationRequest_Reject.cs | 5 +- .../V2/AllocationRequest_RejectResult.cs | 4 +- .../Request/V2/AllocationRequest_Withdraw.cs | 5 +- .../V2/AllocationRequest_WithdrawResult.cs | 4 +- .../Request/V2/IAllocationRequest.cs | 132 +-- .../splice-api-token-allocation-v2/README.md | 23 +- .../expected/.daml-langversion | 0 .../Api/Token/Allocation/V2/Allocation.cs | 60 -- .../V2/AllocationAction_AA_Custom.cs | 4 +- .../Token/Allocation/V2/AllocationResult.cs | 6 +- ...nResult_Output_AllocationResult_Pending.cs | 4 +- ...nResult_Output_AllocationResult_Settled.cs | 4 +- .../Allocation/V2/AllocationSpecification.cs | 10 +- .../Api/Token/Allocation/V2/AllocationView.cs | 12 +- .../Token/Allocation/V2/Allocation_Cancel.cs | 5 +- .../Token/Allocation/V2/Allocation_Settle.cs | 7 +- .../Allocation/V2/Allocation_Withdraw.cs | 5 +- .../Allocation/V2/FinalizedAllocation.cs | 6 +- .../Api/Token/Allocation/V2/IAllocation.cs | 138 +-- .../Token/Allocation/V2/ISettlementFactory.cs | 91 +- .../Token/Allocation/V2/SettlementFactory.cs | 60 -- .../Allocation/V2/SettlementFactoryView.cs | 5 +- .../V2/SettlementFactory_PublicFetch.cs | 4 +- .../V2/SettlementFactory_SettleBatch.cs | 8 +- .../V2/SettlementFactory_SettleBatchResult.cs | 5 +- .../Api/Token/Allocation/V2/SettlementInfo.cs | 7 +- .../Api/Token/Allocation/V2/TransferLeg.cs | 9 +- .../Token/Allocation/V2/TransferLegSide.cs | 9 +- .../splice-api-token-holding-v1/README.md | 22 +- .../expected/.daml-langversion | 0 .../Splice/Api/Token/Holding/V1/Holding.cs | 60 -- .../Api/Token/Holding/V1/HoldingView.cs | 12 +- .../Splice/Api/Token/Holding/V1/IHolding.cs | 52 +- .../Api/Token/Holding/V1/InstrumentId.cs | 5 +- .../Splice/Api/Token/Holding/V1/Lock.cs | 7 +- .../splice-api-token-holding-v2/README.md | 27 +- .../expected/.daml-langversion | 0 .../Splice/Api/Token/Holding/V2/Account.cs | 6 +- .../Splice/Api/Token/Holding/V2/Holding.cs | 60 -- .../Api/Token/Holding/V2/HoldingView.cs | 12 +- .../Splice/Api/Token/Holding/V2/IHolding.cs | 52 +- .../Api/Token/Holding/V2/InstrumentId.cs | 5 +- .../Splice/Api/Token/Holding/V2/Lock.cs | 7 +- .../README.md | 23 +- .../expected/.daml-langversion | 0 .../Api/Token/Transfer/Events/V2/EventLog.cs | 60 -- .../Token/Transfer/Events/V2/EventLogView.cs | 5 +- .../Events/V2/EventLog_HoldingsChange.cs | 10 +- .../V2/EventLog_HoldingsChangeResult.cs | 2 +- .../Api/Token/Transfer/Events/V2/IEventLog.cs | 67 +- .../Transfer/Events/V2/TransferLegSide.cs | 9 +- .../README.md | 23 +- .../expected/.daml-langversion | 0 .../Instruction/V2/ITransferFactory.cs | 91 +- .../Instruction/V2/ITransferInstruction.cs | 126 ++- .../Token/Transfer/Instruction/V2/Transfer.cs | 11 +- .../Instruction/V2/TransferFactory.cs | 60 -- .../Instruction/V2/TransferFactoryView.cs | 5 +- .../V2/TransferFactory_PublicFetch.cs | 4 +- .../V2/TransferFactory_Transfer.cs | 6 +- .../Instruction/V2/TransferInstruction.cs | 60 -- .../TransferInstructionAction_TIA_Custom.cs | 4 +- .../V2/TransferInstructionResult.cs | 6 +- ...put_TransferInstructionResult_Completed.cs | 4 +- ...utput_TransferInstructionResult_Pending.cs | 4 +- .../Instruction/V2/TransferInstructionView.cs | 8 +- .../V2/TransferInstruction_Accept.cs | 5 +- .../V2/TransferInstruction_Reject.cs | 5 +- .../V2/TransferInstruction_Withdraw.cs | 5 +- .../Snapshots/splice-test-token-v1/README.md | 27 +- .../expected/.daml-langversion | 0 .../expected/Splice/Test/Token/V1/Token.cs | 27 +- .../Splice/Test/Token/V1/TokenAllocation.cs | 27 +- .../Splice/Test/Token/V1/TokenRules.cs | 39 +- .../Test/Token/V1/TokenTransferOffer.cs | 27 +- .../splice-test-token-v1/intermediate.binpb | Bin 56097 -> 83353 bytes .../splice-token-standard-utils/README.md | 32 +- .../emits-no-types | 9 +- .../expected/.daml-langversion | 0 .../expected/.gitkeep | 4 + .../StdlibPackagesRequireForFieldTypeTests.cs | 24 +- .../StdlibPackagesTests.cs | 2 +- .../SubmissionExtensionsEmitterTests.cs | 47 +- .../TemplateCodeGenTests.cs | 96 +- .../TemplateEmitterTests.cs | 272 ++++-- .../TestHelpers/CapturingLogger.cs | 39 + .../TestHelpers/DamlModelBuilder.cs | 2 +- .../TestHelpers/EmittedSubmissionShape.cs | 22 + .../TestHelpers/GeneratorFactory.cs | 9 +- .../TypeReferenceQualifierTests.cs | 38 +- .../Daml.Codegen.CSharp.Tests/UnifiedDiff.cs | 11 +- .../VariantEmitterTests.cs | 51 +- .../VersioningApiSurfaceTests.cs | 1 - .../ViewDescriptorPairingCompilesTests.cs | 100 ++ .../ArchiveViaContractIdTests.cs | 110 +++ .../ConformanceCorpusTests.cs | 56 +- .../ContractKeyOnActiveContractTests.cs | 133 +++ .../EmittedContractIdJsonTests.cs | 59 ++ .../ExerciseByKeyCommandTests.cs | 71 ++ .../FakeLedgerClient.cs | 43 +- .../GenericRecordFacetTests.cs | 50 + .../HoldingInterfaceChoiceTests.cs | 84 ++ .../InterfaceViewEnrichmentTests.cs | 56 ++ .../KeyDescriptorWitnessTests.cs | 63 ++ .../MarkerTests.cs | 15 +- .../OutcomeRoundTripTests.cs | 10 +- .../ProfileRoundTripTests.cs | 4 +- .../RelabelAsyncTests.cs | 54 +- .../RelabelResultTests.cs | 29 +- .../RichRecordChoiceTests.cs | 13 +- .../RichRecordRoundTripTests.cs | 20 +- .../SerializeThenReadRecordRoundTripTests.cs | 168 ++++ .../SubmissionExtensionsTests.cs | 37 +- .../SuitRoundTripTests.cs | 10 +- .../TypeCornersRoundTripTests.cs | 388 ++++++++ .../ActAsIgnoringSubmitterFakeClient.cs | 37 +- .../AuthorizingFakeClient.cs | 37 +- .../CommandIdConformanceTests.cs | 197 ++++ .../CommandIdFakeClientBase.cs | 101 ++ .../CommandIdHonoringFakeClient.cs | 40 + .../ConformanceProbe.cs | 7 +- .../ConformingFakeClient.cs | 55 +- .../HangingSubscriptionConformanceTests.cs | 37 +- .../SubmitterMergingFakeClient.cs | 37 +- .../SuppliedCommandIdIgnoringFakeClient.cs | 43 + .../UnsetCommandIdFakeClient.cs | 40 + .../LedgerClientExtensionsTests.cs | 177 +++- .../LedgerClientSubmitterInfoTests.cs | 190 ++-- .../LedgerOperationExceptionTests.cs | 35 +- .../SingleCommandExtensionsTests.cs | 342 +++++++ .../StakeholderResumeCompileGuardTests.cs | 75 +- .../StreamerSnapshotTests.cs | 393 ++++++++ .../AcsSnapshotEntryTests.cs | 206 +++- .../ChoiceResultFromCreatedContractsTests.cs | 14 +- tests/Daml.Runtime.Tests/CommandIdTests.cs | 40 + tests/Daml.Runtime.Tests/CommandTypesTests.cs | 251 +++++ .../Daml.Runtime.Tests/ContractIdJsonTests.cs | 254 +++++ .../ContractStreamEventTests.cs | 126 ++- .../ContractTypesTests.Data.cs | 198 +++- .../ContractTypesTests.TemplateInterfaces.cs | 192 +++- .../CreatedShapeAgreementTests.cs | 141 +++ .../Daml.Runtime.Tests.csproj | 4 + .../DamlJsonSerializerBasicTests.cs | 6 +- .../DamlJsonSerializerStructuralTests.cs | 50 +- .../DamlJsonSerializerValueOverloadsTests.cs | 12 +- .../DamlLfJsonReaderLimitsTests.cs | 267 +++++ .../DamlLfJsonReaderRepeatedDecodeTests.cs | 94 ++ .../DamlLfJsonReaderScalarTests.cs | 286 ++++++ .../DamlLfJsonReaderStdlibGenericsTests.cs | 916 ++++++++++++++++++ .../DamlLfJsonReaderStructuralTests.cs | 748 ++++++++++++++ .../DamlLfJsonReaderTests.cs | 100 ++ .../DamlLfJsonReaderWireSamplesTests.cs | 487 ++++++++++ tests/Daml.Runtime.Tests/DamlRecordFacet.cs | 12 + .../DamlRecordGenericInterfaceTests.cs | 33 + .../DamlTypesTests.Scalars.cs | 27 + .../DamlValueExtensionsTests.cs | 8 + .../DamlValueStructuralEqualityTests.cs | 52 + .../EventCollectionCopyTests.cs | 552 +++++++++++ .../ExerciseOutcomeProjectionTests.cs | 17 +- .../ExerciseOutcomeTests.cs | 24 +- .../ExercisedEventEqualityTests.cs | 141 +++ .../Daml.Runtime.Tests/MinLedgerTimeTests.cs | 118 +++ tests/Daml.Runtime.Tests/PartyJsonTests.cs | 13 + .../ReassignmentPairingFieldTypingTests.cs | 10 +- tests/Daml.Runtime.Tests/RecordedAtHolder.cs | 12 + .../SerializedWireShapeMatchesCaptureTests.cs | 150 +++ tests/Daml.Runtime.Tests/StdlibTests.cs | 245 ++++- .../StreamWitnessPartiesGuardTests.cs | 203 ++++ .../SynchronizerIdFieldTypingTests.cs | 16 +- .../Daml.Runtime.Tests/SynchronizerIdTests.cs | 42 + .../TimestampWireShapeAgreementTests.cs | 75 ++ .../TransactionResultTests.cs | 477 ++++++++- .../TransactionTreeTests.cs | 59 +- .../TransportFailureShapeAgreementTests.cs | 168 ++++ .../Daml.Runtime.Tests/ViewDescriptorTests.cs | 88 ++ .../WitnessPartiesTypedTests.cs | 13 +- tests/Directory.Build.props | 18 +- tests/Directory.Packages.props | 12 +- tests/Shared/TestNamingConventionTests.cs | 51 + tests/wire-samples/README.md | 122 +++ tests/wire-samples/daml/WireTypes.daml | 96 ++ tests/wire-samples/daml/daml.yaml | 12 + .../data/acs_interface_view_holding.json | 150 +++ .../data/acs_wildcard_verbose_false.json | 328 +++++++ .../data/acs_wildcard_verbose_true.json | 328 +++++++ .../data/create_asset_numeric_edges.json | 50 + .../data/create_keyed_contract_key.json | 53 + tests/wire-samples/data/create_marker.json | 49 + .../data/create_wirerecord_empty.json | 78 ++ .../data/create_wirerecord_populated.json | 96 ++ .../wire-samples/data/exercise_describe.json | 54 ++ tests/wire-samples/data/exercise_ping.json | 46 + tests/wire-samples/data/exercise_relabel.json | 121 +++ .../data/probe_nested_optional_matrix.json | 53 + 552 files changed, 30830 insertions(+), 6837 deletions(-) create mode 100644 conformance/contractkeys/ContractKeys.daml create mode 100644 conformance/contractkeys/KeyBuilders.daml create mode 100644 conformance/contractkeys/contractkeys.dar create mode 100644 conformance/contractkeys/daml.yaml create mode 100644 conformance/crossmodulecollision/CollisionA.daml create mode 100644 conformance/crossmodulecollision/CollisionB.daml create mode 100644 conformance/crossmodulecollision/crossmodulecollision.dar create mode 100644 conformance/crossmodulecollision/daml.yaml create mode 100644 conformance/defaulttarget/DefaultTarget.daml create mode 100644 conformance/defaulttarget/daml.yaml create mode 100644 conformance/defaulttarget/defaulttarget.dar create mode 100644 jvm-helper/src/test/resources/blank-build-version.txt delete mode 100644 samples/QuickstartExample/Generated/.daml-langversion create mode 100644 scripts/check-license-headers.test.sh create mode 100644 src/Daml.Codegen.CSharp/CodeGen/ChoiceArgumentInfo.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExerciserOverloads.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ResultStructs.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.SubmissionParameters.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/ChoiceSubmitterParameter.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/EmittedUsings.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/OptionalRepresentation.cs create mode 100644 src/Daml.Codegen.CSharp/CodeGen/PackageQualifiedResolver.cs create mode 100644 src/Daml.Codegen.CSharp/CodegenException.cs delete mode 100644 src/Daml.Codegen.CSharp/ConsoleLogger.cs delete mode 100644 src/Daml.Codegen.CSharp/ICodegenLogger.cs create mode 100644 src/Daml.Codegen.CSharp/VerbosityConsoleLoggerProvider.cs rename src/Daml.Codegen.CSharp/{Model => Versioning}/FourPartPackageVersion.cs (96%) create mode 100644 src/Daml.Codegen.Intermediate/Daml.Codegen.Intermediate.csproj rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/DamlDataType.cs (97%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/DamlPackage.cs (98%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/DamlTemplate.cs (91%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/DamlType.cs (53%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/DarModel.cs (67%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/IDarSource.cs (50%) rename src/{Daml.Codegen.CSharp => Daml.Codegen.Intermediate}/Model/PackageVersionParser.cs (97%) create mode 100644 src/Daml.Codegen.Intermediate/README.md delete mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/.daml-langversion create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.Credit.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.CurrentBalance.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/AccountKey.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Calendar.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ContractIdentifiers.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.Extend.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/HolidayKey.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.Reschedule.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleKey.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleView.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.Revise.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/ContractIdentifiers.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/Note.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Box.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Branch.cs rename src/Daml.Codegen.Testing.Conformance/Generated/{ => Richtypes}/ContractIdentifiers.cs (82%) create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Crate.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Describe.cs delete mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Holding.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Reissue.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Slot.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.Rebox.cs create mode 100644 src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.cs create mode 100644 src/Daml.Ledger.Abstractions.Testing.Conformance/CommandIdConformanceFixture.cs delete mode 100644 src/Daml.Ledger.Abstractions/Extensions/PartyOverloads.cs create mode 100644 src/Daml.Ledger.Abstractions/Extensions/SingleCommandExtensions.cs create mode 100644 src/Daml.Ledger.Abstractions/Extensions/StreamerSnapshot.cs create mode 100644 src/Daml.Runtime/Commands/MinLedgerTime.cs create mode 100644 src/Daml.Runtime/Contracts/EventCollections.cs create mode 100644 src/Daml.Runtime/Contracts/KeyDescriptor.cs create mode 100644 src/Daml.Runtime/Contracts/ViewDescriptor.cs create mode 100644 src/Daml.Runtime/Serialization/ContractIdJsonConverter.cs create mode 100644 src/Daml.Runtime/Serialization/DamlJsonConverters.cs create mode 100644 src/Daml.Runtime/Serialization/DamlLfJsonReader.cs create mode 100644 src/Daml.Runtime/Serialization/OpaqueStringIdJsonConverter.cs create mode 100644 src/Daml.Runtime/Stdlib/Optional.cs create mode 100644 src/Daml.Runtime/Streams/AcsSnapshotEntryExtensions.cs create mode 100644 src/Daml.Runtime/Streams/InterfaceAcsSnapshotEntry.cs create mode 100644 src/Daml.Runtime/Streams/InterfaceStreamEvent.cs rename tests/Daml.Codegen.CSharp.Cli.Tests/{ConsoleRedirectionCollection.cs => ConsoleRedirectionCollectionDefinition.cs} (90%) create mode 100644 tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterByKeyCommandTests.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/CliExitCodeTests.cs rename tests/Daml.Codegen.CSharp.Tests/{CodeGenEdgeCaseTests.InterfacePlaceholders.cs => CodeGenEdgeCaseTests.InterfaceShadowedRecords.cs} (67%) create mode 100644 tests/Daml.Codegen.CSharp.Tests/CodegenLoggingTests.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/ConformancePackageContentTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedContractKeySlotCompilesTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedFacetDeclarationCompilesTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedInterfaceChoiceExerciserInvocationTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedPayloadPropertyCollidingWithTemplateNameCompilesTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedSharedViewCompilesTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedSubmitterOverloadResolutionTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/EmittedViewFieldCollisionCompilesTests.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/ICodegenLoggerTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/OptionalRepresentationTests.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/cross-module-collision.dar delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Holding.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Holding.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction.cs delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/.daml-langversion delete mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.daml-langversion create mode 100644 tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.gitkeep create mode 100644 tests/Daml.Codegen.CSharp.Tests/TestHelpers/CapturingLogger.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/TestHelpers/EmittedSubmissionShape.cs create mode 100644 tests/Daml.Codegen.CSharp.Tests/ViewDescriptorPairingCompilesTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/ArchiveViaContractIdTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/ContractKeyOnActiveContractTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/EmittedContractIdJsonTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/ExerciseByKeyCommandTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/GenericRecordFacetTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/HoldingInterfaceChoiceTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/InterfaceViewEnrichmentTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/KeyDescriptorWitnessTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/SerializeThenReadRecordRoundTripTests.cs create mode 100644 tests/Daml.Codegen.Testing.Conformance.Tests/TypeCornersRoundTripTests.cs create mode 100644 tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdConformanceTests.cs create mode 100644 tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdFakeClientBase.cs create mode 100644 tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdHonoringFakeClient.cs create mode 100644 tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SuppliedCommandIdIgnoringFakeClient.cs create mode 100644 tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/UnsetCommandIdFakeClient.cs create mode 100644 tests/Daml.Ledger.Abstractions.Tests/SingleCommandExtensionsTests.cs create mode 100644 tests/Daml.Ledger.Abstractions.Tests/StreamerSnapshotTests.cs create mode 100644 tests/Daml.Runtime.Tests/ContractIdJsonTests.cs create mode 100644 tests/Daml.Runtime.Tests/CreatedShapeAgreementTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderLimitsTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderRepeatedDecodeTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderScalarTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderStdlibGenericsTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderStructuralTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlLfJsonReaderWireSamplesTests.cs create mode 100644 tests/Daml.Runtime.Tests/DamlRecordFacet.cs create mode 100644 tests/Daml.Runtime.Tests/DamlRecordGenericInterfaceTests.cs create mode 100644 tests/Daml.Runtime.Tests/EventCollectionCopyTests.cs create mode 100644 tests/Daml.Runtime.Tests/ExercisedEventEqualityTests.cs create mode 100644 tests/Daml.Runtime.Tests/MinLedgerTimeTests.cs create mode 100644 tests/Daml.Runtime.Tests/RecordedAtHolder.cs create mode 100644 tests/Daml.Runtime.Tests/SerializedWireShapeMatchesCaptureTests.cs create mode 100644 tests/Daml.Runtime.Tests/StreamWitnessPartiesGuardTests.cs create mode 100644 tests/Daml.Runtime.Tests/TimestampWireShapeAgreementTests.cs create mode 100644 tests/Daml.Runtime.Tests/TransportFailureShapeAgreementTests.cs create mode 100644 tests/Daml.Runtime.Tests/ViewDescriptorTests.cs create mode 100644 tests/Shared/TestNamingConventionTests.cs create mode 100644 tests/wire-samples/README.md create mode 100644 tests/wire-samples/daml/WireTypes.daml create mode 100644 tests/wire-samples/daml/daml.yaml create mode 100644 tests/wire-samples/data/acs_interface_view_holding.json create mode 100644 tests/wire-samples/data/acs_wildcard_verbose_false.json create mode 100644 tests/wire-samples/data/acs_wildcard_verbose_true.json create mode 100644 tests/wire-samples/data/create_asset_numeric_edges.json create mode 100644 tests/wire-samples/data/create_keyed_contract_key.json create mode 100644 tests/wire-samples/data/create_marker.json create mode 100644 tests/wire-samples/data/create_wirerecord_empty.json create mode 100644 tests/wire-samples/data/create_wirerecord_populated.json create mode 100644 tests/wire-samples/data/exercise_describe.json create mode 100644 tests/wire-samples/data/exercise_ping.json create mode 100644 tests/wire-samples/data/exercise_relabel.json create mode 100644 tests/wire-samples/data/probe_nested_optional_matrix.json diff --git a/.editorconfig b/.editorconfig index c910d85..c5c3caf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,6 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 + # EditorConfig - https://editorconfig.org root = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 256ad48..0269888 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 + repos: - repo: local hooks: @@ -6,4 +9,4 @@ repos: entry: scripts/check-license-headers.sh language: script pass_filenames: false - files: \.(cs|scala)$ + files: ^(src|tests|jvm-helper|samples|scripts|conformance|proto|CONTEXT\.md|Directory\.Build\.props|coverage\.settings\.xml|\.editorconfig|\.pre-commit-config\.yaml)(/|$) diff --git a/CONTEXT.md b/CONTEXT.md index 4f53b75..c2d1029 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -22,8 +22,11 @@ _Avoid_: "decoded DAR", "AST file" The Scala binary that reads a `.dar` via `daml-lf-archive` and emits an Intermediate DAR. Runs only at codegen time — never at application runtime. Coupling to `daml-lf-archive` is confined to this binary. Shipped as a JAR inside the `dpm codegen-cs` bundle and -executed against the host JDK (a dpm install precondition). The helper ships only -inside that OCI bundle — its source is not part of the public repository. +executed against the host JDK (a dpm install precondition). The helper ships +inside that OCI bundle — its source is not part of the public repository. It is +also packaged standalone as `daml-dar-to-proto`: GitHub releases will attach +the runnable jar and the Intermediate DAR proto schema, so non-C# SDKs can run the +JVM helper directly to turn a `.dar` into an Intermediate DAR. _Avoid_: "Scala helper", "decoder service", "ast extractor" **AstToIntermediate translator**: @@ -71,7 +74,7 @@ _Avoid_: "the cli", "codegen-cs tool", "codegen-cs plugin", "the container" **`PackageEmitContext`**: The immutable per-package value the C# emitter threads through its emit methods: the root namespace, the `TypeReferenceQualifier`, the per-package data-type lookup, and the local -enum / variant / interface-placeholder / choice-argument name sets. Built once per package by +enum / variant / interface / choice-argument name sets. Built once per package by `PackageEmitContext.ForPackage`; read-only during emission. Replaces the mutable `_current*` / `_local*` instance fields the emitter used to clear at the start of each package. _Avoid_: "codegen state", "the current-package fields", "emit scratch" @@ -123,6 +126,86 @@ slots) is pulled out as the pure `ChoiceCreatedSlots.Extract` helper and unit-te Distinct from `SubmissionExtensionsEmitter`: creating a contract is not exercising a choice. _Avoid_: "choice helper", "the exercise writer", "the async wrapper generator", "choice-arg owner" +**Descriptor witness**: +A generated `static` value whose *type* names two or more types that belong together, passed +as an ordinary argument so a call site infers all of them at once. C# performs no partial +type-argument inference — a caller either supplies every type argument or none — so whenever +a surface needs two related types and only one is spellable at the call site, the pair travels +on a descriptor instead. `ViewDescriptor` pairs an interface marker with its +view record, `Choice` pairs a template with a choice's argument and +result, and `KeyDescriptor` pairs a keyed template with its key type. A descriptor may be +empty (a pure type witness, like `ViewDescriptor`) or carry codecs (like `Choice`, and like +`KeyDescriptor`, which carries the key decode). `Choice` and `ViewDescriptor` are plain statics +on the type they describe. `KeyDescriptor` is instead reached through a `static abstract` member +on a facet interface, so generic code finds it without reflection — that is the forward +convention new descriptors follow, not a description of the existing two. +_Avoid_: "marker", "phantom type" (those name the facet interface, not the value), "the +metadata object", "type token" + +**Contract key**: +The identifier a keyed template's contracts can be looked up by. It is a property of an +*active contract*, not of a template payload — the ledger sends it on every keyed created +event, so it is read, not computed. A payload that has never been to the ledger has no key. +Keys are not unique: several active contracts may share one, lookups return a first match by +an order the ledger only partly guarantees — contracts created in the current transaction +first, then explicitly disclosed contracts in the order the command listed them, then +contracts known to the participant in no guaranteed order — and enforcing uniqueness is the +consuming application's job. +A generated payload type carries a static `Key` **descriptor witness**. That is not a +counter-example to the above: a static describes the *template*, stating which key type this +template's contracts are looked up by. It never asserts that a payload instance knows its own +key, which is the promise ADR 0013 found unkeepable. +_Avoid_: "the key projection" (that names the Daml-side expression, not the value), +"key accessor", "computing a contract's key" + +**Key type**: +The generated C# type of a contract key — a record, a tuple, or a bare primitive — fully +constructible and serializable by a caller who has never seen the contract. Distinct from a +**key value**, which is an inhabitant of it. The key type is what makes by-key commands +possible: the caller builds a key value out of data it already holds. Generated for every +keyed template, independent of whether anything about the key can be analysed. +_Avoid_: "the key", "key record" (a key may be a tuple or a bare `Party`) + +**Key expression**: +The Daml-side projection from a template payload to a key value. It exists in the Daml-LF +archive and is deliberately **not** carried in the Intermediate AST: representing it means +representing arbitrary value construction, and a partial representation yields a silently +wrong key in a published package. Its absence is a decision, not a gap. It is never inlined +in the archive — the compiler lifts it to a generated top-level value that the template body +applies — so reading one is evaluation, not field access. +_Avoid_: "key projection" unqualified, "the key function", "a record of field projections" + +**Supported input**: +The set of Daml-LF versions a released codegen accepts. Supported means +**proven by a fixture exercised in CI** — not merely decoded by the reader, and not merely +emittable by some compiler. A version the reader accepts but no fixture covers is a gap, and +is named as one. +_Avoid_: "the supported envelope" (jargon; say what it is), "supported SDK version" (the SDK +range and the Daml-LF range no longer coincide and must be stated separately) + +**Default Daml-LF version**: +Daml-LF **2.2** — what the compiler emits when a project sets no target, on both the 3.4 and +3.5 lines, silently and with no diagnostic. Project scaffolding does not write a target +either, so this is the version an ordinary consumer produces without deciding to. The +`defaulttarget` conformance fixture pins no target for exactly this reason, so the default is +covered on both the read and the emit path. +_Avoid_: treating 2.1 as "the normal case", "LF 2.x" as a single accepted range (the JVM read +path enumerates versions; the .NET path does not) + +**Maintainers**: +The parties responsible for a contract key. Declared in Daml as a function *from the key +type* to a list of parties — never from the payload — so a maintainer analysis is a party +projection whose **projection source** is the key. +_Avoid_: "key owners", "key signatories" + +**Projection source**: +The binder a static party projection reads from: the template payload (`signatory`, +`observer`), the contract key (`maintainer`), or a choice argument (choice-level +`controller` / `observer`). Carried on every party-analysis verdict, because a projection is +only meaningful against the binder it was written against. A projection whose source cannot +be determined is `Dynamic`. +_Avoid_: "the binder" alone, "field owner" + ## Example dialogue > **Dev**: Where does the JVM dependency go? I thought consumers shouldn't need a JDK. diff --git a/Daml.Codegen.CSharp.slnx b/Daml.Codegen.CSharp.slnx index 3ec4221..236827a 100644 --- a/Daml.Codegen.CSharp.slnx +++ b/Daml.Codegen.CSharp.slnx @@ -10,6 +10,7 @@ + diff --git a/Directory.Build.props b/Directory.Build.props index 8d5fa92..11cbf9d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,17 @@ true true latest-recommended - $(NoWarn);CA1062;CA2225;CA1711;CA1721;CA1000;CA1720;CA1305;CA1024;CA1707;CA1822;CA1850;CA1852;CA1859 + true + $(NoWarn);CA1062;CA1852;CA1859 + + + true + true + true + snupkg + true Peaceful Studio OÜ @@ -20,9 +30,9 @@ icon.png - 0.4.1-preview.1 - 0.4.1.0 - 0.4.1.0 + 0.5.0-preview.1 + 0.5.0.0 + 0.5.0.0 $(MSBuildThisFileDirectory)output/nuget diff --git a/Directory.Packages.props b/Directory.Packages.props index 2db58f1..bf041ee 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,18 +4,20 @@ - + + + + - - - - - - - + + + + + + diff --git a/conformance/README.md b/conformance/README.md index ba040d0..29514cd 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -1,12 +1,99 @@ # Conformance corpus -DAML models that define the type shapes codegen claims to support. Compiled to -`richtypes.dar` and shipped (compiled + embedded) in `Daml.Codegen.Testing.Conformance`. +DAML models that define the type shapes codegen claims to support. Three corpora are +shipped (compiled + embedded) in `Daml.Codegen.Testing.Conformance`: `richtypes` below, +plus the contract-key and default-target corpora described further down. The committed `.dar` is the source of truth for the package build. To grow the corpus (new type shapes), edit `RichTypes.daml`, then rebuild and commit the DAR: cd conformance/richtypes && dpm build && cp .daml/dist/richtypes-*.dar ./richtypes.dar +After rebuilding the DAR, refresh the generated tree the package ships +(`scripts/refresh-conformance.sh`) and the pinned SHAs the determinism gate +compares against (`scripts/codegen-determinism.sh --update`); both read this DAR. + +`RichRecord` covers the primitive, collection and nominal shapes; `TypeCorners` +covers the harder corners — parameterized records and variants (`Box`, `Slot`) +instantiated in a template payload, `GenMap` keyed by `Party` and by `Int`, +`Either`, `Tuple2`/`Tuple3`, the recursive record `Branch`, an `Optional` nested +directly inside an `Optional` (`maybeMaybeNote`) and one separated from its outer +`Optional` by an intervening record (`nestedNote`), an `Optional` over a record's +own type variable (`Crate`) and one carried by an `Either` arm (`noteOrRank`) — +positions C# nullable syntax cannot spell, so they emit the `Optional` wrapper +instead — and the `Numeric` scale extremes 0 and 37. The `Holding` interface carries choices (`Describe`, `Reissue`) as well as a +view, so the interface-choice emitter path is exercised through a real DAR rather +than a synthetic package. + +Contract keys are deliberately absent: they need a Daml-LF version above the +`--target` this corpus pins, and retargeting it would cost the LF 2.1 coverage it +exists to ship, so keys live in their own package below. +`RichTypesCorpusDarCharacterizationTests` asserts both the pin and the absence of +keyed templates, so moving the pin or adding a keyed template here has to be a +deliberate act. + +The `contractkeys` package carries the contract-key shapes, and pins +`--target=2.3` because neither 2.1 nor 2.2 can express a contract key at all. It +is compiled and shipped by `Daml.Codegen.Testing.Conformance` alongside +`richtypes`, and its DAR is reachable from +`ConformanceCorpus.OpenDar(ConformancePackage.ContractKeys)`. The four key shapes +it covers are the ones real key-bearing Daml packages use: a record built from +several payload fields (`Account`), a record whose field comes from a projection +nested inside a payload record (`Holiday`), a record built by a function declared +in another module, sharing no field name with the payload (`Schedule`), and a +bare `Party` key whose maintainer clause names the key binder itself +(`Steward`). `ContractKeysCorpusDarCharacterizationTests` reads the DAR and +asserts each of them, so a fixture edit that flattens a shape fails rather than +quietly narrowing the evidence. Rebuild its DAR the same way: + + cd conformance/contractkeys && dpm build && \ + cp .daml/dist/contractkeys-*.dar ./contractkeys.dar + +`contractkeys` is a shipped package, so rebuilding its DAR also requires +refreshing the generated tree (`scripts/refresh-conformance.sh`) — without that +the `Generated/Contractkeys/` sources the package compiles go stale silently. +The determinism gate reads only `richtypes`, so `codegen-determinism.sh` does +not need re-running for a contract-key-only change. + +The `crossmodulecollision` package is a codegen-drift regression +fixture rather than a type-shape corpus: two modules (`CollisionA`, +`CollisionB`) each declare a same-named `Retag` choice-argument record with a +different field list, guarding against a cross-module simple-name collision in +the emitter. Its generated output is pinned by the `cross-module-collision` +drift snapshot under `tests/Daml.Codegen.CSharp.Tests/Snapshots`; rebuild its +DAR the same way: + + cd conformance/crossmodulecollision && dpm build && \ + cp .daml/dist/crossmodulecollision-*.dar ./crossmodulecollision.dar + +That snapshot regenerates from its own vendored copy of this DAR, not from the +one here, so a rebuild is only half the job: copy the result over +`tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/cross-module-collision.dar` +and refresh the snapshot (`scripts/refresh-snapshot.sh cross-module-collision`). +Nothing enforces that the two copies agree — skip the copy and the snapshot goes +on regenerating cleanly from the stale one. + +The `defaulttarget` package is a single boring template whose only job is +to be compiled with **no `build-options:` block at all**. `damlc` picks a +Daml-LF target when a project does not request one, and it announces that +choice nowhere, so a scaffolded project hands the toolchain a version the rest +of this corpus never exercises. The omission is load-bearing: adding +`--target=` here — to any version, including the one currently emitted — turns +the fixture into a restatement of the pin and stops it tracking the default. +`DefaultTargetDarCharacterizationTests` reads this DAR off disk and asserts the +version it actually carries, so a moved default surfaces as a failing +assertion rather than as silence. It is a shipped package too: the emitter runs +over it and `Daml.Codegen.Testing.Conformance` compiles and ships the result, so +the default target is covered on the emit path and not only on the read one. +Rebuild its DAR the same way: + + cd conformance/defaulttarget && dpm build && \ + cp .daml/dist/defaulttarget-*.dar ./defaulttarget.dar + +Like `contractkeys`, rebuilding this DAR also requires refreshing the generated +tree (`scripts/refresh-conformance.sh`). The determinism gate reads only +`richtypes`, so `codegen-determinism.sh` does not need re-running for a +default-target-only change. + The decision record for the conformance package and its live-ledger gate is kept in the project's internal ADR collection. diff --git a/conformance/contractkeys/ContractKeys.daml b/conformance/contractkeys/ContractKeys.daml new file mode 100644 index 0000000..c9f08cc --- /dev/null +++ b/conformance/contractkeys/ContractKeys.daml @@ -0,0 +1,87 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module ContractKeys where + +import KeyBuilders (ScheduleKey, ScheduleView(..), toScheduleKey) + +data AccountKey = AccountKey + with + custodian : Party + label : Text + deriving (Eq, Show) + +template Account + with + custodian : Party + label : Text + balance : Int + where + signatory custodian + key AccountKey custodian label : AccountKey + maintainer key.custodian + + choice Credit : ContractId Account + with + delta : Int + controller custodian + do create this with balance = balance + delta + + nonconsuming choice CurrentBalance : Int + controller custodian + do pure balance + +data Calendar = Calendar + with + id : Text + holidays : [Text] + deriving (Eq, Show) + +data HolidayKey = HolidayKey + with + provider : Party + id : Text + deriving (Eq, Show) + +template Holiday + with + provider : Party + calendar : Calendar + where + signatory provider + key HolidayKey provider calendar.id : HolidayKey + maintainer key.provider + + choice Extend : ContractId Holiday + with + added : Text + controller provider + do create this with calendar = calendar with holidays = added :: calendar.holidays + +template Schedule + with + view : ScheduleView + where + signatory view.issuer + key toScheduleKey view : ScheduleKey + maintainer key.custodian + + choice Reschedule : ContractId Schedule + with + rescheduled : Text + controller view.issuer + do create this with view = view with reference = rescheduled + +template Steward + with + steward : Party + charter : Text + where + signatory steward + key steward : Party + maintainer key + + choice Revise : ContractId Steward + with + revised : Text + controller steward + do create this with charter = revised diff --git a/conformance/contractkeys/KeyBuilders.daml b/conformance/contractkeys/KeyBuilders.daml new file mode 100644 index 0000000..17289aa --- /dev/null +++ b/conformance/contractkeys/KeyBuilders.daml @@ -0,0 +1,20 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module KeyBuilders where + +data ScheduleView = ScheduleView + with + issuer : Party + reference : Text + deriving (Eq, Show) + +data ScheduleKey = ScheduleKey + with + custodian : Party + id : Text + deriving (Eq, Show) + +toScheduleKey : ScheduleView -> ScheduleKey +toScheduleKey view = ScheduleKey with + custodian = view.issuer + id = view.reference diff --git a/conformance/contractkeys/contractkeys.dar b/conformance/contractkeys/contractkeys.dar new file mode 100644 index 0000000000000000000000000000000000000000..3783b556430cec77ff9fb366afb70986ccb9b4bf GIT binary patch literal 400687 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
    cIx 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

    LHInav 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Ξ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 K{p{mkUcUhFssP^r literal 0 HcmV?d00001 diff --git a/conformance/contractkeys/daml.yaml b/conformance/contractkeys/daml.yaml new file mode 100644 index 0000000..cedb579 --- /dev/null +++ b/conformance/contractkeys/daml.yaml @@ -0,0 +1,11 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 +sdk-version: 3.5.2 +name: contractkeys +source: . +version: 0.0.1 +dependencies: + - daml-prim + - daml-stdlib +build-options: + - --target=2.3 diff --git a/conformance/crossmodulecollision/CollisionA.daml b/conformance/crossmodulecollision/CollisionA.daml new file mode 100644 index 0000000..193aecd --- /dev/null +++ b/conformance/crossmodulecollision/CollisionA.daml @@ -0,0 +1,15 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module CollisionA where + +template Agreement + with + operator : Party + where + signatory operator + + choice Retag : ContractId Agreement + with + newOperator : Party + controller operator + do create this with operator = newOperator diff --git a/conformance/crossmodulecollision/CollisionB.daml b/conformance/crossmodulecollision/CollisionB.daml new file mode 100644 index 0000000..c521fb3 --- /dev/null +++ b/conformance/crossmodulecollision/CollisionB.daml @@ -0,0 +1,17 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module CollisionB where + +template Offer + with + owner : Party + memo : Text + where + signatory owner + + choice Retag : ContractId Offer + with + label : Text + count : Int + controller owner + do create this with memo = label diff --git a/conformance/crossmodulecollision/crossmodulecollision.dar b/conformance/crossmodulecollision/crossmodulecollision.dar new file mode 100644 index 0000000000000000000000000000000000000000..0f1483fcbb0389a3f8dc98cdb6879eb7b28591d9 GIT binary patch literal 257010 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%0DV-KO<LeDIK<& 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 literal 0 HcmV?d00001 diff --git a/conformance/crossmodulecollision/daml.yaml b/conformance/crossmodulecollision/daml.yaml new file mode 100644 index 0000000..0134085 --- /dev/null +++ b/conformance/crossmodulecollision/daml.yaml @@ -0,0 +1,12 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 +sdk-version: 3.5.2 +name: crossmodulecollision +source: . +version: 0.0.1 +dependencies: + - daml-prim + - daml-stdlib +build-options: + - --target=2.1 + - -Wno-upgrade-interfaces diff --git a/conformance/defaulttarget/DefaultTarget.daml b/conformance/defaulttarget/DefaultTarget.daml new file mode 100644 index 0000000..3a6dc50 --- /dev/null +++ b/conformance/defaulttarget/DefaultTarget.daml @@ -0,0 +1,11 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module DefaultTarget where + +template Note + with + owner : Party + body : Text + revision : Int + where + signatory owner diff --git a/conformance/defaulttarget/daml.yaml b/conformance/defaulttarget/daml.yaml new file mode 100644 index 0000000..d95ea24 --- /dev/null +++ b/conformance/defaulttarget/daml.yaml @@ -0,0 +1,9 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 +sdk-version: 3.5.2 +name: defaulttarget +source: . +version: 0.0.1 +dependencies: + - daml-prim + - daml-stdlib diff --git a/conformance/defaulttarget/defaulttarget.dar b/conformance/defaulttarget/defaulttarget.dar new file mode 100644 index 0000000000000000000000000000000000000000..84cd200a756b33aba02a5ea7094b317185fa9735 GIT binary patch literal 359335 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°u>hPcs{C=JZSNP|yWFGMr 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 z zTSbQpkktc>7uW5r6@fo?g1tX2OaJzs6IE45EVprBqF(hL90WMj>)W*w?ftXw^=}Ib zB+XidWIPF^H?QpkRK>7a^VS&cE?LcPxZ_lJ?Bs^^MfnFBq1pq%9VR6*4I~x6NJHP*tLIb{v0aj%-EaY&Om!kWd)i^s8??X) z(ur{GY1f+AP{I6W1?S{qm}%Gg{*93-<>ueC(2tp8Z)Egtq|nppi8R?e>ypUqiSF#* zD~PIXKEUUC1oZx|z{Q{bHXp<0({Cm4RpFNcN!u zM%*jrpQx1cK~NlTdd3OIF1LM=RolP>CDKHRoPekbC3>Y)@S#SW)tbfZ;Bqo5=%XmB z^$xu?gOs(R4?@BhApy3}5iQ<`7GFe*52EEcBETCF;EM==2JL=Zo#;FGQ8gS+RC|*v z*K;6KKKzKNwl7z%_dup{*ny}Pb#xxcR16o=TX#P_qUzge;v5%7M9glOy`1yPl^Z;e zsU0RHs)gkukO%G6!+g~PzkLLEB8&D<#1d*4Rzo}Y%{z#9eJ`^^K?+i7=O;mRy5ZGvMURW?{W^^2HApm%l`p3)|g65v%(V zKPKk_PeU^9vM01qkW^#$WL^uP0yW%PuF(M7XNBanYsyNK{kXh1@R=Xh_qO?H?*HaC4k_ zEc(E$jDfycG&%W4Ex;Qf;RliyusioxIMQk*0X3Xk^l3l9=kQ1Jih|nV{bY^Zvs_DC z6*pQp-!$?HtLkA6psL`^2CB_mOFI=e6F1*X5coJO4gw!JBZb+U72SLb0989otX6W$ zrQ->q7hZaXrQQ0JtK}Z~v`6lur;2vqQ{OEXPJg_m$gBVtotoHY3PK zFxfM3Rk=wMpD@$Bmt$8BHjF*W!Q1p9AE9K=;8Nv|q`r;@9ZPOKhM3f?vY*EbFa*7SOCXEr^^92vm2qn3D=3RRpwTl*-!_LgH8nKqX-Up&RE}SPDM$! zjCHY#IQ;jFwZlR>c$-){kHu~#)&EdY3d56ilD7?V<-V3H6c5|b&$@_TFce?rbB>Ev3Ozky zFTMny*rJuSPmg$uFLOC-MJprcHi*?ubaa;O4O4{OpHn%Pgk*3se90mmHxDf~!zdo+ zkDh#%)kEiz)f2SLAAR`348tMW3}aoj5ors<(` zH~xIS7#8LAb#4wnCz2&u=qug;mgozS$z)sY2cp89dGvG!Ux<<`oN)%kL@^!O-$aY_ zed#$L`O=f)>TLT3NSuMhCJ$Tq*VFnj@%wbgVi&LIKc5??AA>yw=3awavhz=DD2u+R z*GPu}ib6*as7A%w7L#qO!FMbOXr@ zYc}Ey(2ys8WT#tD5pIFm{w4c7&-0>Q3(KJuZ-ALRxs#plN=5h-X3I$SS&I9Gj|Rqi z9ohhRf^snh-5^BxwU%vuYL668^db0T3Q^FNL4+RzDy`?-2xGk(ZNQ5txg&+?`vsb^ z?Ulv(ijNEpO%sLnqG$Ke zZyx2J77(~Imr1PHmLaRko=xU8zmtDp9?_^bDZ6F%EsWtzdR%4e2C66&Qg}Qz9enpt zQOLRQSWs=f@Ie2DQn1m0V#c6GZ9V%Y&v`WahI}$h=!zJ%`HI>IsaSOV?WXd$Xt>jC z+8n)g|Lfss&bzACWG*4}uc3|pqmH)<3mFSx_c70A;KIu6j}@2#tHSr-Ez05jmdNtv zNUDz+on229LR`rHBJT?S&1E?qO)9E=T}q_bO{$+nWAMm=m=ng|>c!-eT+e!n>EH&A zC#SR|_X-8tL^0&`Ec+F0>TvANaZ2BNlu74-ZHw<`ikfqXa5xE^KJ>V zNpq@`6y~b{ow2ty!%O?V!HN`BUc3uuz89f_XP(`oop?o+_oKw@N7#JrS9Kh?$s|px<39Ruo;)khJSKcf zp0(l!Cj19zDBbF;<6vC`j|f~EHoaW!e?FV({mzfj)cV);oWmpXfS2j7(T`@6KUCJG zd?70gDQ^{nG8ImFJAYLDBdmQu*16BEpdy9DU2^#?#rUm8n&MUQ`Fx5Rg8lYxpsT5> zW@kjgmQUZ%H7IeHOqSX~_VM?}ywtMw>)aJdyIXq2)|Hkv%R@mYN1L6H`?tiyS0&c1 z`wX6gs>%Ws8p;|NkoP3`@4J+klqoc#o?>exs1YNckH>b5zYVlnC-oeVbKmd$X8F9Z z@pO5Md2do`woSKCq5bDSb5E%oZ8gthrLpju;T!QwgD^-+WAQ(#$ecmXb{ee!p*U*EDmjdbg(N0J54ccAM(T!01H(}%a>0X?724Qam zOeAC*8q7TVj8g>m4Z>z=TWnsPx<3mpTsswdw*N4+OrZAfl5^;;x6>5*=S0&)yia#WQDWYfw!_Fe7ew}I=*I&~MUwhrlYjGNVd1yH^+ZNo^UWaM5 zKVwyn-tOLo?jDC8IIb1#^iGDz(2H*Z;f99L)vKc&y1kH-VGWwexy$m=$Y}^|FgO*) zk%!-9P5z79O?iLgW3k+O7b|d#@c35C@?`u?xgA8JZ=wIsXzpNjB=62C0xtbm2 zi5ll2v53lVbqW;y)zzF)VNNA;R$r>^;XwKAU-q*$_epQ;cX>-Tgu1R|C(?sGgZ zC3+;EwZ+-X5`VHu3xMGu1c>IN-^#l7$VTlGU={cthnL0fk25;pLJ{F+cCm{zFgo$!oHcuuGTt z!dW_07CXdZu<-^)Bz7xcnLmo&gVl^e;sXu()5}qGkIr%XHzx$ecWK#M-{}v27`{o;Vrb z71}9<&28j3yy2H$#HESAyP*PUn2;*Rq~rDT(TSUT}Q{NNOu2t6C*55c)Ku4zNR() zeKwc39Q#vX_8(5_0*MxW)Q5tQX<1=JRc-*xU){A1leG@r)sDh* zPvbCBij_ymvv9tZ=dchJapk~Cy&h7=%PM^^O8ED}RU$5C^53#{a}L+?e6Ag&`-6XR zLUkZ|o%i|w+cb2Swx3Ls}%<~)bO{Dr=e!Go3+5*@H^pLpJ*gQU}Qr(=? zi3lDNs$I<{y?-%Wi?+&oL5;ykbDflG!I-) z?!&c9QD=A&yBytP+}+#5wKzxag{yNB$$nASZdhTd*D_e=b*ppaWnDbNdr$H(M)Tba zdNvL7F!aiPP?>H4!{~7{&l8lPUN9x!KFqUJoFZKs`p>7#JH<1A;OsZ$*kY(3p--f; zPh`?3MV-Y$@=uC{6654&Tj(~ddqNmp8&t-2c00U(lMF)FtW-PPABVU3GkND3*yJyv zE$)3`&yHU7+pel}dR`^0Ygb=ZxAAD*=kJV(-TA(gJ(U z{REy)zS1TwP1n}cskhfWyQLYYdYV^p86>QgN}7MjRY)2YIw3s)*>6Q~ZOh!dpuc@w@&6SSjF-BMkV>si9pA%Z^mQVaOs#Y8 z%22b^*DMy4E*12%*oN!hdk*D!)lrhcX{oH4F348O!&d54gXrMg6trvhfL_RG24k0o zb*3H>CxqND4WnHBnsFo$#b@;38^(9uh87*ikD87j&FwupkUvPGn+s|XSHc~?>^vmv zTjEU@`09+~%Xbo+aeA&2ns1^=xxcwZizQVvQJKj?8Ujr1cN365^rD+NO_mD^);ZnL zZ!V?#f8g7Hj5C$}GZh)}d_?@}m|@qHSuEdYvR-cWxnTJ5s@y6er(C%kdQUbL$Otp1 zc6c6X&U1_x2o1$Iv7EB*HOg+v_WsI&*o(B%RLRRoU{GfN!9#B3h8_q@=+aRMB%3Ut zm<;S0vSM#1^p+%F!aQdtUn2Z8o@sG#nttaZKp1conQ+~$drPWyn_~N0@8}MFHGboc zZdHBbPHGkZ^Bpw1>q2Z*KTp@7D47}dAcTG7cLjH`3q1J)2mM!#2j<%}Bj|z47wx?| zn^Acq=&f&G&h9uWd&ivctN8N^+Xs+S_OVi=IUtO$%uZ>_-JB^K?E$`+@X+P?_gxJ~^}=xZFy zXdVZTiYSx@W0nTrFAYx5`qf5^A>7daQE`bE-OGPRx78!6YVlH+unOHPaBHlEX`|a@ zY1eJ&&T1o4;G)~&D3bK$vexY!dJWed1KR%myE9A$*7FsO+kJyu7WO;R^gH#TyJGmA z4&5c)MI@uQ=tU%kH{nGj#R1RfyXlQP)zZSojXUY1JGqNUrK3Ccmt88itr%U}w>Icu znb+Mo=k2SJ3>T5miE08;{w7XW0J6T~ru8TQ@7UeR8yk|KIIg+7Oy;FKzLC3G(1+QuBq^kJ1AztD6^nle-SqirBFTIyG$%K1%Ch7V*9 z`gx)u(x%$*51do#%qZ$Tv!0#&UNKhg;7#e`(6)i>nNjpOBL*AH>~C!mON;#9Uw8(>c)#DTbgV6h%jj{Oz%}ndGXJI< zHrPBFj=tuV*5H*^;g#0rm1eKl;HGnB&t$|5%Qu`NI>)Lg!9IZMqK~dM3Bp8$<+K{4 zaL?t2*DzLL$4RSj?ib(W*rnn;g<$NT*ppT(xnH;!;nirdKTTKheng?v!TKWaLe)D@ zWkQMlH-vmKO~sow*)I*R9sMj7Zx4NsgZv-*Ug4RsNQwKJ!VhADBh>gAGNVP5ds4?d z9Qb84d|!`aJ=pL;lqZ);rgjqV8w#T%Q^R3OD2Fabrd+z=1A%hKf%HJafHx-dG>zp? zn0zuCX4><#xmtkT8 z=649EePd37fIT~Jzsj>rih3PQmCTF;H#YKx&S1%;z+9xqOs$PU;=F2Nd8x|Z4=R5T zrvh_NLtMJ29hb3G!ynb5A1Kszv3+7WKnKk{-uXLx(u$byo_M2PwS z_wwSB>~`iUZXROlc2%UdNik@VZ=o#)vV`$*~pUg z$Wrjg686}VDoS&nxZu4UN z5>DYIjQpYTrSIuylO3J8o{0~+H5m41n}1_uPd*6JwHq1WlBEc_uq>8Ip7aU7%MFRf z3eRfB&3$?Dft*gwXqqflzwn<&eU5I(m;Uf62f?4|%+H~P|8lf0O1!8pOO^r(n-=!c z0~W%2Jv($w=KM!JJ5qoL$l|2{mJ0XA@yeREM*($af2ONme~fCq_*Yp>;N7fSE7O9x z{ByedZ!4wuE1|Qldf3{(@~s}vr01mlZP{zp>ry+VQVgr33Q5luS23wv?ytsBZL#&l z^Nih(N+Ix((90+09b2bz5$z9tvG(kzl&+@F+ZRG*JY%9!EwZpST^|xm=J!(S=!);8STLB2L5V3d?OOgjDX&VYGVZZK zsx8W-W1hha?WE`Ty1qQg*SeYaI@|FP#6|Sh4z^Jo@OCqAx%IeTxB2|I4#{sd%qQt) zE}O0${ByM9(|$u$HYkMr6DOnqNEYv?H6jcWy?nbK@+47$PlgGiYCg*?{MI~Ukp4GC zQlnC1O;j$@VP;xBHJFx4J~gAjbG;pdGU~yw?WVOP-2&sq zW3#TD4Zs9+%Y00@nsx0r?lug5cC3}Do@rAlS9znqS-&J?#4LNQyjedjWXLQY@&0Ij zla@EdJkoQsK96eZ164ydmA*Pb1y`I{o$@J^;Cl)`Gi#g}hJprxG}gH=&j#LVaY-s? z&){`$jq^-uQJ%olz^Pg?gn{Itr94b-7n_J;;} zzz;m}dfmr`5Ec-`0t7)qVa-Lj{~Chmux*6J&Jll#j}0XC7Q(M3^;X39LOXcZ9hDhi zWAa0oNL|CVIEp28iW!4B&%`7}#p>18H!=Q_{BA!& zJH_#{xC~I9<54Q8-YecM-qET_dN%S$7GV(}BLvX19lJsoq$NIj$=(t6du$V~Qp zf&16#yPSnb`ktg(ZSvs5tUP0ZldW|SBbYQXcWc;#aPt(hc}lf;DzdCn-7cD=wU{ir&y`(_MWw{HV?|@p^v_JbmT1L>3e!X+d2g{TON&g zl%+1nMkBT!TXCLD6E^J>@V3flEQDq(SdVzHY_%vpjAU_S6}~!Cs?NKPc^Aq3&g*Ce zSB`N)@&bG};r!FN#f@S?H6lGJ+UH#%DNF~kQx7Fmp_x@zZ-}hL&VH3)OufBZk80Yw zgRt&iC=zRg-j1UvzlY~l_@Z#jEFA1MenJ9YGiIud2KF4j>`bM0>hh1Bvq~ZLLK;G+ z*nj%FScgB8L!_l*UNBlJWsoUZkudixzZ1k|v5kC^{RqulxO|iiONw!3lMJ1a_?KX( zR$>Z?xcnMY{l&Qk1q;uw;^Yert+0%~wr1p@Ob<13Fr~z+BsohSx)+-QnCelV z&(qAL4A?9Aiy2dAaO(O$$(37(ncDbHp20aj_D(F=?FDeCsW^2#p5(}Nlnytl=gNha z4jXW3zYU>)ZCZl|_catEeWQ?5L-x|(rvzEVRK_4g0^w0SY&C?;%U>Z@C`xe7j5QGDiyh6xdW*bf)KQxb#9&|JL_rb(2NJqSr zXd`5z!B=&fA!Is$ZrM<0A!?C8GZOQ6z>`X9nHTyWC+IWW;$)R7j6X2|Ll8Q%1TPPT z=&huyEb%3_>yr5^CSJbYjCX}>vr(LcQ=ACHLSj|rD-%QZzQ5L%Bif!_1ygmU&U=iw za^y2rpP@YlzzHKdhd#NJwbDgbq}+>|M#gK&vt_^ZQ^<+~i1$-4j!j6(WLtAE(pYuy z$CJHdyL=^W8=4T|Kr&B1b>WR305oz@ooof9V5l^}I;@I~u}K){Bz#EDrcm@no#&W5 z{XC(s4eKxsE1z9q1g$JF#ka>jF~vWMFCpb5N)j*Z8APFdChWU**fTIti76ky3`+be z)?6#TH|D{m5P-G}4Vyt*Rt=lMSk?*qi;l=XFUPbLI+%PI=EtXSO38wokfOG>&U);V za3RHi=z}%J#G#Oj#&TcbHKr%0!ecZ~HigF+3aVlGIALn5>->k4#phDGVV0~4f)p&- zIVr}!)*Fz6)KY<&=bv!SWez8C&T$SWNzOSBeFzUc)`~H`%X$Wx*4E#y6~oSdb1H-+ zq=bG^7doEgI-Dfk!bwO`>Uo454Dabf4&trqt*wKTBIF?WOJlICBh_m+%j}SYRELK- z=Rv(;a!v|wp<&v6ZS-pnu5@MJ>|~m85bWAz-=a+7{>9j6ltPb;F ztS*9A4>QO_3ib?BpW#~y%Lm#o%!=oPFw8g(FM86Ix`=DxS1T{$1O`ce+3 z%IZxT@DM=gXjy9Fbjc=|SHQEz(G_&fyU@gKGQgULiv< zy=4>6I9Dx>HBVZ&XJ5RxMg$Cdg@im7a8tD^>;H%esDpXZQ$Ec zkXa?A%10NaGTQn~Y!$EJhAef4EDeT(kO~E5&H-p8WfT{*(jbZ@-LMcXLQ_8+Cn&c> z1~bUjv1?&3DdEFnCb=OMxm8gWdTrwG7cJ;5>N6>=QBv&3uHWg?n*JsYnOnc~Tq1{# zzD+1`@>03JG5s%?@a1NID$PjV#F&U@{#p%7AY0A1nGv=j&-z#;Hii@S&4B#!Rd8&C z1M~Zp9CeKy_xmL${^UwK)I&3u2{k9gQIcwYAH6UT(nap!gsUTf0<%0-zmF#9HO9Q! z7~cg)oZ`8&3yLh=RNDShZ0hrkIJH8u<>8j6n!Xk38~Q@SrAn!zT^h@xTbRc~o2j5_HFny9o{W^VZEz(@U|f*x>7PYqvJ+6`Y(_!-?*%Nqo(zFCNe?|VTB~?!ILJ_jK1GvjUGJx(oaVUX=x|WRKA|}c&>^a z*H(O9VQy%WLt7_Q$@hg+ifope28%Iin}87WwTgcnm{+Lz$ANi8aod$7{@Ta!qeZUe zk%U&UL*p^TN2ld)B3{%KUOPW`an?NUuj(KdoO>)1S~Ytt5>bl~39W8D3yG~vQGo=A zS(TIYQ%A#sru`(%;}!|tuM10huFHFgn#TcNhPkc&!1<koKX^Hqj2fo2qZC z)XL?Ucw)%=X-~cMg}W3lwdH-iiq(6sG_mJm$_>5cGk#6wh-EWFpSxP-YEe#E<^m!^ zIGcIe@?PEbuwky&_)|fhY0KQ7ZB0!e)S#cMHLCUTxvK<>PxJ8pp?;^P4R(-!g>`8y z`ZK=SDnD6Ub>PUNcW9pQE)mZg$W*np4GP-YxbAf>+EgC@deZ!;&Tvb>u=1Eja5fm0 zLE)wM>tjYR42tOg>G2{+3*#}lmlOCpw2)qHMlPUc6bq;sMJt$5;3GUZm$#|BGLZ|$ zYaR@T#{CE*QLlwJtxObNvOhhD4_gSm1rA#n4w%*`Zob*4JJA-CI*?8GD5TV%Ke z{H7)IT?0?|tYSxDc*DfI5n@Mlh=XZi7_&{X#rTO|gqJOrpZ?Mw-jRptkw=VIvuevX z5=ZE#29NbU))S96%X^TPJ;nv$M~g2P_8;%RT0naoh#fC5E=V2y-=QKJ9(9B~c9ia( ztwOx`?DhQRf?Pxc&fu){x_z{_FY^(E(2*^?0c{d=&sub-g*Ozw%O-K0cxM^jkPoDb z?{iy7!89Qu6VIic@P1zHCkK|Z{Y72QVNq8QeU#4Mi@vDjuz2j~;I$L6)#CjKWiIR( z&=K@$*pS$Xz2Jlrn+{!#Rt{1}V25*1!;>S3jlL$SBcj_d-HXd|VT#{z=Tie-M8nyk z2fC|B4q2D&1(tZa4bh>B?pa8OcDEsTX|H>hutNlE!N&Z$nLJ42jJGG{;qdk0F!^w} zeK?eS@=1L~qj{jQ8>x8aX0KdQ=-HR&NaXl<-oRjeU1efE1F)r43zOmS&par{fVtxL2ip0|QF z#S>!DrZ_7-+Mfqz%^>S7+jwse^tX=m*aO~59-<#Wz}w41$WrVlgP)JLqPI5Qi@9#R z|IxXu4(pGL33F7IVB7s6h>Zj z)o;@XZikqi?M3WH&Wh4@+CvYGdo1cFNNiYPVxw0c! zYl2rL`}5CZo^S)0kSI_F^f3Wp_XlTLz0T5}mx7 z!pl@^sj8@h*WvV9!;JYQ2OvG3To29EZ=T{svpvCLB@KR@ zgew(PjQ;T&G`qdh4O)w6aqKA*m{=?_4YayCvSErlLYpJQll* z3foR{1ZCBMj&D50gs`3_-L>Mnj|ZyfoyIF{!6L?)9_P!Gc_W;oUx{YLsEf3TCycfO z!j@DyWkHxi$v=hK#B-ry_+73gTh@#lJ!#%j8EI7cKlyMI8Z=fdQgYtCYPX5;+h=oe z+7q4^pT$@tc9~Vh#O4W1`S|~e&A*GW=|HzD3t-0Qv!DA2^$6V}!tr-2k>e=PQWaq7 zsSiW`qZb$f>;!^xn5spO`m8%}VI7L6qOzFc*f%oNhaArYi?Pu`a?AIRQdyn3h87XL2u zimWP%RGo|23~Dqhjr#|`kypPX8oC*M5b`g;{06OY<~NK>(kI8hdaD--3;{c1dv6mK zSrZm5NZb3c0zRLd%)Y+KBEJ;FnpP!ix5UWK)Lo)-YvRk9XIcJ6aftZUNztL3+%bar z9pd=7A_~`PAnBd!=J|(D?>vByp!rYcNJ*VU(mYLb1vnmJ;`8}W+;MTYKkSHo{Nnr0 zm%`oi-Dw2yJPJst03^f%60)3BWQ1)*IDa(vW9+^pu84aZ9fuKDN-h6RIUJ}O2~<@C zic^~VB@Q-2e38A4^6ht8w|5LCL+UumBWt2^srhb6Y$F?=$`rPuqSiRRHR`4Pat``j z$x0SjK?dO$inm>6Oxl_YGJ^9b*)G3K%Exz6_Jh}OPcuuh4!Ekk=4chxPtur-5X&zi zvsBV%+y7Xi+QOlod8t_7U^rMr8FWUpxq( zIqlcNLCMQ+Gbm$!n6p2upD6luchzNV*TyGJ8UPRCck*9C z2j7GsiiW6d-jqn5l#(9m$ab+Z&aR&oD;*aLCtnb@FtH5IS#GaMMjIym^7SpfT_*I% z4H2V>eDizVBy*IJsH8lPxV4^W4ny91Ec}fs9`(11OT>r}~!-qYR z?Z*Dy#w=lR?3ER`UnfXhp_d0BuGm|svcd%S!|t{0zihFdlj)^0^Gk>}RC&0GrdRjp zWxDm;>)vVDoLui-!4U_48}5oBd=5sN&9B@FKeywjpdkO&%OR}{gD<_{%@*V*O@b&(sJHk z>O#yS;U#o}0ad=QE?CM`?wD~{05^G$c%2vGnPi0oz$9N#6ZzC`@NIu%p4h3W31+!( zTAdKxZMCYhr2n<)X{{ghC>8#X=$Ti<+eg&5E#E4ZhI$sE8t3T=lZJdq6%+X~wXfeO zJ|G~@X@68A*=t53+wAlHiO3bJL#SpfW%+1FJ!5bR$9^QTya;ga)U|fV5y>qdvO-z6 zEUomoUg{?7Id?Tsy}_d#`F^9_wdP3kO}QX7v9s(9O0Z+T;%t9{<#VRnzh2uT#->^= zVgekmM*^Q~u6Z`@a}Mxe&tumwwQeZ!QVx?eIrZCuvCvs$N9^ujkl(glh9utGYY}0` zrD+s!yXvwJatf)Zygh!6={xjOS`)eUyWL5(@?VWm>s>)n<0X_OaPciQ$AAQvk|oNg z;^&=TL2cRo%1nSb&$Z4`A&n1dZ3Ph$=Z!(M2_QNeo==dgOOLgfE!O)J`^>Owsey&> zdA@AgHA5gxz#FrG6JN$zTkrbmMaoul(Dm21)}L5g@l#rk`fa03$3dra^3tK}?SK`| zw&;AJCez%(b zn;-u=zi$bp2E|1MsNXpE+saudWM;jfYPFVhq(OWbyID-R*-3qRzmju6qtfR1o9{1N zc0*3hHzq59l+SCnEPkt0 z8dN$-rT$Zd+|9Y};Bw($jk)7s_ZXt$57*erqm|xUntgrEg;!+Tn ziwSmxXo&wb(%j4yeMhqJCvNRknpPNUZt#@MlBGD@cWs^r(qd6B(i!2W+hY2A z!Q`c@1qCj?3qX+jQf^LTpDN13YO$`#LozyJcI%E;t9mxwyEcjB5-g3}smi#jLd<=U z`ShUsZEvTPvjSy3!W(*G%wKQ^GYE-mL(7KqKoo7NKRIm-sI#6z90eDtLpIHq-2Ke|AV7>yN?JL&7(iamPOZ^xtn(RE?kt0?>BaJK75RKP`MlM_CPP5lCIiCQx4bCt z^4p*$+z-;!Z|6ovnLGU~H&6i2j$;1P+V-1XZKhgH?;PJ}sW0A)i6!Lt+AKJkPD?-K z2Hg54o;kAaNJSjgarP58f->V$)~q8^zNilTs@p!wj$yd(DS}PRlyY(Fn(|lNO%+8? zxOB5Lwy^TZ*uB9gnVrBgubY^$%A6Ssgv+3Em0J&fJujD!hLp+g)Sh12=E{div`0?a ze!ot1KFf}I4OXj@=v@0kZm$pt861=QJ*)C|;>?$?BoPu5YVskLB5Hju3Ni27N4z1D zmU-vZd-cvcMBbb5VByE4jKSgy#r)9Qgc=a1UX3x=Oht&jtgz;BnMvJhCfYH{W&2Ug zmXd;nlAMK#nqtz_a^COjW?>KC#}?b)#n#-=_S%-JpGNj#7jN&Q()zXv>JV4wzGTDo zJix!Swu3R$uC+?t+p;M#Ef}RdTXXJ8ETMHOc*S`0x|Vi}oS@ynoP`d>o#qp;_3rS- z?xzD%>`E2Gh8}TlrBbYcteJp~@%Cxu`;t@w)k3`TSO8tHQ6*s!n(8ANlfrz{6XHKH8@K!5h>xiVOiOJ#o40t+_2HYQrc)X*w z<5)yE=pj7Ygj0@VjaO;>#q+#oY^GE$NV*~7-^}pFohV57IZ)UaY*tZe+5c&0-P=uo zbM#u0g|UjgG(9ZQrgsww<(=PUT^fKJEav*#C1wODLIWPJ6v9RAX)D=QXsG#kreji5 zEu$Oak0M=KXk>azRaR{HO)xDCy`_e4$KHywT}e>f8XsjVwK;qwSev0BVkVNh$JI4n zYx;sS7@E{Zs`~GO-;CAhVXaTjV9!EYSP|gB$~nK*HSZ+}vO;mIyZ<055qOfdc3Xga8Bq1%U+21%)6GBpi$cnS+sFfRH%?2{DJjEcn5E zaG1RvkCThFJ&%kOkF1vk!pY6r(Sb+G#oEov9^qzfA?xDe=)!|Q2nZm}p#UMEAOZ@5 z!35v}NDB}M4gtXggoKdh{N_loxdj4g0S55{faXAm5CCBT2Z02^V4yinP>>f6vqL_@ zApT#%prylj5bS}B<9^tLND_a@)L!wx-Q#~xdahok;!8|G3m(fbSIqjlp*^irqzGU* zCh!ilI|$~<4|FP<4&)k<-+~T0|RhKvBX(uzuU9$(U}7P zT?k?08f~!p0R6J8geN=o+6EQsd|0U%P<9-ejU8d2z|qBD7Rk=Jkw7p}_si0oVk5L9 zQZW5WieXUGlmbD{;!L;7uNZ*wpXgHtKtpi<|3cr@72)zQ4&-3gb_h5R1P%itK>{!! z00x8#f%!oQK@b1|3S$sQ+E$#sevp*ukF$@eDrAxGo0e$29_xyi zGRaB*najvHSzD(&l6}H{kW}^T91VYvXFrTU9MPH(0$(`7{7hUL-YC*>Inui`6AzS2 z8+f|o)R8-qIU|PU?ECIHz6Z}!LBCE-r`@NW_BBRJJtqN(`WI&?>`F>|TMvn2*=bu% z^LC(p=_0{0{Md(~li`KZd|Aj|g+uV#tII(0j9g=IvL;8<;1&i6Axf-bW1QgtorkaP zfaowUTY@Pf{pbEKMQ%*5oHM?tmyrzeJ_r zqhcp#e`zvk@jtN_VpfyJL3fb>n%n+|HD4a#fN+7?{cp8L0tJBx5#hwF!+>eVvU;4UjgFW}rz+IGK}cTv<2g z>d}9~j-(JDdxq{HJ2ZFy55unRg0OIOfLs6Hv|b2^079Th7!<}2h9LQYNFgDB1(+WK z1Hb@qm93TJ?0trIk=;zLVf&U=S13k$0K=yHJYMqkA zJ!$q|cRqX?dh%>0q?m#T!ZFix_cf#Ai=|`Mw8u2)tK>V(>>$T~{gu`|R)S7xgDn>W zt72%iFIq~fV)0;i+LU(c{}C2nqq-=?Zy)-0B_7GNypM609X*}IU=huqN;0+lPCip4 z$ro?=90U67kAq~#0$EmY8`D$Aj4J}Ztcy_ngOW+oBX^6)$(pi&3MykB

    k|dc#}T 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∾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 show holdingView.amount) + + choice Reissue : ContractId Holding + with + newAmount : Decimal + controller holder this + do + create Asset with issuer = holder this, amount = newAmount + >>= \cid -> pure (toInterfaceContractId @Holding cid) + +template Asset + with + issuer : Party + amount : Decimal + where + signatory issuer + + interface instance Holding for Asset where + view = HoldingView with amount + holder = issuer + template RichRecord with owner : Party @@ -34,8 +95,11 @@ template RichRecord tags : [Text] attributes : TextMap Text marker : ContractId Marker + holdingCid : ContractId Holding + holdingCids : [ContractId Holding] profile : Profile outcome : Outcome + suit : Suit fee : Numeric 2 where signatory owner @@ -46,3 +110,31 @@ template RichRecord controller owner do create this with label = newLabel + +template TypeCorners + with + owner : Party + boxedText : Box Text + boxedProfile : Box Profile + slot : Slot Int + nestedNote : Optional (Box (Optional Text)) + maybeMaybeNote : Optional (Optional Text) + crate : Crate Text + quotaByParty : Map Party Int + labelByRank : Map Int Text + rankOrLabel : Either Int Text + noteOrRank : Either (Optional Text) Int + pair : (Text, Int) + triple : (Text, Int, Bool) + branch : Branch + whole : Numeric 0 + finest : Numeric 37 + where + signatory owner + + nonconsuming choice Rebox : ContractId TypeCorners + with + replacement : Box Text + controller owner + do + create this with boxedText = replacement diff --git a/conformance/richtypes/daml.yaml b/conformance/richtypes/daml.yaml index 6588386..00d8881 100644 --- a/conformance/richtypes/daml.yaml +++ b/conformance/richtypes/daml.yaml @@ -1,6 +1,6 @@ -# Copyright (c) 2026 Peaceful Studio OÜ +# Copyright 2026 Peaceful Studio OÜ # SPDX-License-Identifier: Apache-2.0 -sdk-version: 3.4.11 +sdk-version: 3.5.2 name: richtypes source: . version: 0.0.1 @@ -9,3 +9,4 @@ dependencies: - daml-stdlib build-options: - --target=2.1 + - -Wno-upgrade-interfaces diff --git a/conformance/richtypes/richtypes.dar b/conformance/richtypes/richtypes.dar index 7c69fd1d70004e1c3ddfcf038a78d0a58bab70d7..cbe3221e985e428de450c446846af1ade6aa4bce 100644 GIT binary patch delta 279284 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 z

    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

    sBS{>r0Kn%KP3?AAH!izDj_`K%zYsr|5nJW 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

    9{l3hzVGfqVOV7dN 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 Vbhs;rpwlOjd~|d^wm-Lr{|hG8q+9?1 delta 243529 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ℑW<}L4+l>M-k;F3kQi{ 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

    ZW)WOZyb4|s5ccfb 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 P__E}KKw3wBr3?Q9D}5l< diff --git a/jvm-helper/README.md b/jvm-helper/README.md index 076be7d..81f3dd6 100644 --- a/jvm-helper/README.md +++ b/jvm-helper/README.md @@ -28,12 +28,12 @@ sbt test sbt assembly ``` -The fat JAR is written to `target/scala-2.13/daml-codegen-jvm-helper.jar`. +The fat JAR is written to `target/scala-2.13/daml-dar-to-proto.jar`. ## Run ```bash -java -jar target/scala-2.13/daml-codegen-jvm-helper.jar \ +java -jar target/scala-2.13/daml-dar-to-proto.jar \ --dar path/to/contracts.dar \ --out path/to/output.binpb ``` diff --git a/jvm-helper/build.sbt b/jvm-helper/build.sbt index 88353cf..111b8a7 100644 --- a/jvm-helper/build.sbt +++ b/jvm-helper/build.sbt @@ -1,6 +1,8 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 ThisBuild / scalaVersion := "2.13.16" ThisBuild / organization := "studio.peaceful.daml.codegen" -ThisBuild / version := "0.1.0-SNAPSHOT" +ThisBuild / version := sys.env.get("DAML_DAR_TO_PROTO_VERSION").map(_.trim).filter(_.nonEmpty).getOrElse("0.0.0-dev") resolvers += "Daml" at "https://repo1.maven.org/maven2/" @@ -8,7 +10,12 @@ lazy val damlLfArchiveVersion = "3.5.9" lazy val jvmHelper = (project in file(".")) .settings( - name := "daml-codegen-jvm-helper", + name := "daml-dar-to-proto", + Compile / resourceGenerators += Def.task { + val versionFile = (Compile / resourceManaged).value / "daml-dar-to-proto-version.txt" + IO.write(versionFile, version.value) + Seq(versionFile) + }.taskValue, Compile / PB.protoSources := Seq((ThisBuild / baseDirectory).value / ".." / "proto"), Compile / PB.targets := Seq( scalapb.gen() -> (Compile / sourceManaged).value / "scalapb" @@ -19,7 +26,7 @@ lazy val jvmHelper = (project in file(".")) "org.scalatest" %% "scalatest" % "3.2.19" % Test ), assembly / mainClass := Some("studio.peaceful.daml.codegen.helper.Decode"), - assembly / assemblyJarName := "daml-codegen-jvm-helper.jar", + assembly / assemblyJarName := "daml-dar-to-proto.jar", assembly / assemblyMergeStrategy := { case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard case PathList("META-INF", "versions", _, "module-info.class") => MergeStrategy.discard diff --git a/jvm-helper/project/build.properties b/jvm-helper/project/build.properties index db1723b..8492baf 100644 --- a/jvm-helper/project/build.properties +++ b/jvm-helper/project/build.properties @@ -1 +1,4 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 + sbt.version=1.10.5 diff --git a/jvm-helper/project/plugins.sbt b/jvm-helper/project/plugins.sbt index 9a1c1e2..e0bc1bc 100644 --- a/jvm-helper/project/plugins.sbt +++ b/jvm-helper/project/plugins.sbt @@ -1,3 +1,5 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7") libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.11.17" diff --git a/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/Decode.scala b/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/Decode.scala index b33cd89..d3952dd 100644 --- a/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/Decode.scala +++ b/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/Decode.scala @@ -6,6 +6,7 @@ package studio.peaceful.daml.codegen.helper import studio.peaceful.daml.codegen.intermediate.intermediate_dar.IntermediateDar import java.io.{BufferedOutputStream, FileOutputStream, IOException, OutputStream} +import java.nio.charset.StandardCharsets import java.nio.file.{Files, Path, Paths} /** Entry point for the JVM codegen helper. @@ -29,19 +30,22 @@ import java.nio.file.{Files, Path, Paths} object Decode { private val Usage: String = - """Usage: daml-codegen-jvm-helper --dar --out [--schema-only] + """Usage: daml-dar-to-proto --dar --out [--schema-only] | |Options: | --schema-only Opt into the patch-version-insensitive schema-mode decode path. | The default is full-decode + static party-expression analysis, - | which is patch-version-sensitive but enables typed-actAs codegen.""".stripMargin + | which is patch-version-sensitive but enables typed-actAs codegen. + | --version Print the build version and exit.""".stripMargin + + private val VersionResourceName = "daml-dar-to-proto-version.txt" def main(args: Array[String]): Unit = sys.exit(runCli(args.toList)) /** Drives the CLI end-to-end and returns the process exit code. * - * - `0` — success (or `--help`) - * - `1` — runtime failure (DAR read, decode, or write) + * - `0` — success (or `--help` / `--version`) + * - `1` — runtime failure (DAR read, decode, write, or missing version resource) * - `2` — CLI usage error (unknown arg, missing required arg) * * Side-effecting on stdout/stderr; visible for testing so tests need not @@ -52,6 +56,15 @@ object Decode { case Right(CliCommand.Help) => println(Usage) 0 + case Right(CliCommand.Version) => + readBuildVersion() match { + case Right(version) => + println(version) + 0 + case Left(message) => + System.err.println(message) + 1 + } case Right(CliCommand.Run(parsed)) => run(parsed) match { case Right(()) => 0 @@ -134,22 +147,44 @@ object Decode { } } + /** Reads the build version baked into the classpath at assembly time. + * Visible for testing; a missing, unreadable, or blank resource is a + * `Left`, never a silent default. + */ + def readBuildVersion(resourceName: String = VersionResourceName): Either[String, String] = + Option(getClass.getClassLoader.getResourceAsStream(resourceName)) match { + case None => + Left(s"Build version resource $resourceName is missing from the classpath") + case Some(stream) => + try { + val version = new String(stream.readAllBytes(), StandardCharsets.UTF_8).trim + if (version.isEmpty) Left(s"Build version resource $resourceName is blank") + else Right(version) + } catch { + case e: IOException => + Left(s"Build version resource $resourceName could not be read: ${e.getMessage}") + } finally stream.close() + } + /** Parsed command-line arguments. */ final case class ParsedArgs(darPath: Path, outPath: Path, schemaOnly: Boolean = false) - /** Outcome of parsing CLI args: either run with parsed args, or print help. */ + /** Outcome of parsing CLI args: run with parsed args, print help, or print the version. */ sealed trait CliCommand object CliCommand { final case class Run(args: ParsedArgs) extends CliCommand case object Help extends CliCommand + case object Version extends CliCommand } - /** Parses `--dar --out ` (or `--help`) from a list of CLI - * arguments. `--help` is a distinct successful outcome, not an error, - * so that downstream tooling probing `--help` sees exit code 0. + /** Parses `--dar --out ` (or `--help` / `--version`) from a + * list of CLI arguments. `--help` and `--version` are distinct successful + * outcomes, not errors, so that downstream tooling probing them sees exit + * code 0; `--help` takes precedence when both are present. */ def parseArgs(args: List[String]): Either[String, CliCommand] = { if (args.contains("--help")) Right(CliCommand.Help) + else if (args.contains("--version")) Right(CliCommand.Version) else { @scala.annotation.tailrec def loop( diff --git a/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzer.scala b/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzer.scala index 5a90440..16bd384 100644 --- a/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzer.scala +++ b/jvm-helper/src/main/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzer.scala @@ -46,7 +46,7 @@ object PartyExpressionAnalyzer { payloadFields, resolveValue, currentPackageId, - throughToPartiesIndirection = true, + throughValueIndirection = true, ) ) PartyAnalysisResult.Static(payloadFields.toList) @@ -59,25 +59,17 @@ object PartyExpressionAnalyzer { acc: mutable.ListBuffer[String], resolveValue: ValueResolver, currentPackageId: Ref.PackageId, - throughToPartiesIndirection: Boolean, - ): Boolean = expr match { + throughValueIndirection: Boolean, + ): Boolean = stripLocations(expr) match { case _: Ast.ENil => true case Ast.ECons(_, front, tail) => front.toSeq.forall(collectPayloadFieldProjection(_, templateParam, acc)) && - collectPayloadFields(tail, templateParam, acc, resolveValue, currentPackageId, throughToPartiesIndirection) - - case Ast.EApp(callee, Ast.EVar(arg)) - if throughToPartiesIndirection && arg.toString == templateParam => - resolveArity1ValueBody(callee, resolveValue) match { - case Some((binder, body)) => - collectPayloadFields(body, binder, acc, resolveValue, currentPackageId, throughToPartiesIndirection = false) - case None => false - } + collectPayloadFields(tail, templateParam, acc, resolveValue, currentPackageId, throughValueIndirection) - case app: Ast.EApp if throughToPartiesIndirection => - resolveChoicePartyIndirection(app, templateParam, acc, resolveValue, currentPackageId) + case app: Ast.EApp if throughValueIndirection => + resolvePartyValueIndirection(app, templateParam, acc, resolveValue, currentPackageId) case _ => false } @@ -89,18 +81,25 @@ object PartyExpressionAnalyzer { final case class PayloadField(field: String) extends PartyBinding } - /** Resolves the per-choice `controller ` / `observer ` - * indirection this SDK compiles to a two-or-more-argument application of a - * synthesized value to the template parameter and the choice argument — - * `App(Val(), [this, arg])` — whose defining expression reduces to - * `\this -> let ds = this. in \arg -> toParties ds`. The extra - * choice-argument binder distinguishes it from the arity-1 template-level - * signatory/observer indirection, which stays Dynamic when it chains through - * `toParties`. Only the imported single-`Party` list wrapper - * `DA.Internal.Template.Functions.toParties` applied to a bound template - * payload field resolves; every other shape stays Dynamic. + /** Resolves the `signatory ` / `observer ` / `controller + * ` indirection this SDK compiles to an application of a synthesized + * value to the template parameter — `App(Val(), [this])` at template + * level and `App(Val(), [this, arg])` per choice. The defining + * expression resolves either by reducing to an imported single-`Party` list + * wrapper applied to a bound template payload field, or, at the + * template-level arity only, by being a literal list of payload-field + * projections with no further value indirection. Every other shape stays + * Dynamic. + * + * Two names in `DA.Internal.Template.Functions` denote that wrapper: the + * class selector `$$ctoParties` damlc synthesizes for the single-`Party` + * instance, typed `Party -> [Party]`, and `toParties`, the overloaded class + * method that selector is drawn from. The numbered selectors + * `$$ctoParties1` and `$$ctoParties2` are the `[Party]` and `Optional Party` + * instances, so resolving through them would name a field of the wrong type + * as a single party; neither name is matched. */ - private def resolveChoicePartyIndirection( + private def resolvePartyValueIndirection( app: Ast.EApp, templateParam: String, acc: mutable.ListBuffer[String], @@ -108,18 +107,32 @@ object PartyExpressionAnalyzer { currentPackageId: Ref.PackageId, ): Boolean = { val (head, args) = flattenApplication(app) - if (args.length < 2) return false - args.head match { - case Ast.EVar(contractArg) if contractArg.toString == templateParam => + args.headOption.map(stripLocations) match { + case Some(Ast.EVar(contractArg)) if contractArg.toString == templateParam => unwrapTypeApplications(head) match { case Ast.EVal(ref) => - resolveValue(ref) match { + resolveValue(ref).map(stripLocations) match { case Some(Ast.EAbs((binder, _), body)) => val environment = mutable.Map[String, PartyBinding](binder.toString -> PartyBinding.TemplateParam) - reduceSingletonPartyField(body, environment, currentPackageId) match { + reduceSingletonPartyField( + body, + environment, + resolveValue, + currentPackageId, + mutable.Set[Ref.ValueRef](ref), + ) match { case Some(field) => acc += field; true - case None => false + case None => + val isTemplateLevelIndirection = args.length == 1 + isTemplateLevelIndirection && collectPayloadFields( + body, + binder.toString, + acc, + resolveValue, + currentPackageId, + throughValueIndirection = false, + ) } case _ => false } @@ -131,27 +144,36 @@ object PartyExpressionAnalyzer { private def flattenApplication(expr: Ast.Expr): (Ast.Expr, List[Ast.Expr]) = { @tailrec - def go(e: Ast.Expr, acc: List[Ast.Expr]): (Ast.Expr, List[Ast.Expr]) = e match { + def go(e: Ast.Expr, acc: List[Ast.Expr]): (Ast.Expr, List[Ast.Expr]) = stripLocations(e) match { case Ast.EApp(fun, arg) => go(fun, arg :: acc) case other => (other, acc) } go(expr, Nil) } + /** Reduces an expression the analyzer expects to yield a single-element `List + * Party` to the payload field that element projects, walking `Let` and `Abs` + * binders and unfolding same-package value applications. `unfoldedValues` + * holds the values already unfolded on this path, so a value that applies + * itself, directly or through a mutual chain, reduces to no field instead of + * spinning. + */ private def reduceSingletonPartyField( expr: Ast.Expr, environment: mutable.Map[String, PartyBinding], + resolveValue: ValueResolver, currentPackageId: Ref.PackageId, - ): Option[String] = expr match { + unfoldedValues: mutable.Set[Ref.ValueRef], + ): Option[String] = stripLocations(expr) match { case Ast.ELet(binding, body) => binding.binder.foreach(name => environment(name.toString) = resolvePartyBinding(binding.bound, environment) ) - reduceSingletonPartyField(body, environment, currentPackageId) + reduceSingletonPartyField(body, environment, resolveValue, currentPackageId, unfoldedValues) case Ast.EAbs((binder, _), body) => environment(binder.toString) = PartyBinding.Opaque - reduceSingletonPartyField(body, environment, currentPackageId) + reduceSingletonPartyField(body, environment, resolveValue, currentPackageId, unfoldedValues) case Ast.EApp(callee, arg) => unwrapTypeApplications(callee) match { @@ -160,6 +182,22 @@ object PartyExpressionAnalyzer { case PartyBinding.PayloadField(field) => Some(field) case _ => None } + + case Ast.EVal(ref) if ref.packageId == currentPackageId && !unfoldedValues.contains(ref) => + unfoldedValues += ref + val argumentBinding = resolvePartyBinding(arg, environment) + resolveValue(ref).map(stripLocations).flatMap { + case Ast.EAbs((binder, _), body) => + reduceSingletonPartyField( + body, + mutable.Map[String, PartyBinding](binder.toString -> argumentBinding), + resolveValue, + currentPackageId, + unfoldedValues, + ) + case _ => None + } + case _ => None } @@ -169,34 +207,35 @@ object PartyExpressionAnalyzer { private def resolvePartyBinding( expr: Ast.Expr, environment: mutable.Map[String, PartyBinding], - ): PartyBinding = expr match { + ): PartyBinding = stripLocations(expr) match { case Ast.EVar(name) => environment.getOrElse(name.toString, PartyBinding.Opaque) - case Ast.ERecProj(_, field, Ast.EVar(record)) - if environment.get(record.toString).contains(PartyBinding.TemplateParam) => - PartyBinding.PayloadField(field.toString) + case Ast.ERecProj(_, field, record) => + stripLocations(record) match { + case Ast.EVar(name) if environment.get(name.toString).contains(PartyBinding.TemplateParam) => + PartyBinding.PayloadField(field.toString) + case _ => PartyBinding.Opaque + } case _ => PartyBinding.Opaque } + private val SinglePartyToPartiesNames: Set[String] = Set("toParties", "$$ctoParties") + private def isImportedToParties(ref: Ref.ValueRef, currentPackageId: Ref.PackageId): Boolean = ref.packageId != currentPackageId && ref.qualifiedName.module.toString == "DA.Internal.Template.Functions" && - ref.qualifiedName.name.toString.endsWith("toParties") + SinglePartyToPartiesNames.contains(ref.qualifiedName.name.toString) - private def resolveArity1ValueBody( - callee: Ast.Expr, - resolveValue: ValueResolver, - ): Option[(String, Ast.Expr)] = - unwrapTypeApplications(callee) match { - case Ast.EVal(ref) => - resolveValue(ref).collect { case Ast.EAbs((binder, _), body) => (binder.toString, body) } - case _ => None - } + @tailrec + private def stripLocations(expr: Ast.Expr): Ast.Expr = expr match { + case Ast.ELocation(_, inner) => stripLocations(inner) + case other => other + } @tailrec - private def unwrapTypeApplications(expr: Ast.Expr): Ast.Expr = expr match { + private def unwrapTypeApplications(expr: Ast.Expr): Ast.Expr = stripLocations(expr) match { case Ast.ETyApp(inner, _) => unwrapTypeApplications(inner) case other => other } @@ -205,10 +244,14 @@ object PartyExpressionAnalyzer { expr: Ast.Expr, templateParam: String, acc: mutable.ListBuffer[String], - ): Boolean = expr match { - case Ast.ERecProj(_, field, Ast.EVar(record)) if record.toString == templateParam => - acc += field.toString - true + ): Boolean = stripLocations(expr) match { + case Ast.ERecProj(_, field, record) => + stripLocations(record) match { + case Ast.EVar(name) if name.toString == templateParam => + acc += field.toString + true + case _ => false + } case _ => false } diff --git a/jvm-helper/src/test/resources/blank-build-version.txt b/jvm-helper/src/test/resources/blank-build-version.txt new file mode 100644 index 0000000..c0f4529 --- /dev/null +++ b/jvm-helper/src/test/resources/blank-build-version.txt @@ -0,0 +1,2 @@ + + diff --git a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/DecodeSpec.scala b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/DecodeSpec.scala index f5282d9..385d78e 100644 --- a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/DecodeSpec.scala +++ b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/DecodeSpec.scala @@ -78,6 +78,49 @@ class DecodeSpec extends AnyWordSpec with Matchers with EitherValues with Option Decode.runCli(List("--help")) shouldBe 0 } + "recognise --version as a distinct request, not an error" in { + Decode.parseArgs(List("--version")) shouldBe Right(Decode.CliCommand.Version) + } + + "exit with code 0 and print exactly the build version on --version" in { + val expectedVersion = Decode.readBuildVersion().value + val out = new java.io.ByteArrayOutputStream() + Console.withOut(out) { Decode.runCli(List("--version")) } shouldBe 0 + out.toString("UTF-8") shouldBe expectedVersion + System.lineSeparator() + expectedVersion shouldBe expectedVersion.trim + expectedVersion should not be empty + } + + "exit with code 0 on --version even when mixed with other arguments" in { + val expectedVersion = Decode.readBuildVersion().value + val out = new java.io.ByteArrayOutputStream() + Console.withOut(out) { Decode.runCli(List("--dar", "a.dar", "--version")) } shouldBe 0 + out.toString("UTF-8") shouldBe expectedVersion + System.lineSeparator() + } + + "prefer --help over --version when both are present" in { + Decode.parseArgs(List("--version", "--help")) shouldBe Right(Decode.CliCommand.Help) + val out = new java.io.ByteArrayOutputStream() + Console.withOut(out) { Decode.runCli(List("--version", "--help")) } shouldBe 0 + out.toString("UTF-8") should include("Usage: daml-dar-to-proto") + } + + "mention --version in the usage text printed on --help" in { + val out = new java.io.ByteArrayOutputStream() + Console.withOut(out) { Decode.runCli(List("--help")) } shouldBe 0 + out.toString("UTF-8") should include("--version") + } + + "report a Left when the version resource is absent from the classpath" in { + val missing = "nonexistent-version-resource.txt" + Decode.readBuildVersion(missing).left.toOption.value should include(missing) + } + + "report a Left when the version resource is blank" in { + val blank = "blank-build-version.txt" + Decode.readBuildVersion(blank).left.toOption.value should include(blank) + } + "exit with code 2 on parse error" in { Decode.runCli(List("--bogus")) shouldBe 2 } @@ -97,7 +140,7 @@ class DecodeSpec extends AnyWordSpec with Matchers with EitherValues with Option } } - "default decode threads real PartyAnalyses.compute output into the proto: every template's built-in Archive choice gets a genuine Static([]) observers verdict" in { + "default decode threads real PartyAnalyses.compute output into the proto: every template's built-in Archive choice gets a genuine Static([]) observers verdict, and AnsRules resolves its signatories and its Archive controllers to Static([dso])" in { Files.exists(TemplatesFixtureDar) shouldBe true val outPath = Files.createTempFile("intermediate-dar-default-static-", ".binpb") try { @@ -106,13 +149,23 @@ class DecodeSpec extends AnyWordSpec with Matchers with EitherValues with Option val allTemplates = (proto.main.toSeq ++ proto.dependencies).flatMap(_.modules).flatMap(_.templates) allTemplates should not be empty forAll(allTemplates) { tmpl => - tmpl.signatories.value.shape.isDynamic shouldBe true val archive = tmpl.choices .find(_.name == "Archive") .getOrElse(fail(s"built-in Archive choice not found on template ${tmpl.name}")) archive.observers.value.shape.isStatic shouldBe true archive.observers.value.getStatic.payloadFields shouldBe empty } + val ansRules = allTemplates + .find(_.name == "AnsRules") + .getOrElse(fail("AnsRules template not found in the fixture DAR")) + ansRules.signatories.value.getStatic.payloadFields shouldBe Seq("dso") + ansRules.choices + .find(_.name == "Archive") + .getOrElse(fail("Archive choice not found on AnsRules")) + .controllers + .value + .getStatic + .payloadFields shouldBe Seq("dso") } finally { Files.deleteIfExists(outPath) } diff --git a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyAnalysesSpec.scala b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyAnalysesSpec.scala index f3eb4f5..e52507b 100644 --- a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyAnalysesSpec.scala +++ b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyAnalysesSpec.scala @@ -37,8 +37,8 @@ class PartyAnalysesSpec extends AnyWordSpec with Matchers with EitherValues { ansRulesAnalysis.observers shouldBe PartyAnalysisResult.Static(Nil) } - "keep AnsRules signatories Dynamic because the resolved body chains through a let binding" in { - ansRulesAnalysis.signatories shouldBe PartyAnalysisResult.Dynamic + "resolve AnsRules signatories to the dso payload field through the located toParties indirection" in { + ansRulesAnalysis.signatories shouldBe PartyAnalysisResult.Static(List("dso")) } } } diff --git a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzerSpec.scala b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzerSpec.scala index 078d4bc..2eacb2e 100644 --- a/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzerSpec.scala +++ b/jvm-helper/src/test/scala/studio/peaceful/daml/codegen/helper/PartyExpressionAnalyzerSpec.scala @@ -65,6 +65,11 @@ class PartyExpressionAnalyzerSpec extends AnyWordSpec with Matchers { private def appOf(fun: Ast.Expr, arg: Ast.Expr): Ast.Expr = Ast.EApp(fun, arg) + private val SomeLocation: Ref.Location = + Ref.Location(SpecPackageId, SpecModule, "dictSignatory", (1, 1), (1, 2)) + + private def located(expr: Ast.Expr): Ast.Expr = Ast.ELocation(SomeLocation, expr) + private def app2Of(fun: Ast.Expr, arg0: Ast.Expr, arg1: Ast.Expr): Ast.Expr = Ast.EApp(Ast.EApp(fun, arg0), arg1) @@ -97,6 +102,20 @@ class PartyExpressionAnalyzerSpec extends AnyWordSpec with Matchers { imports = Ast.DeclaredImports(Set.empty), ) + private def choiceControllerWrappedBy( + wrapperName: String + ): (Ast.Expr, PartyExpressionAnalyzer.ValueResolver) = { + val wrapper = + importedValueRef("some-imported-package-id", "DA.Internal.Template.Functions", wrapperName) + val resolver = resolverOver( + "dictController" -> absOf( + "this", + absOf("arg", appOf(wrapper, recProjOnThis("owner", binder = "this"))), + ) + ) + (app2Of(selfValueRef("dictController"), varOf(ThisBinder), varOf("arg")), resolver) + } + "PartyExpressionAnalyzer.analyze" should { "resolve a single payload-field signatory to Static" in { @@ -306,6 +325,20 @@ class PartyExpressionAnalyzerSpec extends AnyWordSpec with Matchers { PartyAnalysisResult.Static(List("owner")) } + "resolve a controller field through the synthesized toParties class selector to Static" in { + val (expr, resolver) = choiceControllerWrappedBy("$$ctoParties") + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Static(List("owner")) + } + + Seq("$$ctoParties1", "$$ctoParties2").foreach { selectorName => + s"return Dynamic when the choice indirection wraps the differently typed $selectorName selector" in { + val (expr, resolver) = choiceControllerWrappedBy(selectorName) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Dynamic + } + } + "return Dynamic when the choice indirection wraps a non-toParties helper" in { val helper = importedValueRef("some-imported-package-id", "Some.Other.Module", "resolveDelegate") val resolver = resolverOver( @@ -345,7 +378,7 @@ class PartyExpressionAnalyzerSpec extends AnyWordSpec with Matchers { PartyAnalysisResult.Dynamic } - "return Dynamic for the single-argument toParties indirection of a template signatory" in { + "resolve the single-argument toParties indirection of a template signatory to Static" in { val resolver = resolverOver( "dictSignatory" -> absOf( "this", @@ -353,6 +386,94 @@ class PartyExpressionAnalyzerSpec extends AnyWordSpec with Matchers { ) ) val expr = Ast.EApp(selfValueRef("dictSignatory"), varOf(ThisBinder)) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Static(List("issuer")) + } + + "resolve a controller reached through a same-package value definition to Static" in { + val resolver = resolverOver( + "dictParties" -> absOf( + "p", + letOf("ds", recProjOnThis("owner", binder = "p"), appOf(ToParties, varOf("ds"))), + ), + "dictController" -> absOf( + "this", + absOf("arg", appOf(selfValueRef("dictParties"), varOf("this"))), + ), + ) + val expr = app2Of(selfValueRef("dictController"), varOf(ThisBinder), varOf("arg")) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Static(List("owner")) + } + + "return Dynamic when the same-package value definition projects the choice argument" in { + val resolver = resolverOver( + "dictParties" -> absOf( + "p", + letOf("ds", recProjOnThis("owner", binder = "p"), appOf(ToParties, varOf("ds"))), + ), + "dictController" -> absOf( + "this", + absOf("arg", appOf(selfValueRef("dictParties"), recProjOnThis("requester", binder = "arg"))), + ), + ) + val expr = app2Of(selfValueRef("dictController"), varOf(ThisBinder), varOf("arg")) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Dynamic + } + + "resolve a template signatory whose reduction carries source locations" in { + val resolver = resolverOver( + "dictSignatory" -> absOf( + "this", + located( + letOf( + "ds", + recProjOnThis("dso", binder = "this"), + located(appOf(ToParties, located(varOf("ds")))), + ) + ), + ) + ) + val expr = Ast.EApp(selfValueRef("dictSignatory"), varOf(ThisBinder)) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Static(List("dso")) + } + + "resolve a located controller delegating to a located template signatory" in { + val resolver = resolverOver( + "dictSignatory" -> absOf( + "this", + located( + letOf( + "ds", + recProjOnThis("dso", binder = "this"), + located(appOf(ToParties, located(varOf("ds")))), + ) + ), + ), + "dictController" -> absOf( + "this", + absOf("arg", located(appOf(selfValueRef("dictSignatory"), located(varOf("this"))))), + ), + ) + val expr = app2Of(selfValueRef("dictController"), varOf(ThisBinder), varOf("arg")) + PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe + PartyAnalysisResult.Static(List("dso")) + } + + "return Dynamic when a located projection roots in the choice argument" in { + val resolver = resolverOver( + "dictSignatory" -> absOf( + "p", + located(letOf("ds", recProjOnThis("dso", binder = "p"), appOf(ToParties, varOf("ds")))), + ), + "dictController" -> absOf( + "this", + absOf("arg", located(appOf(selfValueRef("dictSignatory"), located(recProjOnThis("requester", binder = "arg"))))), + ), + ) + val expr = app2Of(selfValueRef("dictController"), varOf(ThisBinder), varOf("arg")) PartyExpressionAnalyzer.analyze(expr, ThisBinder, resolver, SpecPackageId) shouldBe PartyAnalysisResult.Dynamic } diff --git a/proto/intermediate_dar.proto b/proto/intermediate_dar.proto index 726adc5..c81bd21 100644 --- a/proto/intermediate_dar.proto +++ b/proto/intermediate_dar.proto @@ -1,6 +1,16 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +// This file is a versioned public contract between producers of the +// Intermediate DAR (the JVM helper, published standalone as +// `daml-dar-to-proto`) and its consumers (the C# emitter and any +// third-party SDK). Field numbers are append-only: they are never reused +// or renumbered. The orderings and normalisations documented in comments +// below are load-bearing for deterministic output and are part of the +// contract. When published as a release asset, this file ships as +// `intermediate_dar-.proto` beside the matching +// `daml-dar-to-proto-.jar`. + syntax = "proto3"; package daml.codegen.intermediate; diff --git a/samples/QuickstartExample/Generated/.daml-langversion b/samples/QuickstartExample/Generated/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/samples/QuickstartExample/Generated/Quickstart/Iou.Transfer.cs b/samples/QuickstartExample/Generated/Quickstart/Iou.Transfer.cs index 167b6bd..37107e6 100644 --- a/samples/QuickstartExample/Generated/Quickstart/Iou.Transfer.cs +++ b/samples/QuickstartExample/Generated/Quickstart/Iou.Transfer.cs @@ -14,7 +14,9 @@ public sealed partial record Iou /// /// Choice argument type for Transfer. /// - public sealed record Transfer([property: DamlFieldAttribute("newOwner")] Party NewOwner) : IDamlRecord + public sealed record Transfer( + [property: DamlFieldAttribute("newOwner")] Party NewOwner + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/samples/QuickstartExample/Generated/Quickstart/Iou.cs b/samples/QuickstartExample/Generated/Quickstart/Iou.cs index d75f334..33c3df3 100644 --- a/samples/QuickstartExample/Generated/Quickstart/Iou.cs +++ b/samples/QuickstartExample/Generated/Quickstart/Iou.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -21,7 +22,12 @@ namespace Quickstart; /// /// Generated from Daml template Iou:Iou /// -public sealed partial record Iou([property: DamlFieldAttribute("issuer")] Party Issuer, [property: DamlFieldAttribute("owner")] Party Owner, [property: DamlFieldAttribute("currency")] string Currency, [property: DamlFieldAttribute("amount")] decimal Amount) : ITemplate +public sealed partial record Iou( + [property: DamlFieldAttribute("issuer")] Party Issuer, + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("currency")] string Currency, + [property: DamlFieldAttribute("amount")] decimal Amount +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("61d1c8472218a119a9e73167b9e9af82bfed91bf5ae5a89a82da27c10ea7f763", "Iou", "Iou"); @@ -79,6 +85,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Iou. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -89,7 +96,7 @@ public sealed record Contract(ContractId Id, Iou Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Iou.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Quickstart.Iou.FromRecord(@event.CreateArguments)); } } @@ -98,7 +105,9 @@ public static Contract FromCreatedEvent(CreatedEvent @event) => /// One field per template the choice creates; cardinality follows the choice's /// return type (single, optional, list). /// -public sealed record TransferResult(ContractId Iou) +public sealed record TransferResult( + ContractId Iou +) { /// /// Projects an upstream transaction's created contracts to a typed . @@ -155,7 +164,7 @@ public static ExerciseOutcome FromCreatedContracts(IEnumerable /// Static <Choice>Async extension methods for . /// One method per create-bearing choice; each delegates to -/// +/// /// and projects success via <Choice>Result.FromCreatedContracts. /// public static class IouExtensions @@ -189,10 +198,10 @@ public static ExerciseCommand TransferCommand( /// The choice argument. /// Controller party from the Daml controller clause, routed into the submission's actAs set. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferAsync( + public static Task> TransferAsync( this ContractId contractId, ILedgerWriter client, Iou.Transfer argument, @@ -206,18 +215,14 @@ public static async Task> TransferAsync( SubmitterInfo submitter = owner; - var command = contractId.TransferCommand(argument); - - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); - - return outcome.ProjectCommitted(tx => TransferResult.FromCreatedContracts(tx.CreatedContracts)); + return contractId.TransferAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); } /// @@ -231,8 +236,8 @@ public static async Task> TransferAsync( /// The choice argument. /// The submitter party set (actAs + optional readAs). /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> TransferAsync( this ContractId contractId, @@ -248,14 +253,7 @@ public static async Task> TransferAsync( var command = contractId.TransferCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => TransferResult.FromCreatedContracts(tx.CreatedContracts)); } @@ -270,8 +268,8 @@ public static async Task> TransferAsync( /// The ledger client. /// The choice argument. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static Task> TransferAsync( this Iou.Contract contract, @@ -295,6 +293,45 @@ public static Task> TransferAsync( timeout, cancellationToken); } + + /// + /// Exercises the Transfer choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Iou> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> TransferAsync( + this Iou.Contract contract, + ILedgerWriter client, + Iou.Transfer argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.TransferAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } } /// @@ -310,34 +347,49 @@ public static class IouSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, Iou payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Issuer; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } + + /// + /// Returns the observers of a payload — + /// the parties named in the Daml observer clause, derived from + /// the payload's Party properties in declaration order. Useful + /// for inspecting / asserting the observer set without exercising a + /// choice. + /// + /// The contract payload. + public static IReadOnlyList Observers(Iou payload) + { + ArgumentNullException.ThrowIfNull(payload); + return new Party[] + { + payload.Owner + }; + } } /// /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class IouNonContractExtensions @@ -364,15 +416,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -382,14 +434,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/samples/TokenStandardV2/NuGet.config b/samples/TokenStandardV2/NuGet.config index 0c56c53..e3ce278 100644 --- a/samples/TokenStandardV2/NuGet.config +++ b/samples/TokenStandardV2/NuGet.config @@ -1,5 +1,8 @@ + + + diff --git a/samples/TokenStandardV2/Program.cs b/samples/TokenStandardV2/Program.cs index 9240c6f..d9de161 100644 --- a/samples/TokenStandardV2/Program.cs +++ b/samples/TokenStandardV2/Program.cs @@ -22,8 +22,8 @@ Console.WriteLine("1. ACS filter over the IHolding (V2) interface:"); Console.WriteLine( - $" ACS query ready: {nameof(ILedgerStreamer)}.{nameof(ILedgerStreamer.SubscribeActiveAsync)}<{nameof(IHolding)}>()" - + $" → IAsyncEnumerable<{nameof(AcsSnapshotEntry)}<{nameof(IHolding)}>>\n"); + $" ACS query ready: {nameof(ILedgerStreamer)}.{nameof(ILedgerStreamer.SubscribeActiveAsync)}({nameof(IHolding)}.View)" + + $" → IAsyncEnumerable<{nameof(InterfaceAcsSnapshotEntry)}<{nameof(IHolding)}, {nameof(HoldingView)}>>\n"); Console.WriteLine("2. Accepting a TransferInstruction (V2) via its typed interface choice:"); var transferId = new ContractId("00transferinstruction"); @@ -56,4 +56,4 @@ Console.WriteLine($" commands: {submission.Commands.Count}, actAs: {string.Join(", ", submission.ActAs ?? [])}\n"); Console.WriteLine("Done. Against a live ledger these commands submit through an ILedgerWriter,"); -Console.WriteLine("and active IHolding contracts stream through ILedgerStreamer.SubscribeActiveAsync."); +Console.WriteLine("and active IHolding contracts stream through ILedgerStreamer.SubscribeActiveAsync(IHolding.View)."); diff --git a/samples/TokenStandardV2/README.md b/samples/TokenStandardV2/README.md index b6d8ac1..6c41659 100644 --- a/samples/TokenStandardV2/README.md +++ b/samples/TokenStandardV2/README.md @@ -19,9 +19,18 @@ float on the release-time counter segment. To build it, populate `local-feed/` with the packed V2 `.nupkg` files and the packed `Daml.Runtime` / `Daml.Ledger.Abstractions`, then `dotnet build`. CI does -this automatically in the Splice publish pipeline via -`.github/scripts/verify-sample-tokenstandard-v2.sh`, which builds the sample -against the freshly packed feed as a focused V2 compile-gate before publishing. +this automatically in the Splice publish pipeline, building the sample against +the freshly packed feed as a focused V2 compile-gate before publishing. + +## Isolated package cache + +`NuGet.config` pins `globalPackagesFolder` to `.nuget-packages/`, so restores +here never write to the machine-wide `~/.nuget/packages`. `local-feed/` carries +locally packed builds of versions that also exist on nuget.org, and NuGet never +re-fetches a version whose folder already exists — without that pin, one sample +build would leave local packs permanently shadowing the real published packages +for every other project on the machine. Keep the pin, and keep +`.nuget-packages/` out of the feed. ## Package version floats diff --git a/scripts/bundle-entrypoint/dpm-codegen-cs b/scripts/bundle-entrypoint/dpm-codegen-cs index 514dbae..11383c9 100755 --- a/scripts/bundle-entrypoint/dpm-codegen-cs +++ b/scripts/bundle-entrypoint/dpm-codegen-cs @@ -23,7 +23,7 @@ EOF } BUNDLE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -HELPER_JAR="${BUNDLE_ROOT}/bin/daml-codegen-jvm-helper.jar" +HELPER_JAR="${BUNDLE_ROOT}/bin/daml-dar-to-proto.jar" EMITTER="${BUNDLE_ROOT}/bin/Daml.Codegen.CSharp.Cli" DAR="" diff --git a/scripts/bundle-entrypoint/dpm-codegen-cs.cmd b/scripts/bundle-entrypoint/dpm-codegen-cs.cmd index 13e0348..92f4932 100644 --- a/scripts/bundle-entrypoint/dpm-codegen-cs.cmd +++ b/scripts/bundle-entrypoint/dpm-codegen-cs.cmd @@ -4,7 +4,7 @@ rem SPDX-License-Identifier: Apache-2.0 setlocal enableextensions enabledelayedexpansion set "BUNDLE_ROOT=%~dp0" -set "HELPER_JAR=%BUNDLE_ROOT%bin\daml-codegen-jvm-helper.jar" +set "HELPER_JAR=%BUNDLE_ROOT%bin\daml-dar-to-proto.jar" set "EMITTER=%BUNDLE_ROOT%bin\Daml.Codegen.CSharp.Cli.exe" set "DAR=" diff --git a/scripts/check-license-headers.sh b/scripts/check-license-headers.sh index 4e400f0..84e08fb 100755 --- a/scripts/check-license-headers.sh +++ b/scripts/check-license-headers.sh @@ -6,37 +6,352 @@ set -euo pipefail usage() { cat < Copyright Peaceful Studio OÜ + SPDX-License-Identifier: Apache-2.0 + +The two lines must be adjacent, in that order, within the first +$HEADER_SEARCH_LINES lines, and must match literally apart from the four-digit +year. Both the holder and the licence identifier are asserted, so a header +naming a different holder, or declaring a different licence, fails. A licence +substring appearing somewhere near the top of a file is not enough: this +repository migrated away from "All rights reserved", and the gate has to be +able to see a regression back to it. + +Every file type present under the scan paths must be classified, either in +COMMENT_PREFIX_BY_TYPE (a header is required, written with that comment prefix) +or in TYPES_CARRYING_NO_HEADER (data and markup this repository does not +header). A type in neither list fails the run rather than being skipped, so a +new file type cannot enter the tree unchecked. Files with no extension are read +as scripts: they must start with a shebang and carry the '#' header. + +Each classified comment type must match at least one file. A type that matches +nothing means the check has stopped running, not that it passed. + +A .licenseignore beside ROOT, one path prefix per line, drops paths whose +headers are a third party's to maintain. Its absence only ever widens the scan, +so a tree without one is checked more strictly, never less. + +Under a git work tree the walk is intersected with the index, so build output +and other ignored trees are out of scope while anything staged for commit is +in. A tree git knows nothing about is walked whole: like a missing +.licenseignore, that only ever widens the scan. + +With --fix, insert the header into any file reported as missing one, below a +shebang or an '@echo off' line where one leads the file. +With --print-scan-paths, list the scan paths and exit; list-agreement gates +derive their scope from that output rather than restating it. +ROOT defaults to the repository root; pass one to check a tree elsewhere. + +Exit codes: 0 all headers present, 1 at least one header missing or malformed, +2 the check could not be carried out. EOF } -case "${1:-}" in - -h | --help) - usage - exit 0 - ;; -esac +SCAN_PATHS=( + src + tests + jvm-helper + samples + scripts + conformance + proto + CONTEXT.md + Directory.Build.props + coverage.settings.xml + .editorconfig + .pre-commit-config.yaml +) + +COMMENT_PREFIX_BY_TYPE=( + "cmd:rem" + "cs://" + "daml:--" + "editorconfig:#" + "properties:#" + "proto://" + "sbt://" + "scala://" + "sh:#" + "yaml:#" +) + +TYPES_CARRYING_NO_HEADER=( + binpb + config + csproj + dar + json + manifest + md + props + sha256 + targets + txt + xml +) -if ! command -v addlicense >/dev/null 2>&1; then - echo "addlicense not found. Install: go install github.com/google/addlicense@v1.1.1" >&2 - exit 127 +HOLDER="Peaceful Studio OÜ" +SPDX_LINE="SPDX-License-Identifier: Apache-2.0" +HEADER_SEARCH_LINES=10 +IGNORE_FILE=".licenseignore" +SCRIPT_TYPE="shebang script" + +FIX="" +while [ "$#" -gt 0 ]; do + case "$1" in + -h | --help) + usage + exit 0 + ;; + --print-scan-paths) + printf '%s\n' "${SCAN_PATHS[@]}" + exit 0 + ;; + --fix) + FIX="1" + shift + ;; + -*) + echo "check-license-headers.sh: unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + *) + break + ;; + esac +done + +if [ "$#" -gt 1 ]; then + echo "check-license-headers.sh: unexpected extra arguments: ${*:2}" >&2 + exit 2 fi -action=(-check) -if [ "${1:-}" = "--fix" ]; then - action=() +root="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +if [ ! -d "$root" ]; then + echo "check-license-headers.sh: root is not a directory: $root" >&2 + exit 2 fi +cd "$root" + +for path in "${SCAN_PATHS[@]}"; do + if [ ! -e "$path" ]; then + echo "check-license-headers.sh: scan path is missing from the working tree: $path" >&2 + echo "A scan path that does not resolve would silently drop that part of the tree" >&2 + echo "from the run and still report success." >&2 + exit 2 + fi +done + +ignored_prefixes=() +if [ -f "$IGNORE_FILE" ]; then + while IFS= read -r line || [ -n "$line" ]; do + line="${line%%#*}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [ -n "$line" ] || continue + ignored_prefixes+=("${line%/}") + done <"$IGNORE_FILE" +fi + +is_ignored() { + local file="$1" prefix + for prefix in ${ignored_prefixes+"${ignored_prefixes[@]}"}; do + case "$file" in + "$prefix" | "$prefix"/*) return 0 ;; + esac + done + return 1 +} + +prefix_for_type() { + local wanted="$1" entry + for entry in "${COMMENT_PREFIX_BY_TYPE[@]}"; do + if [ "${entry%%:*}" = "$wanted" ]; then + printf '%s' "${entry#*:}" + return 0 + fi + done + return 1 +} + +type_carries_no_header() { + local wanted="$1" entry + for entry in "${TYPES_CARRYING_NO_HEADER[@]}"; do + if [ "$entry" = "$wanted" ]; then + return 0 + fi + done + return 1 +} + +header_matches() { + local file="$1" prefix="$2" + head -n "$HEADER_SEARCH_LINES" "$file" \ + | awk -v prefix="$prefix" -v holder="$HOLDER" -v spdx="$SPDX_LINE" ' + { sub(/\r$/, ""); line[NR] = $0 } + END { + copyright = "^" prefix " Copyright [0-9][0-9][0-9][0-9] " holder "$" + identifier = prefix " " spdx + for (i = 1; i < NR; i++) { + if (line[i] ~ copyright && line[i + 1] == identifier) { + exit 0 + } + } + exit 1 + } + ' +} + +first_line() { + head -n 1 "$1" | tr -d '\r' +} + +leading_lines_to_preserve() { + case "$(first_line "$1")" in + '#!'* | '@echo off' | '@ECHO OFF') printf '1' ;; + *) printf '0' ;; + esac +} + +uses_carriage_returns() { + local first + first="$(head -n 1 "$1")" + case "$first" in + *$'\r') return 0 ;; + esac + return 1 +} + +insert_header() { + local file="$1" prefix="$2" preserved terminator staged + preserved="$(leading_lines_to_preserve "$file")" + terminator=$'\n' + if uses_carriage_returns "$file"; then + terminator=$'\r\n' + fi + staged="$(mktemp "${TMPDIR:-/tmp}/license-header.XXXXXX")" + trap "rm -f -- $(printf '%q' "$staged")" EXIT INT TERM + { + if [ "$preserved" -gt 0 ]; then + head -n "$preserved" "$file" + fi + printf -- '%s Copyright %s %s%s' "$prefix" "$(date +%Y)" "$HOLDER" "$terminator" + printf -- '%s %s%s' "$prefix" "$SPDX_LINE" "$terminator" + tail -n "+$((preserved + 1))" "$file" + } >"$staged" + cat -- "$staged" >"$file" + rm -f -- "$staged" + trap - EXIT INT TERM +} + +walked_files() { + find "${SCAN_PATHS[@]}" \ + \( -type d \( -name bin -o -name obj -o -name target -o -name .daml \ + -o -name .git -o -name Snapshots -o -name Generated \) -prune \) -o \ + \( -type f -print \) \ + | LC_ALL=C sort +} + +files_tracked_by_git() { + git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0 + git -c core.quotePath=false ls-files --cached -- "${SCAN_PATHS[@]}" | LC_ALL=C sort +} + +scan_files() { + local walked tracked + walked="$(walked_files)" || return 1 + tracked="$(files_tracked_by_git)" || return 1 + if [ -z "$tracked" ]; then + printf '%s' "$walked" + return 0 + fi + comm -12 <(printf '%s\n' "$walked") <(printf '%s\n' "$tracked") +} + +declare -A files_checked_by_type=() +unclassified_types=() +data_files_without_extension=() +missing_header=0 +checked_total=0 + +assert_header() { + local file="$1" prefix="$2" type="$3" + files_checked_by_type["$type"]=$((${files_checked_by_type["$type"]:-0} + 1)) + checked_total=$((checked_total + 1)) + if header_matches "$file" "$prefix"; then + return 0 + fi + if [ -n "$FIX" ]; then + insert_header "$file" "$prefix" + return 0 + fi + echo "$file" + missing_header=1 +} + +listing="$(scan_files)" || { + echo "check-license-headers.sh: could not enumerate the scan paths (see above)." >&2 + echo "Part of the tree was unreadable, so those files were never checked." >&2 + exit 2 +} + +while IFS= read -r file; do + [ -n "$file" ] || continue + if is_ignored "$file"; then + continue + fi + base="${file##*/}" + if [ "${base%.*}" = "$base" ]; then + if [ "$(head -c 2 -- "$file")" = '#!' ]; then + assert_header "$file" '#' "$SCRIPT_TYPE" + else + data_files_without_extension+=("$file") + fi + continue + fi + extension="${base##*.}" + if prefix="$(prefix_for_type "$extension")"; then + assert_header "$file" "$prefix" "$extension" + elif type_carries_no_header "$extension"; then + continue + else + unclassified_types+=("$extension $file") + fi +done <<<"$listing" + +if [ "${#unclassified_types[@]}" -gt 0 ]; then + echo "check-license-headers.sh: unclassified file type(s) under the scan paths:" >&2 + printf ' %s\n' "${unclassified_types[@]}" >&2 + echo "Add each type to COMMENT_PREFIX_BY_TYPE with its comment prefix, or to" >&2 + echo "TYPES_CARRYING_NO_HEADER. An unclassified type would otherwise go unchecked." >&2 + exit 2 +fi + +if [ "${#data_files_without_extension[@]}" -gt 0 ]; then + echo "check-license-headers.sh: file(s) without an extension and without a shebang:" >&2 + printf ' %s\n' "${data_files_without_extension[@]}" >&2 + echo "The gate reads an extensionless file as a script and cannot classify these." >&2 + echo "Give each an extension, or list it in $IGNORE_FILE when its header is a third" >&2 + echo "party's to maintain." >&2 + exit 2 +fi + +for entry in "${COMMENT_PREFIX_BY_TYPE[@]}" "$SCRIPT_TYPE:#"; do + classified_type="${entry%%:*}" + if [ "${files_checked_by_type["$classified_type"]:-0}" -eq 0 ]; then + echo "check-license-headers.sh: no $classified_type files matched under the scan paths." >&2 + echo "A classified type that matches nothing means the check is not running rather" >&2 + echo "than that it passed. Remove the type or fix the scan paths." >&2 + exit 2 + fi +done + +echo "checked $checked_total file(s) across ${#files_checked_by_type[@]} comment type(s)" >&2 -exec addlicense "${action[@]}" -s=only -l apache -c "Peaceful Studio OÜ" \ - -ignore '**/bin/**' \ - -ignore '**/obj/**' \ - -ignore '**/target/**' \ - -ignore '**/*.proto' \ - -ignore '**/*.xml' \ - -ignore '**/Snapshots/**' \ - -ignore '**/Generated/**' \ - src tests jvm-helper samples scripts +exit "$missing_header" diff --git a/scripts/check-license-headers.test.sh b/scripts/check-license-headers.test.sh new file mode 100644 index 0000000..3969ff3 --- /dev/null +++ b/scripts/check-license-headers.test.sh @@ -0,0 +1,283 @@ +#!/usr/bin/env bash +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 + +set -uo pipefail + +usage() { + cat <"$root/src/Thing.cs" + headered '//' 'class ThingSpec' >"$root/tests/ThingSpec.scala" + headered '//' 'name := "fixture"' >"$root/jvm-helper/build.sbt" + headered '//' 'syntax = "proto3";' >"$root/proto/fixture.proto" + headered '#' 'sbt.version=1.10.5' >"$root/jvm-helper/project/build.properties" + headered '--' 'module Fixture where' >"$root/conformance/Fixture.daml" + headered '#' 'sdk-version: 0.0.0' >"$root/conformance/daml.yaml" + headered '#' 'echo fixture' >"$root/samples/run.sh" + headered '#' 'repos: []' >"$root/.pre-commit-config.yaml" + headered '#' 'root = true' >"$root/.editorconfig" + + printf '@echo off\nrem Copyright 2026 %s\nrem %s\necho fixture\n' \ + "$HOLDER" "$SPDX_LINE" >"$root/scripts/entrypoint.cmd" + printf '#!/usr/bin/env bash\n# Copyright 2026 %s\n# %s\necho fixture\n' \ + "$HOLDER" "$SPDX_LINE" >"$root/scripts/entrypoint" + chmod +x "$root/scripts/entrypoint" + + printf '# Fixture\n' >"$root/CONTEXT.md" + printf '\n' >"$root/Directory.Build.props" + printf '\n' >"$root/coverage.settings.xml" +} + +drop_lines() { + local file="$1" first="$2" last="$3" + awk -v first="$first" -v last="$last" 'NR < first || NR > last' "$file" >"$file.reduced" + cat "$file.reduced" >"$file" + rm -f "$file.reduced" +} + +replace_in_file() { + local file="$1" pattern="$2" replacement="$3" + sed "s/$pattern/$replacement/" "$file" >"$file.replaced" + cat "$file.replaced" >"$file" + rm -f "$file.replaced" +} + +failures=0 + +report() { + local outcome="$1" label="$2" + if [ "$outcome" = "pass" ]; then + echo "PASS: $label" + else + echo "FAIL: $label" + failures=$((failures + 1)) + fi +} + +assert_exit() { + local label="$1" expected="$2" tree="$3" flag="${4:-}" + local actual=0 + if [ -n "$flag" ]; then + bash "$gate" "$flag" "$tree" >/dev/null 2>&1 || actual=$? + else + bash "$gate" "$tree" >/dev/null 2>&1 || actual=$? + fi + if [ "$actual" -eq "$expected" ]; then + report pass "$label" + else + report fail "$label — expected exit $expected, got $actual" + fi +} + +new_tree() { + local tree="$work_dir/$1" + build_tree "$tree" + printf '%s' "$tree" +} + +complete_tree="$(new_tree complete)" +assert_exit "a tree with every header present passes" 0 "$complete_tree" + +foreign_holder_tree="$(new_tree foreign-holder)" +replace_in_file "$foreign_holder_tree/src/Thing.cs" \ + "Copyright 2026 .*" "Copyright 1999 Some Other Corp. All rights reserved." +assert_exit "a .cs header naming another copyright holder fails the gate" 1 "$foreign_holder_tree" + +foreign_licence_tree="$(new_tree foreign-licence)" +replace_in_file "$foreign_licence_tree/src/Thing.cs" "Apache-2\.0" "GPL-3.0" +assert_exit "a .cs header declaring another licence fails the gate" 1 "$foreign_licence_tree" + +no_spdx_tree="$(new_tree no-spdx)" +drop_lines "$no_spdx_tree/src/Thing.cs" 2 2 +assert_exit "a .cs header that lost only its SPDX line fails the gate" 1 "$no_spdx_tree" + +split_header_tree="$(new_tree split-header)" +awk 'NR == 1 { print; print ""; next } { print }' "$split_header_tree/src/Thing.cs" \ + >"$split_header_tree/src/Thing.split" +cat "$split_header_tree/src/Thing.split" >"$split_header_tree/src/Thing.cs" +rm -f "$split_header_tree/src/Thing.split" +assert_exit "a copyright and SPDX line no longer adjacent fails the gate" 1 "$split_header_tree" + +mutated_daml_tree="$(new_tree mutated-daml)" +replace_in_file "$mutated_daml_tree/conformance/Fixture.daml" "Apache-2\.0" "Apache-2X0" +assert_exit "a .daml SPDX line reading Apache-2X0 fails the gate" 1 "$mutated_daml_tree" + +for stripped in sbt properties cmd; do + tree="$(new_tree "headerless-$stripped")" + case "$stripped" in + sbt) drop_lines "$tree/jvm-helper/build.sbt" 1 2 ;; + properties) drop_lines "$tree/jvm-helper/project/build.properties" 1 2 ;; + cmd) drop_lines "$tree/scripts/entrypoint.cmd" 2 3 ;; + esac + assert_exit "a headerless .$stripped no general tool knows fails the gate" 1 "$tree" +done + +unclassified_tree="$(new_tree unclassified)" +printf 'body { color: red }\n' >"$unclassified_tree/src/theme.css" +assert_exit "a file type classified nowhere is reported, not skipped" 2 "$unclassified_tree" + +opaque_tree="$(new_tree opaque)" +printf '3.4.11\n' >"$opaque_tree/src/version" +assert_exit "a file with neither extension nor shebang is reported" 2 "$opaque_tree" + +no_daml_tree="$(new_tree no-daml)" +rm "$no_daml_tree/conformance/Fixture.daml" +assert_exit "a classified comment type that matches nothing is reported" 2 "$no_daml_tree" + +missing_path_tree="$(new_tree missing-path)" +rm -rf "$missing_path_tree/conformance" +assert_exit "a scan path missing from the tree is reported" 2 "$missing_path_tree" + +vendored_tree="$(new_tree vendored)" +mkdir -p "$vendored_tree/src/vendor" +printf '// Copyright (c) 2025 Another Vendor. All rights reserved.\n// %s\n\nsyntax = "proto3";\n' \ + "$SPDX_LINE" >"$vendored_tree/src/vendor/foreign.proto" +assert_exit "a third-party header with no ignore file fails the gate" 1 "$vendored_tree" +printf 'src/vendor/\n' >"$vendored_tree/.licenseignore" +assert_exit "a third-party header under a .licenseignore path passes" 0 "$vendored_tree" + +ignored_artifact_tree="$(new_tree ignored-artifact)" +git -C "$ignored_artifact_tree" init --quiet >/dev/null 2>&1 +printf 'src/build-output/\n' >"$ignored_artifact_tree/.gitignore" +mkdir -p "$ignored_artifact_tree/src/build-output" +printf 'not source\n' >"$ignored_artifact_tree/src/build-output/Vendor.dll" +git -C "$ignored_artifact_tree" add --all >/dev/null 2>&1 +assert_exit "a git-ignored build artifact is out of scope under a git work tree" 0 "$ignored_artifact_tree" +git -C "$ignored_artifact_tree" add --force src/build-output/Vendor.dll >/dev/null 2>&1 +assert_exit "the same artifact once tracked is reported, so the pass was the index's doing" 2 "$ignored_artifact_tree" + +fix_tree="$(new_tree fix)" +drop_lines "$fix_tree/conformance/Fixture.daml" 1 2 +drop_lines "$fix_tree/jvm-helper/build.sbt" 1 2 +drop_lines "$fix_tree/jvm-helper/project/build.properties" 1 2 +drop_lines "$fix_tree/scripts/entrypoint.cmd" 2 3 +drop_lines "$fix_tree/scripts/entrypoint" 2 3 +assert_exit "--fix writes the missing headers" 0 "$fix_tree" --fix +assert_exit "the tree --fix wrote then passes a plain check" 0 "$fix_tree" + +if [ "$(head -n 1 "$fix_tree/scripts/entrypoint")" = "#!/usr/bin/env bash" ]; then + report pass "--fix keeps the shebang on the first line" +else + report fail "--fix moved the shebang off the first line" +fi + +if [ "$(head -n 1 "$fix_tree/scripts/entrypoint.cmd")" = "@echo off" ]; then + report pass "--fix keeps '@echo off' on the first line" +else + report fail "--fix moved '@echo off' off the first line" +fi + +if [ -x "$fix_tree/scripts/entrypoint" ]; then + report pass "--fix keeps the executable bit" +else + report fail "--fix dropped the executable bit" +fi + +# Run --fix on a fresh stripped tree with TMPDIR redirected inside the tree so we +# can verify that staging files land in a known location and are cleaned up. +staging_check_tree="$(new_tree staging-check)" +drop_lines "$staging_check_tree/conformance/Fixture.daml" 1 2 +mkdir -p "$staging_check_tree/.tmpdir" +TMPDIR="$staging_check_tree/.tmpdir" bash "$gate" --fix "$staging_check_tree" >/dev/null 2>&1 +staging_leftovers="$(find "$staging_check_tree/.tmpdir" -name 'license-header.*')" +if [ -z "$staging_leftovers" ]; then + report pass "--fix leaves no staging file behind" +else + report fail "--fix left staging files behind: $staging_leftovers" +fi + +# Verify CRLF preservation: create a .cmd with CRLF line endings (no header), +# run --fix, and assert the file still uses CRLF after the header is inserted. +crlf_tree="$(new_tree crlf)" +printf '@echo off\r\necho fixture\r\n' >"$crlf_tree/scripts/entrypoint.cmd" +assert_exit "--fix on a CRLF .cmd exits 0" 0 "$crlf_tree" --fix +if grep -q $'\r' "$crlf_tree/scripts/entrypoint.cmd"; then + report pass "--fix preserves CRLF in .cmd files" +else + report fail "--fix dropped CRLF line endings in a .cmd file" +fi + +printed_paths="$(bash "$gate" --print-scan-paths)" +missing_from_print="" +for path in src tests jvm-helper samples scripts conformance proto CONTEXT.md \ + Directory.Build.props coverage.settings.xml .editorconfig .pre-commit-config.yaml; do + if ! printf '%s\n' "$printed_paths" | grep -qxF -- "$path"; then + missing_from_print="$missing_from_print $path" + fi +done +if [ -z "$missing_from_print" ]; then + report pass "--print-scan-paths lists every scan path the gate walks" +else + report fail "--print-scan-paths omitted:$missing_from_print" +fi + +if [ "$failures" -ne 0 ]; then + echo "$failures assertion(s) failed." + exit 1 +fi + +echo "All assertions passed." diff --git a/scripts/codegen-pipeline.sh b/scripts/codegen-pipeline.sh index 0d6715d..9d26594 100755 --- a/scripts/codegen-pipeline.sh +++ b/scripts/codegen-pipeline.sh @@ -31,7 +31,7 @@ Options: --runtime-version Daml.Runtime NuGet version for the generated .csproj. Recommended when --publish-nuget is set; omitting it leaves a wildcard (*) dependency. - --helper-jar Path to daml-codegen-jvm-helper.jar. Overriding this + --helper-jar Path to daml-dar-to-proto.jar. Overriding this skips the freshness check; you own the JAR's currency. (default: the repo JAR, reassembled from source when stale via scripts/ensure-jvm-helper-jar.sh) diff --git a/scripts/ensure-jvm-helper-jar.sh b/scripts/ensure-jvm-helper-jar.sh index 909be46..372fbff 100644 --- a/scripts/ensure-jvm-helper-jar.sh +++ b/scripts/ensure-jvm-helper-jar.sh @@ -8,7 +8,7 @@ usage() { cat < -# Example: ./scripts/refresh-snapshot.sh splice-api-token-holding-v1 +usage() { + cat <] [--binpb-only | --skip-binpb] [--helper-jar ] + +Refreshes a drift-detection snapshot under +tests/Daml.Codegen.CSharp.Tests/Snapshots/ in two steps: + + 1. Regenerates the vendored intermediate.binpb from the DAR vendored beside + it, using a JVM helper assembled from the working tree's sources — so the + proto cannot go stale behind a helper change. + 2. Regenerates the expected/ tree from that intermediate.binpb using the + current codegen source. + +Both artifacts are checked in. CI's determinism-gate job (Bundle-level +determinism + conformance drift gate) regenerates every intermediate.binpb the +same way and fails on any byte delta; the drift tests then hold expected/ to the +committed proto, so commit both together. + +Options: + --all Refresh every snapshot family instead of a single one. + --binpb-only Regenerate only the intermediate.binpb protos, skipping + the .NET build and the drift tests (CI's drift oracle + runs this before comparing the protos against HEAD). + --skip-binpb Keep the vendored protos and only regenerate expected/ + (emitter-only iteration without a JVM toolchain). + --helper-jar Path to daml-dar-to-proto.jar. Overriding this skips + the freshness check; you own the JAR's currency. + (default: the repo JAR, reassembled from source when + stale via scripts/ensure-jvm-helper-jar.sh) + +Example: $0 splice-api-token-holding-v1 +EOF + exit "${1:-1}" +} SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +SNAPSHOTS_ROOT="$PROJECT_ROOT/tests/Daml.Codegen.CSharp.Tests/Snapshots" -if [[ $# -lt 1 ]]; then - echo "Usage: $0 " - echo "Example: $0 splice-api-token-holding-v1" - exit 1 -fi +ALL=false +REFRESH_BINPB=true +REFRESH_EXPECTED=true +HELPER_JAR="" +SNAPSHOT_NAME="" + +require_value() { + [[ $# -ge 2 && "$2" != -* ]] || { echo "refresh-snapshot.sh: $1 requires a value" >&2; usage; } +} -SNAPSHOT_NAME="$1" -if [[ -z "$SNAPSHOT_NAME" ]]; then - echo "Error: snapshot name must not be empty" - exit 1 +while [[ $# -gt 0 ]]; do + case "$1" in + --all) ALL=true; shift 1 ;; + --binpb-only) REFRESH_EXPECTED=false; shift 1 ;; + --skip-binpb) REFRESH_BINPB=false; shift 1 ;; + --helper-jar) require_value "$@"; HELPER_JAR="$2"; shift 2 ;; + -h|--help) usage 0 ;; + -*) echo "refresh-snapshot.sh: unknown arg: $1" >&2; usage ;; + *) + if [[ -n "$SNAPSHOT_NAME" ]]; then + echo "refresh-snapshot.sh: exactly one snapshot name (or --all) expected" >&2 + usage + fi + SNAPSHOT_NAME="$1"; shift 1 ;; + esac +done + +if [[ "$REFRESH_BINPB" == false && "$REFRESH_EXPECTED" == false ]]; then + echo "refresh-snapshot.sh: --binpb-only and --skip-binpb together leave nothing to refresh" >&2 + usage fi -if [[ "$SNAPSHOT_NAME" =~ [/\\] ]] || [[ "$SNAPSHOT_NAME" == *".."* ]]; then - echo "Error: snapshot name must not contain path separators or '..': $SNAPSHOT_NAME" - exit 1 + +FAMILIES=() +if [[ "$ALL" == true ]]; then + if [[ -n "$SNAPSHOT_NAME" ]]; then + echo "refresh-snapshot.sh: --all takes no snapshot name" >&2 + usage + fi + while IFS= read -r family; do + FAMILIES+=("$family") + done < <(cd "$SNAPSHOTS_ROOT" && find . -mindepth 1 -maxdepth 1 -type d | sed 's|^\./||' | LC_ALL=C sort) + if [[ ${#FAMILIES[@]} -eq 0 ]]; then + echo "refresh-snapshot.sh: no snapshot families found under $SNAPSHOTS_ROOT" >&2 + exit 1 + fi +else + if [[ -z "$SNAPSHOT_NAME" ]]; then + usage + fi + if [[ "$SNAPSHOT_NAME" =~ [/\\] ]] || [[ "$SNAPSHOT_NAME" == *".."* ]]; then + echo "Error: snapshot name must not contain path separators or '..': $SNAPSHOT_NAME" >&2 + exit 1 + fi + if [[ ! -d "$SNAPSHOTS_ROOT/$SNAPSHOT_NAME" ]]; then + echo "Error: snapshot not found: $SNAPSHOTS_ROOT/$SNAPSHOT_NAME" >&2 + exit 1 + fi + FAMILIES=("$SNAPSHOT_NAME") fi -SNAPSHOT_DIR="$PROJECT_ROOT/tests/Daml.Codegen.CSharp.Tests/Snapshots/$SNAPSHOT_NAME" -BINPB_PATH="$SNAPSHOT_DIR/intermediate.binpb" -EXPECTED_DIR="$SNAPSHOT_DIR/expected" -if [[ ! -f "$BINPB_PATH" ]]; then - echo "Error: IntermediateDar proto not found at $BINPB_PATH" - exit 1 +if [[ "$REFRESH_BINPB" == true ]]; then + for family in "${FAMILIES[@]}"; do + dar="$SNAPSHOTS_ROOT/$family/$family.dar" + if [[ ! -f "$dar" ]]; then + echo "refresh-snapshot.sh: DAR not found: $dar" >&2 + echo "Every snapshot family vendors the DAR its intermediate.binpb is generated from;" >&2 + echo "without it the proto cannot be proven fresh. Vendor the DAR, or pass --skip-binpb" >&2 + echo "to keep the vendored proto." >&2 + exit 1 + fi + done + + command -v java >/dev/null 2>&1 || { echo "refresh-snapshot.sh: 'java' not found on PATH" >&2; exit 1; } + + if [[ -z "$HELPER_JAR" ]]; then + HELPER_JAR="$("$SCRIPT_DIR/ensure-jvm-helper-jar.sh")" + elif [[ ! -f "$HELPER_JAR" ]]; then + echo "refresh-snapshot.sh: JVM helper JAR not found: $HELPER_JAR" >&2 + echo "Build it with: scripts/ensure-jvm-helper-jar.sh" >&2 + exit 1 + fi + + for family in "${FAMILIES[@]}"; do + echo "refresh-snapshot.sh: regenerating $family/intermediate.binpb from $family.dar" + java -jar "$HELPER_JAR" \ + --dar "$SNAPSHOTS_ROOT/$family/$family.dar" \ + --out "$SNAPSHOTS_ROOT/$family/intermediate.binpb" >/dev/null + done fi -dotnet build "$PROJECT_ROOT/src/Daml.Codegen.CSharp.Cli" -c Release +if [[ "$REFRESH_EXPECTED" == true ]]; then + dotnet build "$PROJECT_ROOT/src/Daml.Codegen.CSharp.Cli" -c Release + + for family in "${FAMILIES[@]}"; do + SNAPSHOT_DIR="$SNAPSHOTS_ROOT/$family" + BINPB_PATH="$SNAPSHOT_DIR/intermediate.binpb" + EXPECTED_DIR="$SNAPSHOT_DIR/expected" + + if [[ ! -f "$BINPB_PATH" ]]; then + echo "Error: IntermediateDar proto not found at $BINPB_PATH" >&2 + exit 1 + fi -STAGING_DIR="$(mktemp -d "$SNAPSHOT_DIR/expected.regen.XXXXXX")" -trap 'rm -rf "$STAGING_DIR"' EXIT + STAGING_DIR="$(mktemp -d "$SNAPSHOT_DIR/expected.regen.XXXXXX")" + trap 'rm -rf "$STAGING_DIR"' EXIT -dotnet run --project "$PROJECT_ROOT/src/Daml.Codegen.CSharp.Cli" -c Release --no-build -- \ - --intermediate "$BINPB_PATH" \ - -o "$STAGING_DIR" \ - --target-framework net10.0 --verbosity 1 + dotnet run --project "$PROJECT_ROOT/src/Daml.Codegen.CSharp.Cli" -c Release --no-build -- \ + --intermediate "$BINPB_PATH" \ + -o "$STAGING_DIR" \ + --target-framework net10.0 --verbosity 1 -rm -rf "$EXPECTED_DIR" -mv "$STAGING_DIR" "$EXPECTED_DIR" -trap - EXIT + PLACEHOLDER_PATH="$EXPECTED_DIR/.gitkeep" + PLACEHOLDER_BACKUP="" + if [[ -f "$PLACEHOLDER_PATH" ]]; then + PLACEHOLDER_BACKUP="$(mktemp)" + cp "$PLACEHOLDER_PATH" "$PLACEHOLDER_BACKUP" + fi -dotnet test --project "$PROJECT_ROOT/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj" \ - -c Release -- --filter-class "*DriftDetectionTests" + rm -rf "$EXPECTED_DIR" + mv "$STAGING_DIR" "$EXPECTED_DIR" + trap - EXIT -git -C "$PROJECT_ROOT" add "$EXPECTED_DIR" + if [[ -n "$PLACEHOLDER_BACKUP" ]]; then + if [[ -z "$(find "$EXPECTED_DIR" -type f)" ]]; then + mv "$PLACEHOLDER_BACKUP" "$PLACEHOLDER_PATH" + else + rm -f "$PLACEHOLDER_BACKUP" + fi + fi + done + + dotnet test --project "$PROJECT_ROOT/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj" \ + -c Release -- --filter-class "*DriftDetectionTests" +fi + +for family in "${FAMILIES[@]}"; do + if [[ "$REFRESH_BINPB" == true ]]; then + git -C "$PROJECT_ROOT" add "$SNAPSHOTS_ROOT/$family/intermediate.binpb" + fi + if [[ "$REFRESH_EXPECTED" == true ]]; then + git -C "$PROJECT_ROOT" add "$SNAPSHOTS_ROOT/$family/expected" + fi +done echo "" -echo "Snapshot '$SNAPSHOT_NAME' refreshed and staged." -echo "Review the diff with: git diff --cached tests/Daml.Codegen.CSharp.Tests/Snapshots/$SNAPSHOT_NAME/expected" +if [[ "$ALL" == true ]]; then + echo "Snapshots refreshed and staged: ${FAMILIES[*]}" +else + echo "Snapshot '${FAMILIES[0]}' refreshed and staged." +fi +echo "Review the diff with: git diff --cached tests/Daml.Codegen.CSharp.Tests/Snapshots" diff --git a/scripts/verify-consumer-build.sh b/scripts/verify-consumer-build.sh index bb75329..99d0544 100755 --- a/scripts/verify-consumer-build.sh +++ b/scripts/verify-consumer-build.sh @@ -43,7 +43,7 @@ Options: read from Directory.Build.props ). --package-license SPDX license stamped into generated packages (default: \$PACKAGE_LICENSE, else unset). - --helper-jar daml-codegen-jvm-helper.jar (only needed for --dar + --helper-jar daml-dar-to-proto.jar (only needed for --dar inputs). Overriding this skips the freshness check; you own the JAR's currency. Default: the repo JAR, reassembled from source when stale via @@ -111,12 +111,18 @@ NUGET_CONFIG="$OUT/NuGet.config" if [[ -z "$KEEP" ]]; then rm -rf "$OUT" fi -mkdir -p "$EMIT_ROOT" "$FEED" +export NUGET_PACKAGES="$OUT/.nuget-packages" +rm -rf "$NUGET_PACKAGES" +mkdir -p "$EMIT_ROOT" "$FEED" "$NUGET_PACKAGES" derive_csproj() { find "$1" -maxdepth 2 -name '*.csproj' | head -1 } +find_unresolved_stdlib_leaks() { + grep -rhoE 'No\.Package\.Metadata\.[A-Za-z0-9_]+' "$1" --include='*.cs' 2>/dev/null | sort -u | tr '\n' ' ' || true +} + EMITTED_PROJECT_DIRS=() emit_from_intermediate() { @@ -128,7 +134,7 @@ emit_from_intermediate() { "$CLI_BIN" "${cli_args[@]}" local cs_count leaks cs_count="$(find "$dest" -name '*.cs' 2>/dev/null | wc -l | tr -d ' ')" - leaks="$(grep -rhoE 'No\.Package\.Metadata\.[A-Za-z0-9_]+' "$dest" --include='*.cs' 2>/dev/null | sort -u | tr '\n' ' ')" + leaks="$(find_unresolved_stdlib_leaks "$dest")" echo " cs=$cs_count leaks=[$leaks]" [[ -z "$leaks" ]] || { echo "verify-consumer-build.sh: unresolved-stdlib leaks emitted for $name: $leaks" >&2; exit 4; } EMITTED_PROJECT_DIRS+=("$dest") diff --git a/src/Daml.Codegen.CSharp.Cli/Daml.Codegen.CSharp.Cli.csproj b/src/Daml.Codegen.CSharp.Cli/Daml.Codegen.CSharp.Cli.csproj index 8abd40e..c125274 100644 --- a/src/Daml.Codegen.CSharp.Cli/Daml.Codegen.CSharp.Cli.csproj +++ b/src/Daml.Codegen.CSharp.Cli/Daml.Codegen.CSharp.Cli.csproj @@ -8,7 +8,6 @@ Daml.Codegen.CSharp.Cli Daml.Codegen.CSharp.Cli Major - $(NoWarn);CS1591 @@ -18,6 +17,7 @@ + diff --git a/src/Daml.Codegen.CSharp.Cli/Program.cs b/src/Daml.Codegen.CSharp.Cli/Program.cs index 0754073..73d1256 100644 --- a/src/Daml.Codegen.CSharp.Cli/Program.cs +++ b/src/Daml.Codegen.CSharp.Cli/Program.cs @@ -4,13 +4,13 @@ using System.CommandLine; using Daml.Codegen.CSharp; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; using Daml.Codegen.CSharp.Versioning; using Daml.Codegen.Intermediate; +using Microsoft.Extensions.Logging; namespace Daml.Codegen.CSharp.Cli; -internal static class Program +internal static partial class Program { internal static async Task Main(string[] args) { @@ -234,17 +234,21 @@ internal static async Task Main(string[] args) internal static async Task RunCodegen(CodegenArgs args, CancellationToken cancellationToken) { - var logger = new ConsoleLogger(args.Verbosity); + using var loggerFactory = LoggerFactory.Create(builder => + builder.AddProvider(new VerbosityConsoleLoggerProvider(args.Verbosity)) + .SetMinimumLevel(LogLevel.Trace)); + var logger = loggerFactory.CreateLogger(); try { - logger.Info($"Daml C# Codegen v{typeof(Program).Assembly.GetName().Version}"); - logger.Info($"Output directory: {args.OutputDirectory.FullName}"); + var emitterVersion = typeof(Program).Assembly.GetName().Version; + LogBanner(logger, emitterVersion); + LogOutputDirectory(logger, args.OutputDirectory.FullName); if (!args.OutputDirectory.Exists) { args.OutputDirectory.Create(); - logger.Debug($"Created output directory: {args.OutputDirectory.FullName}"); + LogCreatedOutputDirectory(logger, args.OutputDirectory.FullName); } await GenerateFromIntermediate(args.IntermediateFile, args, logger, cancellationToken); @@ -252,30 +256,28 @@ internal static async Task RunCodegen(CodegenArgs args, CancellationToken c } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { - logger.Error( - "Code generation was canceled. " + - $"Partially written files may remain in '{args.OutputDirectory.FullName}'."); + LogCanceled(logger, args.OutputDirectory.FullName); return 130; } catch (Exception ex) { - logger.Error($"Code generation failed: {ex.Message}"); + LogFailed(logger, ex.Message); var rootCauseMessage = ex.GetBaseException().Message; if (rootCauseMessage != ex.Message) { - logger.Error($"Root cause: {rootCauseMessage}"); + LogRootCause(logger, rootCauseMessage); } if (args.Verbosity >= 3) { - logger.Error(ex.ToString()); + LogFailureDetail(logger, ex.ToString()); } return 1; } } - private static async Task GenerateFromIntermediate(FileInfo file, CodegenArgs args, ConsoleLogger logger, CancellationToken cancellationToken) + private static async Task GenerateFromIntermediate(FileInfo file, CodegenArgs args, ILogger logger, CancellationToken cancellationToken) { - logger.Info($"Reading IntermediateDar: {file.Name}"); + LogReadingIntermediate(logger, file.Name); IntermediateDar proto; await using (var stream = file.OpenRead()) { @@ -284,9 +286,9 @@ private static async Task GenerateFromIntermediate(FileInfo file, CodegenArgs ar cancellationToken.ThrowIfCancellationRequested(); var dar = IntermediateDarReader.Read(proto); - logger.Info($" Package: {dar.MainPackage.Name} v{dar.MainPackage.Version}"); - logger.Info($" Modules: {dar.MainPackage.Modules.Count}"); - logger.Debug($" Dependencies: {dar.Dependencies.Count}"); + LogPackage(logger, dar.MainPackage.Name, dar.MainPackage.Version); + LogModuleCount(logger, dar.MainPackage.Modules.Count); + LogDependencyCount(logger, dar.Dependencies.Count); var effectiveCounter = args.ReleaseCountersFile is not null ? ResolveReleaseCounter(args.ReleaseCountersFile, ResolveCodegenVersion(args), dar.MainPackage.Name, dar.MainPackage.Version, logger) @@ -307,12 +309,14 @@ private static int ResolveReleaseCounter( string codegenVersion, string packageName, Version packageVersion, - ConsoleLogger logger) + ILogger logger) { var store = JsonReleaseCounterStore.OpenOrCreate(storeFile.FullName); var version = NuGetVersionResolver.Compute(packageVersion, codegenVersion, store); - logger.Info($" Release counter: codegen_version={codegenVersion}; {packageName} {packageVersion.Major}.{packageVersion.Minor}.{Math.Max(0, packageVersion.Build)} version={version}"); + var threePartVersion = $"{packageVersion.Major}.{packageVersion.Minor}.{Math.Max(0, packageVersion.Build)}"; + var resolvedVersion = version.ToString(); + LogReleaseCounter(logger, codegenVersion, packageName, threePartVersion, resolvedVersion); return version.Generation; } @@ -331,12 +335,12 @@ private static CodeGenOptions BuildOptions(CodegenArgs args, int emitterCounter) EmitterCounter = emitterCounter, PackageLicenseExpression = args.PackageLicenseExpression, VersionSuffix = args.VersionSuffix, + PublishesReferencedPackages = args.ReleaseCountersFile is not null, RepositoryUrl = args.RepositoryUrl, }; - private static async Task WriteGeneratedFiles(IReadOnlyList generatedFiles, CodegenArgs args, ConsoleLogger logger, CancellationToken cancellationToken) + private static async Task WriteGeneratedFiles(IReadOnlyList generatedFiles, CodegenArgs args, ILogger logger, CancellationToken cancellationToken) { - var written = 0; foreach (var file in generatedFiles) { var filePath = Path.Combine(args.OutputDirectory.FullName, file.RelativePath); @@ -354,11 +358,54 @@ private static async Task WriteGeneratedFiles(IReadOnlyList { await File.WriteAllTextAsync(filePath, file.Content, cancellationToken); } - logger.Debug($" Generated: {file.RelativePath}"); - written++; + LogGeneratedFile(logger, file.RelativePath); } - return written; } + + [LoggerMessage(EventId = 2000, Level = LogLevel.Information, Message = "Daml C# Codegen v{AssemblyVersion}")] + private static partial void LogBanner(ILogger logger, Version? assemblyVersion); + + [LoggerMessage(EventId = 2001, Level = LogLevel.Information, Message = "Output directory: {OutputDirectory}")] + private static partial void LogOutputDirectory(ILogger logger, string outputDirectory); + + [LoggerMessage(EventId = 2002, Level = LogLevel.Debug, Message = "Created output directory: {OutputDirectory}")] + private static partial void LogCreatedOutputDirectory(ILogger logger, string outputDirectory); + + [LoggerMessage( + EventId = 2003, + Level = LogLevel.Error, + Message = "Code generation was canceled. Partially written files may remain in '{OutputDirectory}'.")] + private static partial void LogCanceled(ILogger logger, string outputDirectory); + + [LoggerMessage(EventId = 2004, Level = LogLevel.Error, Message = "Code generation failed: {Reason}")] + private static partial void LogFailed(ILogger logger, string reason); + + [LoggerMessage(EventId = 2005, Level = LogLevel.Error, Message = "Root cause: {RootCause}")] + private static partial void LogRootCause(ILogger logger, string rootCause); + + [LoggerMessage(EventId = 2006, Level = LogLevel.Error, Message = "{FailureDetail}")] + private static partial void LogFailureDetail(ILogger logger, string failureDetail); + + [LoggerMessage(EventId = 2007, Level = LogLevel.Information, Message = "Reading IntermediateDar: {FileName}")] + private static partial void LogReadingIntermediate(ILogger logger, string fileName); + + [LoggerMessage(EventId = 2008, Level = LogLevel.Information, Message = " Package: {PackageName} v{PackageVersion}")] + private static partial void LogPackage(ILogger logger, string packageName, Version packageVersion); + + [LoggerMessage(EventId = 2009, Level = LogLevel.Information, Message = " Modules: {ModuleCount}")] + private static partial void LogModuleCount(ILogger logger, int moduleCount); + + [LoggerMessage(EventId = 2010, Level = LogLevel.Debug, Message = " Dependencies: {DependencyCount}")] + private static partial void LogDependencyCount(ILogger logger, int dependencyCount); + + [LoggerMessage( + EventId = 2011, + Level = LogLevel.Information, + Message = " Release counter: codegen_version={CodegenVersion}; {PackageName} {PackageVersion} version={ResolvedVersion}")] + private static partial void LogReleaseCounter(ILogger logger, string codegenVersion, string packageName, string packageVersion, string resolvedVersion); + + [LoggerMessage(EventId = 2012, Level = LogLevel.Debug, Message = " Generated: {RelativePath}")] + private static partial void LogGeneratedFile(ILogger logger, string relativePath); } internal sealed record CodegenArgs( diff --git a/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.FileScaffold.cs b/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.FileScaffold.cs index 945f41e..6c5eb7b 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.FileScaffold.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.FileScaffold.cs @@ -70,7 +70,7 @@ private string BuildFileContent(IndentWriter bodyIndent, StringBuilder bodySb) return headerSb.ToString(); } - private void RequireCommonNamespaces(IndentWriter indent) + private static void RequireCommonNamespaces(IndentWriter indent) { indent.Require(RuntimeNamespaces.Data); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.cs b/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.cs index db4403d..d901554 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/CSharpCodeGenerator.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using System.Text.RegularExpressions; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; namespace Daml.Codegen.CSharp.CodeGen; @@ -10,8 +12,14 @@ namespace Daml.Codegen.CSharp.CodeGen; /// /// Generates C# code from Daml packages. /// -public sealed partial class CSharpCodeGenerator(CodeGenOptions options, ICodegenLogger logger) +/// Emission options. +/// +/// Where progress and warnings go. Omit it — or pass null — and the generator stays silent. +/// +public sealed partial class CSharpCodeGenerator(CodeGenOptions options, ILogger? logger = null) { + private readonly ILogger _log = logger ?? NullLogger.Instance; + private readonly Regex? _rootFilter = options.RootFilter is not null ? new Regex(options.RootFilter, RegexOptions.Compiled) : null; @@ -25,37 +33,28 @@ public IReadOnlyList Generate(IDarSource dar) { var files = new List(); - var resolver = new DarCrossPackageResolver(dar, logger); + var resolver = new DarCrossPackageResolver(dar, _log); - // Generate code for the main package files.AddRange(GeneratePackage(resolver, dar.MainPackage)); - // Optionally generate code for dependencies if (options.IncludeDependencies) { foreach (var dep in dar.Dependencies) { - logger.Debug($"Generating code for dependency: {dep.Name}"); + LogGeneratingDependency(_log, dep.Name); files.AddRange(GeneratePackage(resolver, dep)); } } - // Generate project file if requested if (options.GenerateProjectFile) { - // Resolve every cross-package id encountered during codegen against the DAR - // archive. If a foreign package is genuinely missing from the DAR (i.e. - // GetPackageById returns null), warn — silently dropping it would emit a - // csproj that references types whose NuGet package never gets a - // , surfacing later as opaque CS0246 at consumer - // build time. var externalRefs = new List(); foreach (var id in resolver.DiscoveredExternalPackageIds) { var pkg = dar.GetPackageById(id); if (pkg is null) { - logger.Warning($"External package id {id[..Math.Min(16, id.Length)]}… is not present in the DAR — no will be emitted for it. Generated code that references it will fail to compile."); + LogExternalPackageMissing(_log, id[..Math.Min(16, id.Length)]); continue; } if (IsStdlibPackage(pkg.Name) || IsPlaceholderPackageName(pkg.Name)) @@ -65,21 +64,11 @@ public IReadOnlyList Generate(IDarSource dar) externalRefs.Add(pkg); } var projectGenerator = new ProjectFileGenerator(options); - // Pass the actually-emitted file set (post-RootFilter, post-IncludeDependencies) - // so the LangVersion pin tracks what's in this project, not what's in the - // unfiltered main package — see ProjectFileGenerator.RequiresCSharp13. - files.Add(projectGenerator.GenerateProjectFile(dar.MainPackage, externalRefs, files)); + files.Add(projectGenerator.GenerateProjectFile(dar.MainPackage, externalRefs)); files.Add(projectGenerator.GenerateReadme(dar.MainPackage)); files.Add(projectGenerator.GenerateIcon()); } - var requiresCSharp13 = files.Any(f => - f.RelativePath.EndsWith(".cs", StringComparison.Ordinal) - && ProjectFileGenerator.ContentRequiresCSharp13(f.Content)); - files.Add(GeneratedFile.Text( - ".daml-langversion", - requiresCSharp13 ? "13\n" : string.Empty)); - return files; } @@ -99,10 +88,10 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve var variantEmitter = new VariantEmitter(context, resolver, options, mapper); var recordSerialization = new RecordSerializationEmitter(context, resolver, options, mapper); var recordEmitter = new RecordEmitter(context, options, recordSerialization); - var interfaceEmitter = new InterfaceEmitter(context, mapper, choiceEmitter, options); + var interfaceEmitter = new InterfaceEmitter(context, mapper, resolver, choiceEmitter, options); var rootNamespace = context.RootNamespace; var submissionExtensions = new SubmissionExtensionsEmitter(context, options, _party); - var templateEmitter = new TemplateEmitter(context, resolver, mapper, recordSerialization, choiceEmitter, submissionExtensions, options); + var templateEmitter = new TemplateEmitter(context, resolver, recordSerialization, choiceEmitter, submissionExtensions, options, logger); var allTemplateNames = package.Modules .SelectMany(m => m.Templates) @@ -111,41 +100,49 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve foreach (var module in package.Modules) { - // Build a lookup of data types by name for populating template fields var dataTypesByName = module.DataTypes .Where(dt => dt.Definition is DamlRecordDefinition) .ToDictionary(dt => dt.Name, dt => (DamlRecordDefinition)dt.Definition!); - // Generate templates foreach (var template in module.Templates) { if (_rootFilter is not null && !_rootFilter.IsMatch($"{module.Name}:{template.Name}")) { - logger.Debug($"Skipping template {module.Name}:{template.Name} (filtered)"); + LogSkippingTemplate(_log, module.Name, template.Name); continue; } - // Get fields from corresponding data type if template has none - var fields = template.Fields.Count > 0 - ? template.Fields - : (dataTypesByName.TryGetValue(template.Name, out var recordDef) ? recordDef.Fields : []); + if (!dataTypesByName.TryGetValue(template.Name, out var recordDef)) + { + var sameNamed = module.DataTypes.FirstOrDefault(dt => dt.Name == template.Name); + var cause = sameNamed is null + ? "no data type of that name exists in the module" + : $"the same-named data type is a {sameNamed.Definition.GetType().Name}"; + + throw new CodegenException( + $"Template '{module.Name}:{template.Name}' has no same-named record definition in its module: {cause}. " + + "An LF template payload is always a same-named record carrying the template's fields, so " + + "this means the model is malformed and no payload type can be emitted."); + } - var code = GenerateTemplate(context, templateEmitter, package, module, template, fields); + var code = GenerateTemplate(context, templateEmitter, package, module, template, recordDef.Fields); var path = RelativeFilePath(rootNamespace, $"{EmitterHelpers.SanitizeIdentifier(template.Name)}.cs"); yield return GeneratedFile.Text(path, code); } - // Generate data types (skip templates and choice argument types - they're generated as nested types) foreach (var dataType in module.DataTypes) { - // Skip data types that correspond to templates - they're generated as part of the template if (allTemplateNames.Contains(dataType.Name)) { continue; } - // Skip data types that are choice argument types - they're generated as nested types in the template + if (context.LocalInterfaceQualifiedNames.Contains($"{module.Name}:{dataType.Name}")) + { + continue; + } + if (context.LocalChoiceArgToTemplate.ContainsKey($"{module.Name}:{dataType.Name}")) { continue; @@ -157,7 +154,6 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve yield return GeneratedFile.Text(path, code); } - // Generate choice argument types as nested classes in partial template files foreach (var template in module.Templates) { if (_rootFilter is not null && !_rootFilter.IsMatch($"{module.Name}:{template.Name}")) @@ -167,7 +163,6 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve foreach (var choice in template.Choices) { - // Check if this choice's argument type is a data type that should be nested if (choice.ArgumentType is DamlTypeRef typeRef && context.DataTypes.TryGetValue($"{typeRef.Module}:{typeRef.Name}", out var argDataType) && argDataType.Definition is DamlRecordDefinition) @@ -183,12 +178,11 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve } } - // Generate interfaces foreach (var iface in module.Interfaces) { if (_rootFilter is not null && !_rootFilter.IsMatch($"{module.Name}:{iface.Name}")) { - logger.Debug($"Skipping interface {module.Name}:{iface.Name} (filtered)"); + LogSkippingInterface(_log, module.Name, iface.Name); continue; } @@ -199,7 +193,6 @@ private IEnumerable GeneratePackage(ICrossPackageResolver resolve } } - // Generate ContractIdentifiers helper class if enabled if (options.GenerateContractIdentifiers) { var allTemplates = package.Modules @@ -296,7 +289,6 @@ private GeneratedFile GenerateContractIdentifiersFile( indent.Require(RuntimeNamespaces.Contracts); indent.Require($"static {RuntimeNamespaces.Contracts}.TemplateExtensions"); - // Class documentation if (options.GenerateXmlDocs) { indent.AppendLine("/// "); @@ -309,7 +301,6 @@ private GeneratedFile GenerateContractIdentifiersFile( indent.AppendLine("{"); indent.Indent(); - // Generate a property for each template for (int i = 0; i < templates.Count; i++) { var (module, template) = templates[i]; @@ -325,7 +316,6 @@ private GeneratedFile GenerateContractIdentifiersFile( indent.AppendLine($"public static string {templateClassName} {{ get; }} = GetTemplateId<{templateClassName}>();"); - // Add blank line between properties, but not after the last one if (i < templates.Count - 1) { indent.AppendLine(); @@ -360,4 +350,19 @@ private string GenerateNestedChoiceArgumentType( RequireCommonNamespaces(indent); templateEmitter.WriteNestedChoiceArgumentType(indent, template, choice, argDataType); }); + + [LoggerMessage(EventId = 1000, Level = LogLevel.Debug, Message = "Generating code for dependency: {PackageName}")] + private static partial void LogGeneratingDependency(ILogger logger, string packageName); + + [LoggerMessage( + EventId = 1001, + Level = LogLevel.Warning, + Message = "External package id {PackageIdPrefix}\u2026 is not present in the DAR \u2014 no will be emitted for it. Generated code that references it will fail to compile.")] + private static partial void LogExternalPackageMissing(ILogger logger, string packageIdPrefix); + + [LoggerMessage(EventId = 1002, Level = LogLevel.Debug, Message = "Skipping template {ModuleName}:{TemplateName} (filtered)")] + private static partial void LogSkippingTemplate(ILogger logger, string moduleName, string templateName); + + [LoggerMessage(EventId = 1003, Level = LogLevel.Debug, Message = "Skipping interface {ModuleName}:{InterfaceName} (filtered)")] + private static partial void LogSkippingInterface(ILogger logger, string moduleName, string interfaceName); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceArgumentInfo.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceArgumentInfo.cs new file mode 100644 index 0000000..ca6483f --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceArgumentInfo.cs @@ -0,0 +1,46 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.Intermediate.Model; + +namespace Daml.Codegen.CSharp.CodeGen; + +/// +/// Resolved C# shape of a choice's argument, produced by +/// and consumed by every exerciser, +/// command-builder, and descriptor emission path. +/// +/// +/// The C# type name of the argument record — the sanitized choice name for a +/// same-package nested record, a resolver-qualified name for an external reference, or +/// DamlUnit for an argument-less choice (genuine Unit or the synthetic +/// stdlib Archive). +/// +/// +/// The argument record's fields when the argument is a same-package record whose +/// definition is in scope; null for the argument-less shape and for external +/// references, whose definitions live in another package. +/// +/// +/// True when the argument record is generated nested inside the template class, so +/// parameter types must qualify it as TemplateName.TypeName. +/// +internal sealed record ChoiceArgumentInfo( + string TypeName, + IReadOnlyList? Fields, + bool IsNestedTemplateArg) +{ + /// + /// True when the exerciser takes an argument parameter — false for the + /// argument-less DamlUnit shape, whose encoding the emitter supplies itself. + /// + public bool HasArgument => TypeName != RuntimeTypeNames.DamlUnit; + + /// + /// The argument's parameter type at an emission site inside + /// 's extensions — template-qualified for a + /// nested record, the bare otherwise. + /// + public string ParameterType(string templateClassName) => + IsNestedTemplateArg ? $"{templateClassName}.{TypeName}" : TypeName; +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceCreatedSlots.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceCreatedSlots.cs index c375b18..bc306b7 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ChoiceCreatedSlots.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceCreatedSlots.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -26,30 +26,16 @@ internal enum CreatedCardinality /// /// /// checks this record for presence (a non- slot is an interface slot) and then branches on : for a fully-emitted interface marker, the comparison reads +/// langword="null"/> slot is an interface slot) and then reads /// {marker}.InterfaceId.ModuleName/EntityName off the generated marker at /// runtime, mirroring how the template branch reads {template}.TemplateId. Local -/// interface refs resolve to a C# name whose class shape RecordEmitter alone decides — -/// it always emits the LF-mandated accompanying record as a throwing ITemplate -/// stub with no InterfaceId member (see -/// RecordEmitter.WriteInterfacePlaceholderRecord) — so those stay on the -/// string-literal comparison baked at codegen time instead of trusting a generated -/// symbol that this slot cannot itself confirm exists. and -/// remain here as the slot's resolved identity for both the -/// literal comparison and testing. +/// and foreign interface refs alike resolve to a marker carrying that static, so both +/// take the same branch. and remain +/// here as the slot's resolved identity for testing. /// /// The interface's declaring Daml module name. /// The interface's entity (declaration) name. -/// -/// when the slot targets a local interface ref — RecordEmitter -/// always emits the local placeholder record as a throwing stub with no -/// InterfaceId, so the projector must match via string literals rather than a -/// generated symbol. for foreign interfaces resolved against -/// another package's own module declarations, which carry no equivalent -/// placeholder-record concept in this package's emission. -/// -internal sealed record InterfaceMatcher(string ModuleName, string EntityName, bool IsPlaceholder); +internal sealed record InterfaceMatcher(string ModuleName, string EntityName); /// /// One declared ContractId T-bearing slot in a choice's return type. @@ -131,6 +117,13 @@ private static IReadOnlyList Disambiguate(List + /// Recognizes ContractId T as a created slot, Optional and list wrappers as + /// cardinality carriers, and DA.Types tuples as products to flatten over. A slot + /// inherits the cardinality of the wrapper it sits under, so [ContractId T] yields a + /// list slot rather than a single one. Records, variants, primitives, type variables and + /// Unit contribute nothing — codegen treats them as non-creating return types. + /// private static void Walk( PackageEmitContext context, ICrossPackageResolver resolver, DamlTypeMapper mapper, DamlType type, @@ -139,9 +132,6 @@ private static void Walk( { switch (type) { - // ContractId T — single-create slot. Inherit `parentCardinality` from any - // wrapping Optional/List so a `[ContractId T]` becomes a List slot rather - // than a Single one. case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.ContractId }, Arguments: [var arg] }: { var (templateName, csharpName, interfaceMatcher) = ResolveContractIdTarget(context, resolver, mapper, arg); @@ -152,15 +142,12 @@ private static void Walk( Interface: interfaceMatcher)); return; } - // Optional (ContractId T) — recurse with Optional cardinality. case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var inner] }: Walk(context, resolver, mapper, inner, slots, CreatedCardinality.Optional); return; - // [ContractId T] — recurse with List cardinality. case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List }, Arguments: [var inner] }: Walk(context, resolver, mapper, inner, slots, CreatedCardinality.List); return; - // Daml tuple (DA.Types:Tuple2/3/.../N) — flatten over the components. case DamlTypeApp { Base: DamlTypeRef { Module: "DA.Types", Name: var tupleName } } app when tupleName.StartsWith("Tuple", StringComparison.Ordinal): for (var i = 0; i < app.Arguments.Count; i++) @@ -169,8 +156,6 @@ when tupleName.StartsWith("Tuple", StringComparison.Ordinal): } return; default: - // Records, variants, primitives, type vars, plain Unit — no created-slot - // contribution. Codegen treats these as non-creating return types. return; } } @@ -192,24 +177,23 @@ private static (string FieldName, string CSharpTemplateType, InterfaceMatcher? I return (fieldName, csharpName, ResolveInterfaceMatcher(context, resolver, typeRef)); } default: - // Type variable or otherwise opaque target — fall back to the mapped C# - // name and a synthetic field name. Generated code may not compile in this - // case; callers will see a clear loud failure at consumer build time. var mapped = mapper.MapType(arg); return ("Created", mapped, null); } } - // Mirrors the interface-marker branches of DarCrossPackageResolver.Resolve: a slot - // targets a Daml interface exactly when the resolver would emit an interface-marker - // name for it. Local interfaces live in the placeholder set; foreign interfaces are - // read from the referenced package's interface declarations. + /// + /// Mirrors the interface-marker branches of : a slot + /// targets a Daml interface exactly when the resolver would emit an interface-marker name for + /// it. Local interfaces live in ; + /// foreign ones are read from the referenced package's interface declarations. + /// private static InterfaceMatcher? ResolveInterfaceMatcher(PackageEmitContext context, ICrossPackageResolver resolver, DamlTypeRef typeRef) { if (context.IsLocalRef(typeRef)) { - var isLocalInterface = context.InterfacePlaceholderQualifiedNames.Contains($"{typeRef.Module}:{typeRef.Name}"); - return isLocalInterface ? new InterfaceMatcher(typeRef.Module, typeRef.Name, IsPlaceholder: true) : null; + var isLocalInterface = context.LocalInterfaceQualifiedNames.Contains($"{typeRef.Module}:{typeRef.Name}"); + return isLocalInterface ? new InterfaceMatcher(typeRef.Module, typeRef.Name) : null; } var isForeignInterface = resolver.LookupPackage(typeRef.PackageId) is { } pkg @@ -218,6 +202,6 @@ private static (string FieldName, string CSharpTemplateType, InterfaceMatcher? I && pkg.Modules.Any(module => module.Name == typeRef.Module && module.Interfaces.Any(iface => iface.Name == typeRef.Name)); - return isForeignInterface ? new InterfaceMatcher(typeRef.Module, typeRef.Name, IsPlaceholder: false) : null; + return isForeignInterface ? new InterfaceMatcher(typeRef.Module, typeRef.Name) : null; } } diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExerciserOverloads.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExerciserOverloads.cs new file mode 100644 index 0000000..556fb8e --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExerciserOverloads.cs @@ -0,0 +1,244 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.Intermediate.Model; + +namespace Daml.Codegen.CSharp.CodeGen; + +internal sealed partial class ChoiceEmitter +{ + /// + /// Emits the readAs-capable <Choice>Async overload on + /// ContractId<TemplateName> that takes an explicit + /// SubmitterInfo instead of named Party parameters. + /// Companion to the ergonomic named-Party overload for choices whose + /// created contracts are visible to an observer but not to the submitter — + /// the caller supplies readAs parties the payload cannot derive. + /// Emitted alongside the named-Party overload whenever controllers are + /// statically resolvable; the dynamic-controller case already surfaces a + /// SubmitterInfo parameter on its sole overload. + /// + private void WriteSubmitterInfoChoiceAsyncExerciser( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes) + { + var choiceName = SanitizeIdentifier(choice.Name); + var resultName = $"{choiceName}Result"; + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Exercises the {choice.Name} choice with an explicit and projects the resulting transaction's created contracts to a typed ."); + indent.AppendLine("/// Companion to the named-Party overload for the case where the submitter must"); + indent.AppendLine("/// read contracts it does not act as — the choice's created contracts are visible to an"); + indent.AppendLine("/// observer but not to the submitter, so the caller supplies the readAs parties."); + indent.AppendLine("/// "); + indent.AppendLine("/// The contract on which to exercise the choice."); + indent.AppendLine("/// The ledger client."); + if (hasArg) + { + indent.AppendLine("/// The choice argument."); + } + indent.AppendLine("/// The submitter party set (actAs + optional readAs)."); + WriteSubmissionParameterDocs(indent); + } + + indent.AppendLine($"public static async Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); + indent.Indent(); + indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); + indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); + if (hasArg) + { + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument,"); + } + indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter,"); + WriteSubmissionParametersAndCloseSignature(indent); + indent.Dedent(); + indent.AppendLine("{"); + indent.Indent(); + + indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); + + WriteExerciserCommandDispatchAndProject(indent, choice, templateClassName, dataTypes); + + indent.Dedent(); + indent.AppendLine("}"); + } + + /// + /// Emits the readAs-capable sibling of : + /// the same TemplateName.Contract receiver, but with an explicit + /// SubmitterInfo replacing the payload-derived parties. Without it a caller + /// holding a fetched contract has to unwrap contract.Id by hand the moment the + /// submission needs readAs or a multi-party actAs, because the + /// payload-derived overload can only ever submit the parties the payload names. + /// + private void WriteSubmitterInfoContractChoiceAsyncExerciser( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes) => + WriteContractChoiceAsyncExerciser( + indent, + choice, + templateClassName, + dataTypes, + [ + $"/// Exercises the {choice.Name} choice on a fetched contract with an", + "/// explicit . Companion to the payload-derived overload for", + "/// multi-party submissions and for callers who must supply readAs parties the", + "/// payload cannot name. Delegates to the", + $"/// ContractId<{templateClassName}> overload.", + ], + declaresSubmitterInfoParameter: true, + ["submitter"]); + + private void WriteExerciserCommandDispatchAndProject( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes) + { + var choiceName = SanitizeIdentifier(choice.Name); + var resultName = $"{choiceName}Result"; + var hasArg = GetChoiceArgumentInfo(choice, dataTypes).HasArgument; + + indent.AppendLine(); + indent.AppendLine(hasArg + ? $"var command = contractId.{choiceName}Command(argument);" + : $"var command = contractId.{choiceName}Command();"); + + indent.AppendLine(); + indent.AppendLine("var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false);"); + indent.AppendLine(); + indent.AppendLine($"return outcome.ProjectCommitted(tx => {resultName}.FromCreatedContracts(tx.CreatedContracts));"); + } + + /// + /// Emits the sibling <Choice>Async overload that receives the + /// generated nested TemplateName.Contract — the type + /// TemplateName.Contract.FromCreatedEvent returns — instead of a bare + /// ContractId<TemplateName>. Targeting the nested record (rather + /// than the runtime Contract<T> base) keeps the overload reachable + /// from a FromCreatedEvent result without an intermediate allocation. + /// Because the receiver carries the payload, the wrapper reads every + /// controller / observer party off contract.Data and delegates to the + /// ContractId<T> overload — the caller passes zero parties. Emitted + /// only when controllers are statically resolvable to payload fields; the + /// dynamic case has no payload-derivable submitter, so no Contract + /// overload is generated and callers stay on the named-parameter + /// ContractId<T> path. + /// + private void WriteSingleContractChoiceAsyncExerciser( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes, + DamlPartyAnalysis controllers, + DamlPartyAnalysis observers) + { + var (controllerFieldNames, readAsFieldNames) = + party.PartitionControllerAndObserverFieldNames(controllers, observers); + + WriteContractChoiceAsyncExerciser( + indent, + choice, + templateClassName, + dataTypes, + [ + $"/// Exercises the {choice.Name} choice on a fetched contract,", + "/// reading every controller and observer party off the contract payload so the", + "/// caller passes no parties. Delegates to the", + $"/// ContractId<{templateClassName}> overload.", + ], + declaresSubmitterInfoParameter: false, + [.. controllerFieldNames + .Concat(readAsFieldNames) + .Select(fieldName => $"contract.Data.{MemberName(fieldName, templateClassName)}")]); + } + + private void WriteContractChoiceAsyncExerciser( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes, + IReadOnlyList summaryLines, + bool declaresSubmitterInfoParameter, + IReadOnlyList forwardedSubmitterArguments) + { + var choiceName = SanitizeIdentifier(choice.Name); + var resultName = $"{choiceName}Result"; + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + foreach (var summaryLine in summaryLines) + { + indent.AppendLine(summaryLine); + } + indent.AppendLine("/// "); + indent.AppendLine("/// The fetched contract on which to exercise the choice."); + indent.AppendLine("/// The ledger client."); + if (hasArg) + { + indent.AppendLine("/// The choice argument."); + } + if (declaresSubmitterInfoParameter) + { + indent.AppendLine("/// The submitter party set (actAs + optional readAs)."); + } + WriteSubmissionParameterDocs(indent); + } + + indent.AppendLine($"public static Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); + indent.Indent(); + indent.AppendLine($"this {templateClassName}.Contract contract,"); + indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); + if (hasArg) + { + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument,"); + } + if (declaresSubmitterInfoParameter) + { + indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter,"); + } + WriteSubmissionParametersAndCloseSignature(indent); + indent.Dedent(); + indent.AppendLine("{"); + indent.Indent(); + + indent.AppendLine("ArgumentNullException.ThrowIfNull(contract);"); + indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); + if (hasArg) + { + indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); + } + + indent.AppendLine(); + indent.AppendLine($"return contract.Id.{choiceName}Async("); + indent.Indent(); + indent.AppendLine("client,"); + if (hasArg) + { + indent.AppendLine("argument,"); + } + foreach (var submitterArgument in forwardedSubmitterArguments) + { + indent.AppendLine($"{submitterArgument},"); + } + indent.AppendLine("workflowId,"); + indent.AppendLine("commandId,"); + indent.AppendLine("timeout,"); + indent.AppendLine("cancellationToken);"); + indent.Dedent(); + + indent.Dedent(); + indent.AppendLine("}"); + } +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExercisers.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExercisers.cs index 92ae6c8..37b4b71 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExercisers.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ContractIdExercisers.cs @@ -1,45 +1,15 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; internal sealed partial class ChoiceEmitter { - /// - /// Emits the <Choice>Result nested record (typed result struct) and a - /// static FromCreatedContracts(...) projector for every choice on - /// whose return type carries one or more - /// ContractId Ts. See . - /// - /// Writer positioned at the emission point in the template's file. - /// The template whose choices are scanned for created-contract slots. - /// - /// Fully-qualified C# namespace of the emitted template. Used to global::-qualify - /// in-package template references inside the projector body so positional record - /// properties on the result type (e.g. a slot named Agreement) cannot shadow - /// the template type when looking up Agreement.TemplateId. - /// - internal void WriteChoiceResultStructs(IndentWriter indent, DamlTemplate template, string moduleNamespace) - { - foreach (var choice in template.Choices) - { - var slots = ChoiceCreatedSlots.Extract(context, resolver, mapper, choice.ReturnType); - if (slots.Count == 0) - { - continue; - } - - WriteSingleChoiceResultStruct(indent, choice, slots, moduleNamespace); - } - } - /// /// Returns true when has at least one choice that - /// (a) creates contracts ( yields a non-empty list) and - /// (b) does not go through the codegen's argument-type fallback (B3 — no - /// argument.ToRecord() on a stub). + /// (a) creates contracts ( yields a non-empty list). /// private bool TemplateHasEmittableAsyncExercisers( DamlTemplate template, @@ -52,11 +22,6 @@ private bool TemplateHasEmittableAsyncExercisers( { continue; } - var (_, _, isFallback, _) = GetChoiceArgumentInfo(choice, dataTypes); - if (isFallback) - { - continue; - } return true; } return false; @@ -97,14 +62,12 @@ internal void WriteChoiceAsyncExercisersClass( return; } - RequireAsyncExerciserNamespaces(indent); + EmittedUsings.RequireAsyncExerciserNamespaces(indent); var partyFields = fields .Where(f => f.Type is DamlPrimitiveType { Primitive: DamlPrimitive.Party }) .ToDictionary(f => f.Name, f => f, StringComparer.Ordinal); - // Re-validate template-level observers against actual payload fields - // — same defensive check the SubmissionExtensions emitter does. var templateObservers = party.ValidatePayloadParties(template.Observers, partyFields); indent.AppendLine(); @@ -113,7 +76,7 @@ internal void WriteChoiceAsyncExercisersClass( indent.AppendLine("/// "); indent.AppendLine($"/// Static <Choice>Async extension methods for ."); indent.AppendLine("/// One method per create-bearing choice; each delegates to"); - indent.AppendLine("/// "); + indent.AppendLine("/// "); indent.AppendLine($"/// and projects success via <Choice>Result.FromCreatedContracts."); indent.AppendLine("/// "); } @@ -130,12 +93,6 @@ internal void WriteChoiceAsyncExercisersClass( continue; } - var (_, _, isFallback, _) = GetChoiceArgumentInfo(choice, dataTypes); - if (isFallback) - { - continue; - } - if (!first) { indent.AppendLine(); @@ -143,7 +100,7 @@ internal void WriteChoiceAsyncExercisersClass( var controllers = party.ValidatePayloadParties(choice.Controllers, partyFields); var choiceObservers = party.ValidatePayloadParties(choice.Observers, partyFields); var effectiveReadAs = party.UnionStaticParties(templateObservers, choiceObservers); - WriteContractIdChoiceCommandBuilder(indent, choice, templateClassName, dataTypes); + WriteChoiceCommandBuilder(indent, choice, templateClassName, dataTypes); indent.AppendLine(); WriteSingleChoiceAsyncExerciser( indent, choice, templateClassName, dataTypes, controllers, effectiveReadAs); @@ -157,6 +114,10 @@ internal void WriteChoiceAsyncExercisersClass( indent.AppendLine(); WriteSingleContractChoiceAsyncExerciser( indent, choice, templateClassName, dataTypes, controllers, effectiveReadAs); + + indent.AppendLine(); + WriteSubmitterInfoContractChoiceAsyncExerciser( + indent, choice, templateClassName, dataTypes); } first = false; } @@ -165,6 +126,13 @@ internal void WriteChoiceAsyncExercisersClass( indent.AppendLine("}"); } + /// + /// The choice receiver here is a contract id, so no payload is in scope and observer parties + /// can only be surfaced as named Party parameters. Observer-only fields — named in an + /// observer clause but not as controllers — follow the controller parameters in declaration + /// order, and the emitted body routes controllers into the submitter's actAs set and + /// observers into its readAs set. + /// private void WriteSingleChoiceAsyncExerciser( IndentWriter indent, DamlChoice choice, @@ -175,18 +143,12 @@ private void WriteSingleChoiceAsyncExerciser( { var choiceName = SanitizeIdentifier(choice.Name); var resultName = $"{choiceName}Result"; - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; var staticControllers = controllers.Source == DamlPartySource.Static && controllers.Parties.Count > 0; - // Observers-only (payload fields named in the observer clauses but NOT - // also named as controllers). The choice receiver is a ContractId, so - // we have no payload in scope — observer parties only become emittable - // as named Party params. We surface them in declaration order on top - // of the controller params; the body then routes controllers into - // SubmitterInfo.actAs and observers into SubmitterInfo.readAs. var (controllerParams, readAsParams) = staticControllers ? party.PartitionControllersAndObservers(controllers, observers) : (new List(), new List()); @@ -238,23 +200,17 @@ private void WriteSingleChoiceAsyncExerciser( { indent.AppendLine("/// The submitter party set (actAs + optional readAs)."); } - indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); - indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice."); - indent.AppendLine("/// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome."); - indent.AppendLine("/// Cancellation token."); + WriteSubmissionParameterDocs(indent); } - // Method signature. - indent.AppendLine($"public static async Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); + var asyncModifier = staticControllers ? string.Empty : "async "; + indent.AppendLine($"public static {asyncModifier}Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); indent.Indent(); indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); if (hasArg) { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"{argParamType} argument,"); + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument,"); } if (staticControllers) @@ -272,26 +228,18 @@ private void WriteSingleChoiceAsyncExerciser( { indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter,"); } - indent.AppendLine("string? workflowId = null,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); - indent.AppendLine("TimeSpan? timeout = null,"); - indent.AppendLine("CancellationToken cancellationToken = default)"); + WriteSubmissionParametersAndCloseSignature(indent); indent.Dedent(); indent.AppendLine("{"); indent.Indent(); indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); - // When controllers are statically resolvable, build the SubmitterInfo - // locally — actAs from the named Party params, readAs from the - // observer-only Party params (if any). if (staticControllers) { indent.AppendLine(); if (controllerParams.Count == 1 && readAsParams.Count == 0) { - // Single controller, no extra readAs — rely on Party -> - // SubmitterInfo implicit conversion. Avoids a HashSet allocation. indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter = {controllerParams[0]};"); } else if (readAsParams.Count == 0) @@ -311,589 +259,28 @@ private void WriteSingleChoiceAsyncExerciser( indent.AppendLine($"readAs: new {context.Qualifier.Qualify("HashSet", context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.Party, context.RootNamespace)}> {{ {string.Join(", ", readAsParams)} }});"); indent.Dedent(); } - } - - WriteExerciserCommandDispatchAndProject(indent, choice, templateClassName, dataTypes); - - indent.Dedent(); - indent.AppendLine("}"); - } - /// - /// Emits the readAs-capable <Choice>Async overload on - /// ContractId<TemplateName> that takes an explicit - /// SubmitterInfo instead of named Party parameters. - /// Companion to the ergonomic named-Party overload for choices whose - /// created contracts are visible to an observer but not to the submitter — - /// the caller supplies readAs parties the payload cannot derive. - /// Emitted alongside the named-Party overload whenever controllers are - /// statically resolvable; the dynamic-controller case already surfaces a - /// SubmitterInfo parameter on its sole overload. - /// - private void WriteSubmitterInfoChoiceAsyncExerciser( - IndentWriter indent, - DamlChoice choice, - string templateClassName, - IReadOnlyDictionary dataTypes) - { - var choiceName = SanitizeIdentifier(choice.Name); - var resultName = $"{choiceName}Result"; - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Exercises the {choice.Name} choice with an explicit and projects the resulting transaction's created contracts to a typed ."); - indent.AppendLine("/// Companion to the named-Party overload for the case where the submitter must"); - indent.AppendLine("/// read contracts it does not act as — the choice's created contracts are visible to an"); - indent.AppendLine("/// observer but not to the submitter, so the caller supplies the readAs parties."); - indent.AppendLine("/// "); - indent.AppendLine("/// The contract on which to exercise the choice."); - indent.AppendLine("/// The ledger client."); - if (hasArg) - { - indent.AppendLine("/// The choice argument."); - } - indent.AppendLine("/// The submitter party set (actAs + optional readAs)."); - indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); - indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice."); - indent.AppendLine("/// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome."); - indent.AppendLine("/// Cancellation token."); - } - - indent.AppendLine($"public static async Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); - indent.Indent(); - indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); - if (hasArg) - { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"{argParamType} argument,"); - } - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter,"); - indent.AppendLine("string? workflowId = null,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); - indent.AppendLine("TimeSpan? timeout = null,"); - indent.AppendLine("CancellationToken cancellationToken = default)"); - indent.Dedent(); - indent.AppendLine("{"); - indent.Indent(); - - indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); - - WriteExerciserCommandDispatchAndProject(indent, choice, templateClassName, dataTypes); - - indent.Dedent(); - indent.AppendLine("}"); - } - - /// - /// Emits the <Choice>Command(this ContractId<TemplateName> contractId, ...) - /// builder that constructs the choice's - /// without submitting it. Emitted once per choice and shared by both the named-Party and - /// SubmitterInfo <Choice>Async overloads (see - /// and ) — - /// they exercise the identical choice on the identical ContractId<T> type and - /// therefore build the identical command. - /// - private void WriteContractIdChoiceCommandBuilder( - IndentWriter indent, - DamlChoice choice, - string templateClassName, - IReadOnlyDictionary dataTypes) - { - var choiceName = SanitizeIdentifier(choice.Name); - var commandMethodName = $"{choiceName}Command"; - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Builds the for the {choice.Name} choice on this contract id."); - indent.AppendLine("/// "); - indent.AppendLine("/// The contract on which to exercise the choice."); - if (hasArg) - { - indent.AppendLine("/// The choice argument."); - } - } - - indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)} {commandMethodName}("); - indent.Indent(); - if (hasArg) - { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); - indent.AppendLine($"{argParamType} argument)"); - } - else - { - indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId)"); - } - indent.Dedent(); - indent.AppendLine("{"); - indent.Indent(); - - indent.AppendLine("ArgumentNullException.ThrowIfNull(contractId);"); - if (hasArg) - { - indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); - } - - var argExpr = hasArg ? "argument.ToRecord()" : $"{context.Qualifier.Qualify(RuntimeTypeNames.DamlUnit, context.RootNamespace)}.Instance"; - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)}("); - indent.Indent(); - indent.AppendLine($"{templateClassName}.TemplateId,"); - indent.AppendLine("contractId,"); - indent.AppendLine($"new {context.Qualifier.Qualify(RuntimeTypeNames.ChoiceName, context.RootNamespace)}(\"{choice.Name}\"),"); - indent.AppendLine($"{argExpr});"); - indent.Dedent(); - - indent.Dedent(); - indent.AppendLine("}"); - } - - private void WriteExerciserCommandDispatchAndProject( - IndentWriter indent, - DamlChoice choice, - string templateClassName, - IReadOnlyDictionary dataTypes) - { - var choiceName = SanitizeIdentifier(choice.Name); - var resultName = $"{choiceName}Result"; - var (argTypeName, _, _, _) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; - - indent.AppendLine(); - indent.AppendLine(hasArg - ? $"var command = contractId.{choiceName}Command(argument);" - : $"var command = contractId.{choiceName}Command();"); - - indent.AppendLine(); - indent.AppendLine($"var submission = {context.Qualifier.Qualify(RuntimeTypeNames.CommandsSubmission, context.RootNamespace)}.Single(command)"); - indent.Indent(); - indent.AppendLine($".WithCommandId(commandId ?? new {context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}(Guid.NewGuid().ToString()));"); - indent.Dedent(); - indent.AppendLine("if (!string.IsNullOrEmpty(workflowId))"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"submission = submission.WithWorkflowId(new {context.Qualifier.Qualify(RuntimeTypeNames.WorkflowId, context.RootNamespace)}(workflowId));"); - indent.Dedent(); - indent.AppendLine("}"); - indent.AppendLine(); - indent.AppendLine("var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false);"); - indent.AppendLine(); - indent.AppendLine($"return outcome.ProjectCommitted(tx => {resultName}.FromCreatedContracts(tx.CreatedContracts));"); - } - - /// - /// Emits the sibling <Choice>Async overload that receives the - /// generated nested TemplateName.Contract — the type - /// TemplateName.Contract.FromCreatedEvent returns — instead of a bare - /// ContractId<TemplateName>. Targeting the nested record (rather - /// than the runtime Contract<T> base) keeps the overload reachable - /// from a FromCreatedEvent result without an intermediate allocation. - /// Because the receiver carries the payload, the wrapper reads every - /// controller / observer party off contract.Data and delegates to the - /// ContractId<T> overload — the caller passes zero parties. Emitted - /// only when controllers are statically resolvable to payload fields; the - /// dynamic case has no payload-derivable submitter, so no Contract - /// overload is generated and callers stay on the named-parameter - /// ContractId<T> path. - /// - private void WriteSingleContractChoiceAsyncExerciser( - IndentWriter indent, - DamlChoice choice, - string templateClassName, - IReadOnlyDictionary dataTypes, - DamlPartyAnalysis controllers, - DamlPartyAnalysis observers) - { - var choiceName = SanitizeIdentifier(choice.Name); - var resultName = $"{choiceName}Result"; - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; - - var (controllerFieldNames, readAsFieldNames) = - party.PartitionControllerAndObserverFieldNames(controllers, observers); - var partyArguments = controllerFieldNames - .Concat(readAsFieldNames) - .Select(fieldName => $"contract.Data.{MemberName(fieldName, templateClassName)}") - .ToList(); - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Exercises the {choice.Name} choice on a fetched contract,"); - indent.AppendLine("/// reading every controller and observer party off the contract payload so the"); - indent.AppendLine("/// caller passes no parties. Delegates to the"); - indent.AppendLine($"/// ContractId<{templateClassName}> overload."); - indent.AppendLine("/// "); - indent.AppendLine("/// The fetched contract on which to exercise the choice."); - indent.AppendLine("/// The ledger client."); - if (hasArg) - { - indent.AppendLine("/// The choice argument."); - } - indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); - indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice."); - indent.AppendLine("/// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome."); - indent.AppendLine("/// Cancellation token."); - } - - indent.AppendLine($"public static Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>> {choiceName}Async("); - indent.Indent(); - indent.AppendLine($"this {templateClassName}.Contract contract,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); - if (hasArg) - { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"{argParamType} argument,"); - } - indent.AppendLine("string? workflowId = null,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); - indent.AppendLine("TimeSpan? timeout = null,"); - indent.AppendLine("CancellationToken cancellationToken = default)"); - indent.Dedent(); - indent.AppendLine("{"); - indent.Indent(); - - indent.AppendLine("ArgumentNullException.ThrowIfNull(contract);"); - indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); - if (hasArg) - { - indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); - } - - indent.AppendLine(); - indent.AppendLine($"return contract.Id.{choiceName}Async("); - indent.Indent(); - indent.AppendLine("client,"); - if (hasArg) - { - indent.AppendLine("argument,"); - } - foreach (var partyArgument in partyArguments) - { - indent.AppendLine($"{partyArgument},"); - } - indent.AppendLine("workflowId,"); - indent.AppendLine("commandId,"); - indent.AppendLine("timeout,"); - indent.AppendLine("cancellationToken);"); - indent.Dedent(); - - indent.Dedent(); - indent.AppendLine("}"); - } - - private void WriteSingleChoiceResultStruct( - IndentWriter indent, - DamlChoice choice, - IReadOnlyList slots, - string moduleNamespace) - { - RequireAsyncExerciserNamespaces(indent); - indent.Require("System.Collections.Generic"); - - var choiceName = SanitizeIdentifier(choice.Name); - var resultName = $"{choiceName}Result"; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Typed projection of the contracts created by the {choice.Name} choice."); - indent.AppendLine("/// One field per template the choice creates; cardinality follows the choice's"); - indent.AppendLine("/// return type (single, optional, list)."); - indent.AppendLine("/// "); - } - - // Record header — primary constructor with one parameter per slot. - var parameters = string.Join(", ", slots.Select(s => $"{SlotPropertyType(s)} {s.FieldName}")); - indent.AppendLine($"public sealed record {resultName}({parameters})"); - indent.AppendLine("{"); - indent.Indent(); - - WriteFromCreatedContractsProjector(indent, resultName, slots, moduleNamespace); - - indent.Dedent(); - indent.AppendLine("}"); - indent.AppendLine(); - } - - private string SlotPropertyType(ChoiceCreatedSlot slot) => slot.Cardinality switch - { - CreatedCardinality.Single => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>", - CreatedCardinality.Optional => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>?", - CreatedCardinality.List => $"{context.Qualifier.Qualify("IReadOnlyList", context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>>", - _ => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>", - }; - - private void WriteFromCreatedContractsProjector( - IndentWriter indent, - string resultName, - IReadOnlyList slots, - string moduleNamespace) - { - // Helper: qualify in-package template references with the module namespace - // (and `global::`) so positional record properties on the result type cannot - // shadow the template type. Cross-package types are emitted by codegen with - // their own qualifier already, so we only prefix bare names — heuristically - // detected by checking for an embedded dot. - string Q(string templateName) => - templateName.Contains('.', StringComparison.Ordinal) - ? templateName - : $"global::{moduleNamespace}.{templateName}"; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Projects an upstream transaction's created contracts to a typed ."); - indent.AppendLine("/// Returns:"); - indent.AppendLine("/// "); - indent.AppendLine("/// when every expected template's cardinality matches."); - indent.AppendLine("/// when at least one required slot's template is missing from the transaction."); - indent.AppendLine("/// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one."); - indent.AppendLine("/// "); - indent.AppendLine("/// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly."); - indent.AppendLine("/// "); - } - - indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}> FromCreatedContracts(IEnumerable<{context.Qualifier.Qualify(RuntimeTypeNames.CreatedContract, context.RootNamespace)}> created)"); - indent.AppendLine("{"); - indent.Indent(); - - indent.AppendLine("ArgumentNullException.ThrowIfNull(created);"); - - // Group slots by template ref. When the same template appears more than once - // in the choice's return type (e.g. `(ContractId Half, ContractId Half)`), - // every slot pointing at the same template shares one bucket of created - // contracts; we then distribute that bucket across the slots in declaration - // order. The earlier `if/else if`-per-slot shape silently dropped duplicates - // on the floor: the second slot's branch was unreachable, so its bucket - // stayed empty and the projector returned `.None` for any caller using the - // duplicate-template feature this PR otherwise advertises. - var templateGroups = new List<(string TemplateRef, InterfaceMatcher? Interface, List SlotIndexes)>(); - for (var i = 0; i < slots.Count; i++) - { - var templateRef = Q(slots[i].CSharpTemplateType); - var slotInterface = slots[i].Interface; - var groupIndex = -1; - for (var g = 0; g < templateGroups.Count; g++) - { - // Key on TemplateRef *and* the interface matcher, not TemplateRef alone — - // a template and an interface marker can share the same generated C# name - // (e.g. template `IFactory` vs the marker generated for interface `Factory`), - // and merging their slots would pick whichever slot's Interface came first, - // matching the other slot on the wrong branch. - if (string.Equals(templateGroups[g].TemplateRef, templateRef, StringComparison.Ordinal) - && templateGroups[g].Interface == slotInterface) - { - groupIndex = g; - break; - } - } - - if (groupIndex < 0) - { - templateGroups.Add((templateRef, slots[i].Interface, new List { i })); - } - else - { - templateGroups[groupIndex].SlotIndexes.Add(i); - } - } - - // One bucket per unique template. Slots fan out from these buckets below. - for (var g = 0; g < templateGroups.Count; g++) - { - indent.AppendLine($"var templateMatches{g} = new List();"); - } - - indent.AppendLine("foreach (var item in created)"); - indent.AppendLine("{"); - indent.Indent(); - for (var g = 0; g < templateGroups.Count; g++) - { - var prefix = g == 0 ? "if" : "else if"; - var group = templateGroups[g]; - if (group.Interface is not null) - { - // Interface-marker slots carry no public TemplateId; a created contract - // surfaces the interfaces it implements via InterfaceIds. Match on the - // interface's (module, entity), sourced from InterfaceMatcher.IsPlaceholder. - indent.Require("System.Linq"); - indent.AppendLine($"{prefix} (item.InterfaceIds.Any(interfaceId =>"); - indent.Indent(); - if (group.Interface.IsPlaceholder) - { - indent.AppendLine($"string.Equals(interfaceId.ModuleName, \"{group.Interface.ModuleName}\", StringComparison.Ordinal)"); - indent.AppendLine($"&& string.Equals(interfaceId.EntityName, \"{group.Interface.EntityName}\", StringComparison.Ordinal)))"); - } - else - { - indent.AppendLine($"string.Equals(interfaceId.ModuleName, {group.TemplateRef}.InterfaceId.ModuleName, StringComparison.Ordinal)"); - indent.AppendLine($"&& string.Equals(interfaceId.EntityName, {group.TemplateRef}.InterfaceId.EntityName, StringComparison.Ordinal)))"); - } - indent.Dedent(); - } - else - { - indent.AppendLine($"{prefix} (string.Equals(item.TemplateId.ModuleName, {group.TemplateRef}.TemplateId.ModuleName, StringComparison.Ordinal)"); - indent.Indent(); - indent.AppendLine($"&& string.Equals(item.TemplateId.EntityName, {group.TemplateRef}.TemplateId.EntityName, StringComparison.Ordinal))"); - indent.Dedent(); - } - indent.AppendLine("{"); + indent.AppendLine(); + indent.AppendLine($"return contractId.{choiceName}Async("); indent.Indent(); - indent.AppendLine($"templateMatches{g}.Add(item.ContractId);"); - indent.Dedent(); - indent.AppendLine("}"); - } - indent.Dedent(); - indent.AppendLine("}"); - - // Distribute each template bucket across its slots in declaration order. - // Single/Optional slots take one match each; List slots drain the remainder. - // Validation against the per-slot cardinality runs in the next loop, against - // these `matches{i}` locals — so the existing None/Many semantics are preserved. - for (var i = 0; i < slots.Count; i++) - { - indent.AppendLine($"var matches{i} = new List();"); - } - for (var g = 0; g < templateGroups.Count; g++) - { - var slotIndexes = templateGroups[g].SlotIndexes; - indent.AppendLine($"var templateMatchIndex{g} = 0;"); - for (var k = 0; k < slotIndexes.Count; k++) + indent.AppendLine("client,"); + if (hasArg) { - var slotIndex = slotIndexes[k]; - var slot = slots[slotIndex]; - switch (slot.Cardinality) - { - case CreatedCardinality.Single: - case CreatedCardinality.Optional: - indent.AppendLine($"if (templateMatchIndex{g} < templateMatches{g}.Count)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"matches{slotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); - indent.AppendLine($"templateMatchIndex{g}++;"); - indent.Dedent(); - indent.AppendLine("}"); - break; - - case CreatedCardinality.List: - // List slots accept any cardinality. When a List slot appears - // alongside Single/Optional slots for the same template, the - // List slot drains whatever is left after the upstream slots - // claim their share — declaration order wins. - indent.AppendLine($"while (templateMatchIndex{g} < templateMatches{g}.Count)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"matches{slotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); - indent.AppendLine($"templateMatchIndex{g}++;"); - indent.Dedent(); - indent.AppendLine("}"); - break; - } + indent.AppendLine("argument,"); } - - // Whatever is left after distribution counts as "extra of this template". - // For Single/Optional-only slot groups, those leftovers are what trigger - // the existing `Many` outcome below (each Single/Optional slot's local - // bucket has at most 1, so the leftover never becomes part of `matchesN` - // — we have to surface it back into one of the slot buckets so the - // cardinality validator sees the total population). Strategy: append the - // leftover into the *last* slot for this group, which is then counted by - // the validator. If the last slot is Single/Optional, this trips its - // count > 1 branch (correct: the consumer asked for one, ledger created - // many). If the last slot is List, the leftover was already drained by - // the while loop above. - var lastSlotIndex = slotIndexes[^1]; - indent.AppendLine($"while (templateMatchIndex{g} < templateMatches{g}.Count)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"matches{lastSlotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); - indent.AppendLine($"templateMatchIndex{g}++;"); + indent.AppendLine("submitter,"); + indent.AppendLine("workflowId,"); + indent.AppendLine("commandId,"); + indent.AppendLine("timeout,"); + indent.AppendLine("cancellationToken);"); indent.Dedent(); - indent.AppendLine("}"); - } - - // Cardinality validation per slot. - for (var i = 0; i < slots.Count; i++) - { - var slot = slots[i]; - var local = $"matches{i}"; - indent.AppendLine(); - switch (slot.Cardinality) - { - case CreatedCardinality.Single: - indent.AppendLine($"if ({local}.Count == 0)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.None();"); - indent.Dedent(); - indent.AppendLine("}"); - indent.AppendLine($"if ({local}.Count > 1)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.Many({local}.Count, {local});"); - indent.Dedent(); - indent.AppendLine("}"); - break; - case CreatedCardinality.Optional: - indent.AppendLine($"if ({local}.Count > 1)"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.Many({local}.Count, {local});"); - indent.Dedent(); - indent.AppendLine("}"); - break; - case CreatedCardinality.List: - // No upper-bound check — list slots accept any cardinality including zero. - break; - } } - - // Build the result. - indent.AppendLine(); - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.One(new {resultName}("); - indent.Indent(); - for (var i = 0; i < slots.Count; i++) + else { - var slot = slots[i]; - var local = $"matches{i}"; - var separator = i == slots.Count - 1 ? "" : ","; - // Same shadowing-avoidance rationale as the match loop above: - // the result type's positional property `` must not - // shadow the template type when constructing the typed ContractId. - var templateRef = Q(slot.CSharpTemplateType); - switch (slot.Cardinality) - { - case CreatedCardinality.Single: - indent.AppendLine($"{slot.FieldName}: new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>({local}[0]){separator}"); - break; - case CreatedCardinality.Optional: - indent.AppendLine($"{slot.FieldName}: {local}.Count == 1 ? new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>({local}[0]) : null{separator}"); - break; - case CreatedCardinality.List: - indent.AppendLine($"{slot.FieldName}: {local}.ConvertAll(c => new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>(c)){separator}"); - break; - } + WriteExerciserCommandDispatchAndProject(indent, choice, templateClassName, dataTypes); } - indent.Dedent(); - indent.AppendLine("));"); indent.Dedent(); indent.AppendLine("}"); - indent.AppendLine(); } } diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.InterfaceExtensions.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.InterfaceExtensions.cs index ee4a1fc..feeeedb 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.InterfaceExtensions.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.InterfaceExtensions.cs @@ -1,33 +1,12 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; internal sealed partial class ChoiceEmitter { - internal void WriteInterfaceMethod(IndentWriter indent, DamlChoice method, IReadOnlyDictionary dataTypes) - { - var methodName = SanitizeIdentifier(method.Name); - var returnType = mapper.MapType(method.ReturnType); - var (argTypeName, _, _, _) = GetChoiceArgumentInfo(method, dataTypes); - - if (options.GenerateXmlDocs) - { - indent.AppendLine($"// Interface method {method.Name}."); - } - - if (argTypeName == "DamlUnit") - { - indent.AppendLine($"// Choice {method.Name}() -> {returnType}"); - } - else - { - indent.AppendLine($"// Choice {method.Name}({argTypeName}) -> {returnType}"); - } - } - internal void WriteInterfaceChoiceExtensions( IndentWriter indent, DamlInterface iface, @@ -40,7 +19,7 @@ internal void WriteInterfaceChoiceExtensions( indent.AppendLine("/// One method per choice; each submits an interface-typed"); indent.AppendLine($"/// built via"); indent.AppendLine($"/// "); - indent.AppendLine("/// through "); + indent.AppendLine("/// through "); indent.AppendLine($"/// and surfaces the raw —"); indent.AppendLine("/// interface choices have no typed <Choice>Result projection because the"); indent.AppendLine("/// implementing template (and therefore the produced contracts' shapes) is unknown"); @@ -57,7 +36,7 @@ internal void WriteInterfaceChoiceExtensions( return; } - RequireAsyncExerciserNamespaces(indent); + EmittedUsings.RequireAsyncExerciserNamespaces(indent); indent.AppendLine($"public static class {extensionsClassName}"); indent.AppendLine("{"); @@ -92,7 +71,7 @@ private void WriteInterfaceChoiceExtensionMethod( WriteInterfaceChoiceCommandBuilder(indent, choice, interfaceName, commandMethodName, argTypeName, hasArg, requiresArgumentNullCheck, argExpr); indent.AppendLine(); - WriteInterfaceChoiceAsyncMethod(indent, choice, interfaceName, commandMethodName, methodName, argTypeName, hasArg); + WriteInterfaceChoiceAsyncMethod(indent, choice, commandMethodName, methodName, argTypeName, hasArg, interfaceName, SubmitterInfoParameter()); } private void WriteInterfaceChoiceCommandBuilder( @@ -143,21 +122,28 @@ private void WriteInterfaceChoiceCommandBuilder( indent.AppendLine("}"); } + /// + /// The emitted signature mirrors the concrete-template async exerciser but skips the typed + /// result projection: an interface choice does not know the implementing template at the call + /// site, so the most useful return shape is the raw transaction outcome the ledger client + /// surfaces. + /// private void WriteInterfaceChoiceAsyncMethod( IndentWriter indent, DamlChoice choice, - string interfaceName, string commandMethodName, string methodName, string argTypeName, - bool hasArg) + bool hasArg, + string interfaceName, + ChoiceSubmitterParameter submitter) { if (options.GenerateXmlDocs) { indent.AppendLine("/// "); indent.AppendLine($"/// Exercises the {choice.Name} interface choice on this contract id, submitting the"); indent.AppendLine("/// resulting through"); - indent.AppendLine("/// and awaiting the outcome."); + indent.AppendLine("/// ."); indent.AppendLine("/// "); indent.AppendLine("/// The interface-typed contract id to exercise on."); indent.AppendLine("/// The ledger client."); @@ -165,18 +151,11 @@ private void WriteInterfaceChoiceAsyncMethod( { indent.AppendLine("/// The choice argument."); } - indent.AppendLine("/// The party submitting the command."); - indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); - indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice."); - indent.AppendLine("/// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome."); - indent.AppendLine("/// Cancellation token."); + indent.AppendLine($"/// {submitter.DocSummary}"); + WriteSubmissionParameterDocs(indent); } - // Method signature mirrors the concrete-template Async shape, - // but skips the typed Result projection: interface choices do not know - // the implementing template at the call site, so the most useful return shape - // is the raw ExerciseOutcome the ledger client surfaces. - indent.AppendLine($"public static async Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.TransactionResult, context.RootNamespace)}>> {methodName}("); + indent.AppendLine($"public static Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.TransactionResult, context.RootNamespace)}>> {methodName}("); indent.Indent(); indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{interfaceName}> contractId,"); indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); @@ -184,32 +163,20 @@ private void WriteInterfaceChoiceAsyncMethod( { indent.AppendLine($"{argTypeName} argument,"); } - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.Party, context.RootNamespace)} actAs,"); - indent.AppendLine("string? workflowId = null,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); - indent.AppendLine("TimeSpan? timeout = null,"); - indent.AppendLine("CancellationToken cancellationToken = default)"); + indent.AppendLine($"{submitter.TypeName} {submitter.Name},"); + WriteSubmissionParametersAndCloseSignature(indent); indent.Dedent(); indent.AppendLine("{"); indent.Indent(); indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); + indent.AppendLine(); + indent.AppendLine(hasArg ? $"var command = contractId.{commandMethodName}(argument);" : $"var command = contractId.{commandMethodName}();"); indent.AppendLine(); - indent.AppendLine($"var submission = {context.Qualifier.Qualify(RuntimeTypeNames.CommandsSubmission, context.RootNamespace)}.Single(command)"); - indent.Indent(); - indent.AppendLine($".WithCommandId(commandId ?? new {context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}(Guid.NewGuid().ToString()));"); - indent.Dedent(); - indent.AppendLine("if (!string.IsNullOrEmpty(workflowId))"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"submission = submission.WithWorkflowId(new {context.Qualifier.Qualify(RuntimeTypeNames.WorkflowId, context.RootNamespace)}(workflowId));"); - indent.Dedent(); - indent.AppendLine("}"); - indent.AppendLine(); - indent.AppendLine("return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false);"); + indent.AppendLine($"return client.TrySubmitSingleAsync(command, {submitter.Name}, workflowId, commandId, timeout, cancellationToken);"); indent.Dedent(); indent.AppendLine("}"); diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.NonContractExercisers.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.NonContractExercisers.cs index bc89c64..a898b5a 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.NonContractExercisers.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.NonContractExercisers.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -11,8 +11,8 @@ internal sealed partial class ChoiceEmitter { DamlPrimitiveType { Primitive: DamlPrimitive.Unit } => context.Qualifier.Qualify(RuntimeTypeNames.Unit, context.RootNamespace), DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, - Arguments: [DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } }] } => - throw new NotSupportedException("Codegen does not support nested Optional types (Optional (Optional t)). C# nullable syntax cannot represent the Some Nothing / Nothing distinction without a wrapper type. Refactor the Daml signature, or open a feature request to introduce a representable CLR model."), + Arguments: [DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } } or DamlTypeVar] } => + mapper.MapType(returnType), DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var arg] } => $"{MapNonContractReturnType(arg)}?", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List }, Arguments: [var arg] } => @@ -43,6 +43,9 @@ private string RenderNonContractReturnDecoder( string valueExpr) => returnType switch { DamlPrimitiveType { Primitive: DamlPrimitive.Unit } => context.Qualifier.Qualify(RuntimeTypeNames.Unit, context.RootNamespace) + ".Value", + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, + Arguments: [DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } } or DamlTypeVar] } => + mapper.FromValue(returnType, valueExpr), DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var arg] } => $"{valueExpr}.AsOptional().HasValue ? {RenderNonContractReturnDecoder(arg, $"{valueExpr}.AsOptional().Value!")} : null", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List }, Arguments: [var arg] } => @@ -70,21 +73,8 @@ internal bool TryWriteNonContractChoiceExtensions( { var className = SanitizeIdentifier(template.Name); - // Mirror the gate applied to Async emission for CID-returning - // choices: skip choices whose argument type went through the - // codegen fallback path. The fallback emits a field-less Arg - // stub with no ToRecord(), so `argument.ToRecord()` would not compile - // in consumer output. var emittable = template.Choices - .Where(c => - { - if (ChoiceCreatedSlots.Extract(context, resolver, mapper, c.ReturnType).Count > 0) - { - return false; - } - var (_, _, isFallback, _) = GetChoiceArgumentInfo(c, dataTypes); - return !isFallback; - }) + .Where(c => ChoiceCreatedSlots.Extract(context, resolver, mapper, c.ReturnType).Count == 0) .ToList(); if (emittable.Count == 0) @@ -92,7 +82,7 @@ internal bool TryWriteNonContractChoiceExtensions( return false; } - RequireAsyncExerciserNamespaces(indent); + EmittedUsings.RequireAsyncExerciserNamespaces(indent); indent.AppendLine(); if (options.GenerateXmlDocs) @@ -101,7 +91,7 @@ internal bool TryWriteNonContractChoiceExtensions( indent.AppendLine($"/// Async exerciser extensions for contract IDs whose choices"); indent.AppendLine("/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.)."); indent.AppendLine("/// Each method submits the choice via"); - indent.AppendLine("/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result"); + indent.AppendLine("/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result"); indent.AppendLine("/// into ExerciseOutcome<TReturn>."); indent.AppendLine("/// "); } @@ -115,13 +105,12 @@ internal bool TryWriteNonContractChoiceExtensions( { indent.AppendLine(); } - WriteNonContractChoiceCommandBuilder(indent, emittable[i], className, dataTypes); + WriteChoiceCommandBuilder(indent, emittable[i], className, dataTypes); indent.AppendLine(); - WriteSingleNonContractChoiceAsyncExerciser(indent, emittable[i], className, dataTypes); + WriteSingleNonContractChoiceAsyncExerciser( + indent, emittable[i], className, dataTypes, SubmitterInfoParameter()); } - // Per-choice projector helpers, emitted after the public methods so they - // visually follow the call sites that reference them. foreach (var choice in emittable) { indent.AppendLine(); @@ -134,84 +123,19 @@ internal bool TryWriteNonContractChoiceExtensions( return true; } - /// - /// Emits the <Choice>Command(this ContractId<TemplateName> contractId, ...) - /// builder that constructs the choice's - /// without submitting it. Called from 's - /// generated <Choice>Async method. - /// - private void WriteNonContractChoiceCommandBuilder( - IndentWriter indent, - DamlChoice choice, - string templateClassName, - IReadOnlyDictionary dataTypes) - { - var choiceName = SanitizeIdentifier(choice.Name); - var commandMethodName = $"{choiceName}Command"; - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Builds the for the {choice.Name} choice on this contract id."); - indent.AppendLine("/// "); - indent.AppendLine("/// The contract on which to exercise the choice."); - if (hasArg) - { - indent.AppendLine("/// The choice argument."); - } - } - - indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)} {commandMethodName}("); - indent.Indent(); - if (hasArg) - { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); - indent.AppendLine($"{argParamType} argument)"); - } - else - { - indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId)"); - } - indent.Dedent(); - indent.AppendLine("{"); - indent.Indent(); - - indent.AppendLine("ArgumentNullException.ThrowIfNull(contractId);"); - if (hasArg) - { - indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); - } - - var argExpr = hasArg ? "argument.ToRecord()" : EmptyArgumentExpression(choice); - indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)}("); - indent.Indent(); - indent.AppendLine($"{templateClassName}.TemplateId,"); - indent.AppendLine("contractId,"); - indent.AppendLine($"new {context.Qualifier.Qualify(RuntimeTypeNames.ChoiceName, context.RootNamespace)}(\"{choice.Name}\"),"); - indent.AppendLine($"{argExpr});"); - indent.Dedent(); - - indent.Dedent(); - indent.AppendLine("}"); - } - private void WriteSingleNonContractChoiceAsyncExerciser( IndentWriter indent, DamlChoice choice, string templateClassName, - IReadOnlyDictionary dataTypes) + IReadOnlyDictionary dataTypes, + ChoiceSubmitterParameter submitter) { var choiceName = SanitizeIdentifier(choice.Name); var returnTypeName = MapNonContractReturnType(choice.ReturnType); - var (argTypeName, _, _, isNestedTemplateArg) = GetChoiceArgumentInfo(choice, dataTypes); - var hasArg = argTypeName != "DamlUnit"; + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; - StdlibPackages.RequireForFieldType(resolver, indent, choice.ReturnType); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, choice.ReturnType); if (options.GenerateXmlDocs) { @@ -226,11 +150,8 @@ private void WriteSingleNonContractChoiceAsyncExerciser( { indent.AppendLine("/// The choice argument."); } - indent.AppendLine("/// The party submitting the command."); - indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); - indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice."); - indent.AppendLine("/// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome."); - indent.AppendLine("/// Cancellation token."); + indent.AppendLine($"/// {submitter.DocSummary}"); + WriteSubmissionParameterDocs(indent); } indent.AppendLine($"public static async Task<{context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{returnTypeName}>> {choiceName}Async("); @@ -239,41 +160,23 @@ private void WriteSingleNonContractChoiceAsyncExerciser( indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.ILedgerWriter, context.RootNamespace)} client,"); if (hasArg) { - var argParamType = isNestedTemplateArg - ? $"{templateClassName}.{argTypeName}" - : argTypeName; - indent.AppendLine($"{argParamType} argument,"); + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument,"); } - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.Party, context.RootNamespace)} actAs,"); - indent.AppendLine("string? workflowId = null,"); - indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); - indent.AppendLine("TimeSpan? timeout = null,"); - indent.AppendLine("CancellationToken cancellationToken = default)"); + indent.AppendLine($"{submitter.TypeName} {submitter.Name},"); + WriteSubmissionParametersAndCloseSignature(indent); indent.Dedent(); indent.AppendLine("{"); indent.Indent(); indent.AppendLine("ArgumentNullException.ThrowIfNull(client);"); - indent.AppendLine(); + indent.AppendLine(hasArg ? $"var command = contractId.{choiceName}Command(argument);" : $"var command = contractId.{choiceName}Command();"); indent.AppendLine(); - indent.AppendLine($"var submission = {context.Qualifier.Qualify(RuntimeTypeNames.CommandsSubmission, context.RootNamespace)}.Single(command)"); - indent.Indent(); - indent.AppendLine($".WithCommandId(commandId ?? new {context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}(Guid.NewGuid().ToString()));"); - indent.Dedent(); - indent.AppendLine("if (!string.IsNullOrEmpty(workflowId))"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine($"submission = submission.WithWorkflowId(new {context.Qualifier.Qualify(RuntimeTypeNames.WorkflowId, context.RootNamespace)}(workflowId));"); - indent.Dedent(); - indent.AppendLine("}"); - - indent.AppendLine(); - indent.AppendLine("var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false);"); + indent.AppendLine($"var outcome = await client.TrySubmitSingleAsync(command, {submitter.Name}, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false);"); indent.AppendLine(); indent.AppendLine($"return outcome.ProjectCommitted(tx => Project{choiceName}Result(tx, contractId.Value));"); @@ -290,6 +193,13 @@ private void WriteSingleNonContractChoiceAsyncExerciser( /// present (mirrors the cardinality semantics of upstream's /// tx.ExerciseResult<T>(choiceName)). /// + /// + /// The emitted filter keys on contract id, template id and choice name together, so a nested + /// exercise of the same choice on another contract in the same transaction is not returned by + /// mistake. The template id is compared on its module and entity names only, never the full + /// identifier, so package-id drift from an upgrade does not break projection — the same + /// drift-tolerant comparison the created-contract projector uses. + /// private void WriteExerciseProjector( IndentWriter indent, DamlChoice choice, @@ -303,12 +213,6 @@ private void WriteExerciseProjector( indent.AppendLine("{"); indent.Indent(); - // Filter on (ContractId, TemplateId by (ModuleName, EntityName), ChoiceName) - // so nested exercises of the same choice on other contracts within the same - // transaction don't get returned by mistake. TemplateId is matched on - // (ModuleName, EntityName) only — not the full Identifier — so package-id - // drift from upgrades doesn't break projection. Mirrors the same - // drift-tolerant comparison used by the created-contract projector. indent.AppendLine("foreach (var exercised in tx.ExercisedEvents)"); indent.AppendLine("{"); indent.Indent(); diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ResultStructs.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ResultStructs.cs new file mode 100644 index 0000000..bbb5bb0 --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.ResultStructs.cs @@ -0,0 +1,319 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.Intermediate.Model; + +namespace Daml.Codegen.CSharp.CodeGen; + +internal sealed partial class ChoiceEmitter +{ + /// + /// Emits the <Choice>Result nested record (typed result struct) and a + /// static FromCreatedContracts(...) projector for every choice on + /// whose return type carries one or more + /// ContractId Ts. See . + /// + /// Writer positioned at the emission point in the template's file. + /// The template whose choices are scanned for created-contract slots. + /// + /// Fully-qualified C# namespace of the emitted template. Used to global::-qualify + /// in-package template references inside the projector body so positional record + /// properties on the result type (e.g. a slot named Agreement) cannot shadow + /// the template type when looking up Agreement.TemplateId. + /// + internal void WriteChoiceResultStructs(IndentWriter indent, DamlTemplate template, string moduleNamespace) + { + foreach (var choice in template.Choices) + { + var slots = ChoiceCreatedSlots.Extract(context, resolver, mapper, choice.ReturnType); + if (slots.Count == 0) + { + continue; + } + + WriteSingleChoiceResultStruct(indent, choice, slots, moduleNamespace); + } + } + + private void WriteSingleChoiceResultStruct( + IndentWriter indent, + DamlChoice choice, + IReadOnlyList slots, + string moduleNamespace) + { + EmittedUsings.RequireAsyncExerciserNamespaces(indent); + indent.Require("System.Collections.Generic"); + + var choiceName = SanitizeIdentifier(choice.Name); + var resultName = $"{choiceName}Result"; + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Typed projection of the contracts created by the {choice.Name} choice."); + indent.AppendLine("/// One field per template the choice creates; cardinality follows the choice's"); + indent.AppendLine("/// return type (single, optional, list)."); + indent.AppendLine("/// "); + } + + indent.AppendLine($"public sealed record {resultName}("); + indent.Indent(); + for (var i = 0; i < slots.Count; i++) + { + var separator = i == slots.Count - 1 ? "" : ","; + indent.AppendLine($"{SlotPropertyType(slots[i])} {slots[i].FieldName}{separator}"); + } + indent.Dedent(); + indent.AppendLine(")"); + indent.AppendLine("{"); + indent.Indent(); + + WriteFromCreatedContractsProjector(indent, resultName, slots, moduleNamespace); + + indent.Dedent(); + indent.AppendLine("}"); + indent.AppendLine(); + } + + private string SlotPropertyType(ChoiceCreatedSlot slot) => slot.Cardinality switch + { + CreatedCardinality.Single => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>", + CreatedCardinality.Optional => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>?", + CreatedCardinality.List => $"{context.Qualifier.Qualify("IReadOnlyList", context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>>", + _ => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{slot.CSharpTemplateType}>", + }; + + /// + /// A result record's positional property carries the template's own name, so an unqualified + /// reference inside the projector body binds to the property rather than the template type. + /// In-package names are therefore prefixed with global:: and the emitted module + /// namespace. Cross-package references already arrive qualified — an embedded dot is the + /// discriminator. + /// + private static string QualifyInPackageTemplate(string templateName, string moduleNamespace) => + templateName.Contains('.', StringComparison.Ordinal) + ? templateName + : $"global::{moduleNamespace}.{templateName}"; + + /// + /// Slots that name the same template share a single bucket of created contracts, which is + /// then distributed across them in declaration order. A per-slot if/else if + /// chain cannot express that: the second slot's branch is unreachable, so a return type + /// such as (ContractId Half, ContractId Half) would leave its bucket empty and + /// project to None. + /// + /// Buckets are keyed on the qualified template reference and the interface matcher, + /// never the reference alone: a template and an interface marker can generate the same C# + /// name (template IFactory against the marker for interface Factory), and + /// merging them would match one slot's contracts on the other slot's branch. + /// + /// + /// Distribution gives each single- and optional-cardinality slot one match; list slots drain + /// whatever remains. Contracts left over after distribution are appended to the group's last + /// slot so the per-slot cardinality validator counts the full population — for a single or + /// optional slot that trips its Many branch, which is the intended outcome when the + /// ledger created more contracts than the consumer asked for. + /// + /// + private void WriteFromCreatedContractsProjector( + IndentWriter indent, + string resultName, + IReadOnlyList slots, + string moduleNamespace) + { + string Q(string templateName) => QualifyInPackageTemplate(templateName, moduleNamespace); + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Projects an upstream transaction's created contracts to a typed ."); + indent.AppendLine("/// Returns:"); + indent.AppendLine("/// "); + indent.AppendLine("/// when every expected template's cardinality matches."); + indent.AppendLine("/// when at least one required slot's template is missing from the transaction."); + indent.AppendLine("/// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one."); + indent.AppendLine("/// "); + indent.AppendLine("/// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly."); + indent.AppendLine("/// "); + } + + indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}> FromCreatedContracts(IEnumerable<{context.Qualifier.Qualify(RuntimeTypeNames.CreatedContract, context.RootNamespace)}> created)"); + indent.AppendLine("{"); + indent.Indent(); + + indent.AppendLine("ArgumentNullException.ThrowIfNull(created);"); + + var templateGroups = new List<(string TemplateRef, InterfaceMatcher? Interface, List SlotIndexes)>(); + for (var i = 0; i < slots.Count; i++) + { + var templateRef = Q(slots[i].CSharpTemplateType); + var slotInterface = slots[i].Interface; + var groupIndex = -1; + for (var g = 0; g < templateGroups.Count; g++) + { + if (string.Equals(templateGroups[g].TemplateRef, templateRef, StringComparison.Ordinal) + && templateGroups[g].Interface == slotInterface) + { + groupIndex = g; + break; + } + } + + if (groupIndex < 0) + { + templateGroups.Add((templateRef, slots[i].Interface, new List { i })); + } + else + { + templateGroups[groupIndex].SlotIndexes.Add(i); + } + } + + for (var g = 0; g < templateGroups.Count; g++) + { + indent.AppendLine($"var templateMatches{g} = new List();"); + } + + indent.AppendLine("foreach (var item in created)"); + indent.AppendLine("{"); + indent.Indent(); + for (var g = 0; g < templateGroups.Count; g++) + { + var prefix = g == 0 ? "if" : "else if"; + var group = templateGroups[g]; + if (group.Interface is not null) + { + indent.Require("System.Linq"); + indent.AppendLine($"{prefix} (item.InterfaceIds.Any(interfaceId =>"); + indent.Indent(); + indent.AppendLine($"string.Equals(interfaceId.ModuleName, {group.TemplateRef}.InterfaceId.ModuleName, StringComparison.Ordinal)"); + indent.AppendLine($"&& string.Equals(interfaceId.EntityName, {group.TemplateRef}.InterfaceId.EntityName, StringComparison.Ordinal)))"); + indent.Dedent(); + } + else + { + indent.AppendLine($"{prefix} (string.Equals(item.TemplateId.ModuleName, {group.TemplateRef}.TemplateId.ModuleName, StringComparison.Ordinal)"); + indent.Indent(); + indent.AppendLine($"&& string.Equals(item.TemplateId.EntityName, {group.TemplateRef}.TemplateId.EntityName, StringComparison.Ordinal))"); + indent.Dedent(); + } + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"templateMatches{g}.Add(item.ContractId);"); + indent.Dedent(); + indent.AppendLine("}"); + } + indent.Dedent(); + indent.AppendLine("}"); + + for (var i = 0; i < slots.Count; i++) + { + indent.AppendLine($"var matches{i} = new List();"); + } + for (var g = 0; g < templateGroups.Count; g++) + { + var slotIndexes = templateGroups[g].SlotIndexes; + indent.AppendLine($"var templateMatchIndex{g} = 0;"); + for (var k = 0; k < slotIndexes.Count; k++) + { + var slotIndex = slotIndexes[k]; + var slot = slots[slotIndex]; + switch (slot.Cardinality) + { + case CreatedCardinality.Single: + case CreatedCardinality.Optional: + indent.AppendLine($"if (templateMatchIndex{g} < templateMatches{g}.Count)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"matches{slotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); + indent.AppendLine($"templateMatchIndex{g}++;"); + indent.Dedent(); + indent.AppendLine("}"); + break; + + case CreatedCardinality.List: + indent.AppendLine($"while (templateMatchIndex{g} < templateMatches{g}.Count)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"matches{slotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); + indent.AppendLine($"templateMatchIndex{g}++;"); + indent.Dedent(); + indent.AppendLine("}"); + break; + } + } + + var lastSlotIndex = slotIndexes[^1]; + indent.AppendLine($"while (templateMatchIndex{g} < templateMatches{g}.Count)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"matches{lastSlotIndex}.Add(templateMatches{g}[templateMatchIndex{g}]);"); + indent.AppendLine($"templateMatchIndex{g}++;"); + indent.Dedent(); + indent.AppendLine("}"); + } + + for (var i = 0; i < slots.Count; i++) + { + var slot = slots[i]; + var local = $"matches{i}"; + indent.AppendLine(); + switch (slot.Cardinality) + { + case CreatedCardinality.Single: + indent.AppendLine($"if ({local}.Count == 0)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.None();"); + indent.Dedent(); + indent.AppendLine("}"); + indent.AppendLine($"if ({local}.Count > 1)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.Many({local}.Count, {local});"); + indent.Dedent(); + indent.AppendLine("}"); + break; + case CreatedCardinality.Optional: + indent.AppendLine($"if ({local}.Count > 1)"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.Many({local}.Count, {local});"); + indent.Dedent(); + indent.AppendLine("}"); + break; + case CreatedCardinality.List: + break; + } + } + + indent.AppendLine(); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseOutcome, context.RootNamespace)}<{resultName}>.One(new {resultName}("); + indent.Indent(); + for (var i = 0; i < slots.Count; i++) + { + var slot = slots[i]; + var local = $"matches{i}"; + var separator = i == slots.Count - 1 ? "" : ","; + var templateRef = Q(slot.CSharpTemplateType); + switch (slot.Cardinality) + { + case CreatedCardinality.Single: + indent.AppendLine($"{slot.FieldName}: new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>({local}[0]){separator}"); + break; + case CreatedCardinality.Optional: + indent.AppendLine($"{slot.FieldName}: {local}.Count == 1 ? new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>({local}[0]) : null{separator}"); + break; + case CreatedCardinality.List: + indent.AppendLine($"{slot.FieldName}: {local}.ConvertAll(c => new {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateRef}>(c)){separator}"); + break; + } + } + indent.Dedent(); + indent.AppendLine("));"); + + indent.Dedent(); + indent.AppendLine("}"); + indent.AppendLine(); + } +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.SubmissionParameters.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.SubmissionParameters.cs new file mode 100644 index 0000000..20cf361 --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.SubmissionParameters.cs @@ -0,0 +1,23 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +namespace Daml.Codegen.CSharp.CodeGen; + +internal sealed partial class ChoiceEmitter +{ + private static void WriteSubmissionParameterDocs(IndentWriter indent) + { + indent.AppendLine("/// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID."); + indent.AppendLine("/// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice."); + indent.AppendLine("/// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome."); + indent.AppendLine("/// Cancellation token."); + } + + private void WriteSubmissionParametersAndCloseSignature(IndentWriter indent) + { + indent.AppendLine("string? workflowId = null,"); + indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.CommandId, context.RootNamespace)}? commandId = null,"); + indent.AppendLine("TimeSpan? timeout = null,"); + indent.AppendLine("CancellationToken cancellationToken = default)"); + } +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.cs index 123fe9d..04e08b9 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceEmitter.cs @@ -1,23 +1,23 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; -using LedgerNamespaces = Daml.Ledger.Abstractions.LedgerNamespaces; +using Daml.Codegen.Intermediate.Model; using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; namespace Daml.Codegen.CSharp.CodeGen; /// -/// Emits the C# that exercises a choice: the nested <Choice>Arg -/// fallback type, the Choice<Template, Arg, Result> descriptor with its +/// Emits the C# that exercises a choice: the +/// Choice<Template, Arg, Result> descriptor with its /// result decoder, the typed <Choice>Async exercisers (both the /// contract-id-returning and the value-returning flavour), and the interface-choice /// extensions. Constructed once per package over the package's /// , the DAR-scoped , /// the package's , and the shared /// module. Calls the mapper for every type fragment and reads — but does not own — the -/// resolved choice-argument metadata. Distinct from the create/submission path: creating -/// a contract is not exercising a choice. +/// resolved choice-argument metadata; a choice argument it cannot map throws +/// instead of emitting a stub. Distinct from the +/// create/submission path: creating a contract is not exercising a choice. /// internal sealed partial class ChoiceEmitter( PackageEmitContext context, @@ -28,20 +28,24 @@ internal sealed partial class ChoiceEmitter( { /// /// Emits the choice descriptor surface nested inside the template record: the - /// <Choice>Arg fallback type and the Choice<...> property - /// (with its argument encoder and result decoder) for every choice on - /// . + /// Choice<...> property (with its argument encoder and result decoder) + /// for every choice on . /// internal void WriteChoiceDescriptors(IndentWriter indent, DamlTemplate template) { foreach (var choice in template.Choices) { - WriteChoiceArgumentType(indent, choice); WriteChoiceMethod(indent, choice); } } - internal (string TypeName, IReadOnlyList? Fields, bool IsFallback, bool IsNestedTemplateArg) GetChoiceArgumentInfo( + /// + /// Resolves the C# argument shape of : a same-package + /// record reference becomes the nested argument record, Unit and the + /// synthetic stdlib Archive become the argument-less DamlUnit shape, + /// and any other type reference resolves through the cross-package resolver. + /// + internal ChoiceArgumentInfo GetChoiceArgumentInfo( DamlChoice choice, IReadOnlyDictionary dataTypes) { @@ -50,44 +54,28 @@ internal void WriteChoiceDescriptors(IndentWriter indent, DamlTemplate template) && dataTypes.TryGetValue($"{typeRef.Module}:{typeRef.Name}", out var dataType)) { var fields = dataType.Definition is DamlRecordDefinition recordDef ? recordDef.Fields : null; - return (SanitizeIdentifier(choice.Name), fields, false, true); + return new ChoiceArgumentInfo(SanitizeIdentifier(choice.Name), fields, IsNestedTemplateArg: true); } if (choice.ArgumentType is DamlPrimitiveType { Primitive: DamlPrimitive.Unit }) { - return ("DamlUnit", null, false, false); + return new ChoiceArgumentInfo(RuntimeTypeNames.DamlUnit, Fields: null, IsNestedTemplateArg: false); } if (choice.ArgumentType is DamlTypeRef externalRef) { if (IsSyntheticArchive(choice)) { - return ("DamlUnit", null, false, false); + return new ChoiceArgumentInfo(RuntimeTypeNames.DamlUnit, Fields: null, IsNestedTemplateArg: false); } - return (resolver.Resolve(externalRef, context), null, false, false); - } - - return ($"{SanitizeIdentifier(choice.Name)}Arg", null, true, true); - } - - private void WriteChoiceArgumentType(IndentWriter indent, DamlChoice choice) - { - var (_, _, isFallback, _) = GetChoiceArgumentInfo(choice, context.DataTypes); - - if (!isFallback) - { - return; + return new ChoiceArgumentInfo(resolver.Resolve(externalRef, context), Fields: null, IsNestedTemplateArg: false); } - var choiceName = SanitizeIdentifier(choice.Name); - indent.AppendLine($"/// Arguments for the {choice.Name} choice."); - indent.AppendLine($"public sealed record {choiceName}Arg"); - indent.AppendLine("{"); - indent.Indent(); - indent.AppendLine("// TODO: Extract fields from argument type"); - indent.Dedent(); - indent.AppendLine("}"); - indent.AppendLine(); + throw new CodegenException( + $"Cannot emit choice '{choice.Name}': its argument type '{choice.ArgumentType}' does not map " + + "to an exercisable C# argument record (expected a same-package record reference, Unit, or a " + + "resolvable external type reference). Generation fails here instead of emitting an empty " + + $"'{SanitizeIdentifier(choice.Name)}Arg' stub record into generated code."); } private void WriteChoiceMethod(IndentWriter indent, DamlChoice choice) @@ -95,15 +83,10 @@ private void WriteChoiceMethod(IndentWriter indent, DamlChoice choice) var dataTypes = context.DataTypes; var choiceName = SanitizeIdentifier(choice.Name); var returnType = mapper.MapType(choice.ReturnType); - var (argTypeName, _, isFallback, _) = GetChoiceArgumentInfo(choice, dataTypes); - - if (isFallback) - { - return; - } + var argument = GetChoiceArgumentInfo(choice, dataTypes); indent.Require(RuntimeNamespaces.Commands); - StdlibPackages.RequireForFieldType(resolver, indent, choice.ReturnType); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, choice.ReturnType); indent.AppendLine("/// "); indent.AppendLine($"/// Exercise the {choice.Name} choice."); @@ -113,22 +96,22 @@ private void WriteChoiceMethod(IndentWriter indent, DamlChoice choice) } indent.AppendLine("/// "); - var argTypeRef = argTypeName == "DamlUnit" - ? context.Qualifier.Qualify(RuntimeTypeNames.DamlUnit, context.RootNamespace) - : argTypeName; + var argTypeRef = argument.HasArgument + ? argument.TypeName + : context.Qualifier.Qualify(RuntimeTypeNames.DamlUnit, context.RootNamespace); indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.Choice, context.RootNamespace)}<{indent.CurrentTypeName}, {argTypeRef}, {returnType}> Choice{choiceName} {{ get; }} = new()"); indent.AppendLine("{"); indent.Indent(); indent.AppendLine($"Name = new {context.Qualifier.Qualify(RuntimeTypeNames.ChoiceName, context.RootNamespace)}(\"{choice.Name}\"),"); indent.AppendLine($"Consuming = {(choice.Consuming ? "true" : "false")},"); - if (argTypeName == "DamlUnit") + if (argument.HasArgument) { - indent.AppendLine($"ArgumentEncoder = _ => {EmptyArgumentExpression(choice)},"); + indent.AppendLine("ArgumentEncoder = arg => arg.ToRecord(),"); } else { - indent.AppendLine("ArgumentEncoder = arg => arg.ToRecord(),"); + indent.AppendLine($"ArgumentEncoder = _ => {EmptyArgumentExpression(choice)},"); } WriteResultDecoder(indent, choice.ReturnType, returnType); @@ -138,17 +121,17 @@ private void WriteChoiceMethod(IndentWriter indent, DamlChoice choice) indent.AppendLine(); } + /// + /// Only Unit and the primitive ContractId form keep a hand-written short form, + /// where the call site reads better than the helper's output. Everything else — type refs for + /// records, variants and enums included — delegates to the shared from-value conversion, so + /// the decoder inherits the same module-qualified enum dispatch and map, optional and list + /// handling that field deserialization uses. A hand-rolled enum check here matched on the + /// simple name and would route an enum return through a record cast whenever a same-named + /// record existed in another module of the same package. + /// private void WriteResultDecoder(IndentWriter indent, DamlType returnType, string csharpReturnType) { - // Keep the canonical short forms for trivial cases (Unit and the primitive - // ContractId form) where the call-site reads more naturally than the helper's - // output. Every other case — including type-refs (record/variant/enum) — - // delegates to GetFromValueConversion so the result decoder picks up the same - // module-qualified enum dispatch and TextMap/GenMap/Optional/List handling - // that field deserialization uses. Earlier hand-rolled paths here used a - // simple-name enum check that diverged from the module-qualified version, - // and would silently route an enum return through DamlRecord.As<>() when a - // same-named record existed in another module of the same package. switch (returnType) { case DamlPrimitiveType { Primitive: DamlPrimitive.Unit }: @@ -164,16 +147,10 @@ private void WriteResultDecoder(IndentWriter indent, DamlType returnType, string indent.AppendLine($"ResultDecoder = val => {expr}"); } - private static void RequireAsyncExerciserNamespaces(IndentWriter indent) - { - indent.Require("System"); - indent.Require("System.Threading"); - indent.Require("System.Threading.Tasks"); - indent.Require(LedgerNamespaces.Abstractions); - indent.Require(RuntimeNamespaces.Commands); - indent.Require(RuntimeNamespaces.Contracts); - indent.Require(RuntimeNamespaces.Outcomes); - } + private ChoiceSubmitterParameter SubmitterInfoParameter() => new( + context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace), + "submitter", + "The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible."); /// /// True for the built-in stdlib DA.Internal.Template:Archive choice, whose @@ -194,6 +171,188 @@ private string EmptyArgumentExpression(DamlChoice choice) => ? $"{context.Qualifier.Qualify(RuntimeTypeNames.DamlRecord, context.RootNamespace)}.Create()" : $"{context.Qualifier.Qualify(RuntimeTypeNames.DamlUnit, context.RootNamespace)}.Instance"; + /// + /// Emits the <Choice>Command(this ContractId<TemplateName> contractId, ...) + /// builder that constructs the choice's + /// without submitting it. The single command builder shared by every generated + /// <Choice>Async exerciser — the create-projecting ContractId overloads (see + /// WriteSingleChoiceAsyncExerciser / WriteSubmitterInfoChoiceAsyncExerciser) and the + /// non-contract exerciser (see WriteSingleNonContractChoiceAsyncExerciser) alike — they + /// exercise the identical choice on the identical ContractId<T> type and therefore + /// build the identical command. Argument-less choices encode via + /// : the synthetic stdlib Archive argument becomes the + /// empty record Canton's command preprocessor accepts, a genuine Unit argument stays + /// DamlUnit.Instance. + /// + private void WriteChoiceCommandBuilder( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + IReadOnlyDictionary dataTypes) + { + var choiceName = SanitizeIdentifier(choice.Name); + var commandMethodName = $"{choiceName}Command"; + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Builds the for the {choice.Name} choice on this contract id."); + indent.AppendLine("/// "); + indent.AppendLine("/// The contract on which to exercise the choice."); + if (hasArg) + { + indent.AppendLine("/// The choice argument."); + } + } + + indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)} {commandMethodName}("); + indent.Indent(); + if (hasArg) + { + indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId,"); + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument)"); + } + else + { + indent.AppendLine($"this {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{templateClassName}> contractId)"); + } + indent.Dedent(); + indent.AppendLine("{"); + indent.Indent(); + + indent.AppendLine("ArgumentNullException.ThrowIfNull(contractId);"); + if (hasArg) + { + indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); + } + + var argExpr = hasArg ? "argument.ToRecord()" : EmptyArgumentExpression(choice); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseCommand, context.RootNamespace)}("); + indent.Indent(); + indent.AppendLine($"{templateClassName}.TemplateId,"); + indent.AppendLine("contractId,"); + indent.AppendLine($"new {context.Qualifier.Qualify(RuntimeTypeNames.ChoiceName, context.RootNamespace)}(\"{choice.Name}\"),"); + indent.AppendLine($"{argExpr});"); + indent.Dedent(); + + indent.Dedent(); + indent.AppendLine("}"); + } + + /// + /// Emits one <Choice>ByKeyCommand builder per choice on + /// , into the template record body. A key-less template + /// emits nothing. + /// + internal void WriteChoiceByKeyCommandBuilders( + IndentWriter indent, + DamlTemplate template, + string templateClassName, + IReadOnlyDictionary dataTypes) + { + if (template.Key is null) + { + return; + } + + foreach (var choice in template.Choices) + { + WriteChoiceByKeyCommandBuilder(indent, choice, templateClassName, template.Key, dataTypes); + indent.AppendLine(); + } + } + + /// + /// Emits the <Choice>ByKeyCommand(TKey key, ...) builder that constructs the + /// choice's without + /// submitting it, the key-addressed twin of . + /// Emitted into the template record itself rather than the sibling extensions classes, and + /// as a plain static rather than an extension method: the key's C# type is whatever the Daml + /// key type maps to — string and Party included — and extending those would put + /// the method on every value of that type in the consuming project. + /// + private void WriteChoiceByKeyCommandBuilder( + IndentWriter indent, + DamlChoice choice, + string templateClassName, + DamlType keyType, + IReadOnlyDictionary dataTypes) + { + var choiceName = SanitizeIdentifier(choice.Name); + var argument = GetChoiceArgumentInfo(choice, dataTypes); + var hasArg = argument.HasArgument; + var csharpKeyType = PackageQualifiedMapper.MapType(keyType); + + indent.Require(RuntimeNamespaces.Commands); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, keyType); + + if (options.GenerateXmlDocs) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Builds the for the {choice.Name} choice on the contract carrying this key."); + indent.AppendLine("/// "); + indent.AppendLine("/// The contract key to exercise the choice against."); + if (hasArg) + { + indent.AppendLine("/// The choice argument."); + } + indent.AppendLine("/// "); + indent.AppendLine("/// Contract keys are not unique: several active contracts may carry the same key, and"); + indent.AppendLine("/// the ledger resolves this command against a first match by an order it only partly"); + indent.AppendLine("/// guarantees. Keeping a key unique is the application's responsibility."); + indent.AppendLine("/// "); + } + + indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseByKeyCommand, context.RootNamespace)} {choiceName}ByKeyCommand("); + indent.Indent(); + if (hasArg) + { + indent.AppendLine($"{csharpKeyType} key,"); + indent.AppendLine($"{argument.ParameterType(templateClassName)} argument)"); + } + else + { + indent.AppendLine($"{csharpKeyType} key)"); + } + indent.Dedent(); + indent.AppendLine("{"); + indent.Indent(); + + if (PackageQualifiedMapper.MapsToReferenceType(keyType)) + { + indent.AppendLine("ArgumentNullException.ThrowIfNull(key);"); + } + if (hasArg) + { + indent.AppendLine("ArgumentNullException.ThrowIfNull(argument);"); + } + + var argExpr = hasArg ? "argument.ToRecord()" : EmptyArgumentExpression(choice); + indent.AppendLine($"return new {context.Qualifier.Qualify(RuntimeTypeNames.ExerciseByKeyCommand, context.RootNamespace)}("); + indent.Indent(); + indent.AppendLine($"{templateClassName}.TemplateId,"); + indent.AppendLine($"{PackageQualifiedMapper.ToValue(keyType, "key")},"); + indent.AppendLine($"new {context.Qualifier.Qualify(RuntimeTypeNames.ChoiceName, context.RootNamespace)}(\"{choice.Name}\"),"); + indent.AppendLine($"{argExpr});"); + indent.Dedent(); + + indent.Dedent(); + indent.AppendLine("}"); + } + + /// + /// Maps the contract-key slot. The template record nests Contract / ContractId + /// records and one argument record per choice, any of which binds ahead of a package type the + /// key names, so every in-package name in the key slot is resolved global::-qualified — + /// the same treatment the active contract's Key member gets. + /// + private DamlTypeMapper PackageQualifiedMapper => + _packageQualifiedMapper ??= new DamlTypeMapper(context, new PackageQualifiedResolver(resolver)); + + private DamlTypeMapper? _packageQualifiedMapper; + private static bool IsStdlibPackage(string packageName) => StdlibPackages.IsStdlibPackage(packageName); private static bool IsPlaceholderPackageName(string packageName) => StdlibPackages.IsPlaceholderPackageName(packageName); diff --git a/src/Daml.Codegen.CSharp/CodeGen/ChoiceSubmitterParameter.cs b/src/Daml.Codegen.CSharp/CodeGen/ChoiceSubmitterParameter.cs new file mode 100644 index 0000000..c76aefe --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/ChoiceSubmitterParameter.cs @@ -0,0 +1,19 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +namespace Daml.Codegen.CSharp.CodeGen; + +/// +/// The submitter parameter an emitted <Choice>Async method carries: the +/// SubmitterInfo shape, which expresses both actAs and readAs, so a +/// submitter that must read contracts it does not act as stays expressible through the +/// generated surface instead of dropping to a hand-built ExerciseCommand. A single +/// Party reaches the same method through the implicit conversion. +/// +/// The qualified C# type of the parameter. +/// The parameter name, reused as its <param> doc tag. +/// The <param> doc text. +internal sealed record ChoiceSubmitterParameter( + string TypeName, + string Name, + string DocSummary); diff --git a/src/Daml.Codegen.CSharp/CodeGen/CodeGenOptions.cs b/src/Daml.Codegen.CSharp/CodeGen/CodeGenOptions.cs index d4d3c8c..513f624 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/CodeGenOptions.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/CodeGenOptions.cs @@ -106,6 +106,22 @@ public sealed class CodeGenOptions /// public string? VersionSuffix { get; init; } + /// + /// Gets or sets whether this run also produces the NuGet packages for the other + /// DAR packages the generated code references. When true, each such + /// reference is pinned to the exact version this run produces for it — the same + /// and as the main + /// package — which is correct only for a release run that publishes the whole + /// family together under one ordinal. When false (the default), the + /// generation ordinal and prerelease tag a dependency was published with are + /// facts about that publication and are not recoverable from the DAR, so the + /// reference instead floats over every generation and prerelease published + /// against the dependency's intrinsic Major.Minor.Patch. On the CLI this + /// is set by --release-counters, the flag the release pipeline uses to + /// key one generation ordinal across the whole family. + /// + public bool PublishesReferencedPackages { get; init; } + /// /// Gets or sets the repository URL emitted in the generated .csproj's /// <PackageProjectUrl>, <RepositoryUrl>, and diff --git a/src/Daml.Codegen.CSharp/CodeGen/DamlTypeMapper.cs b/src/Daml.Codegen.CSharp/CodeGen/DamlTypeMapper.cs index f6b2c14..799c86f 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/DamlTypeMapper.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/DamlTypeMapper.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -18,7 +18,8 @@ internal sealed class DamlTypeMapper(PackageEmitContext context, ICrossPackageRe private const int MaxTypeDepth = 256; /// Maps to its C# type name. - public string MapType(DamlType type) => MapType(type, depth: 0); + public string MapType(DamlType type) => + MapType(OptionalRepresentation.Rewrite(type, context.Package, resolver), depth: 0); private string MapType(DamlType type, int depth) { @@ -30,11 +31,10 @@ private string MapType(DamlType type, int depth) DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Numeric } } => "decimal", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.ContractId }, Arguments: [var arg] } => $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{MapType(arg, depth + 1)}>", - DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, - Arguments: [DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } }] } => - throw new NotSupportedException("Codegen does not support nested Optional types (Optional (Optional t)). C# nullable syntax cannot represent the Some Nothing / Nothing distinction without a wrapper type. Refactor the Daml signature, or open a feature request to introduce a representable CLR model."), DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var arg] } => $"{MapType(arg, depth + 1)}?", + DamlWrappedOptional wrapped => + $"{context.Qualifier.Qualify(RuntimeTypeNames.Optional, context.RootNamespace)}<{MapType(wrapped.Argument, depth + 1)}>", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List }, Arguments: [var arg] } => $"{context.Qualifier.Qualify("IReadOnlyList", context.RootNamespace)}<{MapType(arg, depth + 1)}>", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.TextMap }, Arguments: [var arg] } => @@ -63,8 +63,16 @@ private string MapType(DamlType type, int depth) /// stub. null outside a generic body. /// public string ToValue(DamlType type, string fieldName, IReadOnlyDictionary? typeVarDelegates = null) => - ToValue(type, fieldName, typeVarDelegates, depth: 0); - + ToValue(OptionalRepresentation.Rewrite(type, context.Package, resolver), fieldName, typeVarDelegates, depth: 0); + + /// + /// The optional arm strips a leading @ from the field name when deriving its local + /// variable. Identifier sanitization escapes a field whose name is a C# keyword — lock, + /// class, event — by prepending @, which is legal on a property but not + /// on the local bound by the is { } __name pattern, so an Optional field called + /// lock would emit the unparsable __@lock. Only the local is stripped; the + /// property reference keeps its escape so the record property stays addressable. + /// private string ToValue(DamlType type, string fieldName, IReadOnlyDictionary? typeVarDelegates, int depth) { ThrowIfTooDeep(depth, nameof(ToValue)); @@ -76,16 +84,10 @@ private string ToValue(DamlType type, string fieldName, IReadOnlyDictionary $"{fieldName}.ToDamlValue()", - // The TrimStart('@') here protects against an Optional field whose name is a - // C# keyword (e.g. `lock`, `class`, `event`). Identifiers.Sanitize escapes those - // by prepending '@', which is valid as a property name but invalid as the - // local-variable name produced by `is { } __` below. Without the trim, - // a Daml `Optional ` field called `lock` produces `__@lock`, which - // does not parse. The trim is local-variable-only — the property reference - // (`{fieldName}`) keeps its `@` escape so the original record property is - // still addressable. DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } } app => $"{fieldName} is {{ }} __{fieldName.TrimStart('@')} ? new {context.Qualifier.Qualify(RuntimeTypeNames.DamlOptional, context.RootNamespace)}({ToValue(app.Arguments[0], $"__{fieldName.TrimStart('@')}", typeVarDelegates, depth + 1)}) : {context.Qualifier.Qualify(RuntimeTypeNames.DamlOptional, context.RootNamespace)}.None", + DamlWrappedOptional wrapped => + $"{fieldName}.{WrappedOptionalSerializer(wrapped.Encoding)}(__optional{depth} => {ToValue(wrapped.Argument, $"__optional{depth}", typeVarDelegates, depth + 1)})", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List } } app => $"new {context.Qualifier.Qualify(RuntimeTypeNames.DamlList, context.RootNamespace)}({fieldName}.Select(x => ({context.Qualifier.Qualify(RuntimeTypeNames.DamlValue, context.RootNamespace)}){ToValue(app.Arguments[0], "x", typeVarDelegates, depth + 1)}).ToList())", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.TextMap } } app => @@ -116,8 +118,38 @@ _ when MapsToFallbackObject(type, depth) => FallbackToValueStub(fieldName), private string FallbackToValueStub(string fieldName) => $"{context.Qualifier.Qualify(RuntimeTypeNames.GenericStub, context.RootNamespace)}.NotImplemented<{context.Qualifier.Qualify(RuntimeTypeNames.DamlValue, context.RootNamespace)}>(\"{fieldName}\")"; + private string FallbackFromValueStub(string valueName) => + $"{context.Qualifier.Qualify(RuntimeTypeNames.GenericStub, context.RootNamespace)}.NotImplemented<{FallbackTypeName}>(\"{valueName.Replace("\"", "\\\"", StringComparison.Ordinal)}\")"; + private bool MapsToFallbackObject(DamlType type, int depth = 0) => MapType(type, depth) == FallbackTypeName; + /// + /// True when renders as a C# + /// reference type, so a parameter of that type can carry an + /// ArgumentNullException.ThrowIfNull guard. Answers false for anything it + /// cannot place with certainty, which costs a missing guard rather than a boxed + /// value-type argument or a guard on an already-nullable parameter. + /// + /// + /// Answers over the same representation pre-pass runs, so an + /// Optional the pre-pass moves off C# nullable syntax and onto the wrapper is placed as the + /// non-nullable reference type it becomes rather than as the nullable one it would have been. + /// + public bool MapsToReferenceType(DamlType type) => + MapsToRewrittenReferenceType(OptionalRepresentation.Rewrite(type, context.Package, resolver)); + + private bool MapsToRewrittenReferenceType(DamlType type) => type switch + { + DamlPrimitiveType { Primitive: DamlPrimitive.Text } => true, + DamlPrimitiveType => false, + DamlWrappedOptional => true, + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Numeric or DamlPrimitive.Optional } } => false, + DamlTypeApp { Base: DamlPrimitiveType } => true, + DamlTypeApp { Base: DamlTypeRef typeRef } => !IsEnumTypeRef(typeRef), + DamlTypeRef typeRef => !IsEnumTypeRef(typeRef), + _ => false, + }; + /// Produces the expression that deserializes back into . /// The Daml type to reconstruct. /// The C# expression referencing the Daml value. @@ -128,7 +160,7 @@ private string FallbackToValueStub(string fieldName) => /// stub. null outside a generic body. /// public string FromValue(DamlType type, string valueName, IReadOnlyDictionary? typeVarDelegates = null) => - FromValue(type, valueName, typeVarDelegates, depth: 0); + FromValue(OptionalRepresentation.Rewrite(type, context.Package, resolver), valueName, typeVarDelegates, depth: 0); private string FromValue(DamlType type, string valueName, IReadOnlyDictionary? typeVarDelegates, int depth) { @@ -141,6 +173,8 @@ private string FromValue(DamlType type, string valueName, IReadOnlyDictionary().Value", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var arg] } => $"{valueName}.AsOptional().HasValue ? {FromValue(arg, $"{valueName}.AsOptional().Value!", typeVarDelegates, depth + 1)} : null", + DamlWrappedOptional wrapped => + $"{context.Qualifier.Qualify(RuntimeTypeNames.Optional, context.RootNamespace)}<{MapType(wrapped.Argument, depth + 1)}>.{WrappedOptionalDeserializer(wrapped.Encoding)}({valueName}, __optional{depth} => {FromValue(wrapped.Argument, $"__optional{depth}", typeVarDelegates, depth + 1)})", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List }, Arguments: [var arg] } => $"({context.Qualifier.Qualify("IReadOnlyList", context.RootNamespace)}<{MapType(arg, depth + 1)}>){valueName}.As<{context.Qualifier.Qualify(RuntimeTypeNames.DamlList, context.RootNamespace)}>().Values.Select(x => {FromValue(arg, "x", typeVarDelegates, depth + 1)}).ToList()", DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.TextMap }, Arguments: [var arg] } => @@ -164,7 +198,11 @@ DamlTypeRef typeRef when IsVariantTypeRef(typeRef) => DamlTypeVar typeVar when TryResolveDelegate(typeVarDelegates, typeVar, out var convert) => $"{convert}({valueName})", DamlTypeVar typeVar => $"{context.Qualifier.Qualify(RuntimeTypeNames.GenericStub, context.RootNamespace)}.NotImplemented<{EmitterHelpers.TypeParameterName(typeVar.Name)}>(\"{typeVar.Name}\")", - _ => $"default({MapType(type, depth)})! /* TODO: Implement deserialization for {type} */" + _ when MapsToFallbackObject(type, depth) => FallbackFromValueStub(valueName), + _ => throw new CodegenException( + $"Cannot emit a deserialization expression for Daml type '{type}'. " + + "The C# code generator does not support this type shape, so generation fails " + + "here instead of emitting a silent 'default!' fallback into generated code.") }; } @@ -183,10 +221,34 @@ private static bool TryResolveDelegate( return false; } - // CS8524 disabled (not CS8509): the no-default switch covers every named - // DamlPrimitive so an out-of-range cast is the only uncovered input. CS8509 - // (a newly-added named member left unhandled) stays an error — that is the - // compiler-enforced checklist for adding a Daml primitive. + /// + /// A switch, not a ternary, for the reason given on : a + /// ternary would emit a newly added in the wrong wire form, in + /// code that still compiles. Only CS8524 is suppressed. + /// +#pragma warning disable CS8524 + private static string WrappedOptionalSerializer(OptionalEncoding encoding) => encoding switch + { + OptionalEncoding.Flat => "ToValue", + OptionalEncoding.NestedChain => "ToChainValue", + }; +#pragma warning restore CS8524 + + /// Suppresses CS8524 for the reason given on . +#pragma warning disable CS8524 + private static string WrappedOptionalDeserializer(OptionalEncoding encoding) => encoding switch + { + OptionalEncoding.Flat => "FromValue", + OptionalEncoding.NestedChain => "FromChainValue", + }; +#pragma warning restore CS8524 + + /// + /// Only CS8524 is suppressed, never CS8509. The switch has no default arm and covers every + /// named , so the sole uncovered input is an out-of-range cast. + /// CS8509 — a newly added named member left unhandled — stays an error, because that warning + /// is the compiler-enforced checklist for adding a Daml primitive. + /// #pragma warning disable CS8524 private string MapBarePrimitiveToCSharp(DamlPrimitive primitive) => primitive switch { @@ -208,7 +270,7 @@ or DamlPrimitive.TextMap }; #pragma warning restore CS8524 - // See MapBarePrimitiveToCSharp for the CS8524-vs-CS8509 rationale. + /// Suppresses CS8524 for the reason given on . #pragma warning disable CS8524 private string GetBarePrimitiveToValueConversion(DamlPrimitive primitive, string fieldName) => primitive switch { @@ -230,7 +292,7 @@ or DamlPrimitive.TextMap }; #pragma warning restore CS8524 - // See MapBarePrimitiveToCSharp for the CS8524-vs-CS8509 rationale. + /// Suppresses CS8524 for the reason given on . #pragma warning disable CS8524 private string GetBarePrimitiveFromValueConversion(DamlPrimitive primitive, string valueName) => primitive switch { diff --git a/src/Daml.Codegen.CSharp/CodeGen/DarCrossPackageResolver.cs b/src/Daml.Codegen.CSharp/CodeGen/DarCrossPackageResolver.cs index ff22186..7309ead 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/DarCrossPackageResolver.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/DarCrossPackageResolver.cs @@ -1,7 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Daml.Codegen.CSharp.CodeGen; @@ -11,10 +13,10 @@ namespace Daml.Codegen.CSharp.CodeGen; /// external-package-id set are DAR-scoped — they live for the resolver's lifetime, /// not per package. /// -internal sealed class DarCrossPackageResolver : ICrossPackageResolver +internal sealed partial class DarCrossPackageResolver : ICrossPackageResolver { private readonly IDarSource _dar; - private readonly ICodegenLogger _logger; + private readonly ILogger _logger; private readonly HashSet _discoveredExternalPackageIds = []; private readonly Dictionary> _foreignChoiceArgCache = []; private readonly Dictionary> _foreignInterfaceCache = []; @@ -22,12 +24,13 @@ internal sealed class DarCrossPackageResolver : ICrossPackageResolver private readonly Dictionary> _foreignInterfaceMarkerNameCache = []; /// Creates a resolver scoped to a single . - public DarCrossPackageResolver(IDarSource dar, ICodegenLogger logger) + /// The archive type refs are resolved against. + /// Where cross-package warnings go; omit it and the resolver stays silent. + public DarCrossPackageResolver(IDarSource dar, ILogger? logger = null) { ArgumentNullException.ThrowIfNull(dar); - ArgumentNullException.ThrowIfNull(logger); _dar = dar; - _logger = logger; + _logger = logger ?? NullLogger.Instance; } /// @@ -46,7 +49,7 @@ public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) if (context.IsLocalRef(typeRef)) { - if (context.InterfacePlaceholderQualifiedNames.Contains($"{typeRef.Module}:{typeRef.Name}")) + if (context.LocalInterfaceQualifiedNames.Contains($"{typeRef.Module}:{typeRef.Name}")) { return context.LocalInterfaceMarkerNames[$"{typeRef.Module}:{typeRef.Name}"]; } @@ -71,7 +74,7 @@ public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) { return context.Qualifier.Qualify(mapped, context.RootNamespace); } - _logger.Warning($"Unmapped stdlib type {foreignPkg.Name}:{typeRef.Module}:{typeRef.Name} — generated code will not compile (no stdlib mapping for this type yet)"); + LogUnmappedStdlibType(_logger, foreignPkg.Name, typeRef.Module, typeRef.Name); return sanitized; } @@ -166,8 +169,7 @@ private IReadOnlyDictionary BuildForeignChoiceArgToTemplate(Daml if (result.TryGetValue(key, out var existingTemplate) && existingTemplate != template.Name) { - _logger.Warning( - $"Choice-argument type {key} in package {pkg.Name} is used by both templates {existingTemplate} and {template.Name} in the same package; keeping {existingTemplate} and ignoring {template.Name}. Rename one choice-argument type to disambiguate."); + LogAmbiguousForeignChoiceArgument(_logger, key, pkg.Name, existingTemplate, template.Name); continue; } result[key] = template.Name; @@ -177,4 +179,16 @@ private IReadOnlyDictionary BuildForeignChoiceArgToTemplate(Daml } return result; } + + [LoggerMessage( + EventId = 1100, + Level = LogLevel.Warning, + Message = "Unmapped stdlib type {PackageName}:{ModuleName}:{TypeName} \u2014 generated code will not compile (no stdlib mapping for this type yet)")] + private static partial void LogUnmappedStdlibType(ILogger logger, string packageName, string moduleName, string typeName); + + [LoggerMessage( + EventId = 1101, + Level = LogLevel.Warning, + Message = "Choice-argument type {ChoiceArgumentKey} in package {PackageName} is used by both templates {KeptTemplate} and {IgnoredTemplate} in the same package; keeping {KeptTemplate} and ignoring {IgnoredTemplate}. Rename one choice-argument type to disambiguate.")] + private static partial void LogAmbiguousForeignChoiceArgument(ILogger logger, string choiceArgumentKey, string packageName, string keptTemplate, string ignoredTemplate); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/EmittedUsings.cs b/src/Daml.Codegen.CSharp/CodeGen/EmittedUsings.cs new file mode 100644 index 0000000..2e65444 --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/EmittedUsings.cs @@ -0,0 +1,35 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using LedgerNamespaces = Daml.Ledger.Abstractions.LedgerNamespaces; +using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; + +namespace Daml.Codegen.CSharp.CodeGen; + +/// +/// The namespaces the emitted async ledger surfaces need in their file-level +/// using block. covers the +/// surfaces that call members +/// directly; adds the namespace hosting +/// the TrySubmitSingleAsync extension method the emitted choice exercisers call +/// on top of it. +/// +internal static class EmittedUsings +{ + internal static void RequireAsyncLedgerCallNamespaces(IndentWriter indent) + { + indent.Require("System"); + indent.Require("System.Threading"); + indent.Require("System.Threading.Tasks"); + indent.Require(LedgerNamespaces.Abstractions); + indent.Require(RuntimeNamespaces.Commands); + indent.Require(RuntimeNamespaces.Contracts); + indent.Require(RuntimeNamespaces.Outcomes); + } + + internal static void RequireAsyncExerciserNamespaces(IndentWriter indent) + { + RequireAsyncLedgerCallNamespaces(indent); + indent.Require(LedgerNamespaces.AbstractionsExtensions); + } +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/EmitterHelpers.cs b/src/Daml.Codegen.CSharp/CodeGen/EmitterHelpers.cs index 270cb6e..a0f1b12 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/EmitterHelpers.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/EmitterHelpers.cs @@ -1,6 +1,8 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Codegen.Intermediate.Model; + namespace Daml.Codegen.CSharp.CodeGen; /// @@ -35,6 +37,25 @@ internal static string GetTypeParametersDeclaration(IReadOnlyList typePa return $"<{string.Join(", ", typeParams.Select(TypeParameterName))}>"; } + /// + /// Builds the where T : notnull clauses trailing a generic record's or variant's + /// declaration; empty for a non-generic type. A Daml type variable ranges only over + /// serialisable Daml types, none of which is nullable — the nullable positions are exactly + /// the ones Daml spells Optional, which renders as an + /// explicit ? or as a wrapper rather than as a bare type variable. Left + /// unconstrained, a field typed by the type variable reports a nullable read-state through + /// at every reference-type + /// instantiation, so a reflection-driven reader decodes it as an Optional the wire + /// never carried. + /// + internal static string GetTypeParameterConstraints(IReadOnlyList typeParams) + { + if (typeParams.Count == 0) + return string.Empty; + + return string.Concat(typeParams.Select(param => $" where {TypeParameterName(param)} : notnull")); + } + /// /// Derives the injected converter-delegate parameter name for a Daml type /// variable — the delegate a generic record/variant's ToRecord/FromRecord @@ -47,7 +68,7 @@ internal static string ConverterParameterName(string damlTypeParam) => /// /// Maps each Daml type-variable name to its , /// threaded into the 's ToValue/FromValue - /// so a field resolves to its injected converter. + /// so a field resolves to its injected converter. /// internal static IReadOnlyDictionary ConverterNameMap(IReadOnlyList typeParams) => typeParams.ToDictionary(param => param, ConverterParameterName); diff --git a/src/Daml.Codegen.CSharp/CodeGen/EnumEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/EnumEmitter.cs index 8f9ae90..13215f9 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/EnumEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/EnumEmitter.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; diff --git a/src/Daml.Codegen.CSharp/CodeGen/ICrossPackageResolver.cs b/src/Daml.Codegen.CSharp/CodeGen/ICrossPackageResolver.cs index 3da1e6a..4f871f3 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ICrossPackageResolver.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ICrossPackageResolver.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; diff --git a/src/Daml.Codegen.CSharp/CodeGen/Identifiers.cs b/src/Daml.Codegen.CSharp/CodeGen/Identifiers.cs index 6800423..5577e5b 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/Identifiers.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/Identifiers.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Text; namespace Daml.Codegen.CSharp.CodeGen; @@ -139,7 +140,7 @@ private static bool BeginsWithHexEscapeBody(string encodedTail) => && IsLowerHexDigit(encodedTail[3]) && IsLowerHexDigit(encodedTail[4]); - private static string HexEscape(char c) => "_u" + ((int)c).ToString("x4"); + private static string HexEscape(char c) => "_u" + ((int)c).ToString("x4", CultureInfo.InvariantCulture); private static bool IsLowerHexDigit(char c) => c is >= '0' and <= '9' or >= 'a' and <= 'f'; @@ -203,14 +204,23 @@ public static string ToPascalCase(string name) } /// - /// Derives the C# member identifier for a Daml field, PascalCasing and - /// sanitising the Daml name and disambiguating with a trailing _ when - /// the result would equal (illegal in - /// C#: CS0542 member names cannot be the same as their enclosing type). The - /// Daml wire name is unaffected — only the emitted C# identifier changes. + /// Derives the C# member identifier for a Daml field: leaf-sanitised + /// (), PascalCased, disambiguated with a trailing + /// _ when the result would equal + /// (illegal in C#: CS0542 member names cannot be the same as their enclosing + /// type), and keyword-escaped last. The order is load-bearing: escaping first + /// leaves spending its capitalisation on the + /// @ rather than on the letter behind it, so Daml lock would + /// emit a lowercase @lock among PascalCased siblings. Escaping last, the + /// escape is in fact unreachable — never returns a + /// lowercase-initial name and every entry of the C# keyword set is lowercase — + /// but it is composed anyway so the identifier stays legal if the casing step + /// changes. A leaf Daml name never contains a ., so + /// covers everything would. + /// The Daml wire name is unaffected — only the emitted C# identifier changes. /// internal static string MemberName(string damlFieldName, string enclosingTypeName) => - Disambiguate(ToPascalCase(Sanitize(damlFieldName)), enclosingTypeName); + EscapeKeyword(Disambiguate(ToPascalCase(SanitizeBare(damlFieldName)), enclosingTypeName)); /// /// Builds the C# marker-interface name for a Daml interface: the sanitised @@ -221,7 +231,12 @@ internal static string MemberName(string damlFieldName, string enclosingTypeName /// package (template, record, enum, or variant) can legally sanitise to an /// interface marker (e.g. record IFactory alongside interface /// Factory), which would otherwise emit two public IFactory - /// declarations in the same namespace (CS0101). Shared by the interface emitter + /// declarations in the same namespace (CS0101). The name is built from + /// with applied last: + /// escaping first would emit I@event, which parses as two identifiers + /// rather than one. The escape is unreachable — the I prefix leaves an + /// uppercase-initial name and every C# keyword is lowercase — and is composed + /// only so the prefix is not the sole guarantee. Shared by the interface emitter /// and the type resolver so a reference to an interface names the same marker on /// the field-type path as on the choice-exercise path — callers must pass the /// declaring package's widened top-level type-name set @@ -237,12 +252,12 @@ internal static string MemberName(string damlFieldName, string enclosingTypeName /// internal static string InterfaceMarkerName(string interfaceName, IReadOnlySet reservedTypeNames) { - var marker = "I" + Sanitize(interfaceName); + var marker = "I" + SanitizeBare(interfaceName); while (reservedTypeNames.Contains(marker)) { marker += "_"; } - return marker; + return EscapeKeyword(marker); } /// diff --git a/src/Daml.Codegen.CSharp/CodeGen/IndentWriter.cs b/src/Daml.Codegen.CSharp/CodeGen/IndentWriter.cs index a6f4287..ec9531a 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/IndentWriter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/IndentWriter.cs @@ -47,13 +47,14 @@ private void WriteIndentIfAtLineStart() _atLineStart = false; } - // Emit LF explicitly rather than via `StringBuilder.AppendLine` (which uses - // `Environment.NewLine`). Generated source is published in NuGet packages - // and compared by `DriftDetectionTests` byte-for-byte; OS-dependent line - // endings would make a Windows codegen run produce different bytes from a - // macOS/Linux one for no behaviour change. LF is the conventional choice - // for cross-platform source distribution and matches what `.editorconfig` - // pins repo-wide. + /// + /// Line endings are written explicitly rather than through StringBuilder.AppendLine, + /// which uses . Generated source ships in NuGet packages and + /// is compared byte for byte by the drift tests, so an OS-dependent newline would make a + /// Windows codegen run differ from a macOS or Linux one for no change in behaviour. LF is the + /// conventional choice for cross-platform source distribution and matches what + /// .editorconfig pins repo-wide. + /// private const char Newline = '\n'; public void AppendLine(string? line = null) diff --git a/src/Daml.Codegen.CSharp/CodeGen/InterfaceEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/InterfaceEmitter.cs index 43b9f9a..31129f1 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/InterfaceEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/InterfaceEmitter.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; namespace Daml.Codegen.CSharp.CodeGen; @@ -10,42 +10,58 @@ namespace Daml.Codegen.CSharp.CodeGen; /// Emits the C# for a Daml interface: the marker interface declaration with its /// facet (and the optional /// facet when the interface -/// carries a view type), the static interface metadata, the per-choice method -/// signatures, and the sibling static class hosting the typed interface-choice -/// exercisers. Choice emission is delegated to the package's +/// carries a view type), the static interface metadata, the view enrichment (the static +/// View witness for any view type that resolves to a non-generic record, see +/// ; plus instance properties +/// mirroring the view's fields when that record is package-local and viewed by exactly +/// one interface, see for +/// that boundary), the per-choice method signatures, and the sibling static class hosting +/// the typed interface-choice exercisers. Choice emission is delegated to the package's /// and view types are resolved through the package's /// . Constructed once per package over the package's -/// , that , the -/// , and the shared . The caller -/// owns the file scaffold and the common usings; this emitter writes the interface body -/// into the provided . +/// , that , the DAR-scoped +/// , the , and the shared +/// . The caller owns the file scaffold and the common +/// usings; this emitter writes the interface body into the provided +/// . /// internal sealed class InterfaceEmitter( PackageEmitContext context, DamlTypeMapper mapper, + ICrossPackageResolver resolver, ChoiceEmitter choiceEmitter, CodeGenOptions options) { /// - /// Writes the interface declaration, its static metadata, the per-choice method - /// signatures, and the sibling interface-choice exerciser class for + /// Writes the interface declaration, its static metadata, its view enrichment, the + /// per-choice method signatures, and the sibling interface-choice exerciser class for /// into . /// internal void WriteInterfaceType(IndentWriter indent, DamlPackage package, DamlModule module, DamlInterface iface) { - var dataTypes = context.DataTypes; + var interfaceName = context.LocalInterfaceMarkerNames[$"{module.Name}:{iface.Name}"]; + var viewType = iface.ViewType is not null ? mapper.MapType(iface.ViewType) : null; + var stampedViewRecord = StampedViewRecordDefinition(iface); if (options.GenerateXmlDocs) { indent.AppendLine("/// "); indent.AppendLine($"/// Generated from Daml interface {module.Name}:{iface.Name}"); indent.AppendLine("/// "); + if (stampedViewRecord is not null) + { + indent.AppendLine("/// "); + indent.AppendLine($"/// Instance properties mirror the fields of the interface view ,"); + indent.AppendLine("/// which implements this marker, so a view can be read through a marker-typed variable."); + indent.AppendLine("/// == between marker-typed variables compares by reference equality; view payloads"); + indent.AppendLine($"/// materialize as concrete values, whose record value equality"); + indent.AppendLine("/// applies once concretely typed."); + indent.AppendLine("/// "); + } } - var interfaceName = context.LocalInterfaceMarkerNames[$"{module.Name}:{iface.Name}"]; indent.CurrentTypeName = interfaceName; - var viewType = iface.ViewType is not null ? mapper.MapType(iface.ViewType) : null; var interfaces = viewType is not null ? $"{context.Qualifier.Qualify(RuntimeTypeNames.IDamlInterface, context.RootNamespace)}, {context.Qualifier.Qualify(RuntimeTypeNames.IHasView, context.RootNamespace)}<{viewType}>" : context.Qualifier.Qualify(RuntimeTypeNames.IDamlInterface, context.RootNamespace); @@ -56,9 +72,14 @@ internal void WriteInterfaceType(IndentWriter indent, DamlPackage package, DamlM WriteInterfaceMetadata(indent, package, module, iface); - foreach (var method in iface.Choices) + if (viewType is not null && context.HasWitnessableViewRecord(iface)) { - choiceEmitter.WriteInterfaceMethod(indent, method, dataTypes); + WriteViewWitness(indent, interfaceName, viewType); + } + + if (stampedViewRecord is not null) + { + WriteViewFieldProperties(indent, stampedViewRecord); } indent.Dedent(); @@ -71,6 +92,40 @@ internal void WriteInterfaceType(IndentWriter indent, DamlPackage package, DamlM } } + private DamlRecordDefinition? StampedViewRecordDefinition(DamlInterface iface) => + iface.ViewType is DamlTypeRef viewRef + && context.IsLocalRef(viewRef) + && context.LocalViewRecordMarkerNames.ContainsKey($"{viewRef.Module}:{viewRef.Name}") + ? context.LocalViewRecord(viewRef) + : null; + + private void WriteViewWitness(IndentWriter indent, string interfaceName, string viewTypeName) + { + var descriptorType = context.Qualifier.Qualify(RuntimeTypeNames.ViewDescriptor, context.RootNamespace); + if (options.GenerateXmlDocs) + { + indent.AppendLine($"/// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument."); + } + indent.AppendLine($"public static {descriptorType}<{interfaceName}, {viewTypeName}> View {{ get; }} = new();"); + indent.AppendLine(); + } + + private void WriteViewFieldProperties(IndentWriter indent, DamlRecordDefinition viewRecord) + { + foreach (var field in viewRecord.Fields) + { + var csharpType = mapper.MapType(field.Type); + var memberName = Identifiers.MemberName(field.Name, indent.CurrentTypeName); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, field.Type); + if (options.GenerateXmlDocs) + { + indent.AppendLine($"/// Gets the {field.Name} field of the interface view."); + } + indent.AppendLine($"{csharpType} {memberName} {{ get; }}"); + indent.AppendLine(); + } + } + private void WriteInterfaceMetadata(IndentWriter indent, DamlPackage package, DamlModule module, DamlInterface iface) { indent.Require("System"); diff --git a/src/Daml.Codegen.CSharp/CodeGen/OptionalRepresentation.cs b/src/Daml.Codegen.CSharp/CodeGen/OptionalRepresentation.cs new file mode 100644 index 0000000..e901671 --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/OptionalRepresentation.cs @@ -0,0 +1,268 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.Intermediate.Model; + +namespace Daml.Codegen.CSharp.CodeGen; + +/// +/// Decides, once and before translation, which of a type tree's Optionals C# nullable +/// syntax can carry and which need the runtime wrapper. The rule needs the path from the +/// root — is this Optional inside another Optional, is it a type argument to a generic, +/// is it a GenMap key — which a root-down walk has natively and the bottom-up +/// recursive translator never does. Rewriting up front keeps the translator stateless. +/// +internal static class OptionalRepresentation +{ + private const int MaxTypeDepth = 256; + + /// + /// Returns with every Optional in a wrapper position replaced by + /// a . Pure, and idempotent on its own output. + /// + /// The type to rewrite. + /// + /// The package was read from. A type ref declared in the same + /// package carries no package id, so this is what such a ref resolves against. + /// + /// Resolves type refs that name another package. + /// + /// An Optional is passed as a type argument to a generic whose declaration wraps that + /// same type parameter in an Optional. + /// + public static DamlType Rewrite(DamlType type, DamlPackage localPackage, ICrossPackageResolver resolver) + { + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(localPackage); + ArgumentNullException.ThrowIfNull(resolver); + return Rewrite(type, localPackage, resolver, required: null, parentIsOptional: false, depth: 0); + } + + private static DamlType Rewrite( + DamlType type, + DamlPackage localPackage, + ICrossPackageResolver resolver, + OptionalEncoding? required, + bool parentIsOptional, + int depth) + { + if (depth > MaxTypeDepth) + { + throw new InvalidDataException( + $"Deciding Optional representation exceeded the maximum Daml type depth of {MaxTypeDepth}. " + + "The type is too deeply nested to emit safely."); + } + + return type switch + { + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var argument] } app => + RewriteOptional(app, argument, localPackage, resolver, required, parentIsOptional, depth), + DamlWrappedOptional wrapped => + wrapped with + { + Argument = Rewrite( + wrapped.Argument, + localPackage, + resolver, + required: null, + parentIsOptional: IsChainLevel(wrapped.Encoding), + depth + 1), + }, + DamlTypeApp { Base: DamlTypeRef typeRef } app => + app with + { + Arguments = + [ + .. app.Arguments.Select((argument, index) => + RewriteGenericArgument(typeRef, argument, index, localPackage, resolver, depth)), + ], + }, + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.GenMap }, Arguments: [var key, var value] } app => + app with { Arguments = RewriteGenMapArguments(key, value, localPackage, resolver, depth) }, + DamlTypeApp app => + app with + { + Arguments = + [ + .. app.Arguments.Select(argument => + Rewrite(argument, localPackage, resolver, required: null, parentIsOptional: false, depth + 1)), + ], + }, + _ => type, + }; + } + + private static DamlType RewriteOptional( + DamlTypeApp app, + DamlType argument, + DamlPackage localPackage, + ICrossPackageResolver resolver, + OptionalEncoding? required, + bool parentIsOptional, + int depth) + { + var rewrittenArgument = Rewrite(argument, localPackage, resolver, required: null, parentIsOptional: true, depth + 1); + var encoding = parentIsOptional || IsOptional(argument) + ? OptionalEncoding.NestedChain + : required ?? (argument is DamlTypeVar ? OptionalEncoding.Flat : (OptionalEncoding?)null); + return encoding is { } chosen + ? new DamlWrappedOptional(rewrittenArgument, chosen) + : app with { Arguments = [rewrittenArgument] }; + } + + /// + /// Rewrites a GenMap's two type arguments. A GenMap is emitted as a C# dictionary, whose + /// key type parameter is notnull: a null key is rejected at compile time and + /// throws on insert, so C# nullable syntax cannot carry an Optional in that position and the + /// key takes the wrapper in the flat encoding, which leaves the wire form unchanged. The + /// value sits in an ordinary dictionary slot and stays nullable. + /// + private static IReadOnlyList RewriteGenMapArguments( + DamlType key, + DamlType value, + DamlPackage localPackage, + ICrossPackageResolver resolver, + int depth) => + [ + Rewrite(key, localPackage, resolver, OptionalEncoding.Flat, parentIsOptional: false, depth + 1), + Rewrite(value, localPackage, resolver, required: null, parentIsOptional: false, depth + 1), + ]; + + /// + /// Rewrites one type argument of a generic — emitted or a parametric stdlib generic, + /// which are alike in that a C# type argument cannot carry a ? — refusing the + /// substitution the emitter cannot encode. A generic's body is emitted once from its + /// declaration, so an Optional the declaration wraps around a type parameter gets its + /// chain-or-flat encoding decided there, blind to the argument each use site substitutes. + /// Substituting an Optional adds a level adjacent to that one: the two sides then + /// disagree, and the composed converter writes one array level short of the chain + /// encoding — a value the participant accepts and reads as a different Optional. + /// + private static DamlType RewriteGenericArgument( + DamlTypeRef typeRef, + DamlType argument, + int index, + DamlPackage localPackage, + ICrossPackageResolver resolver, + int depth) + { + if (IsOptional(argument) + && OptionalWrappedParameter(resolver, localPackage, typeRef, index, []) is { } parameter) + { + throw new CodegenException( + $"Codegen does not support a Daml Optional as the '{parameter}' type argument of " + + $"{typeRef.Module}:{typeRef.Name}. That declaration wraps '{parameter}' in an Optional, and a " + + "generic's body is emitted once from its declaration, so the Optional levels the declaration " + + "contributes and the ones the type argument contributes cannot agree on the nested-chain array " + + "encoding — the value would go on the wire one array level short and be read back as a different " + + $"Optional. Refactor the Daml signature so no Optional is passed through '{parameter}'."); + } + + return Rewrite(argument, localPackage, resolver, OptionalEncoding.Flat, parentIsOptional: false, depth + 1); + } + + /// + /// The name of the type parameter at of the type + /// names when that declaration wraps the parameter in an + /// Optional, or null when it does not — including when the declaration cannot be + /// resolved, which leaves emission to the arms that already report an unresolvable ref. + /// is the set of declaration slots already on the walk, so a + /// recursive data type terminates. + /// + private static string? OptionalWrappedParameter( + ICrossPackageResolver resolver, + DamlPackage referringPackage, + DamlTypeRef typeRef, + int index, + HashSet visited) + { + var declaringPackage = DeclaringPackage(resolver, referringPackage, typeRef); + var declaration = declaringPackage?.Modules + .Where(module => module.Name == typeRef.Module) + .SelectMany(module => module.DataTypes) + .FirstOrDefault(dataType => dataType.Name == typeRef.Name); + if (declaringPackage is null || declaration is null || index >= declaration.TypeParams.Count) + { + return null; + } + if (!visited.Add($"{declaringPackage.PackageId}:{typeRef.Module}:{typeRef.Name}:{index}")) + { + return null; + } + + var parameter = declaration.TypeParams[index]; + return DeclaredTypes(declaration) + .Any(declared => WrapsParameterInOptional(resolver, declaringPackage, declared, parameter, visited)) + ? parameter + : null; + } + + private static DamlPackage? DeclaringPackage( + ICrossPackageResolver resolver, DamlPackage referringPackage, DamlTypeRef typeRef) => + string.IsNullOrEmpty(typeRef.PackageId) || typeRef.PackageId == referringPackage.PackageId + ? referringPackage + : resolver.LookupPackage(typeRef.PackageId); + + private static IEnumerable DeclaredTypes(DamlDataType declaration) => declaration.Definition switch + { + DamlRecordDefinition record => record.Fields.Select(field => field.Type), + DamlVariantDefinition variant => variant.Constructors + .Select(constructor => constructor.ArgumentType) + .OfType(), + _ => [], + }; + + /// + /// Whether puts directly + /// beneath an Optional. Only an adjacent Optional matters: any other constructor between + /// the two keeps the declaration's decision and the use site's decision independent, and + /// both are then locally right. + /// + private static bool WrapsParameterInOptional( + ICrossPackageResolver resolver, + DamlPackage declaringPackage, + DamlType declaredType, + string parameter, + HashSet visited) => declaredType switch + { + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var argument] } => + IsParameterBeneathOptionals(argument, parameter) + || WrapsParameterInOptional(resolver, declaringPackage, argument, parameter, visited), + DamlTypeApp { Base: DamlTypeRef nested } app => + app.Arguments + .Select((argument, index) => (Argument: argument, Index: index)) + .Any(slot => + WrapsParameterInOptional(resolver, declaringPackage, slot.Argument, parameter, visited) + || (IsParameterBeneathOptionals(slot.Argument, parameter) + && OptionalWrappedParameter(resolver, declaringPackage, nested, slot.Index, visited) is not null)), + DamlTypeApp app => + app.Arguments.Any(argument => + WrapsParameterInOptional(resolver, declaringPackage, argument, parameter, visited)), + _ => false, + }; + + private static bool IsParameterBeneathOptionals(DamlType declaredType, string parameter) => declaredType switch + { + DamlTypeVar typeVar => typeVar.Name == parameter, + DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional }, Arguments: [var argument] } => + IsParameterBeneathOptionals(argument, parameter), + _ => false, + }; + + /// + /// A switch, not an equality test, so a newly added is a + /// compile error here rather than silently costing its argument a chain level. Only CS8524 — + /// the out-of-range cast — is suppressed. + /// +#pragma warning disable CS8524 + private static bool IsChainLevel(OptionalEncoding encoding) => encoding switch + { + OptionalEncoding.Flat => false, + OptionalEncoding.NestedChain => true, + }; +#pragma warning restore CS8524 + + private static bool IsOptional(DamlType type) => + type is DamlWrappedOptional + or DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } }; +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/PackageEmitContext.cs b/src/Daml.Codegen.CSharp/CodeGen/PackageEmitContext.cs index ce0f6d1..503231f 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/PackageEmitContext.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/PackageEmitContext.cs @@ -1,18 +1,19 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Microsoft.Extensions.Logging; namespace Daml.Codegen.CSharp.CodeGen; /// /// Immutable per-package value the C# emitter threads through its emit methods: the /// root namespace, the , the per-package -/// data-type lookup, and the local enum / variant / interface-placeholder / -/// choice-argument name sets. Built once per package by +/// data-type lookup, and the local enum / variant / interface / choice-argument +/// name sets. Built once per package by /// ; read-only during emission. /// -internal sealed class PackageEmitContext +internal sealed partial class PackageEmitContext { /// The Daml package this context was built for. public DamlPackage Package { get; } @@ -33,9 +34,9 @@ internal sealed class PackageEmitContext /// /// Sanitised C# names of every top-level type declared anywhere in the package — - /// every template plus every record/enum/variant, excluding interface-placeholder - /// records (they are replaced by the marker itself, so counting them would falsely - /// self-disambiguate) and choice-argument records (they are emitted nested inside + /// every template plus every record/enum/variant, excluding the records LF declares + /// alongside a same-named interface (they are replaced by the marker itself, so counting + /// them would falsely self-disambiguate) and choice-argument records (they are emitted nested inside /// their parent template, not at the top level). The package's C# namespace is flat /// across all its modules, so this set has two consumers: it is the reserved-name /// input disambiguates interface marker @@ -66,10 +67,16 @@ internal sealed class PackageEmitContext public IReadOnlySet LocalVariantQualifiedNames { get; } /// - /// Module-qualified names of records that exist purely as the C# placeholder for a - /// Daml interface declaration. + /// Module-qualified (Module:Name) names of the records LF declares alongside an + /// interface of the same name in the same module. They are not emitted: the marker + /// carries the interface's identity, and ContractId<IMarker> serves the + /// contract-id fields and choice extensions that would otherwise need a record. The set + /// is therefore the emitter's "this local name is an interface, not a record" oracle — + /// read by the record emitter to skip the declaration, by the cross-package resolver and + /// the choice-created-slot walker to resolve a local ref to its marker, and by + /// to reject a view naming an interface. /// - public IReadOnlySet InterfacePlaceholderQualifiedNames { get; } + public IReadOnlySet LocalInterfaceQualifiedNames { get; } /// /// Maps a choice-argument type's module-qualified (Module:Name) name to its @@ -80,6 +87,22 @@ internal sealed class PackageEmitContext /// public IReadOnlyDictionary LocalChoiceArgToTemplate { get; } + /// + /// Maps a record's module-qualified (Module:Name) name to the C# marker name + /// of the single local interface declaring that record as its view type. Only + /// package-local, non-generic record views with exactly one viewing interface and no + /// field that mirrors onto the marker under a different name or over a member the + /// marker already declares are mapped: a dependency package is emitted without + /// knowledge of its dependents, so a foreign view record cannot be stamped with this + /// package's markers; a record stamped with two markers would inherit two explicit + /// implementations of the same identity statics — no most specific implementation, a + /// compile error; and a field whose two mirror names disagree would leave the marker + /// declaring a member the record never implements. The record emitter stamps the + /// marker into the view record's base list, and the interface emitter mirrors the + /// view's fields onto the marker for the same set, so both degrade together. + /// + public IReadOnlyDictionary LocalViewRecordMarkerNames { get; } + /// /// Returns true when points at a type declared in this /// package — either an empty package id (self-reference) or a matching package id. @@ -88,6 +111,47 @@ public bool IsLocalRef(DamlTypeRef typeRef) => string.IsNullOrEmpty(typeRef.PackageId) || typeRef.PackageId == Package.PackageId; + /// + /// Returns true when 's view type can stand as the + /// TView of a , + /// whose TView : IDamlRecord<TView> constraint admits only a non-generic + /// record. A local view reference must resolve to one here; a foreign reference is + /// taken as such, since a dependency package emits its own non-generic records with + /// that facet. A view naming a variant, an enum, a generic record, an interface, or a + /// type this package does not declare therefore degrades to the + /// bare facet rather than an + /// uncompilable witness. + /// + public bool HasWitnessableViewRecord(DamlInterface iface) => + iface.ViewType is DamlTypeRef viewRef + && (!IsLocalRef(viewRef) || LocalViewRecord(viewRef) is not null); + + /// + /// Returns the non-generic record definition names in this + /// package, or null when it names an interface, a generic record, a non-record + /// definition, or a type this package does not declare. Callers that also + /// care about locality must test first — the lookup key + /// carries no package id, so a foreign reference can otherwise collide with a + /// same-named local declaration. + /// + public DamlRecordDefinition? LocalViewRecord(DamlTypeRef viewRef) => + LocalViewRecord(viewRef, DataTypes, LocalInterfaceQualifiedNames); + + private static DamlRecordDefinition? LocalViewRecord( + DamlTypeRef viewRef, + IReadOnlyDictionary dataTypes, + IReadOnlySet localInterfaceQualifiedNames) + { + ArgumentNullException.ThrowIfNull(viewRef); + var viewKey = $"{viewRef.Module}:{viewRef.Name}"; + return !localInterfaceQualifiedNames.Contains(viewKey) + && dataTypes.TryGetValue(viewKey, out var viewDataType) + && viewDataType.TypeParams.Count == 0 + && viewDataType.Definition is DamlRecordDefinition viewRecord + ? viewRecord + : null; + } + private PackageEmitContext( DamlPackage package, string rootNamespace, @@ -97,8 +161,9 @@ private PackageEmitContext( IReadOnlyDictionary localInterfaceMarkerNames, IReadOnlySet localEnumQualifiedNames, IReadOnlySet localVariantQualifiedNames, - IReadOnlySet interfacePlaceholderQualifiedNames, - IReadOnlyDictionary localChoiceArgToTemplate) + IReadOnlySet localInterfaceQualifiedNames, + IReadOnlyDictionary localChoiceArgToTemplate, + IReadOnlyDictionary localViewRecordMarkerNames) { Package = package; RootNamespace = rootNamespace; @@ -108,22 +173,23 @@ private PackageEmitContext( LocalInterfaceMarkerNames = localInterfaceMarkerNames; LocalEnumQualifiedNames = localEnumQualifiedNames; LocalVariantQualifiedNames = localVariantQualifiedNames; - InterfacePlaceholderQualifiedNames = interfacePlaceholderQualifiedNames; + LocalInterfaceQualifiedNames = localInterfaceQualifiedNames; LocalChoiceArgToTemplate = localChoiceArgToTemplate; + LocalViewRecordMarkerNames = localViewRecordMarkerNames; } /// /// Scans and returns a fully-populated immutable context: /// derives the root namespace (honouring ), /// builds the global data-type lookup, and populates the local enum / variant / - /// interface-placeholder / choice-argument name sets. When two templates in the + /// interface / choice-argument name sets. When two templates in the /// package map the same module-qualified choice-argument type, /// (when supplied) receives a warning and the first-seen mapping is kept. /// public static PackageEmitContext ForPackage( DamlPackage package, CodeGenOptions options, - ICodegenLogger? logger = null) + ILogger? logger = null) { ArgumentNullException.ThrowIfNull(package); ArgumentNullException.ThrowIfNull(options); @@ -135,7 +201,7 @@ public static PackageEmitContext ForPackage( var dataTypes = new Dictionary(); var localEnumQualifiedNames = new HashSet(); var localVariantQualifiedNames = new HashSet(); - var interfacePlaceholderQualifiedNames = new HashSet(); + var localInterfaceQualifiedNames = new HashSet(); foreach (var module in package.Modules) { var interfaceNames = module.Interfaces.Select(i => i.Name).ToHashSet(); @@ -153,7 +219,7 @@ public static PackageEmitContext ForPackage( } if (interfaceNames.Contains(dataType.Name)) { - interfacePlaceholderQualifiedNames.Add($"{module.Name}:{dataType.Name}"); + localInterfaceQualifiedNames.Add($"{module.Name}:{dataType.Name}"); } } } @@ -173,8 +239,10 @@ public static PackageEmitContext ForPackage( if (localChoiceArgToTemplate.TryGetValue(key, out var existingTemplate) && existingTemplate != template.Name) { - logger?.Warning( - $"Choice-argument type {key} is used by both templates {existingTemplate} and {template.Name} in the same package; keeping {existingTemplate} and ignoring {template.Name}. Rename one choice-argument type to disambiguate."); + if (logger is not null) + { + LogAmbiguousLocalChoiceArgument(logger, key, existingTemplate, template.Name); + } continue; } localChoiceArgToTemplate[key] = template.Name; @@ -185,6 +253,8 @@ public static PackageEmitContext ForPackage( } var localInterfaceMarkerNames = InterfaceMarkerNames(package, localReservedTypeNames); + var localViewRecordMarkerNames = ViewRecordMarkerNames( + package, dataTypes, localInterfaceQualifiedNames, localInterfaceMarkerNames); return new PackageEmitContext( package, @@ -195,23 +265,107 @@ public static PackageEmitContext ForPackage( localInterfaceMarkerNames, localEnumQualifiedNames, localVariantQualifiedNames, - interfacePlaceholderQualifiedNames, - localChoiceArgToTemplate); + localInterfaceQualifiedNames, + localChoiceArgToTemplate, + localViewRecordMarkerNames); + } + + /// + /// Maps every package-local, non-generic view record with exactly one viewing + /// interface and a clean field mirror to that interface's marker name — the source of + /// . Foreign view references, references to + /// types this package does not declare, generic records, non-record definitions, and + /// the records LF declares alongside a same-named interface are all excluded (only a + /// record this package emits itself can be stamped with a marker); a record viewed by more than one interface is + /// excluded because the stamp would inherit two explicit implementations of the same + /// identity statics — no most specific implementation, a compile error; and a record + /// failing is excluded because the marker would + /// declare a member the stamped record does not implement. + /// + private static IReadOnlyDictionary ViewRecordMarkerNames( + DamlPackage package, + IReadOnlyDictionary dataTypes, + IReadOnlySet localInterfaceQualifiedNames, + IReadOnlyDictionary localInterfaceMarkerNames) + { + var markersByViewRecord = new Dictionary>(); + foreach (var module in package.Modules) + { + foreach (var iface in module.Interfaces) + { + if (iface.ViewType is not DamlTypeRef viewRef + || (!string.IsNullOrEmpty(viewRef.PackageId) && viewRef.PackageId != package.PackageId) + || LocalViewRecord(viewRef, dataTypes, localInterfaceQualifiedNames) is null) + { + continue; + } + + var viewKey = $"{viewRef.Module}:{viewRef.Name}"; + if (!markersByViewRecord.TryGetValue(viewKey, out var markers)) + { + markers = new SortedSet(StringComparer.Ordinal); + markersByViewRecord[viewKey] = markers; + } + markers.Add(localInterfaceMarkerNames[$"{module.Name}:{iface.Name}"]); + } + } + + return markersByViewRecord + .Where(entry => entry.Value.Count == 1) + .Select(entry => (ViewKey: entry.Key, Marker: entry.Value.Single())) + .Where(pair => ViewFieldsMirrorCleanly(dataTypes[pair.ViewKey], pair.Marker)) + .ToDictionary(pair => pair.ViewKey, pair => pair.Marker); + } + + /// + /// Members a generated interface marker declares in its own right, which a mirrored + /// view field must not shadow: the View witness and the InterfaceId + /// identity re-declaration (CS0102). + /// + private static readonly IReadOnlySet MarkerDeclaredMemberNames = + new HashSet(StringComparer.Ordinal) { "View", "InterfaceId" }; + + /// + /// Returns true when every field of mirrors onto + /// under the same C# member name the record itself emits + /// for it, and under no name the marker already declares. The two sides derive their + /// member names independently, each disambiguating only against its own enclosing type + /// (CS0542), so a field PascalCasing to the record's name is emitted as Name_ + /// there and Name on the marker — and vice versa for a field PascalCasing to the + /// marker's name — leaving the record short of a marker member (CS0535). A field + /// PascalCasing to View or InterfaceId would instead redeclare a member + /// the marker already owns (CS0102). Either way the pair is ineligible and the record + /// stays un-stamped beside an un-enriched marker. + /// + private static bool ViewFieldsMirrorCleanly(DamlDataType viewRecord, string markerName) + { + if (viewRecord.Definition is not DamlRecordDefinition record) + { + return false; + } + + var recordClassName = Identifiers.Sanitize(viewRecord.Name); + return record.Fields.All(field => + { + var markerMemberName = Identifiers.MemberName(field.Name, markerName); + return markerMemberName == Identifiers.MemberName(field.Name, recordClassName) + && !MarkerDeclaredMemberNames.Contains(markerMemberName); + }); } /// /// Computes the sanitised C# name of every top-level type declared anywhere in /// — every template plus every record/enum/variant, - /// excluding interface-placeholder records (replaced by the marker itself) and - /// choice-argument records (emitted nested inside their parent template, not at the - /// top level) — the single source of the reserved-name set + /// excluding the records LF declares alongside a same-named interface (replaced by the + /// marker itself) and choice-argument records (emitted nested inside their parent + /// template, not at the top level) — the single source of the reserved-name set /// disambiguates against, shared by /// (for the emitting package) and the cross-package /// resolver (for foreign packages) so both sides derive the same marker name. /// internal static IReadOnlySet ReservedTopLevelTypeNames(DamlPackage package) { - var interfacePlaceholderQualifiedNames = new HashSet(); + var localInterfaceQualifiedNames = new HashSet(); var dataTypeNames = new HashSet(); foreach (var module in package.Modules) { @@ -221,7 +375,7 @@ internal static IReadOnlySet ReservedTopLevelTypeNames(DamlPackage packa dataTypeNames.Add(dataType.Name); if (interfaceNames.Contains(dataType.Name)) { - interfacePlaceholderQualifiedNames.Add($"{module.Name}:{dataType.Name}"); + localInterfaceQualifiedNames.Add($"{module.Name}:{dataType.Name}"); } } } @@ -251,7 +405,7 @@ internal static IReadOnlySet ReservedTopLevelTypeNames(DamlPackage packa foreach (var dataType in module.DataTypes) { var qualifiedName = $"{module.Name}:{dataType.Name}"; - if (interfacePlaceholderQualifiedNames.Contains(qualifiedName) + if (localInterfaceQualifiedNames.Contains(qualifiedName) || choiceArgumentQualifiedNames.Contains(qualifiedName)) { continue; @@ -294,4 +448,10 @@ internal static IReadOnlyDictionary InterfaceMarkerNames( return markers; } + + [LoggerMessage( + EventId = 1200, + Level = LogLevel.Warning, + Message = "Choice-argument type {ChoiceArgumentKey} is used by both templates {KeptTemplate} and {IgnoredTemplate} in the same package; keeping {KeptTemplate} and ignoring {IgnoredTemplate}. Rename one choice-argument type to disambiguate.")] + private static partial void LogAmbiguousLocalChoiceArgument(ILogger logger, string choiceArgumentKey, string keptTemplate, string ignoredTemplate); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/PackageQualifiedResolver.cs b/src/Daml.Codegen.CSharp/CodeGen/PackageQualifiedResolver.cs new file mode 100644 index 0000000..78e3457 --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodeGen/PackageQualifiedResolver.cs @@ -0,0 +1,35 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.Intermediate.Model; + +namespace Daml.Codegen.CSharp.CodeGen; + +/// +/// Decorates an so every in-package resolution comes +/// back global::-qualified with the emitting package's root namespace, while +/// cross-package and stdlib resolutions pass through untouched. Emitting a type reference +/// into a body that declares nearer members or nested types of the same spelling — the +/// active contract's Id / Data / Key members and its nested +/// Contract / ContractId records — needs the qualification applied at +/// resolution, so that it survives the composition a rendered name goes through: an +/// Optional becomes Name? and a list becomes +/// IReadOnlyList<Name>, neither of which a rendered-name comparison matches. +/// +internal sealed class PackageQualifiedResolver(ICrossPackageResolver inner) : ICrossPackageResolver +{ + /// + public IReadOnlySet DiscoveredExternalPackageIds => inner.DiscoveredExternalPackageIds; + + /// + public DamlPackage? LookupPackage(string packageId) => inner.LookupPackage(packageId); + + /// + public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var resolved = inner.Resolve(typeRef, context); + return context.IsLocalRef(typeRef) ? $"global::{context.RootNamespace}.{resolved}" : resolved; + } +} diff --git a/src/Daml.Codegen.CSharp/CodeGen/PartyAnalysis.cs b/src/Daml.Codegen.CSharp/CodeGen/PartyAnalysis.cs index d950f3f..9f90b31 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/PartyAnalysis.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/PartyAnalysis.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -15,6 +15,10 @@ namespace Daml.Codegen.CSharp.CodeGen; /// without duplicating the logic. Shared dependency of the choice-exerciser and /// submission-extension emitters. /// +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Instance members keep this a constructor-injected collaborator of the choice and submission emitters; making them static would replace the seam with a hard dependency on a concrete type.")] internal sealed class PartyAnalysis { /// diff --git a/src/Daml.Codegen.CSharp/CodeGen/ProjectFileGenerator.cs b/src/Daml.Codegen.CSharp/CodeGen/ProjectFileGenerator.cs index 2eec46a..45f87e6 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/ProjectFileGenerator.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/ProjectFileGenerator.cs @@ -1,10 +1,11 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Reflection; using System.Text; -using System.Text.RegularExpressions; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.CSharp.Versioning; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -17,30 +18,12 @@ public sealed class ProjectFileGenerator private readonly CodeGenOptions _options; - // Matches the partial-property syntax emitted by WriteKeyProperty, e.g. - // `public partial string Key { get; }` or `public partial Foo.Bar Key { get; }`. - // The character class `[^;{=]` excludes `{`, `;`, `=` so it can't span across the - // type declaration's own opening brace and into the property body. - private static readonly Regex PartialKeyPropertyPattern = - new(@"\bpartial\s+[^;{=]+?\s+Key\s*\{\s*get;\s*\}", RegexOptions.Compiled); - /// Creates a generator whose output reflects the given codegen options (target framework, naming, etc.). public ProjectFileGenerator(CodeGenOptions options) { _options = options; } - /// - /// Returns true if the given emitted file's content contains the - /// codegen-emitted partial-property Key declaration (the - /// WriteKeyProperty output) that requires C# 13. Surfaced - /// through the emitted .daml-langversion state file so - /// build-integration tooling can decide whether to bump the consuming - /// project's <LangVersion>. - /// - internal static bool ContentRequiresCSharp13(string content) => - PartialKeyPropertyPattern.IsMatch(content); - /// /// Generates a .csproj file for the given package. /// @@ -49,22 +32,9 @@ internal static bool ContentRequiresCSharp13(string content) => /// Other DAR-level packages whose types are referenced by the generated code. /// Each becomes a NuGet PackageReference in the generated csproj. /// - /// - /// The .cs files actually emitted into the same project (after RootFilter and - /// IncludeDependencies). When provided, scanned for partial-property syntax to - /// decide whether to pin <LangVersion>13</LangVersion> — the - /// most precise answer because it tracks what actually goes into the project - /// (a key-bearing dependency added via IncludeDependencies still needs - /// the pin; a keyed template excluded by RootFilter doesn't). When - /// null (back-compat for older callers that don't have access to the - /// post-filter emission set), falls back to scanning 's - /// modules for any key-bearing template — less precise but still functionally - /// safe for the simple case (no IncludeDependencies, no RootFilter). - /// public GeneratedFile GenerateProjectFile( DamlPackage package, - IReadOnlyList? externalReferences = null, - IReadOnlyList? emittedFiles = null) + IReadOnlyList? externalReferences = null) { var packageName = SanitizePackageName(package.Name); var projectFileName = $"{packageName}.csproj"; @@ -72,8 +42,7 @@ public GeneratedFile GenerateProjectFile( var content = GenerateProjectFileContent( package, packageName, - externalReferences ?? [], - emittedFiles); + externalReferences ?? []); return GeneratedFile.Text(projectFileName, content); } @@ -94,6 +63,10 @@ public GeneratedFile GenerateReadme(DamlPackage package) /// project root so the PackageIcon reference packs correctly. The bytes /// are read from the icon embedded in this assembly. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Symmetry with GenerateProjectFile and GenerateReadme: all three are instance entry points on a generator constructed from CodeGenOptions, and callers hold that instance.")] public GeneratedFile GenerateIcon() { const string resourceName = "icon.png"; @@ -119,17 +92,17 @@ private string GenerateReadmeContent(DamlPackage package, string packageId) { var damlVersion = package.Version.ToString(); var sb = new StringBuilder(); - sb.AppendLine($"# {packageId}"); + sb.AppendLine(CultureInfo.InvariantCulture, $"# {packageId}"); sb.AppendLine(); - sb.AppendLine($"C# bindings for the Daml package **`{package.Name}`** `{damlVersion}`,"); + sb.AppendLine(CultureInfo.InvariantCulture, $"C# bindings for the Daml package **`{package.Name}`** `{damlVersion}`,"); sb.AppendLine("generated by [Daml.Codegen.CSharp]."); sb.AppendLine(); sb.AppendLine("Strongly-typed records, choices, variants, and contract IDs for working with"); - sb.AppendLine($"`{package.Name}` contracts from .NET. Generated code — do not edit by hand."); + sb.AppendLine(CultureInfo.InvariantCulture, $"`{package.Name}` contracts from .NET. Generated code — do not edit by hand."); sb.AppendLine(); sb.AppendLine("## Install"); var prereleaseFlag = string.IsNullOrWhiteSpace(_options.VersionSuffix) ? string.Empty : " --prerelease"; - sb.AppendLine($" dotnet add package {packageId}{prereleaseFlag}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" dotnet add package {packageId}{prereleaseFlag}"); sb.AppendLine(); sb.AppendLine("## Usage"); sb.AppendLine("Generated types live under the package's namespace; submit commands and exercise choices"); @@ -138,40 +111,25 @@ private string GenerateReadmeContent(DamlPackage package, string packageId) sb.AppendLine("## Provenance"); sb.AppendLine("| | |"); sb.AppendLine("|---|---|"); - sb.AppendLine($"| Source Daml package | `{package.Name}` `{damlVersion}` |"); + sb.AppendLine(CultureInfo.InvariantCulture, $"| Source Daml package | `{package.Name}` `{damlVersion}` |"); var runtimeVersion = _options.RuntimePackageVersion ?? EmitterLockstepVersion; - sb.AppendLine($"| Runtime dependency | `Daml.Runtime` `{runtimeVersion}` |"); - sb.AppendLine($"| License | {_options.PackageLicenseExpression} |"); + sb.AppendLine(CultureInfo.InvariantCulture, $"| Runtime dependency | `Daml.Runtime` `{runtimeVersion}` |"); + sb.AppendLine(CultureInfo.InvariantCulture, $"| License | {_options.PackageLicenseExpression} |"); sb.AppendLine(); - sb.AppendLine($"[Daml.Codegen.CSharp]: {CodegenToolUrl}"); + sb.AppendLine(CultureInfo.InvariantCulture, $"[Daml.Codegen.CSharp]: {CodegenToolUrl}"); return sb.ToString(); } private string GenerateProjectFileContent( DamlPackage package, string packageName, - IReadOnlyList externalReferences, - IReadOnlyList? emittedFiles) + IReadOnlyList externalReferences) { var sb = new StringBuilder(); sb.AppendLine(""); sb.AppendLine(); sb.AppendLine(" "); - sb.AppendLine($" {EscapeXmlText(_options.TargetFramework)}"); - // Pin C# 13 only when the project's actual emitted code contains the - // partial-property syntax from WriteKeyProperty. - // Anchored to the emission set (not to package.Modules) so that: - // - a key-bearing dependency added via `IncludeDependencies` still pins - // LangVersion (otherwise its emitted partial-property would fail to parse); - // - a keyed template excluded by `RootFilter` does NOT pin LangVersion - // (the syntax never makes it into this project, so raising the SDK floor - // for every consumer is unnecessary). - // When pinned, build machines need .NET 9 SDK or later — documented in the - // CHANGELOG migration note. - if (RequiresCSharp13(package, emittedFiles)) - { - sb.AppendLine(" 13"); - } + sb.AppendLine(CultureInfo.InvariantCulture, $" {EscapeXmlText(_options.TargetFramework)}"); sb.AppendLine(" enable"); if (_options.EnableNullableReferenceTypes) @@ -179,41 +137,36 @@ private string GenerateProjectFileContent( sb.AppendLine(" enable"); } - sb.AppendLine($" {packageName}"); - sb.AppendLine($" {FormatPackageVersion(package.Version)}"); - sb.AppendLine($" C# bindings for Daml package {EscapeXmlText(package.Name)}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {packageName}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {FormatPackageVersion(package.Version)}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" C# bindings for Daml package {EscapeXmlText(package.Name)}"); sb.AppendLine(" Generated by Daml.Codegen.CSharp"); - sb.AppendLine($" {EscapeXmlText(_options.PackageLicenseExpression)}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {EscapeXmlText(_options.PackageLicenseExpression)}"); sb.AppendLine(" README.md"); sb.AppendLine(" icon.png"); if (!string.IsNullOrWhiteSpace(_options.RepositoryUrl)) { var repositoryUrl = EscapeXmlText(_options.RepositoryUrl); - sb.AppendLine($" {repositoryUrl}"); - sb.AppendLine($" {repositoryUrl}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {repositoryUrl}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {repositoryUrl}"); sb.AppendLine(" git"); } - sb.AppendLine($" {EscapeXmlText($"daml;canton;codegen;generated;{package.Name}")}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {EscapeXmlText($"daml;canton;codegen;generated;{package.Name}")}"); sb.AppendLine(" "); sb.AppendLine(); - // Add package references sb.AppendLine(" "); sb.AppendLine(" "); sb.AppendLine(" "); - // Runtime package reference var runtimeVersion = EscapeXmlText(_options.RuntimePackageVersion ?? EmitterLockstepVersion); - sb.AppendLine($" "); - sb.AppendLine($" "); + sb.AppendLine(CultureInfo.InvariantCulture, $" "); + sb.AppendLine(CultureInfo.InvariantCulture, $" "); - // Add cross-DAR package references for any external types referenced in generated code. - // Stdlib packages (daml-prim/daml-stdlib) are filtered out by the generator before this - // point — those types come from Daml.Runtime.Stdlib. foreach (var dep in externalReferences.OrderBy(p => p.Name, StringComparer.Ordinal)) { var depPackageName = SanitizePackageName(dep.Name); - sb.AppendLine($" "); + sb.AppendLine(CultureInfo.InvariantCulture, $" "); } sb.AppendLine(" "); @@ -226,13 +179,7 @@ private string GenerateProjectFileContent( private string FormatPackageVersion(Version darVersion) { - if (darVersion.Build < 0) - { - throw new InvalidOperationException( - $"Daml package version must be 3-part (Major.Minor.Patch) to produce a 4-part M.m.p.g NuGet version, " + - $"but got '{darVersion}'. The IntermediateDarReader guarantees a 3-part version; " + - $"a 2-part version here indicates a regression in the upstream parser."); - } + RequireThreePartIntrinsicVersion(darVersion); if (_options.EmitterCounter < 0) { throw new InvalidOperationException( @@ -242,6 +189,28 @@ private string FormatPackageVersion(Version darVersion) return FourPartPackageVersion.FromIntrinsic(darVersion, _options.EmitterCounter, _options.VersionSuffix).ToString(); } + private string FormatDependencyPackageVersion(Version darVersion) => + _options.PublishesReferencedPackages + ? FormatPackageVersion(darVersion) + : FloatOverEveryPublishedGenerationAndPrerelease(darVersion); + + private static string FloatOverEveryPublishedGenerationAndPrerelease(Version darVersion) + { + RequireThreePartIntrinsicVersion(darVersion); + return string.Create(CultureInfo.InvariantCulture, $"{darVersion.Major}.{darVersion.Minor}.{darVersion.Build}.*-*"); + } + + private static void RequireThreePartIntrinsicVersion(Version darVersion) + { + if (darVersion.Build < 0) + { + throw new InvalidOperationException( + $"Daml package version must be 3-part (Major.Minor.Patch) to produce a 4-part M.m.p.g NuGet version, " + + $"but got '{darVersion}'. The IntermediateDarReader guarantees a 3-part version; " + + $"a 2-part version here indicates a regression in the upstream parser."); + } + } + private static string EscapeXmlText(string value) => System.Security.SecurityElement.Escape(value) ?? string.Empty; @@ -277,27 +246,6 @@ private static string ResolveEmitterLockstepVersion() return metadataSeparator >= 0 ? informational[..metadataSeparator] : informational; } - private static bool RequiresCSharp13(DamlPackage package, IReadOnlyList? emittedFiles) - { - // When the caller provides the post-filter emission set, scan it directly — - // most precise: handles IncludeDependencies (key-bearing dep gets pinned) - // and RootFilter (filtered-out keyed template doesn't force the pin). - if (emittedFiles is not null) - { - return emittedFiles.Any(f => - f.RelativePath.EndsWith(".cs", StringComparison.Ordinal) - && ContentRequiresCSharp13(f.Content)); - } - - // Back-compat fallback: older callers that pass only `package`. Scan the - // package's templates for any key-bearing one — less precise (doesn't see - // IncludeDependencies or RootFilter) but still functionally safe for the - // simple case. Without this fallback, an old caller with a key-bearing - // package would silently produce a `.csproj` lacking 13 and - // the build would fail on a syntax error in the emitted partial property. - return package.Modules.Any(m => m.Templates.Any(t => t.Key is not null)); - } - /// /// Sanitizes a Daml package name to be a valid .NET package/project name. /// Converts to PascalCase and joins with dots (e.g., "cats-markets" -> "Cats.Markets"). @@ -318,16 +266,13 @@ private static string ToPascalCase(string s) return s; } - // Capitalize first letter return char.ToUpperInvariant(s[0]) + s[1..]; } private static string SanitizeIdentifier(string name) { - // Replace invalid characters with underscores var sanitized = string.Concat(name.Select(c => char.IsLetterOrDigit(c) || c == '_' ? c : '_')); - // Ensure it doesn't start with a number if (sanitized.Length > 0 && char.IsDigit(sanitized[0])) { sanitized = "_" + sanitized; diff --git a/src/Daml.Codegen.CSharp/CodeGen/RecordEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/RecordEmitter.cs index 50f2654..7ceb72a 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/RecordEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/RecordEmitter.cs @@ -1,15 +1,13 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; -using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; /// -/// Emits the C# for a Daml record: the sealed record declaration and, for the -/// interface-placeholder case, the throwing -/// stub. The field-bearing serialization surface (constructor parameters, +/// Emits the C# for a Daml record: the sealed record declaration. The field-bearing +/// serialization surface (constructor parameters, /// ToRecord / FromRecord) is delegated to the shared /// so record, template, and nested /// choice-argument output stay byte-identical. Constructed once per package over @@ -26,20 +24,14 @@ internal sealed class RecordEmitter( { /// /// Writes the record declaration and its serialization round-trip for - /// into , routing the - /// interface-placeholder case to its throwing stub. + /// into . /// internal void WriteRecordType(IndentWriter indent, DamlModule module, DamlDataType dataType, DamlRecordDefinition record) { - if (context.InterfacePlaceholderQualifiedNames.Contains($"{module.Name}:{dataType.Name}")) - { - WriteInterfacePlaceholderRecord(indent, module, dataType); - return; - } - var className = EmitterHelpers.SanitizeIdentifier(dataType.Name); indent.CurrentTypeName = className; var typeParams = EmitterHelpers.GetTypeParametersDeclaration(dataType.TypeParams); + var typeParamConstraints = EmitterHelpers.GetTypeParameterConstraints(dataType.TypeParams); var fullClassName = $"{className}{typeParams}"; if (options.GenerateXmlDocs) @@ -54,17 +46,17 @@ internal void WriteRecordType(IndentWriter indent, DamlModule module, DamlDataTy EmitterHelpers.WriteTypeParamDocs(indent, dataType.TypeParams); } - var recordInterface = InterfaceDeclaration(dataType.TypeParams); + var recordInterface = InterfaceDeclaration(module, dataType, className); if (options.UseRecordTypes && options.UsePrimaryConstructors && record.Fields.Count > 0) { indent.Append($"public sealed record {fullClassName}("); serialization.WriteRecordParameters(indent, record.Fields); - indent.AppendLine($"){recordInterface}"); + indent.AppendLine($"){recordInterface}{typeParamConstraints}"); } else { - indent.AppendLine($"public sealed record {fullClassName}{recordInterface}"); + indent.AppendLine($"public sealed record {fullClassName}{recordInterface}{typeParamConstraints}"); } indent.AppendLine("{"); @@ -78,116 +70,28 @@ internal void WriteRecordType(IndentWriter indent, DamlModule module, DamlDataTy } /// - /// Returns the record's interface clause: : IDamlRecord for non-generic records, - /// or empty for generic records, whose ToRecord takes one converter delegate per - /// type parameter and so cannot satisfy the parameterless IDamlRecord.ToRecord() - /// contract — matching the hand-written stdlib generics (Set, Tuple2, NonEmpty). + /// Returns the record's interface clause: for a non-generic record, + /// : IDamlRecord<T> with T naming the record itself (which extends + /// the plain IDamlRecord), preceded by the marker name of the single local + /// interface declaring the record as its view type (see + /// ) so a view answers with + /// its interface's identity through the marker's inherited statics; empty for generic + /// records, whose ToRecord and FromRecord take one converter delegate + /// per type parameter and so can satisfy neither the parameterless + /// IDamlRecord.ToRecord() contract nor the static abstract + /// IDamlRecord<TSelf>.FromRecord(DamlRecord) factory — matching the + /// hand-written stdlib generics (Set, Tuple2, NonEmpty). /// - private string InterfaceDeclaration(IReadOnlyList typeParams) => - typeParams.Count == 0 - ? $" : {context.Qualifier.Qualify(RuntimeTypeNames.IDamlRecord, context.RootNamespace)}" - : string.Empty; - - /// - /// Emits the C# placeholder for a Daml interface declaration. The Daml-LF emits a - /// same-named empty record alongside every interface I where ... so that - /// ContractId I can be expressed at the type level. We surface that record - /// as a sealed record implementing with throwing static - /// metadata: it lets ContractId<I> compile (the runtime constraint is - /// where T : ITemplate) but loudly fails any code path that tries to read - /// I.TemplateId directly — which would be a logic error, since interface - /// placeholders carry no template identity. Interface choices remain exercisable - /// directly on ContractId<I>; there is no supported way to recover a - /// concrete ContractId<TConcrete> from this placeholder. - /// - private void WriteInterfacePlaceholderRecord(IndentWriter indent, DamlModule module, DamlDataType dataType) + private string InterfaceDeclaration(DamlModule module, DamlDataType dataType, string className) { - indent.Require("System"); - indent.Require(RuntimeNamespaces.Contracts); - var className = EmitterHelpers.SanitizeIdentifier(dataType.Name); - var qualifiedDamlName = $"{module.Name}:{dataType.Name}"; - var throwMessage = - $"'{className}' is the C# placeholder for the Daml interface " - + $"'{qualifiedDamlName}' and carries no template metadata. " - + "Exercise interface choices directly on ContractId<" + className + ">; there is no supported coercion to a concrete ContractId."; - - if (options.GenerateXmlDocs) - { - indent.AppendLine("/// "); - indent.AppendLine($"/// Phantom placeholder for the Daml interface {qualifiedDamlName}."); - indent.AppendLine("/// Implements so that ContractId<" + className + ">"); - indent.AppendLine("/// satisfies its where T : ITemplate constraint, but every static"); - indent.AppendLine("/// metadata accessor throws — interface placeholders carry no template identity."); - indent.AppendLine("/// "); - indent.AppendLine("/// "); - indent.AppendLine("/// Interface-instance choices need a concrete template to compile against, but the Daml"); - indent.AppendLine($"/// interface {qualifiedDamlName} has none, so every static metadata accessor on this"); - indent.AppendLine("/// placeholder throws by design. This does not affect interface choices, which the generated"); - indent.AppendLine($"/// extension methods exercise directly on ContractId<{className}> — no coercion"); - indent.AppendLine("/// needed. There is no supported way to recover a concrete ContractId<TConcrete>"); - indent.AppendLine("/// from this placeholder; obtain one independently if concrete metadata is required."); - indent.AppendLine("/// "); - } - - indent.AppendLine($"public sealed record {className} : {context.Qualifier.Qualify(RuntimeTypeNames.ITemplate, context.RootNamespace)}"); - indent.AppendLine("{"); - indent.Indent(); - - void WritePlaceholderDoc(string summary, string? identityPhrase = null) + if (dataType.TypeParams.Count > 0) { - if (options.GenerateXmlDocs) - { - indent.AppendLine($"/// {summary}"); - if (identityPhrase is not null) - { - indent.AppendLine($"/// Thrown unconditionally — this placeholder carries no {identityPhrase} to report."); - } - } + return string.Empty; } - void WriteThrowingMemberDoc(string identityPhrase) => - WritePlaceholderDoc( - $"Always throws — the {qualifiedDamlName} interface placeholder carries no {identityPhrase}.", - identityPhrase); - - WriteThrowingMemberDoc("template identity"); - indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.Identifier, context.RootNamespace)} TemplateId =>"); - indent.Indent(); - indent.AppendLine($"throw new InvalidOperationException(\"{throwMessage}\");"); - indent.Dedent(); - indent.AppendLine(); - WriteThrowingMemberDoc("package identity"); - indent.AppendLine("public static string PackageId =>"); - indent.Indent(); - indent.AppendLine($"throw new InvalidOperationException(\"{throwMessage}\");"); - indent.Dedent(); - indent.AppendLine(); - WriteThrowingMemberDoc("package identity"); - indent.AppendLine("public static string PackageName =>"); - indent.Indent(); - indent.AppendLine($"throw new InvalidOperationException(\"{throwMessage}\");"); - indent.Dedent(); - indent.AppendLine(); - WriteThrowingMemberDoc("package identity"); - indent.AppendLine("public static Version PackageVersion =>"); - indent.Indent(); - indent.AppendLine($"throw new InvalidOperationException(\"{throwMessage}\");"); - indent.Dedent(); - indent.AppendLine(); - WriteThrowingMemberDoc("Daml type identity"); - indent.AppendLine($"public static {context.Qualifier.Qualify(RuntimeTypeNames.DamlTypeDescriptor, context.RootNamespace)} DamlTypeId =>"); - indent.Indent(); - indent.AppendLine($"throw new InvalidOperationException(\"{throwMessage}\");"); - indent.Dedent(); - indent.AppendLine(); - - WritePlaceholderDoc("Converts this placeholder to an empty DamlRecord."); - indent.AppendLine($"public {context.Qualifier.Qualify(RuntimeTypeNames.DamlRecord, context.RootNamespace)} ToRecord() => {context.Qualifier.Qualify(RuntimeTypeNames.DamlRecord, context.RootNamespace)}.Create();"); - indent.AppendLine(); - WritePlaceholderDoc("Creates an empty placeholder instance from a DamlRecord."); - indent.AppendLine($"public static {className} FromRecord({context.Qualifier.Qualify(RuntimeTypeNames.DamlRecord, context.RootNamespace)} record) => new {className}();"); - - indent.Dedent(); - indent.AppendLine("}"); + var recordFacet = $"{context.Qualifier.Qualify(RuntimeTypeNames.IDamlRecord, context.RootNamespace)}<{className}>"; + return context.LocalViewRecordMarkerNames.TryGetValue($"{module.Name}:{dataType.Name}", out var marker) + ? $" : {marker}, {recordFacet}" + : $" : {recordFacet}"; } } diff --git a/src/Daml.Codegen.CSharp/CodeGen/RecordSerializationEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/RecordSerializationEmitter.cs index 487e498..97218f7 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/RecordSerializationEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/RecordSerializationEmitter.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -22,25 +22,26 @@ internal sealed class RecordSerializationEmitter( DamlTypeMapper mapper) { /// - /// Writes the comma-separated primary-constructor parameters for - /// into . + /// Writes the primary-constructor parameters for into + /// , one per line and indented one level, leaving the writer + /// at the start of the line that closes the parameter list. The caller has already + /// written the opening parenthesis and writes the closing one, so the closing + /// parenthesis and any base list land on their own line. /// internal void WriteRecordParameters(IndentWriter indent, IReadOnlyList fields) { - var first = true; - foreach (var field in fields) + indent.AppendLine(); + indent.Indent(); + for (var i = 0; i < fields.Count; i++) { - if (!first) - { - indent.Append(", "); - } - first = false; - + var field = fields[i]; var csharpType = mapper.MapType(field.Type); var fieldName = MemberName(field.Name, indent.CurrentTypeName); - StdlibPackages.RequireForFieldType(resolver, indent, field.Type); - indent.Append($"[property: {DamlFieldAttributeSyntax(field.Name)}] {csharpType} {fieldName}"); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, field.Type); + var separator = i == fields.Count - 1 ? "" : ","; + indent.AppendLine($"[property: {DamlFieldAttributeSyntax(field.Name)}] {csharpType} {fieldName}{separator}"); } + indent.Dedent(); } /// @@ -53,7 +54,7 @@ internal void WriteProperties(IndentWriter indent, IReadOnlyList); + public const string IHasKey = nameof(Daml.Runtime.Contracts.IHasKey<,>); public const string IUpgradeable = nameof(Daml.Runtime.Contracts.IUpgradeable); public const string TransactionResult = nameof(Daml.Runtime.Contracts.TransactionResult); public const string CreatedContract = nameof(Daml.Runtime.Contracts.CreatedContract); public const string IDamlInterface = nameof(Daml.Runtime.Contracts.IDamlInterface); public const string IHasView = nameof(Daml.Runtime.Contracts.IHasView); + public const string ViewDescriptor = nameof(Daml.Runtime.Contracts.ViewDescriptor<,>); + public const string KeyDescriptor = nameof(Daml.Runtime.Contracts.KeyDescriptor<,>); + public const string ContractKey = nameof(Daml.Runtime.Contracts.ContractKey<>); public const string CreatedEvent = nameof(Daml.Runtime.Contracts.CreatedEvent); public const string DamlTypeDescriptor = nameof(Daml.Runtime.Contracts.DamlTypeDescriptor); public const string DamlTypeKind = nameof(Daml.Runtime.Contracts.DamlTypeKind); public const string SubmitterInfo = nameof(Daml.Runtime.Commands.SubmitterInfo); public const string ExerciseCommand = nameof(Daml.Runtime.Commands.ExerciseCommand); + public const string ExerciseByKeyCommand = nameof(Daml.Runtime.Commands.ExerciseByKeyCommand); public const string CommandsSubmission = nameof(Daml.Runtime.Commands.CommandsSubmission); public const string WorkflowId = nameof(Daml.Runtime.Commands.WorkflowId); public const string CommandId = nameof(Daml.Runtime.Commands.CommandId); @@ -61,12 +65,13 @@ internal static class RuntimeTypeNames public const string DayOfWeek = nameof(Daml.Runtime.Stdlib.DayOfWeek); public const string Unit = nameof(Daml.Runtime.Stdlib.Unit); public const string GenericStub = nameof(Daml.Runtime.Stdlib.GenericStub); + public const string Optional = nameof(Daml.Runtime.Stdlib.Optional); - // These stay string literals rather than nameof(...) because each - // constrains a type parameter to a runtime interface (ITemplate or - // IDamlInterface) whose static-abstract members make it unusable as a - // nameof type argument (CS8920), and no concrete implementation is visible - // to the codegen project. + /// + /// These stay string literals rather than nameof. Each constrains a type parameter to + /// a runtime interface whose static abstract members make it unusable as a nameof type + /// argument (CS8920), and no concrete implementation is visible from the codegen project. + /// public const string Contract = "Contract"; public const string IContract = "IContract"; public const string Choice = "Choice"; diff --git a/src/Daml.Codegen.CSharp/CodeGen/StdlibPackages.cs b/src/Daml.Codegen.CSharp/CodeGen/StdlibPackages.cs index 7871dca..7af1498 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/StdlibPackages.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/StdlibPackages.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; namespace Daml.Codegen.CSharp.CodeGen; @@ -106,30 +106,41 @@ internal static bool IsInStdlibPackage(ICrossPackageResolver resolver, DamlTypeR /// Walks and records on every /// namespace the emitted code for that field type needs, classifying stdlib refs /// via . Shared by the choice-exercise and - /// data/template emit paths so both require an identical namespace set. + /// data/template emit paths so both require an identical namespace set. The type runs + /// through + /// first, so the walk sees the same representation the emitter will, rather than + /// deciding a second time which Optionals need the runtime wrapper. /// - internal static void RequireForFieldType(ICrossPackageResolver resolver, IndentWriter indent, DamlType type) + internal static void RequireForFieldType( + ICrossPackageResolver resolver, DamlPackage localPackage, IndentWriter indent, DamlType type) => + RequireForRewrittenType(resolver, indent, OptionalRepresentation.Rewrite(type, localPackage, resolver)); + + private static void RequireForRewrittenType(ICrossPackageResolver resolver, IndentWriter indent, DamlType type) { switch (type) { case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.List } } app: indent.Require("System.Collections.Generic"); indent.Require("System.Linq"); - RequireForFieldType(resolver, indent, app.Arguments[0]); + RequireForRewrittenType(resolver, indent, app.Arguments[0]); break; case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.Optional } } app: - RequireForFieldType(resolver, indent, app.Arguments[0]); + RequireForRewrittenType(resolver, indent, app.Arguments[0]); + break; + case DamlWrappedOptional wrapped: + indent.Require(RuntimeNamespaces.Stdlib); + RequireForRewrittenType(resolver, indent, wrapped.Argument); break; case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.TextMap } } app: indent.Require("System.Collections.Generic"); indent.Require("System.Linq"); - RequireForFieldType(resolver, indent, app.Arguments[0]); + RequireForRewrittenType(resolver, indent, app.Arguments[0]); break; case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.GenMap } } app: indent.Require("System.Collections.Generic"); indent.Require("System.Linq"); - RequireForFieldType(resolver, indent, app.Arguments[0]); - RequireForFieldType(resolver, indent, app.Arguments[1]); + RequireForRewrittenType(resolver, indent, app.Arguments[0]); + RequireForRewrittenType(resolver, indent, app.Arguments[1]); break; case DamlTypeApp { Base: DamlPrimitiveType { Primitive: DamlPrimitive.ContractId } }: indent.Require(RuntimeNamespaces.Contracts); @@ -146,7 +157,7 @@ when IsStdlibTypeRef(resolver, typeRef, parametric: true): indent.Require(RuntimeNamespaces.Stdlib); foreach (var arg in app.Arguments) { - RequireForFieldType(resolver, indent, arg); + RequireForRewrittenType(resolver, indent, arg); } break; case DamlTypeRef typeRef when IsStdlibTypeRef(resolver, typeRef, parametric: false): @@ -163,7 +174,7 @@ when IsStdlibTypeRef(resolver, typeRef, parametric: true): { continue; } - RequireForFieldType(resolver, indent, argument); + RequireForRewrittenType(resolver, indent, argument); } break; case DamlTypeApp app: @@ -173,7 +184,7 @@ when IsStdlibTypeRef(resolver, typeRef, parametric: true): { continue; } - RequireForFieldType(resolver, indent, argument); + RequireForRewrittenType(resolver, indent, argument); } break; } diff --git a/src/Daml.Codegen.CSharp/CodeGen/SubmissionExtensionsEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/SubmissionExtensionsEmitter.cs index a06c132..a986651 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/SubmissionExtensionsEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/SubmissionExtensionsEmitter.cs @@ -1,9 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; -using LedgerNamespaces = Daml.Ledger.Abstractions.LedgerNamespaces; -using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -35,9 +33,10 @@ namespace Daml.Codegen.CSharp.CodeGen; /// /// /// -/// When the static analyzer cannot resolve a signatory expression -/// (anything other than a payload-field projection on the template parameter), -/// codegen falls back to a single SubmitterInfo submitter parameter — +/// When the analysis yields no payload field to derive from — either because +/// the signatory expression is anything other than a payload-field +/// projection on the template parameter, or because it resolves statically to an +/// empty set — codegen falls back to a single SubmitterInfo submitter parameter — /// callers retain full control via SubmitterInfo's implicit conversion /// from a single Party, so the legacy single-party call site stays a /// one-liner. @@ -69,7 +68,7 @@ internal bool TryWriteSubmissionExtensions( var signatories = party.ValidatePayloadParties(template.Signatories, partyFields); var observers = party.ValidatePayloadParties(template.Observers, partyFields); - RequireAsyncExerciserNamespaces(indent); + EmittedUsings.RequireAsyncLedgerCallNamespaces(indent); indent.AppendLine(); if (options.GenerateXmlDocs) @@ -88,8 +87,8 @@ internal bool TryWriteSubmissionExtensions( indent.AppendLine("{"); indent.Indent(); - WriteCreateAsync(indent, className, signatories); - TryWriteObserversHelper(indent, className, observers); + WriteCreateAsync(indent, template.Name, className, signatories); + TryWriteObserversHelper(indent, template.Name, className, observers); indent.Dedent(); indent.AppendLine("}"); @@ -101,12 +100,12 @@ internal bool TryWriteSubmissionExtensions( /// Emits the CreateAsync extension. On the static path, the payload /// alone is enough to derive every signatory — the caller passes only the /// payload and an optional cancellation token, and the wrapper builds a - /// SubmitterInfo from the payload's PascalCased Party - /// properties. On the dynamic path, the caller passes a + /// SubmitterInfo from the payload's Party properties. On the + /// dynamic path, the caller passes a /// SubmitterInfo directly (with implicit conversion from /// a single Party for the single-party ergonomic). /// - private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAnalysis signatories) + private void WriteCreateAsync(IndentWriter indent, string templateName, string className, DamlPartyAnalysis signatories) { var staticParties = signatories.Source == DamlPartySource.Static && signatories.Parties.Count > 0; @@ -127,6 +126,13 @@ private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAn indent.AppendLine("/// a reference to a payload field, so the caller never restates a party that's"); indent.AppendLine("/// already in the record."); } + else if (signatories.Source == DamlPartySource.Static) + { + indent.AppendLine("/// The submitter is passed explicitly via . The Daml"); + indent.AppendLine("/// signatory clause resolved to an empty set, so there is no payload party"); + indent.AppendLine("/// to derive. implicitly converts from a single"); + indent.AppendLine("/// Party, so single-party callers still pass one literal."); + } else { indent.AppendLine("/// The submitter is passed explicitly via . The static"); @@ -165,8 +171,6 @@ private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAn if (staticParties) { - indent.AppendLine("// Each Daml signatory is a payload field; the wrapper builds a SubmitterInfo"); - indent.AppendLine("// from those Party properties so the caller never restates a party."); if (multipleStatic) { indent.AppendLine($"var submitter = new {context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)}(new {context.Qualifier.Qualify("HashSet", context.RootNamespace)}<{context.Qualifier.Qualify(RuntimeTypeNames.Party, context.RootNamespace)}>"); @@ -174,20 +178,26 @@ private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAn indent.Indent(); for (var i = 0; i < signatories.Parties.Count; i++) { - if (signatories.Parties[i] is DamlPartyPayloadField pf) + if (signatories.Parties[i] is not DamlPartyPayloadField pf) { - var prop = Identifiers.ToPascalCase(Identifiers.Sanitize(pf.FieldName)); - var comma = i < signatories.Parties.Count - 1 ? "," : ""; - indent.AppendLine($"payload.{prop}{comma}"); + throw NotAPayloadField(templateName, "signatory", i, signatories.Parties[i]); } + + var prop = Identifiers.MemberName(pf.FieldName, className); + var comma = i < signatories.Parties.Count - 1 ? "," : ""; + indent.AppendLine($"payload.{prop}{comma}"); } indent.Dedent(); indent.AppendLine("});"); } else { - var pf = (DamlPartyPayloadField)signatories.Parties[0]; - var prop = Identifiers.ToPascalCase(Identifiers.Sanitize(pf.FieldName)); + if (signatories.Parties[0] is not DamlPartyPayloadField pf) + { + throw NotAPayloadField(templateName, "signatory", 0, signatories.Parties[0]); + } + + var prop = Identifiers.MemberName(pf.FieldName, className); indent.AppendLine($"{context.Qualifier.Qualify(RuntimeTypeNames.SubmitterInfo, context.RootNamespace)} submitter = payload.{prop};"); } indent.AppendLine(); @@ -204,7 +214,7 @@ private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAn /// when the template's observer clause resolves statically to a /// non-empty set of payload-field references. Returns an /// derived from the payload's - /// PascalCased Party properties so callers can inspect the observer + /// Party properties so callers can inspect the observer /// set directly. /// /// @@ -216,7 +226,7 @@ private void WriteCreateAsync(IndentWriter indent, string className, DamlPartyAn /// wrappers, which is handled in ChoiceResults.cs. /// /// - private void TryWriteObserversHelper(IndentWriter indent, string className, DamlPartyAnalysis observers) + private void TryWriteObserversHelper(IndentWriter indent, string templateName, string className, DamlPartyAnalysis observers) { if (observers.Source != DamlPartySource.Static || observers.Parties.Count == 0) { @@ -232,8 +242,7 @@ private void TryWriteObserversHelper(IndentWriter indent, string className, Daml indent.AppendLine("/// the parties named in the Daml observer clause, derived from"); indent.AppendLine("/// the payload's Party properties in declaration order. Useful"); indent.AppendLine("/// for inspecting / asserting the observer set without exercising a"); - indent.AppendLine("/// choice. The same set is contributed to SubmitterInfo.readAs"); - indent.AppendLine("/// on the emitted <Choice>Async wrappers."); + indent.AppendLine("/// choice."); indent.AppendLine("/// "); indent.AppendLine("/// The contract payload."); } @@ -247,12 +256,14 @@ private void TryWriteObserversHelper(IndentWriter indent, string className, Daml indent.Indent(); for (var i = 0; i < observers.Parties.Count; i++) { - if (observers.Parties[i] is DamlPartyPayloadField pf) + if (observers.Parties[i] is not DamlPartyPayloadField pf) { - var prop = Identifiers.ToPascalCase(Identifiers.Sanitize(pf.FieldName)); - var comma = i < observers.Parties.Count - 1 ? "," : ""; - indent.AppendLine($"payload.{prop}{comma}"); + throw NotAPayloadField(templateName, "observer", i, observers.Parties[i]); } + + var prop = Identifiers.MemberName(pf.FieldName, className); + var comma = i < observers.Parties.Count - 1 ? "," : ""; + indent.AppendLine($"payload.{prop}{comma}"); } indent.Dedent(); indent.AppendLine("};"); @@ -260,14 +271,10 @@ private void TryWriteObserversHelper(IndentWriter indent, string className, Daml indent.AppendLine("}"); } - private static void RequireAsyncExerciserNamespaces(IndentWriter indent) - { - indent.Require("System"); - indent.Require("System.Threading"); - indent.Require("System.Threading.Tasks"); - indent.Require(LedgerNamespaces.Abstractions); - indent.Require(RuntimeNamespaces.Commands); - indent.Require(RuntimeNamespaces.Contracts); - indent.Require(RuntimeNamespaces.Outcomes); - } + private static CodegenException NotAPayloadField( + string templateName, string clause, int index, DamlPartyReference party) => + new($"Daml template {templateName} carries a static {clause} set whose element {index} is a " + + $"{party.GetType().Name} reference rather than a payload field. ValidatePayloadParties demotes " + + "an analysis holding any non-payload-field reference to Dynamic, so this emitter should never " + + "reach one; skipping the element would derive a party set narrower than the clause names."); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/TemplateEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/TemplateEmitter.cs index 8eda0a9..9f36dfa 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/TemplateEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/TemplateEmitter.cs @@ -1,7 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using RuntimeNamespaces = Daml.Runtime.RuntimeNamespaces; namespace Daml.Codegen.CSharp.CodeGen; @@ -9,10 +11,11 @@ namespace Daml.Codegen.CSharp.CodeGen; /// /// Emits the C# for a Daml template: the sealed template record with its /// facet (plus the optional -/// and IUpgradeable -/// facets, plus one IImplements per implemented interface), the static -/// template metadata, the throwing -/// contract-key accessor, the nested ContractId / Contract records, +/// IUpgradeable facet, plus one IImplements per implemented +/// interface, plus IHasKey and its static Key witness when the +/// template declares a contract key), the static template metadata, the nested ContractId / +/// Contract records — the latter carrying the contract key read off the +/// created event when the template declares one — /// and the namespace-level choice / submission extension surface. The /// field-bearing serialization surface (constructor parameters, properties, /// ToRecord / FromRecord) is delegated to the shared @@ -22,22 +25,34 @@ namespace Daml.Codegen.CSharp.CodeGen; /// instances the sibling emitters use, so record, template, and choice output stay /// byte-identical. Constructed once per package over the package's /// , the DAR-scoped , -/// the package's , those three composed emitters, and the -/// shared . The caller owns the file scaffold and the +/// those three composed emitters, and the shared . The +/// contract-key slot is mapped through its own over a +/// , so it does not share the sibling emitters' +/// unqualified names. The caller owns the file scaffold and the /// common usings; this emitter writes the template body into the provided /// . /// -internal sealed class TemplateEmitter( +internal sealed partial class TemplateEmitter( PackageEmitContext context, ICrossPackageResolver resolver, - DamlTypeMapper mapper, RecordSerializationEmitter recordSerialization, ChoiceEmitter choiceEmitter, SubmissionExtensionsEmitter submissionExtensions, - CodeGenOptions options) + CodeGenOptions options, + ILogger? logger = null) { + private const string NestedContractIdTypeName = "ContractId"; + private const string NestedContractTypeName = "Contract"; + private const string KeyMemberName = "Key"; + private const string KeyEncoderMemberName = "KeyEncoder"; + private const string KeyEncoderParameterName = "key"; + private const string KeyDecoderMemberName = "KeyDecoder"; + private const string KeyDecoderParameterName = "value"; + + private readonly ILogger _log = logger ?? NullLogger.Instance; + /// - /// Writes the template record, its static metadata, the optional key accessor, + /// Writes the template record, its static metadata, /// the serialization round-trip, the nested ContractId / Contract /// records, and the sibling choice / submission extension classes for /// into . @@ -60,16 +75,26 @@ internal void WriteTemplateType( } var className = EmitterHelpers.SanitizeIdentifier(template.Name); + if (className is NestedContractIdTypeName or NestedContractTypeName) + { + throw new CodegenException( + $"Daml template {module.Name}:{template.Name} maps to the C# type '{className}', which is also the name of a record this emitter nests inside it. " + + "A nested type may not share the name of its enclosing type (CS0542), so the generated code would not compile. " + + "Rename the template in the Daml model."); + } + indent.CurrentTypeName = className; - var keyType = template.Key is not null ? mapper.MapType(template.Key) : null; + var keyWitness = DescribeKeyWitness(className, template.Key, fields); + var interfacesList = new List { context.Qualifier.Qualify(RuntimeTypeNames.ITemplate, context.RootNamespace) }; - if (keyType is not null) - interfacesList.Add($"{context.Qualifier.Qualify(RuntimeTypeNames.IHasKey, context.RootNamespace)}<{keyType}>"); if (package.UpgradedPackageId is not null) interfacesList.Add(context.Qualifier.Qualify(RuntimeTypeNames.IUpgradeable, context.RootNamespace)); foreach (var implemented in template.Implements) interfacesList.Add($"{context.Qualifier.Qualify(RuntimeTypeNames.IImplements, context.RootNamespace)}<{resolver.Resolve(implemented, context)}>"); + if (keyWitness is not null) + interfacesList.Add(keyWitness.FacetType); + interfacesList.Add($"{context.Qualifier.Qualify(RuntimeTypeNames.IDamlRecord, context.RootNamespace)}<{className}>"); var interfaces = string.Join(", ", interfacesList); if (options.UseRecordTypes && options.UsePrimaryConstructors && fields.Count > 0) @@ -92,9 +117,9 @@ internal void WriteTemplateType( WriteTemplateMetadata(indent, package, module, template); - if (template.Key is not null) + if (keyWitness is not null) { - WriteKeyProperty(indent, template.Key); + WriteKeyWitness(indent, module, template, keyWitness); } if (!options.UsePrimaryConstructors || !options.UseRecordTypes) @@ -107,8 +132,10 @@ internal void WriteTemplateType( choiceEmitter.WriteChoiceDescriptors(indent, template); + choiceEmitter.WriteChoiceByKeyCommandBuilders(indent, template, className, dataTypes); + WriteContractIdClass(indent, className); - WriteContractClass(indent, className); + WriteContractClass(indent, className, template.Key); indent.Dedent(); indent.AppendLine("}"); @@ -215,39 +242,99 @@ private void WriteTemplateMetadata( } } - private static string ToCrefTypeArgument(string csharpType) => - csharpType - .Replace("global::", string.Empty, StringComparison.Ordinal) - .Replace('<', '{') - .Replace('>', '}'); + /// + /// The resolved pieces of a keyed template's IHasKey facet: the facet named in the + /// base list, the descriptor type the witness is declared as, the encode expression over a + /// lambda parameter named key, the decode expression over a lambda parameter named + /// value, and whichever declaration already takes the C# member name Key on + /// the template record. + /// + private sealed record KeyWitness( + string FacetType, + string DescriptorType, + string Encoder, + string Decoder, + bool TemplateNameTakesTheMemberName, + string? FieldTakingTheMemberName) + { + public bool MemberNameIsTaken => + TemplateNameTakesTheMemberName || FieldTakingTheMemberName is not null; + } - private void WriteKeyProperty(IndentWriter indent, DamlType keyType) + private KeyWitness? DescribeKeyWitness( + string className, + DamlType? keyType, + IReadOnlyList fields) + { + if (keyType is null) + { + return null; + } + + var typeArguments = $"<{className}, {PackageQualifiedMapper.MapType(keyType)}>"; + var fieldTakingTheMemberName = fields.FirstOrDefault( + field => Identifiers.MemberName(field.Name, className) == KeyMemberName); + + return new KeyWitness( + $"{context.Qualifier.Qualify(RuntimeTypeNames.IHasKey, context.RootNamespace)}{typeArguments}", + $"{context.Qualifier.Qualify(RuntimeTypeNames.KeyDescriptor, context.RootNamespace)}{typeArguments}", + PackageQualifiedMapper.ToValue(keyType, KeyEncoderParameterName), + PackageQualifiedMapper.FromValue(keyType, KeyDecoderParameterName), + className == KeyMemberName, + fieldTakingTheMemberName?.Name); + } + + /// + /// Writes the keyed template's static Key witness. When the member name is already + /// taken — by a payload field mapping to it (CS0102), or by the template record itself + /// being named Key (CS0542) — only the explicit interface implementation is written. + /// It declares no member name of its own, so the facet stays reachable through a generic + /// constraint while the template keeps the name it had. + /// + private void WriteKeyWitness( + IndentWriter indent, + DamlModule module, + DamlTemplate template, + KeyWitness witness) { indent.Require(RuntimeNamespaces.Contracts); - StdlibPackages.RequireForFieldType(resolver, indent, keyType); - var csharpKeyType = mapper.MapType(keyType); - var crefKeyType = ToCrefTypeArgument(csharpKeyType); - - // Translating the template's `key` Daml expression to a C# projection is - // not yet implemented; the intermediate model carries only the key type, not - // the expression. Until that projection (or an upstream Daml-LF projection) - // lands, the accessor throws. This deliberately reverts the earlier - // partial-property contract: its CS9248 compile gate blocked the automated - // DAR publish pipeline, which has no human to supply an implementing partial. + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, template.Key!); + + if (witness.MemberNameIsTaken) + { + if (witness.FieldTakingTheMemberName is { } fieldName) + { + LogKeyFieldTakesTheWitnessName(_log, module.Name, template.Name, fieldName); + } + else + { + LogTemplateNameTakesTheWitnessName(_log, module.Name, template.Name); + } + + indent.AppendLine($"static {witness.DescriptorType} {witness.FacetType}.{KeyMemberName} {{ get; }} ="); + WriteKeyWitnessInitializer(indent, witness); + return; + } + if (options.GenerateXmlDocs) { - indent.AppendLine("/// "); - indent.AppendLine($"/// Gets the contract key of type {crefKeyType}, satisfying ."); - indent.AppendLine("/// "); - indent.AppendLine("/// "); - indent.AppendLine("/// Throws : the codegen does not"); - indent.AppendLine("/// yet translate the template's key expression into a C# projection."); - indent.AppendLine("/// The key type is fully generated and serializable — construct a key value"); - indent.AppendLine("/// explicitly for key-based ledger operations rather than reading it here."); - indent.AppendLine("/// "); + indent.AppendLine("/// Gets the witness pairing this template with its contract key type and carrying the key codec; passing it to a generic method infers both type parameters from one argument."); } - indent.AppendLine($"public {csharpKeyType} Key => throw new global::System.NotImplementedException("); - indent.AppendLine($" \"Contract-key projection is not generated yet by daml-codegen-csharp; construct the {csharpKeyType} key value explicitly for key-based ledger operations.\");"); + indent.AppendLine($"public static {witness.DescriptorType} {KeyMemberName} {{ get; }} ="); + WriteKeyWitnessInitializer(indent, witness); + } + + private static void WriteKeyWitnessInitializer(IndentWriter indent, KeyWitness witness) + { + indent.Indent(); + indent.AppendLine("new()"); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"{KeyEncoderMemberName} = {KeyEncoderParameterName} => {witness.Encoder},"); + indent.AppendLine($"{KeyDecoderMemberName} = {KeyDecoderParameterName} => {witness.Decoder},"); + indent.Dedent(); + indent.AppendLine("};"); + indent.Dedent(); indent.AppendLine(); } @@ -257,7 +344,8 @@ private void WriteContractIdClass(IndentWriter indent, string className) indent.Require(RuntimeNamespaces.Contracts); if (options.GenerateXmlDocs) indent.AppendLine($"/// Contract ID for {className}."); - indent.AppendLine($"public sealed record ContractId(string Value) : {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{className}>(Value), {context.Qualifier.Qualify(RuntimeTypeNames.IExercises, context.RootNamespace)}<{className}>"); + indent.AppendLine("[global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))]"); + indent.AppendLine($"public sealed record {NestedContractIdTypeName}(string Value) : {context.Qualifier.Qualify(RuntimeTypeNames.ContractId, context.RootNamespace)}<{className}>(Value), {context.Qualifier.Qualify(RuntimeTypeNames.IExercises, context.RootNamespace)}<{className}>"); indent.AppendLine("{"); indent.Indent(); @@ -268,23 +356,95 @@ private void WriteContractIdClass(IndentWriter indent, string className) indent.AppendLine(); } - private void WriteContractClass(IndentWriter indent, string className) + private void WriteContractClass(IndentWriter indent, string className, DamlType? keyType) { indent.Require(RuntimeNamespaces.Contracts); + if (keyType is not null) + { + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, keyType); + } + + var contractKeyType = keyType is null + ? null + : $"{context.Qualifier.Qualify(RuntimeTypeNames.ContractKey, context.RootNamespace)}<{PackageQualifiedMapper.MapType(keyType)}>"; + if (options.GenerateXmlDocs) indent.AppendLine($"/// Active contract for {className}."); - indent.AppendLine($"public sealed record Contract(ContractId Id, {className} Data) : {context.Qualifier.Qualify(RuntimeTypeNames.IContract, context.RootNamespace)}"); + indent.AppendLine($"public sealed record {NestedContractTypeName}({NestedContractIdTypeName} Id, {className} Data) : {context.Qualifier.Qualify(RuntimeTypeNames.IContract, context.RootNamespace)}<{NestedContractIdTypeName}, {className}>"); indent.AppendLine("{"); indent.Indent(); + if (contractKeyType is not null) + { + if (options.GenerateXmlDocs) + indent.AppendLine("/// The contract key read off the created event, decoded and paired with the ledger's hash of it."); + indent.AppendLine($"public required {contractKeyType} Key {{ get; init; }}"); + indent.AppendLine(); + } + if (options.GenerateXmlDocs) - indent.AppendLine("/// Creates a Contract from a CreatedEvent."); - indent.AppendLine($"public static Contract FromCreatedEvent({context.Qualifier.Qualify(RuntimeTypeNames.CreatedEvent, context.RootNamespace)} @event) =>"); + indent.AppendLine($"/// Creates a {NestedContractTypeName} from a CreatedEvent."); + indent.AppendLine($"public static {NestedContractTypeName} FromCreatedEvent({context.Qualifier.Qualify(RuntimeTypeNames.CreatedEvent, context.RootNamespace)} @event) =>"); indent.Indent(); - indent.AppendLine($"new(new ContractId(@event.ContractId), {className}.FromRecord(@event.CreateArguments));"); + if (keyType is null) + { + indent.AppendLine($"new(new {NestedContractIdTypeName}(@event.ContractId), {QualifyInPackage(className)}.FromRecord(@event.CreateArguments));"); + } + else + { + indent.AppendLine("new("); + indent.Indent(); + indent.AppendLine($"new {NestedContractIdTypeName}(@event.ContractId),"); + indent.AppendLine($"{QualifyInPackage(className)}.FromRecord(@event.CreateArguments))"); + indent.Dedent(); + indent.AppendLine("{"); + indent.Indent(); + indent.AppendLine($"Key = @event.ContractKey is {{ }} contractKey"); + indent.Indent(); + indent.AppendLine($"? new {contractKeyType}({PackageQualifiedMapper.FromValue(keyType, "contractKey.Value")}, contractKey.KeyHash)"); + indent.AppendLine($": throw new global::System.InvalidOperationException(\"The created event for contract '\" + @event.ContractId + \"' of keyed template {className} carried no contract key, so the contract key cannot be populated.\"),"); + indent.Dedent(); + indent.Dedent(); + indent.AppendLine("};"); + } indent.Dedent(); indent.Dedent(); indent.AppendLine("}"); } + + /// + /// Maps the contract-key slot and its decoder. The active contract nests Contract + /// and ContractId records and declares Id / Data / Key + /// members, any of which binds ahead of a package type the key names, so every in-package + /// name in the key slot is resolved global::-qualified. + /// + private DamlTypeMapper PackageQualifiedMapper => + _packageQualifiedMapper ??= new DamlTypeMapper(context, new PackageQualifiedResolver(resolver)); + + private DamlTypeMapper? _packageQualifiedMapper; + + /// + /// Prefixes a bare in-package type name with its global::-qualified namespace, + /// so the Id / Data / Key properties of the active contract cannot + /// shadow a type a decoder names. A name that already carries a dot was emitted with its + /// own qualifier by the cross-package resolver and is left alone. Mirrors the same guard + /// the choice-result projector applies for the same reason. + /// + private string QualifyInPackage(string typeName) => + typeName.Contains('.', StringComparison.Ordinal) + ? typeName + : $"global::{context.RootNamespace}.{typeName}"; + + [LoggerMessage( + EventId = 1300, + Level = LogLevel.Warning, + Message = "Template {ModuleName}:{TemplateName} has a field {FieldName} whose C# member is named Key, so the contract-key witness is emitted as an explicit IHasKey implementation only. Reach it through a generic constraint on IHasKey, not through the template type directly.")] + private static partial void LogKeyFieldTakesTheWitnessName(ILogger logger, string moduleName, string templateName, string fieldName); + + [LoggerMessage( + EventId = 1301, + Level = LogLevel.Warning, + Message = "Template {ModuleName}:{TemplateName} maps to a C# type named Key, which a member may not share, so the contract-key witness is emitted as an explicit IHasKey implementation only. Reach it through a generic constraint on IHasKey, not through the template type directly.")] + private static partial void LogTemplateNameTakesTheWitnessName(ILogger logger, string moduleName, string templateName); } diff --git a/src/Daml.Codegen.CSharp/CodeGen/TypeReferenceQualifier.cs b/src/Daml.Codegen.CSharp/CodeGen/TypeReferenceQualifier.cs index bee0c2e..ad78b40 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/TypeReferenceQualifier.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/TypeReferenceQualifier.cs @@ -20,10 +20,12 @@ internal sealed class TypeReferenceQualifier { private const string GlobalPrefix = "global::"; - // ImplicitUsings imports System into every generated file, so a stdlib type - // sharing a BCL simple name (DayOfWeek vs System.DayOfWeek) is an ambiguous - // reference (CS0104) regardless of generated-namespace shadowing. These names - // are always emitted global::-qualified. + /// + /// Implicit usings import System into every generated file, so a stdlib type sharing a + /// BCL simple name — DayOfWeek against System.DayOfWeek — is an ambiguous + /// reference (CS0104) no matter how the generated namespace shadows it. These names are + /// always emitted global::-qualified. + /// private static readonly IReadOnlySet NamesCollidingWithImplicitBclImports = new HashSet(StringComparer.Ordinal) { RuntimeTypeNames.DayOfWeek }; @@ -66,12 +68,15 @@ internal sealed class TypeReferenceQualifier [RuntimeTypeNames.SubmitterInfo] = RuntimeNamespaces.Commands, [RuntimeTypeNames.IExercises] = RuntimeNamespaces.Commands, [RuntimeTypeNames.ExerciseCommand] = RuntimeNamespaces.Commands, + [RuntimeTypeNames.ExerciseByKeyCommand] = RuntimeNamespaces.Commands, [RuntimeTypeNames.CommandsSubmission] = RuntimeNamespaces.Commands, [RuntimeTypeNames.WorkflowId] = RuntimeNamespaces.Commands, [RuntimeTypeNames.CommandId] = RuntimeNamespaces.Commands, [RuntimeTypeNames.ChoiceName] = RuntimeNamespaces.Commands, [RuntimeTypeNames.IDamlInterface] = RuntimeNamespaces.Contracts, [RuntimeTypeNames.IHasView] = RuntimeNamespaces.Contracts, + [RuntimeTypeNames.ViewDescriptor] = RuntimeNamespaces.Contracts, + [RuntimeTypeNames.KeyDescriptor] = RuntimeNamespaces.Contracts, [RuntimeTypeNames.IImplements] = RuntimeNamespaces.Contracts, [RuntimeTypeNames.CreatedEvent] = RuntimeNamespaces.Contracts, [RuntimeTypeNames.DamlTypeDescriptor] = RuntimeNamespaces.Contracts, @@ -86,6 +91,7 @@ internal sealed class TypeReferenceQualifier [RuntimeTypeNames.NonEmpty] = RuntimeNamespaces.Stdlib, [RuntimeTypeNames.Map] = RuntimeNamespaces.Stdlib, [RuntimeTypeNames.Unit] = RuntimeNamespaces.Stdlib, + [RuntimeTypeNames.Optional] = RuntimeNamespaces.Stdlib, [RuntimeTypeNames.GenericStub] = RuntimeNamespaces.Stdlib, [RuntimeTypeNames.ILedgerClient] = LedgerNamespaces.Abstractions, [RuntimeTypeNames.ILedgerWriter] = LedgerNamespaces.Abstractions, diff --git a/src/Daml.Codegen.CSharp/CodeGen/VariantEmitter.cs b/src/Daml.Codegen.CSharp/CodeGen/VariantEmitter.cs index a130166..c7cd22f 100644 --- a/src/Daml.Codegen.CSharp/CodeGen/VariantEmitter.cs +++ b/src/Daml.Codegen.CSharp/CodeGen/VariantEmitter.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.CodeGen; @@ -30,6 +30,7 @@ internal void WriteVariantType(IndentWriter indent, DamlDataType dataType, DamlV indent.Require("System"); var className = EmitterHelpers.SanitizeIdentifier(dataType.Name); var typeParams = EmitterHelpers.GetTypeParametersDeclaration(dataType.TypeParams); + var typeParamConstraints = EmitterHelpers.GetTypeParameterConstraints(dataType.TypeParams); var fullClassName = $"{className}{typeParams}"; if (options.GenerateXmlDocs) @@ -51,7 +52,7 @@ internal void WriteVariantType(IndentWriter indent, DamlDataType dataType, DamlV var delegates = EmitterHelpers.ConverterNameMap(dataType.TypeParams); var variantInterface = InterfaceDeclaration(dataType.TypeParams); - indent.AppendLine($"public abstract record {fullClassName}{variantInterface}"); + indent.AppendLine($"public abstract record {fullClassName}{variantInterface}{typeParamConstraints}"); indent.AppendLine("{"); indent.Indent(); @@ -104,7 +105,7 @@ internal void WriteVariantType(IndentWriter indent, DamlDataType dataType, DamlV if (argType is not null) { - StdlibPackages.RequireForFieldType(resolver, indent, ctor.ArgumentType!); + StdlibPackages.RequireForFieldType(resolver, context.Package, indent, ctor.ArgumentType!); if (options.GenerateXmlDocs) { indent.AppendLine($"/// {ctor.Name} constructor."); diff --git a/src/Daml.Codegen.CSharp/CodegenException.cs b/src/Daml.Codegen.CSharp/CodegenException.cs new file mode 100644 index 0000000..79c251d --- /dev/null +++ b/src/Daml.Codegen.CSharp/CodegenException.cs @@ -0,0 +1,30 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +namespace Daml.Codegen.CSharp; + +/// +/// Thrown when the C# code generator encounters a Daml construct it cannot emit +/// correct code for. Generation fails loudly at emit time instead of writing a +/// silent placeholder (such as a default! expression or an empty stub +/// record) into shipped generated code. +/// +public sealed class CodegenException : Exception +{ + /// Creates the exception without a message. + public CodegenException() + { + } + + /// Creates the exception with a message describing the unmappable construct. + public CodegenException(string message) + : base(message) + { + } + + /// Creates the exception with a message describing the unmappable construct and the underlying cause. + public CodegenException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Daml.Codegen.CSharp/ConsoleLogger.cs b/src/Daml.Codegen.CSharp/ConsoleLogger.cs deleted file mode 100644 index c3de7bf..0000000 --- a/src/Daml.Codegen.CSharp/ConsoleLogger.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -namespace Daml.Codegen.CSharp; - -/// -/// Verbosity-aware console logger used by CSharpCodeGenerator and -/// the CLI. Writes to directly (no Spectre or other -/// CLI-only dependency) so the emitter library remains free of UI -/// dependencies. Verbosity levels: 0 = errors only, 1 = warnings, 2 = info, -/// 3 = debug. -/// -public sealed class ConsoleLogger(int verbosity) : ICodegenLogger -{ - /// Writes an ERROR line to stderr; always shown regardless of verbosity. - public void Error(string message) => - Console.Error.WriteLine($"ERROR: {message}"); - - /// Writes a WARN line to stderr when verbosity is 1 or higher. - public void Warning(string message) - { - if (verbosity >= 1) - { - Console.Error.WriteLine($"WARN: {message}"); - } - } - - /// Writes an INFO line to stdout when verbosity is 2 or higher. - public void Info(string message) - { - if (verbosity >= 2) - { - Console.WriteLine($"INFO: {message}"); - } - } - - /// Writes a DEBUG line to stdout when verbosity is 3 or higher. - public void Debug(string message) - { - if (verbosity >= 3) - { - Console.WriteLine($"DEBUG: {message}"); - } - } -} diff --git a/src/Daml.Codegen.CSharp/Daml.Codegen.CSharp.csproj b/src/Daml.Codegen.CSharp/Daml.Codegen.CSharp.csproj index 02dd727..5cb1bfc 100644 --- a/src/Daml.Codegen.CSharp/Daml.Codegen.CSharp.csproj +++ b/src/Daml.Codegen.CSharp/Daml.Codegen.CSharp.csproj @@ -5,7 +5,6 @@ C# code generator library for Daml smart contracts. Consumes an IntermediateDar protobuf (see Daml.Codegen.Intermediate) and emits strongly-typed C# bindings. daml;canton;blockchain;smart-contracts;codegen README.md - true @@ -23,17 +22,13 @@ - - + + - - - - diff --git a/src/Daml.Codegen.CSharp/ICodegenLogger.cs b/src/Daml.Codegen.CSharp/ICodegenLogger.cs deleted file mode 100644 index 24fcffe..0000000 --- a/src/Daml.Codegen.CSharp/ICodegenLogger.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -namespace Daml.Codegen.CSharp; - -/// -/// Logging contract that writes -/// progress, warnings, and errors against. Implementations may surface -/// messages to a console (), capture them in -/// memory for tests, route them to a host application's logging -/// framework, or discard them entirely. Severity ordering and -/// verbosity-gating semantics are at the implementation's discretion. -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage( - "Naming", - "CA1716:Identifiers should not match keywords", - Justification = "Error/Warning/Info/Debug are the established Daml.Codegen.CSharp logging vocabulary and are not invokable as members from VB.NET hosts in practice.")] -public interface ICodegenLogger -{ - /// Logs an error. Always surfaced. - void Error(string message); - - /// Logs a warning. Surfaced unless the implementation gates on verbosity. - void Warning(string message); - - /// Logs an informational message. - void Info(string message); - - /// Logs a debug-level message; the noisiest tier. - void Debug(string message); -} diff --git a/src/Daml.Codegen.CSharp/IntermediateDarReader.cs b/src/Daml.Codegen.CSharp/IntermediateDarReader.cs index 3ec82f5..8870e4d 100644 --- a/src/Daml.Codegen.CSharp/IntermediateDarReader.cs +++ b/src/Daml.Codegen.CSharp/IntermediateDarReader.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Text.RegularExpressions; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using PbBuiltin = Daml.Codegen.Intermediate.BuiltinType; using PbChoice = Daml.Codegen.Intermediate.Choice; using PbDar = Daml.Codegen.Intermediate.IntermediateDar; @@ -110,8 +110,7 @@ private static DamlPackage ConvertPackage(PbPackage pkg) => private static DamlModule ConvertModule(PbModule module) { var dataTypes = module.DataTypes.Select(ConvertDataType).ToList(); - var dataTypesByName = dataTypes.ToDictionary(dt => dt.Name); - var templates = module.Templates.Select(t => ConvertTemplate(t, dataTypesByName)).ToList(); + var templates = module.Templates.Select(ConvertTemplate).ToList(); var interfaces = module.Interfaces.Select(ConvertInterface).ToList(); return new DamlModule @@ -167,17 +166,11 @@ private static DamlVariantConstructor ConvertVariantConstructor(PbField field) return new DamlVariantConstructor(RequireIdentifier(field.Name, "variant constructor"), ConvertType(field.Type)); } - private static DamlTemplate ConvertTemplate(PbTemplate template, IReadOnlyDictionary dataTypesByName) + private static DamlTemplate ConvertTemplate(PbTemplate template) { - var fields = dataTypesByName.TryGetValue(template.Name, out var dt) - && dt.Definition is DamlRecordDefinition recordDef - ? recordDef.Fields - : []; - return new DamlTemplate { Name = RequireDottedIdentifier(template.Name, "template name"), - Fields = fields, Choices = template.Choices.Select(ConvertChoice).ToList(), Key = template.KeyType is not null ? ConvertType(template.KeyType) : null, Implements = template.Implements.Select(ConvertTypeConRef).ToList(), diff --git a/src/Daml.Codegen.CSharp/README.md b/src/Daml.Codegen.CSharp/README.md index cdff995..99e8777 100644 --- a/src/Daml.Codegen.CSharp/README.md +++ b/src/Daml.Codegen.CSharp/README.md @@ -21,6 +21,7 @@ for example inside a build tool: using Daml.Codegen.CSharp; using Daml.Codegen.CSharp.CodeGen; using Daml.Codegen.Intermediate; +using Microsoft.Extensions.Logging; IntermediateDar proto; await using (var stream = File.OpenRead("intermediate.binpb")) @@ -29,8 +30,14 @@ await using (var stream = File.OpenRead("intermediate.binpb")) } var dar = IntermediateDarReader.Read(proto); -var logger = new ConsoleLogger(verbosity: 1); -var generator = new CSharpCodeGenerator(new CodeGenOptions(), logger); + +using var loggerFactory = LoggerFactory.Create(builder => + builder.AddProvider(new VerbosityConsoleLoggerProvider(verbosity: 1)) + .SetMinimumLevel(LogLevel.Trace)); + +var generator = new CSharpCodeGenerator( + new CodeGenOptions(), + loggerFactory.CreateLogger()); var files = generator.Generate(dar); foreach (var file in files) @@ -39,6 +46,20 @@ foreach (var file in files) } ``` +## Logging + +`CSharpCodeGenerator` reports progress and warnings through +`Microsoft.Extensions.Logging`. The `logger` parameter is optional: omit it and +the generator stays silent, so a host already wired for logging simply hands it +an `ILogger` from its own factory. + +`VerbosityConsoleLoggerProvider` is included for hosts that just want +severity-prefixed console output — errors and warnings on stderr, everything +else on stdout. Its verbosity maps to a minimum level: `0` errors only, `1` +warnings, `2` information, `3` or more debug. Building a factory around it, as +the sample above does, needs the `Microsoft.Extensions.Logging` package; the +library itself depends only on `Microsoft.Extensions.Logging.Abstractions`. + Generated code targets the lockstep-versioned `Daml.Runtime` and `Daml.Ledger.Abstractions` packages. diff --git a/src/Daml.Codegen.CSharp/VerbosityConsoleLoggerProvider.cs b/src/Daml.Codegen.CSharp/VerbosityConsoleLoggerProvider.cs new file mode 100644 index 0000000..562135a --- /dev/null +++ b/src/Daml.Codegen.CSharp/VerbosityConsoleLoggerProvider.cs @@ -0,0 +1,76 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Daml.Codegen.CSharp; + +/// +/// An that writes single-line, severity-prefixed records to the +/// console: errors and warnings to , informational and debug records to +/// . Intended for the command-line entry points; library code takes an +/// and defaults to NullLogger, so nothing is written unless a host +/// opts in. +/// +/// +/// Writes synchronously rather than through a background queue, so a caller that redirects the +/// console streams sees every record the moment the logging call returns. Verbosity maps onto +/// as 0 = errors only, 1 = warnings, 2 = information, 3 = debug. +/// +public sealed class VerbosityConsoleLoggerProvider : ILoggerProvider +{ + private readonly LogLevel _minimumLevel; + + /// Creates a provider gated at the level implied by . + /// 0 = errors only, 1 = warnings, 2 = information, 3 or more = debug. + public VerbosityConsoleLoggerProvider(int verbosity) => + _minimumLevel = verbosity switch + { + <= 0 => LogLevel.Error, + 1 => LogLevel.Warning, + 2 => LogLevel.Information, + _ => LogLevel.Debug, + }; + + /// + public ILogger CreateLogger(string categoryName) => new VerbosityConsoleLogger(_minimumLevel); + + /// + public void Dispose() + { + } + + private sealed class VerbosityConsoleLogger(LogLevel minimumLevel) : ILogger + { + public IDisposable? BeginScope(TState state) + where TState : notnull => null; + + public bool IsEnabled(LogLevel logLevel) => logLevel >= minimumLevel && logLevel != LogLevel.None; + + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + ArgumentNullException.ThrowIfNull(formatter); + var message = formatter(state, exception); + var writer = logLevel >= LogLevel.Warning ? Console.Error : Console.Out; + writer.WriteLine($"{Prefix(logLevel)}: {message}"); + } + + private static string Prefix(LogLevel logLevel) => logLevel switch + { + LogLevel.Critical or LogLevel.Error => "ERROR", + LogLevel.Warning => "WARN", + LogLevel.Information => "INFO", + _ => "DEBUG", + }; + } +} diff --git a/src/Daml.Codegen.CSharp/Model/FourPartPackageVersion.cs b/src/Daml.Codegen.CSharp/Versioning/FourPartPackageVersion.cs similarity index 96% rename from src/Daml.Codegen.CSharp/Model/FourPartPackageVersion.cs rename to src/Daml.Codegen.CSharp/Versioning/FourPartPackageVersion.cs index dc16db7..3116d3b 100644 --- a/src/Daml.Codegen.CSharp/Model/FourPartPackageVersion.cs +++ b/src/Daml.Codegen.CSharp/Versioning/FourPartPackageVersion.cs @@ -3,7 +3,7 @@ using System.Globalization; -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.CSharp.Versioning; /// /// 4-part NuGet version Major.Minor.Patch.Generation, optionally carrying a @@ -24,7 +24,7 @@ internal readonly record struct FourPartPackageVersion( { /// /// Lifts a 3-part DAR-intrinsic (as produced by - /// ) into a 4-part version by + /// ) into a 4-part version by /// attaching the supplied as segment 4 and, /// when supplied, the SemVer (without /// a leading dash). diff --git a/src/Daml.Codegen.CSharp/Versioning/NuGetVersionResolver.cs b/src/Daml.Codegen.CSharp/Versioning/NuGetVersionResolver.cs index 6685198..650ff64 100644 --- a/src/Daml.Codegen.CSharp/Versioning/NuGetVersionResolver.cs +++ b/src/Daml.Codegen.CSharp/Versioning/NuGetVersionResolver.cs @@ -1,8 +1,6 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; - namespace Daml.Codegen.CSharp.Versioning; /// diff --git a/src/Daml.Codegen.Intermediate/Daml.Codegen.Intermediate.csproj b/src/Daml.Codegen.Intermediate/Daml.Codegen.Intermediate.csproj new file mode 100644 index 0000000..3e37cbc --- /dev/null +++ b/src/Daml.Codegen.Intermediate/Daml.Codegen.Intermediate.csproj @@ -0,0 +1,23 @@ + + + + Daml.Codegen.Intermediate + Intermediate DAR contract for Daml C# codegen. Carries the generated IntermediateDar protobuf types and the language-neutral Daml model shared by producers of the intermediate representation and the C# emitter that consumes it. + daml;canton;blockchain;smart-contracts;codegen;protobuf + README.md + + + + + + + + + + + + + + + + diff --git a/src/Daml.Codegen.CSharp/Model/DamlDataType.cs b/src/Daml.Codegen.Intermediate/Model/DamlDataType.cs similarity index 97% rename from src/Daml.Codegen.CSharp/Model/DamlDataType.cs rename to src/Daml.Codegen.Intermediate/Model/DamlDataType.cs index 4b53514..50e2ce5 100644 --- a/src/Daml.Codegen.CSharp/Model/DamlDataType.cs +++ b/src/Daml.Codegen.Intermediate/Model/DamlDataType.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// Represents a user-defined Daml data type. diff --git a/src/Daml.Codegen.CSharp/Model/DamlPackage.cs b/src/Daml.Codegen.Intermediate/Model/DamlPackage.cs similarity index 98% rename from src/Daml.Codegen.CSharp/Model/DamlPackage.cs rename to src/Daml.Codegen.Intermediate/Model/DamlPackage.cs index c19068a..6da8827 100644 --- a/src/Daml.Codegen.CSharp/Model/DamlPackage.cs +++ b/src/Daml.Codegen.Intermediate/Model/DamlPackage.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// Represents a reference to a package dependency. diff --git a/src/Daml.Codegen.CSharp/Model/DamlTemplate.cs b/src/Daml.Codegen.Intermediate/Model/DamlTemplate.cs similarity index 91% rename from src/Daml.Codegen.CSharp/Model/DamlTemplate.cs rename to src/Daml.Codegen.Intermediate/Model/DamlTemplate.cs index 05f73ad..021c346 100644 --- a/src/Daml.Codegen.CSharp/Model/DamlTemplate.cs +++ b/src/Daml.Codegen.Intermediate/Model/DamlTemplate.cs @@ -1,23 +1,25 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// -/// Represents a Daml template. +/// Represents a Daml template. The payload fields are not carried here: in +/// Daml-LF a template's payload is always a record of the same name, so the +/// fields live on that record definition among the module's data types and +/// duplicating them here could only drift. Read them from the +/// entry whose +/// equals and whose +/// is a . /// public sealed class DamlTemplate { /// - /// Gets the template name. + /// Gets the template name. Doubles as the key that locates the template's + /// payload record among its module's data types. /// public required string Name { get; init; } - /// - /// Gets the template fields. - /// - public required IReadOnlyList Fields { get; init; } - /// /// Gets the choices defined on this template. /// diff --git a/src/Daml.Codegen.CSharp/Model/DamlType.cs b/src/Daml.Codegen.Intermediate/Model/DamlType.cs similarity index 53% rename from src/Daml.Codegen.CSharp/Model/DamlType.cs rename to src/Daml.Codegen.Intermediate/Model/DamlType.cs index d50cd55..0e0431f 100644 --- a/src/Daml.Codegen.CSharp/Model/DamlType.cs +++ b/src/Daml.Codegen.Intermediate/Model/DamlType.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// Represents a Daml type. @@ -22,6 +22,10 @@ public sealed record DamlPrimitiveType(DamlPrimitive Primitive) : DamlType; /// /// Enumeration of Daml primitive types. /// +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1720:Identifier contains type name", + Justification = "Members name Daml-LF builtin types; Int64 is the spelling the Daml-LF specification and the intermediate protobuf use.")] public enum DamlPrimitive { /// Daml () — the empty value. @@ -77,9 +81,58 @@ public sealed record DamlTypeApp(DamlType Base, IReadOnlyList Argument /// True when this application is Optional a, i.e. the base is the Optional primitive. public override bool IsOptional => Base is DamlPrimitiveType { Primitive: DamlPrimitive.Optional }; + + /// + /// Compares by value, including element by element. The + /// compiler-synthesized record equality would compare that list by reference, which + /// makes two independently-built but structurally identical type trees unequal. + /// + public bool Equals(DamlTypeApp? other) => + other is not null && Base.Equals(other.Base) && Arguments.SequenceEqual(other.Arguments); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(Base); + foreach (var argument in Arguments) + { + hash.Add(argument); + } + return hash.ToHashCode(); + } } /// /// A type variable. /// public sealed record DamlTypeVar(string Name) : DamlType; + +/// +/// A Daml Optional a in a position C# nullable syntax cannot carry, emitted as the +/// runtime wrapper rather than as t?. Produced only by the representation pre-pass, +/// which is the sole owner of the rule deciding which positions those are. +/// +/// The type the optional carries. +/// The wire encoding this position requires. +public sealed record DamlWrappedOptional(DamlType Argument, OptionalEncoding Encoding) : DamlType +{ + /// + public override bool IsOptional => true; +} + +/// +/// The wire encoding a Daml Optional carries, which depends on its position rather +/// than on its C# representation. +/// +public enum OptionalEncoding +{ + /// JSON null when absent, the bare value when present. + Flat, + + /// + /// JSON [] when absent, [v] when present — the form a participant requires + /// at every level of an Optional chain nested two or more levels deep. + /// + NestedChain +} diff --git a/src/Daml.Codegen.CSharp/Model/DarModel.cs b/src/Daml.Codegen.Intermediate/Model/DarModel.cs similarity index 67% rename from src/Daml.Codegen.CSharp/Model/DarModel.cs rename to src/Daml.Codegen.Intermediate/Model/DarModel.cs index 7470a4e..983c594 100644 --- a/src/Daml.Codegen.CSharp/Model/DarModel.cs +++ b/src/Daml.Codegen.Intermediate/Model/DarModel.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// In-memory representation of an IntermediateDar graph: a main @@ -17,12 +17,6 @@ public sealed class DarModel : IDarSource /// public required IReadOnlyList Dependencies { get; init; } - private Dictionary? _index; - /// - public DamlPackage? GetPackageById(string packageId) - { - _index ??= ((IDarSource)this).AllPackages.ToDictionary(p => p.PackageId); - return _index.GetValueOrDefault(packageId); - } + public DamlPackage? GetPackageById(string packageId) => IDarSource.FindPackageById(this, packageId); } diff --git a/src/Daml.Codegen.CSharp/Model/IDarSource.cs b/src/Daml.Codegen.Intermediate/Model/IDarSource.cs similarity index 50% rename from src/Daml.Codegen.CSharp/Model/IDarSource.cs rename to src/Daml.Codegen.Intermediate/Model/IDarSource.cs index 8c387a5..c2878ee 100644 --- a/src/Daml.Codegen.CSharp/Model/IDarSource.cs +++ b/src/Daml.Codegen.Intermediate/Model/IDarSource.cs @@ -1,13 +1,16 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// Emitter input contract — a main package plus dependency packages, with -/// per-package-id lookup. Implemented by the proto-direct -/// produced by IntermediateDarReader. Keeps the -/// emitter library decoupled from how the DAR is decoded. +/// per-package-id lookup. Two production adapters implement it: the +/// proto-direct , produced by +/// IntermediateDarReader from an IntermediateDar message, and the +/// DAR-direct DarArchive in Daml.Codegen.DarParser, produced by +/// reading a .dar file. Keeps the emitter library decoupled from how the +/// DAR is decoded. /// public interface IDarSource { @@ -21,5 +24,8 @@ public interface IDarSource IEnumerable AllPackages => Dependencies.Prepend(MainPackage); /// Returns the package with the given id, or null if absent. - DamlPackage? GetPackageById(string packageId); + DamlPackage? GetPackageById(string packageId) => FindPackageById(this, packageId); + + internal static DamlPackage? FindPackageById(IDarSource source, string packageId) => + source.AllPackages.FirstOrDefault(package => package.PackageId == packageId); } diff --git a/src/Daml.Codegen.CSharp/Model/PackageVersionParser.cs b/src/Daml.Codegen.Intermediate/Model/PackageVersionParser.cs similarity index 97% rename from src/Daml.Codegen.CSharp/Model/PackageVersionParser.cs rename to src/Daml.Codegen.Intermediate/Model/PackageVersionParser.cs index 260249f..173af3a 100644 --- a/src/Daml.Codegen.CSharp/Model/PackageVersionParser.cs +++ b/src/Daml.Codegen.Intermediate/Model/PackageVersionParser.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; -namespace Daml.Codegen.CSharp.Model; +namespace Daml.Codegen.Intermediate.Model; /// /// Parses a Daml package version string into a . The diff --git a/src/Daml.Codegen.Intermediate/README.md b/src/Daml.Codegen.Intermediate/README.md new file mode 100644 index 0000000..02f574d --- /dev/null +++ b/src/Daml.Codegen.Intermediate/README.md @@ -0,0 +1,35 @@ +# Daml.Codegen.Intermediate + +The intermediate DAR contract shared by every Daml C# codegen path. It carries +two things: + +- the C# types generated from `intermediate_dar.proto`, the versioned wire + format a DAR decoder produces, and +- the language-neutral Daml model (`DarModel`, `DamlPackage`, `DamlTemplate`, + `DamlType`, …) that both producers and the emitter speak. + +Producers write the protobuf; `Daml.Codegen.CSharp` reads it and emits C#. +Reference this package directly only when you write your own producer or your +own consumer of the intermediate representation. + +## Installation + +```bash +dotnet add package Daml.Codegen.Intermediate +``` + +## Usage + +```csharp +using Daml.Codegen.Intermediate; + +IntermediateDar proto; +await using (var stream = File.OpenRead("intermediate.binpb")) +{ + proto = IntermediateDar.Parser.ParseFrom(stream); +} +``` + +## License + +Apache-2.0. See the repository's LICENSE file. diff --git a/src/Daml.Codegen.Testing.Conformance/ConformanceCorpus.cs b/src/Daml.Codegen.Testing.Conformance/ConformanceCorpus.cs index 0ee2bf4..452502a 100644 --- a/src/Daml.Codegen.Testing.Conformance/ConformanceCorpus.cs +++ b/src/Daml.Codegen.Testing.Conformance/ConformanceCorpus.cs @@ -4,21 +4,63 @@ namespace Daml.Codegen.Testing.Conformance; /// -/// Access to the conformance corpus DAR embedded in this package. Consumers -/// upload this DAR to a participant before creating contracts of the corpus +/// One of the Daml packages whose generated types this assembly compiles and whose +/// DAR it embeds. +/// +public enum ConformancePackage +{ + /// + /// The type-shape corpus: primitives, collections, nominal types and the harder + /// type-system corners, built at Daml-LF 2.1. + /// + RichTypes, + + /// + /// The contract-key corpus: a record key built from several payload fields, a record + /// whose field comes from a projection nested inside a payload record, a record built + /// by a function declared in another module, and a bare Party key. Built at + /// Daml-LF 2.3, the earliest 2.x version that can express a contract key. + /// + ContractKeys, + + /// + /// A single template built with no Daml-LF target requested, so it carries whatever + /// version damlc emits by default (2.2 as of Daml SDK 3.5.2), which is what a scaffolded + /// project hands the toolchain. + /// + DefaultTarget, +} + +/// +/// Access to the conformance corpus DARs embedded in this package. Consumers +/// upload these DARs to a participant before creating contracts of the corpus /// templates. /// public static class ConformanceCorpus { /// - /// Opens a stream over the embedded conformance DAR. The caller owns the - /// returned stream and must dispose it. + /// Opens a stream over the embedded DAR. + /// The caller owns the returned stream and must dispose it. /// - public static Stream OpenDar() + public static Stream OpenDar() => OpenDar(ConformancePackage.RichTypes); + + /// + /// Opens a stream over the embedded DAR of . The caller + /// owns the returned stream and must dispose it. + /// + public static Stream OpenDar(ConformancePackage package) { + var resourceName = ResourceNameOf(package); var assembly = typeof(ConformanceCorpus).Assembly; - const string resourceName = "richtypes.dar"; return assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException($"Embedded conformance DAR resource '{resourceName}' was not found."); } + + private static string ResourceNameOf(ConformancePackage package) => package switch + { + ConformancePackage.RichTypes => "richtypes.dar", + ConformancePackage.ContractKeys => "contractkeys.dar", + ConformancePackage.DefaultTarget => "defaulttarget.dar", + _ => throw new ArgumentOutOfRangeException(nameof(package), package, "No conformance DAR is embedded for this package."), + }; } diff --git a/src/Daml.Codegen.Testing.Conformance/Daml.Codegen.Testing.Conformance.csproj b/src/Daml.Codegen.Testing.Conformance/Daml.Codegen.Testing.Conformance.csproj index dc6d532..5e57108 100644 --- a/src/Daml.Codegen.Testing.Conformance/Daml.Codegen.Testing.Conformance.csproj +++ b/src/Daml.Codegen.Testing.Conformance/Daml.Codegen.Testing.Conformance.csproj @@ -6,7 +6,6 @@ Conformance corpus for daml-codegen-csharp: compiled generated types plus the DAR they were generated from, for live-ledger round-trip testing by consumers. Not for production use. daml;canton;codegen;testing;conformance README.md - true @@ -16,6 +15,8 @@ + + diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/.daml-langversion b/src/Daml.Codegen.Testing.Conformance/Generated/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.Credit.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.Credit.cs new file mode 100644 index 0000000..8a9b242 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.Credit.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +public sealed partial record Account +{ + /// + /// Choice argument type for Credit. + /// + public sealed record Credit( + [property: DamlFieldAttribute("delta")] long Delta + ) : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("delta", new DamlInt64(Delta)) + ); + + /// Creates an instance from a DamlRecord. + public static Credit FromRecord(DamlRecord record) => new Credit( + Delta: record.GetRequiredField("delta").As().Value + ); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.CurrentBalance.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.CurrentBalance.cs new file mode 100644 index 0000000..83029b0 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.CurrentBalance.cs @@ -0,0 +1,26 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +public sealed partial record Account +{ + /// + /// Choice argument type for CurrentBalance. + /// + public sealed record CurrentBalance : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create(); + + /// Creates an instance from a DamlRecord. + public static CurrentBalance FromRecord(DamlRecord record) => new CurrentBalance(); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.cs new file mode 100644 index 0000000..0a27589 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Account.cs @@ -0,0 +1,609 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml template ContractKeys:Account +/// +public sealed partial record Account( + [property: DamlFieldAttribute("custodian")] Party Custodian, + [property: DamlFieldAttribute("label")] string Label, + [property: DamlFieldAttribute("balance")] long Balance +) : ITemplate, IHasKey, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb", "ContractKeys", "Account"); + + /// Gets the package ID. + public static string PackageId => "71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb"; + + /// Gets the package name. + public static string PackageName => "contractkeys"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Gets the witness pairing this template with its contract key type and carrying the key codec; passing it to a generic method infers both type parameters from one argument. + public static KeyDescriptor Key { get; } = + new() + { + KeyEncoder = key => key.ToRecord(), + KeyDecoder = value => global::Daml.Codegen.Testing.Conformance.Contractkeys.AccountKey.FromRecord(value.As()), + }; + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("custodian", Custodian.ToDamlValue()), + DamlField.Create("label", new DamlText(Label)), + DamlField.Create("balance", new DamlInt64(Balance)) + ); + + /// Creates an instance from a DamlRecord. + public static Account FromRecord(DamlRecord record) => new Account( + Custodian: Party.FromDamlValue(record.GetRequiredField("custodian").As()), + Label: record.GetRequiredField("label").As().Value, + Balance: record.GetRequiredField("balance").As().Value + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// + /// Exercise the Credit choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice> ChoiceCredit { get; } = new() + { + Name = new ChoiceName("Credit"), + Consuming = true, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => new ContractId(val.As().Value) + }; + + /// + /// Exercise the CurrentBalance choice. + /// + public static Choice ChoiceCurrentBalance { get; } = new() + { + Name = new ChoiceName("CurrentBalance"), + Consuming = false, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => val.As().Value + }; + + /// + /// Builds the for the Archive choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ArchiveByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.AccountKey key) + { + ArgumentNullException.ThrowIfNull(key); + return new ExerciseByKeyCommand( + Account.TemplateId, + key.ToRecord(), + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Builds the for the Credit choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// The choice argument. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand CreditByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.AccountKey key, + Account.Credit argument) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseByKeyCommand( + Account.TemplateId, + key.ToRecord(), + new ChoiceName("Credit"), + argument.ToRecord()); + } + + /// + /// Builds the for the CurrentBalance choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// The choice argument. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand CurrentBalanceByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.AccountKey key, + Account.CurrentBalance argument) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseByKeyCommand( + Account.TemplateId, + key.ToRecord(), + new ChoiceName("CurrentBalance"), + argument.ToRecord()); + } + + /// Contract ID for Account. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for Account. + public sealed record Contract(ContractId Id, Account Data) : IContract + { + /// The contract key read off the created event, decoded and paired with the ledger's hash of it. + public required ContractKey Key { get; init; } + + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new( + new ContractId(@event.ContractId), + global::Daml.Codegen.Testing.Conformance.Contractkeys.Account.FromRecord(@event.CreateArguments)) + { + Key = @event.ContractKey is { } contractKey + ? new ContractKey(global::Daml.Codegen.Testing.Conformance.Contractkeys.AccountKey.FromRecord(contractKey.Value.As()), contractKey.KeyHash) + : throw new global::System.InvalidOperationException("The created event for contract '" + @event.ContractId + "' of keyed template Account carried no contract key, so the contract key cannot be populated."), + }; + } +} + +/// +/// Typed projection of the contracts created by the Credit choice. +/// One field per template the choice creates; cardinality follows the choice's +/// return type (single, optional, list). +/// +public sealed record CreditResult( + ContractId Account +) +{ + /// + /// Projects an upstream transaction's created contracts to a typed . + /// Returns: + /// + /// when every expected template's cardinality matches. + /// when at least one required slot's template is missing from the transaction. + /// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one. + /// + /// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly. + /// + public static ExerciseOutcome FromCreatedContracts(IEnumerable created) + { + ArgumentNullException.ThrowIfNull(created); + var templateMatches0 = new List(); + foreach (var item in created) + { + if (string.Equals(item.TemplateId.ModuleName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Account.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(item.TemplateId.EntityName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Account.TemplateId.EntityName, StringComparison.Ordinal)) + { + templateMatches0.Add(item.ContractId); + } + } + var matches0 = new List(); + var templateMatchIndex0 = 0; + if (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + while (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + + if (matches0.Count == 0) + { + return new ExerciseOutcome.None(); + } + if (matches0.Count > 1) + { + return new ExerciseOutcome.Many(matches0.Count, matches0); + } + + return new ExerciseOutcome.One(new CreditResult( + Account: new ContractId(matches0[0]) + )); + } + +} + + +/// +/// Static <Choice>Async extension methods for . +/// One method per create-bearing choice; each delegates to +/// +/// and projects success via <Choice>Result.FromCreatedContracts. +/// +public static class AccountExtensions +{ + /// + /// Builds the for the Credit choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand CreditCommand( + this ContractId contractId, + Account.Credit argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + Account.TemplateId, + contractId, + new ChoiceName("Credit"), + argument.ToRecord()); + } + + /// + /// Exercises the Credit choice and projects the resulting transaction's created contracts to a typed . + /// One Party parameter is emitted per Daml controller (declaration order). + /// The wrapper builds a from those parties before + /// dispatching to ILedgerWriter. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Controller party from the Daml controller clause, routed into the submission's actAs set. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> CreditAsync( + this ContractId contractId, + ILedgerWriter client, + Account.Credit argument, + Party custodian, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + SubmitterInfo submitter = custodian; + + return contractId.CreditAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Credit choice with an explicit and projects the resulting transaction's created contracts to a typed . + /// Companion to the named-Party overload for the case where the submitter must + /// read contracts it does not act as — the choice's created contracts are visible to an + /// observer but not to the submitter, so the caller supplies the readAs parties. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> CreditAsync( + this ContractId contractId, + ILedgerWriter client, + Account.Credit argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.CreditCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => CreditResult.FromCreatedContracts(tx.CreatedContracts)); + } + + /// + /// Exercises the Credit choice on a fetched contract, + /// reading every controller and observer party off the contract payload so the + /// caller passes no parties. Delegates to the + /// ContractId<Account> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> CreditAsync( + this Account.Contract contract, + ILedgerWriter client, + Account.Credit argument, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.CreditAsync( + client, + argument, + contract.Data.Custodian, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Credit choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Account> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> CreditAsync( + this Account.Contract contract, + ILedgerWriter client, + Account.Credit argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.CreditAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } +} + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class AccountSubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. + /// + /// The ledger client. + /// The contract payload. + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + Account payload, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + SubmitterInfo submitter = payload.Custodian; + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class AccountNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + Account.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + /// + /// Builds the for the CurrentBalance choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand CurrentBalanceCommand( + this ContractId contractId, + Account.CurrentBalance argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + Account.TemplateId, + contractId, + new ChoiceName("CurrentBalance"), + argument.ToRecord()); + } + + /// + /// Exercises the CurrentBalance choice and lifts the choice's exercise result to + /// over long. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> CurrentBalanceAsync( + this ContractId contractId, + ILedgerWriter client, + Account.CurrentBalance argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.CurrentBalanceCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectCurrentBalanceResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Account.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Account.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } + + private static ExerciseOutcome ProjectCurrentBalanceResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Account.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Account.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "CurrentBalance", StringComparison.Ordinal)) + { + var decoded = Account.ChoiceCurrentBalance.ResultDecoder!(exercised.ExerciseResult); + return new ExerciseOutcome.One(decoded); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'CurrentBalance' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/AccountKey.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/AccountKey.cs new file mode 100644 index 0000000..ace37cd --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/AccountKey.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml record AccountKey +/// +public sealed record AccountKey( + [property: DamlFieldAttribute("custodian")] Party Custodian, + [property: DamlFieldAttribute("label")] string Label +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("custodian", Custodian.ToDamlValue()), + DamlField.Create("label", new DamlText(Label)) + ); + + /// Creates an instance from a DamlRecord. + public static AccountKey FromRecord(DamlRecord record) => new AccountKey( + Custodian: Party.FromDamlValue(record.GetRequiredField("custodian").As()), + Label: record.GetRequiredField("label").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Calendar.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Calendar.cs new file mode 100644 index 0000000..9864a7f --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Calendar.cs @@ -0,0 +1,34 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; +using System.Collections.Generic; +using System.Linq; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml record Calendar +/// +public sealed record Calendar( + [property: DamlFieldAttribute("id")] string Id, + [property: DamlFieldAttribute("holidays")] IReadOnlyList Holidays +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("id", new DamlText(Id)), + DamlField.Create("holidays", new DamlList(Holidays.Select(x => (DamlValue)new DamlText(x)).ToList())) + ); + + /// Creates an instance from a DamlRecord. + public static Calendar FromRecord(DamlRecord record) => new Calendar( + Id: record.GetRequiredField("id").As().Value, + Holidays: (IReadOnlyList)record.GetRequiredField("holidays").As().Values.Select(x => x.As().Value).ToList() + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ContractIdentifiers.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ContractIdentifiers.cs new file mode 100644 index 0000000..e603e2b --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ContractIdentifiers.cs @@ -0,0 +1,42 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Contracts; +using static Daml.Runtime.Contracts.TemplateExtensions; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Provides fully qualified contract identifiers for all templates in this package. +/// These identifiers can be used for PQS queries. +/// +public static class ContractIdentifiers +{ + /// + /// Gets the fully qualified template identifier for Account contracts. + /// Format: {packageName}:ContractKeys:Account + /// + public static string Account { get; } = GetTemplateId(); + + /// + /// Gets the fully qualified template identifier for Holiday contracts. + /// Format: {packageName}:ContractKeys:Holiday + /// + public static string Holiday { get; } = GetTemplateId(); + + /// + /// Gets the fully qualified template identifier for Schedule contracts. + /// Format: {packageName}:ContractKeys:Schedule + /// + public static string Schedule { get; } = GetTemplateId(); + + /// + /// Gets the fully qualified template identifier for Steward contracts. + /// Format: {packageName}:ContractKeys:Steward + /// + public static string Steward { get; } = GetTemplateId(); +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.Extend.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.Extend.cs new file mode 100644 index 0000000..13cd6cc --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.Extend.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +public sealed partial record Holiday +{ + /// + /// Choice argument type for Extend. + /// + public sealed record Extend( + [property: DamlFieldAttribute("added")] string Added + ) : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("added", new DamlText(Added)) + ); + + /// Creates an instance from a DamlRecord. + public static Extend FromRecord(DamlRecord record) => new Extend( + Added: record.GetRequiredField("added").As().Value + ); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.cs new file mode 100644 index 0000000..0e2f503 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Holiday.cs @@ -0,0 +1,500 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml template ContractKeys:Holiday +/// +public sealed partial record Holiday( + [property: DamlFieldAttribute("provider")] Party Provider, + [property: DamlFieldAttribute("calendar")] Calendar Calendar +) : ITemplate, IHasKey, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb", "ContractKeys", "Holiday"); + + /// Gets the package ID. + public static string PackageId => "71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb"; + + /// Gets the package name. + public static string PackageName => "contractkeys"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Gets the witness pairing this template with its contract key type and carrying the key codec; passing it to a generic method infers both type parameters from one argument. + public static KeyDescriptor Key { get; } = + new() + { + KeyEncoder = key => key.ToRecord(), + KeyDecoder = value => global::Daml.Codegen.Testing.Conformance.Contractkeys.HolidayKey.FromRecord(value.As()), + }; + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("provider", Provider.ToDamlValue()), + DamlField.Create("calendar", Calendar.ToRecord()) + ); + + /// Creates an instance from a DamlRecord. + public static Holiday FromRecord(DamlRecord record) => new Holiday( + Provider: Party.FromDamlValue(record.GetRequiredField("provider").As()), + Calendar: Calendar.FromRecord(record.GetRequiredField("calendar").As()) + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// + /// Exercise the Extend choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice> ChoiceExtend { get; } = new() + { + Name = new ChoiceName("Extend"), + Consuming = true, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => new ContractId(val.As().Value) + }; + + /// + /// Builds the for the Archive choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ArchiveByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.HolidayKey key) + { + ArgumentNullException.ThrowIfNull(key); + return new ExerciseByKeyCommand( + Holiday.TemplateId, + key.ToRecord(), + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Builds the for the Extend choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// The choice argument. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ExtendByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.HolidayKey key, + Holiday.Extend argument) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseByKeyCommand( + Holiday.TemplateId, + key.ToRecord(), + new ChoiceName("Extend"), + argument.ToRecord()); + } + + /// Contract ID for Holiday. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for Holiday. + public sealed record Contract(ContractId Id, Holiday Data) : IContract + { + /// The contract key read off the created event, decoded and paired with the ledger's hash of it. + public required ContractKey Key { get; init; } + + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new( + new ContractId(@event.ContractId), + global::Daml.Codegen.Testing.Conformance.Contractkeys.Holiday.FromRecord(@event.CreateArguments)) + { + Key = @event.ContractKey is { } contractKey + ? new ContractKey(global::Daml.Codegen.Testing.Conformance.Contractkeys.HolidayKey.FromRecord(contractKey.Value.As()), contractKey.KeyHash) + : throw new global::System.InvalidOperationException("The created event for contract '" + @event.ContractId + "' of keyed template Holiday carried no contract key, so the contract key cannot be populated."), + }; + } +} + +/// +/// Typed projection of the contracts created by the Extend choice. +/// One field per template the choice creates; cardinality follows the choice's +/// return type (single, optional, list). +/// +public sealed record ExtendResult( + ContractId Holiday +) +{ + /// + /// Projects an upstream transaction's created contracts to a typed . + /// Returns: + /// + /// when every expected template's cardinality matches. + /// when at least one required slot's template is missing from the transaction. + /// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one. + /// + /// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly. + /// + public static ExerciseOutcome FromCreatedContracts(IEnumerable created) + { + ArgumentNullException.ThrowIfNull(created); + var templateMatches0 = new List(); + foreach (var item in created) + { + if (string.Equals(item.TemplateId.ModuleName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Holiday.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(item.TemplateId.EntityName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Holiday.TemplateId.EntityName, StringComparison.Ordinal)) + { + templateMatches0.Add(item.ContractId); + } + } + var matches0 = new List(); + var templateMatchIndex0 = 0; + if (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + while (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + + if (matches0.Count == 0) + { + return new ExerciseOutcome.None(); + } + if (matches0.Count > 1) + { + return new ExerciseOutcome.Many(matches0.Count, matches0); + } + + return new ExerciseOutcome.One(new ExtendResult( + Holiday: new ContractId(matches0[0]) + )); + } + +} + + +/// +/// Static <Choice>Async extension methods for . +/// One method per create-bearing choice; each delegates to +/// +/// and projects success via <Choice>Result.FromCreatedContracts. +/// +public static class HolidayExtensions +{ + /// + /// Builds the for the Extend choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand ExtendCommand( + this ContractId contractId, + Holiday.Extend argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + Holiday.TemplateId, + contractId, + new ChoiceName("Extend"), + argument.ToRecord()); + } + + /// + /// Exercises the Extend choice and projects the resulting transaction's created contracts to a typed . + /// One Party parameter is emitted per Daml controller (declaration order). + /// The wrapper builds a from those parties before + /// dispatching to ILedgerWriter. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Controller party from the Daml controller clause, routed into the submission's actAs set. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ExtendAsync( + this ContractId contractId, + ILedgerWriter client, + Holiday.Extend argument, + Party provider, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + SubmitterInfo submitter = provider; + + return contractId.ExtendAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Extend choice with an explicit and projects the resulting transaction's created contracts to a typed . + /// Companion to the named-Party overload for the case where the submitter must + /// read contracts it does not act as — the choice's created contracts are visible to an + /// observer but not to the submitter, so the caller supplies the readAs parties. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ExtendAsync( + this ContractId contractId, + ILedgerWriter client, + Holiday.Extend argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ExtendCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ExtendResult.FromCreatedContracts(tx.CreatedContracts)); + } + + /// + /// Exercises the Extend choice on a fetched contract, + /// reading every controller and observer party off the contract payload so the + /// caller passes no parties. Delegates to the + /// ContractId<Holiday> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ExtendAsync( + this Holiday.Contract contract, + ILedgerWriter client, + Holiday.Extend argument, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ExtendAsync( + client, + argument, + contract.Data.Provider, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Extend choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Holiday> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ExtendAsync( + this Holiday.Contract contract, + ILedgerWriter client, + Holiday.Extend argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ExtendAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } +} + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class HolidaySubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. + /// + /// The ledger client. + /// The contract payload. + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + Holiday payload, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + SubmitterInfo submitter = payload.Provider; + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class HolidayNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + Holiday.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Holiday.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Holiday.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/HolidayKey.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/HolidayKey.cs new file mode 100644 index 0000000..09f1f49 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/HolidayKey.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml record HolidayKey +/// +public sealed record HolidayKey( + [property: DamlFieldAttribute("provider")] Party Provider, + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("provider", Provider.ToDamlValue()), + DamlField.Create("id", new DamlText(Id)) + ); + + /// Creates an instance from a DamlRecord. + public static HolidayKey FromRecord(DamlRecord record) => new HolidayKey( + Provider: Party.FromDamlValue(record.GetRequiredField("provider").As()), + Id: record.GetRequiredField("id").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.Reschedule.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.Reschedule.cs new file mode 100644 index 0000000..8f6d72d --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.Reschedule.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +public sealed partial record Schedule +{ + /// + /// Choice argument type for Reschedule. + /// + public sealed record Reschedule( + [property: DamlFieldAttribute("rescheduled")] string Rescheduled + ) : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("rescheduled", new DamlText(Rescheduled)) + ); + + /// Creates an instance from a DamlRecord. + public static Reschedule FromRecord(DamlRecord record) => new Reschedule( + Rescheduled: record.GetRequiredField("rescheduled").As().Value + ); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.cs new file mode 100644 index 0000000..9e5ade5 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Schedule.cs @@ -0,0 +1,387 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml template ContractKeys:Schedule +/// +public sealed partial record Schedule( + [property: DamlFieldAttribute("view")] ScheduleView View +) : ITemplate, IHasKey, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb", "ContractKeys", "Schedule"); + + /// Gets the package ID. + public static string PackageId => "71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb"; + + /// Gets the package name. + public static string PackageName => "contractkeys"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Gets the witness pairing this template with its contract key type and carrying the key codec; passing it to a generic method infers both type parameters from one argument. + public static KeyDescriptor Key { get; } = + new() + { + KeyEncoder = key => key.ToRecord(), + KeyDecoder = value => global::Daml.Codegen.Testing.Conformance.Contractkeys.ScheduleKey.FromRecord(value.As()), + }; + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("view", View.ToRecord()) + ); + + /// Creates an instance from a DamlRecord. + public static Schedule FromRecord(DamlRecord record) => new Schedule( + View: ScheduleView.FromRecord(record.GetRequiredField("view").As()) + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// + /// Exercise the Reschedule choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice> ChoiceReschedule { get; } = new() + { + Name = new ChoiceName("Reschedule"), + Consuming = true, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => new ContractId(val.As().Value) + }; + + /// + /// Builds the for the Archive choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ArchiveByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.ScheduleKey key) + { + ArgumentNullException.ThrowIfNull(key); + return new ExerciseByKeyCommand( + Schedule.TemplateId, + key.ToRecord(), + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Builds the for the Reschedule choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// The choice argument. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand RescheduleByKeyCommand( + global::Daml.Codegen.Testing.Conformance.Contractkeys.ScheduleKey key, + Schedule.Reschedule argument) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseByKeyCommand( + Schedule.TemplateId, + key.ToRecord(), + new ChoiceName("Reschedule"), + argument.ToRecord()); + } + + /// Contract ID for Schedule. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for Schedule. + public sealed record Contract(ContractId Id, Schedule Data) : IContract + { + /// The contract key read off the created event, decoded and paired with the ledger's hash of it. + public required ContractKey Key { get; init; } + + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new( + new ContractId(@event.ContractId), + global::Daml.Codegen.Testing.Conformance.Contractkeys.Schedule.FromRecord(@event.CreateArguments)) + { + Key = @event.ContractKey is { } contractKey + ? new ContractKey(global::Daml.Codegen.Testing.Conformance.Contractkeys.ScheduleKey.FromRecord(contractKey.Value.As()), contractKey.KeyHash) + : throw new global::System.InvalidOperationException("The created event for contract '" + @event.ContractId + "' of keyed template Schedule carried no contract key, so the contract key cannot be populated."), + }; + } +} + +/// +/// Typed projection of the contracts created by the Reschedule choice. +/// One field per template the choice creates; cardinality follows the choice's +/// return type (single, optional, list). +/// +public sealed record RescheduleResult( + ContractId Schedule +) +{ + /// + /// Projects an upstream transaction's created contracts to a typed . + /// Returns: + /// + /// when every expected template's cardinality matches. + /// when at least one required slot's template is missing from the transaction. + /// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one. + /// + /// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly. + /// + public static ExerciseOutcome FromCreatedContracts(IEnumerable created) + { + ArgumentNullException.ThrowIfNull(created); + var templateMatches0 = new List(); + foreach (var item in created) + { + if (string.Equals(item.TemplateId.ModuleName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Schedule.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(item.TemplateId.EntityName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Schedule.TemplateId.EntityName, StringComparison.Ordinal)) + { + templateMatches0.Add(item.ContractId); + } + } + var matches0 = new List(); + var templateMatchIndex0 = 0; + if (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + while (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + + if (matches0.Count == 0) + { + return new ExerciseOutcome.None(); + } + if (matches0.Count > 1) + { + return new ExerciseOutcome.Many(matches0.Count, matches0); + } + + return new ExerciseOutcome.One(new RescheduleResult( + Schedule: new ContractId(matches0[0]) + )); + } + +} + + +/// +/// Static <Choice>Async extension methods for . +/// One method per create-bearing choice; each delegates to +/// +/// and projects success via <Choice>Result.FromCreatedContracts. +/// +public static class ScheduleExtensions +{ + /// + /// Builds the for the Reschedule choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand RescheduleCommand( + this ContractId contractId, + Schedule.Reschedule argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + Schedule.TemplateId, + contractId, + new ChoiceName("Reschedule"), + argument.ToRecord()); + } + + /// + /// Exercises the Reschedule choice and projects the resulting transaction's created contracts to a typed . + /// The submitter is passed explicitly via — the static + /// analyzer could not resolve the Daml controller clause to payload-field + /// references. implicitly converts from a + /// single Party, so the single-party call site stays a one-liner. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> RescheduleAsync( + this ContractId contractId, + ILedgerWriter client, + Schedule.Reschedule argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.RescheduleCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => RescheduleResult.FromCreatedContracts(tx.CreatedContracts)); + } +} + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class ScheduleSubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitter is passed explicitly via . The static + /// analyzer could not resolve the Daml signatory clause to payload-field + /// references — typically because the expression involves the template key, a + /// constant, or a function call. implicitly converts + /// from a single Party, so single-party callers still pass one literal. + /// + /// The ledger client. + /// The contract payload. + /// The submitter party set (actAs + optional readAs). + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + Schedule payload, + SubmitterInfo submitter, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class ScheduleNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + Schedule.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Schedule.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Schedule.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleKey.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleKey.cs new file mode 100644 index 0000000..6505d8d --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleKey.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml record ScheduleKey +/// +public sealed record ScheduleKey( + [property: DamlFieldAttribute("custodian")] Party Custodian, + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("custodian", Custodian.ToDamlValue()), + DamlField.Create("id", new DamlText(Id)) + ); + + /// Creates an instance from a DamlRecord. + public static ScheduleKey FromRecord(DamlRecord record) => new ScheduleKey( + Custodian: Party.FromDamlValue(record.GetRequiredField("custodian").As()), + Id: record.GetRequiredField("id").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleView.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleView.cs new file mode 100644 index 0000000..090558e --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/ScheduleView.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml record ScheduleView +/// +public sealed record ScheduleView( + [property: DamlFieldAttribute("issuer")] Party Issuer, + [property: DamlFieldAttribute("reference")] string Reference +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("issuer", Issuer.ToDamlValue()), + DamlField.Create("reference", new DamlText(Reference)) + ); + + /// Creates an instance from a DamlRecord. + public static ScheduleView FromRecord(DamlRecord record) => new ScheduleView( + Issuer: Party.FromDamlValue(record.GetRequiredField("issuer").As()), + Reference: record.GetRequiredField("reference").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.Revise.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.Revise.cs new file mode 100644 index 0000000..0dfb435 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.Revise.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +public sealed partial record Steward +{ + /// + /// Choice argument type for Revise. + /// + public sealed record Revise( + [property: DamlFieldAttribute("revised")] string Revised + ) : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("revised", new DamlText(Revised)) + ); + + /// Creates an instance from a DamlRecord. + public static Revise FromRecord(DamlRecord record) => new Revise( + Revised: record.GetRequiredField("revised").As().Value + ); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.cs new file mode 100644 index 0000000..abe7f51 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Contractkeys/Steward.cs @@ -0,0 +1,498 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Contractkeys; + +/// +/// Generated from Daml template ContractKeys:Steward +/// +public sealed partial record Steward( + [property: DamlFieldAttribute("steward")] Party Steward_, + [property: DamlFieldAttribute("charter")] string Charter +) : ITemplate, IHasKey, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb", "ContractKeys", "Steward"); + + /// Gets the package ID. + public static string PackageId => "71331490239cc6dacb44ebb109c5a4086ef8088e7cb873ebc09068a986446abb"; + + /// Gets the package name. + public static string PackageName => "contractkeys"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Gets the witness pairing this template with its contract key type and carrying the key codec; passing it to a generic method infers both type parameters from one argument. + public static KeyDescriptor Key { get; } = + new() + { + KeyEncoder = key => key.ToDamlValue(), + KeyDecoder = value => Party.FromDamlValue(value.As()), + }; + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("steward", Steward_.ToDamlValue()), + DamlField.Create("charter", new DamlText(Charter)) + ); + + /// Creates an instance from a DamlRecord. + public static Steward FromRecord(DamlRecord record) => new Steward( + Steward_: Party.FromDamlValue(record.GetRequiredField("steward").As()), + Charter: record.GetRequiredField("charter").As().Value + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// + /// Exercise the Revise choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice> ChoiceRevise { get; } = new() + { + Name = new ChoiceName("Revise"), + Consuming = true, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => new ContractId(val.As().Value) + }; + + /// + /// Builds the for the Archive choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ArchiveByKeyCommand( + Party key) + { + return new ExerciseByKeyCommand( + Steward.TemplateId, + key.ToDamlValue(), + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Builds the for the Revise choice on the contract carrying this key. + /// + /// The contract key to exercise the choice against. + /// The choice argument. + /// + /// Contract keys are not unique: several active contracts may carry the same key, and + /// the ledger resolves this command against a first match by an order it only partly + /// guarantees. Keeping a key unique is the application's responsibility. + /// + public static ExerciseByKeyCommand ReviseByKeyCommand( + Party key, + Steward.Revise argument) + { + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseByKeyCommand( + Steward.TemplateId, + key.ToDamlValue(), + new ChoiceName("Revise"), + argument.ToRecord()); + } + + /// Contract ID for Steward. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for Steward. + public sealed record Contract(ContractId Id, Steward Data) : IContract + { + /// The contract key read off the created event, decoded and paired with the ledger's hash of it. + public required ContractKey Key { get; init; } + + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new( + new ContractId(@event.ContractId), + global::Daml.Codegen.Testing.Conformance.Contractkeys.Steward.FromRecord(@event.CreateArguments)) + { + Key = @event.ContractKey is { } contractKey + ? new ContractKey(Party.FromDamlValue(contractKey.Value.As()), contractKey.KeyHash) + : throw new global::System.InvalidOperationException("The created event for contract '" + @event.ContractId + "' of keyed template Steward carried no contract key, so the contract key cannot be populated."), + }; + } +} + +/// +/// Typed projection of the contracts created by the Revise choice. +/// One field per template the choice creates; cardinality follows the choice's +/// return type (single, optional, list). +/// +public sealed record ReviseResult( + ContractId Steward +) +{ + /// + /// Projects an upstream transaction's created contracts to a typed . + /// Returns: + /// + /// when every expected template's cardinality matches. + /// when at least one required slot's template is missing from the transaction. + /// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one. + /// + /// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly. + /// + public static ExerciseOutcome FromCreatedContracts(IEnumerable created) + { + ArgumentNullException.ThrowIfNull(created); + var templateMatches0 = new List(); + foreach (var item in created) + { + if (string.Equals(item.TemplateId.ModuleName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Steward.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(item.TemplateId.EntityName, global::Daml.Codegen.Testing.Conformance.Contractkeys.Steward.TemplateId.EntityName, StringComparison.Ordinal)) + { + templateMatches0.Add(item.ContractId); + } + } + var matches0 = new List(); + var templateMatchIndex0 = 0; + if (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + while (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + + if (matches0.Count == 0) + { + return new ExerciseOutcome.None(); + } + if (matches0.Count > 1) + { + return new ExerciseOutcome.Many(matches0.Count, matches0); + } + + return new ExerciseOutcome.One(new ReviseResult( + Steward: new ContractId(matches0[0]) + )); + } + +} + + +/// +/// Static <Choice>Async extension methods for . +/// One method per create-bearing choice; each delegates to +/// +/// and projects success via <Choice>Result.FromCreatedContracts. +/// +public static class StewardExtensions +{ + /// + /// Builds the for the Revise choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand ReviseCommand( + this ContractId contractId, + Steward.Revise argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + Steward.TemplateId, + contractId, + new ChoiceName("Revise"), + argument.ToRecord()); + } + + /// + /// Exercises the Revise choice and projects the resulting transaction's created contracts to a typed . + /// One Party parameter is emitted per Daml controller (declaration order). + /// The wrapper builds a from those parties before + /// dispatching to ILedgerWriter. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Controller party from the Daml controller clause, routed into the submission's actAs set. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReviseAsync( + this ContractId contractId, + ILedgerWriter client, + Steward.Revise argument, + Party steward, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + SubmitterInfo submitter = steward; + + return contractId.ReviseAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Revise choice with an explicit and projects the resulting transaction's created contracts to a typed . + /// Companion to the named-Party overload for the case where the submitter must + /// read contracts it does not act as — the choice's created contracts are visible to an + /// observer but not to the submitter, so the caller supplies the readAs parties. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ReviseAsync( + this ContractId contractId, + ILedgerWriter client, + Steward.Revise argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ReviseCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ReviseResult.FromCreatedContracts(tx.CreatedContracts)); + } + + /// + /// Exercises the Revise choice on a fetched contract, + /// reading every controller and observer party off the contract payload so the + /// caller passes no parties. Delegates to the + /// ContractId<Steward> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReviseAsync( + this Steward.Contract contract, + ILedgerWriter client, + Steward.Revise argument, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ReviseAsync( + client, + argument, + contract.Data.Steward_, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Revise choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Steward> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReviseAsync( + this Steward.Contract contract, + ILedgerWriter client, + Steward.Revise argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ReviseAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } +} + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class StewardSubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. + /// + /// The ledger client. + /// The contract payload. + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + Steward payload, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + SubmitterInfo submitter = payload.Steward_; + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class StewardNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + Steward.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Steward.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Steward.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/ContractIdentifiers.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/ContractIdentifiers.cs new file mode 100644 index 0000000..06627d3 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/ContractIdentifiers.cs @@ -0,0 +1,24 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Contracts; +using static Daml.Runtime.Contracts.TemplateExtensions; + +namespace Daml.Codegen.Testing.Conformance.Defaulttarget; + +/// +/// Provides fully qualified contract identifiers for all templates in this package. +/// These identifiers can be used for PQS queries. +/// +public static class ContractIdentifiers +{ + /// + /// Gets the fully qualified template identifier for Note contracts. + /// Format: {packageName}:DefaultTarget:Note + /// + public static string Note { get; } = GetTemplateId(); +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/Note.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/Note.cs new file mode 100644 index 0000000..a183963 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Defaulttarget/Note.cs @@ -0,0 +1,196 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Defaulttarget; + +/// +/// Generated from Daml template DefaultTarget:Note +/// +public sealed partial record Note( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("body")] string Body, + [property: DamlFieldAttribute("revision")] long Revision +) : ITemplate, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("47653650113a3f03de4e970a2133a3e36e7443b8966fd5f4b5f509bef6b6ac25", "DefaultTarget", "Note"); + + /// Gets the package ID. + public static string PackageId => "47653650113a3f03de4e970a2133a3e36e7443b8966fd5f4b5f509bef6b6ac25"; + + /// Gets the package name. + public static string PackageName => "defaulttarget"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("owner", Owner.ToDamlValue()), + DamlField.Create("body", new DamlText(Body)), + DamlField.Create("revision", new DamlInt64(Revision)) + ); + + /// Creates an instance from a DamlRecord. + public static Note FromRecord(DamlRecord record) => new Note( + Owner: Party.FromDamlValue(record.GetRequiredField("owner").As()), + Body: record.GetRequiredField("body").As().Value, + Revision: record.GetRequiredField("revision").As().Value + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// Contract ID for Note. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for Note. + public sealed record Contract(ContractId Id, Note Data) : IContract + { + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new(new ContractId(@event.ContractId), global::Daml.Codegen.Testing.Conformance.Defaulttarget.Note.FromRecord(@event.CreateArguments)); + } +} + + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class NoteSubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. + /// + /// The ledger client. + /// The contract payload. + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + Note payload, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + SubmitterInfo submitter = payload.Owner; + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class NoteNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + Note.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, Note.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, Note.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Asset.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Asset.cs index 6eeea46..02563f5 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Asset.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Asset.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,13 +21,16 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml template RichTypes:Asset /// -public sealed partial record Asset([property: DamlFieldAttribute("issuer")] Party Issuer, [property: DamlFieldAttribute("amount")] decimal Amount) : ITemplate, IImplements +public sealed partial record Asset( + [property: DamlFieldAttribute("issuer")] Party Issuer, + [property: DamlFieldAttribute("amount")] decimal Amount +) : ITemplate, IImplements, IDamlRecord { /// Gets the template identifier. - public static Identifier TemplateId { get; } = new("8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef", "RichTypes", "Asset"); + public static Identifier TemplateId { get; } = new("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "Asset"); /// Gets the package ID. - public static string PackageId => "8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef"; + public static string PackageId => "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; /// Gets the package name. public static string PackageName => "richtypes"; @@ -62,6 +66,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Asset. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -72,7 +77,7 @@ public sealed record Contract(ContractId Id, Asset Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Asset.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Daml.Codegen.Testing.Conformance.Richtypes.Asset.FromRecord(@event.CreateArguments)); } } @@ -90,25 +95,23 @@ public static class AssetSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, Asset payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Issuer; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -117,7 +120,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AssetNonContractExtensions @@ -144,15 +147,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -162,14 +165,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Box.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Box.cs new file mode 100644 index 0000000..2666c13 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Box.cs @@ -0,0 +1,33 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; +using Daml.Runtime.Stdlib; +using System; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml record Box +/// Type parameters: a +/// +/// Type parameter a +public sealed record Box( + [property: DamlFieldAttribute("item")] TA Item +) where TA : notnull +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord(Func convertTA) => DamlRecord.Create( + DamlField.Create("item", convertTA(Item)) + ); + + /// Creates an instance from a DamlRecord. + public static Box FromRecord(DamlRecord record, Func convertTA) => new Box( + Item: convertTA(record.GetRequiredField("item")) + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Branch.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Branch.cs new file mode 100644 index 0000000..ac82a5e --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Branch.cs @@ -0,0 +1,34 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; +using System.Collections.Generic; +using System.Linq; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml record Branch +/// +public sealed record Branch( + [property: DamlFieldAttribute("label")] string Label, + [property: DamlFieldAttribute("children")] IReadOnlyList Children +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("label", new DamlText(Label)), + DamlField.Create("children", new DamlList(Children.Select(x => (DamlValue)x.ToRecord()).ToList())) + ); + + /// Creates an instance from a DamlRecord. + public static Branch FromRecord(DamlRecord record) => new Branch( + Label: record.GetRequiredField("label").As().Value, + Children: (IReadOnlyList)record.GetRequiredField("children").As().Values.Select(x => Branch.FromRecord(x.As())).ToList() + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/ContractIdentifiers.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/ContractIdentifiers.cs similarity index 82% rename from src/Daml.Codegen.Testing.Conformance/Generated/ContractIdentifiers.cs rename to src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/ContractIdentifiers.cs index 2b5288d..1219e8c 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/ContractIdentifiers.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/ContractIdentifiers.cs @@ -33,4 +33,10 @@ public static class ContractIdentifiers /// Format: {packageName}:RichTypes:RichRecord /// public static string RichRecord { get; } = GetTemplateId(); + + /// + /// Gets the fully qualified template identifier for TypeCorners contracts. + /// Format: {packageName}:RichTypes:TypeCorners + /// + public static string TypeCorners { get; } = GetTemplateId(); } diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Crate.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Crate.cs new file mode 100644 index 0000000..ca34116 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Crate.cs @@ -0,0 +1,33 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; +using Daml.Runtime.Stdlib; +using System; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml record Crate +/// Type parameters: a +/// +/// Type parameter a +public sealed record Crate( + [property: DamlFieldAttribute("item")] Optional Item +) where TA : notnull +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord(Func convertTA) => DamlRecord.Create( + DamlField.Create("item", Item.ToValue(__optional0 => convertTA(__optional0))) + ); + + /// Creates an instance from a DamlRecord. + public static Crate FromRecord(DamlRecord record, Func convertTA) => new Crate( + Item: Optional.FromValue(record.GetRequiredField("item"), __optional0 => convertTA(__optional0)) + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Describe.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Describe.cs new file mode 100644 index 0000000..bd653db --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Describe.cs @@ -0,0 +1,29 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml record Describe +/// +public sealed record Describe( + [property: DamlFieldAttribute("prefix")] string Prefix +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("prefix", new DamlText(Prefix)) + ); + + /// Creates an instance from a DamlRecord. + public static Describe FromRecord(DamlRecord record) => new Describe( + Prefix: record.GetRequiredField("prefix").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Holding.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Holding.cs deleted file mode 100644 index 3acd647..0000000 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Holding.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Daml.Codegen.Testing.Conformance.Richtypes; - -/// -/// Phantom placeholder for the Daml interface RichTypes:Holding. -/// Implements so that ContractId<Holding> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface RichTypes:Holding has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<Holding> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record Holding : ITemplate -{ - /// Always throws — the RichTypes:Holding interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'RichTypes:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the RichTypes:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'RichTypes:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the RichTypes:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'RichTypes:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the RichTypes:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'RichTypes:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the RichTypes:Holding interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'RichTypes:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static Holding FromRecord(DamlRecord record) => new Holding(); -} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/HoldingView.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/HoldingView.cs index 7ca124c..95c6782 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/HoldingView.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/HoldingView.cs @@ -12,7 +12,9 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml record HoldingView /// -public sealed record HoldingView([property: DamlFieldAttribute("amount")] decimal Amount) : IDamlRecord +public sealed record HoldingView( + [property: DamlFieldAttribute("amount")] decimal Amount +) : IHolding, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/IHolding.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/IHolding.cs index b3f5967..8c0c59d 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/IHolding.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/IHolding.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,16 +20,23 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml interface RichTypes:Holding /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IHolding : IDamlInterface, IHasView { /// Gets the interface identifier. static Identifier IDamlInterface.InterfaceId => InterfaceId; /// Gets the interface identifier. - public static new Identifier InterfaceId { get; } = new("8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef", "RichTypes", "Holding"); + public static new Identifier InterfaceId { get; } = new("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "Holding"); /// Gets the package ID. - static string IDamlInterface.PackageId => "8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef"; + static string IDamlInterface.PackageId => "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; /// Gets the package name. static string IDamlInterface.PackageName => "richtypes"; @@ -37,10 +45,14 @@ public interface IHolding : IDamlInterface, IHasView static Version IDamlInterface.PackageVersion => new(0, 0, 1); /// Gets the compile-time Daml type descriptor. - static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef", "RichTypes", "Holding"), DamlTypeKind.Interface, "richtypes"); + static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "Holding"), DamlTypeKind.Interface, "richtypes"); + + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the amount field of the interface view. + decimal Amount { get; } - // Interface method Archive. - // Choice Archive() -> DamlUnit } /// @@ -48,7 +60,7 @@ public interface IHolding : IDamlInterface, IHasView /// One method per choice; each submits an interface-typed /// built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -72,34 +84,120 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); + var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); + } + + /// + /// Builds the interface-typed for the Describe choice on this contract id. + /// The wire-level template_id slot carries the interface id — Canton's + /// ledger API resolves the concrete implementing template at the participant. + /// + /// The interface-typed contract id to exercise on. + /// The choice argument. + public static ExerciseCommand DescribeCommand( + this ContractId contractId, + Describe argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return ExerciseCommand.ForInterface(contractId, new ChoiceName("Describe"), argument.ToRecord()); + } + + /// + /// Exercises the Describe interface choice on this contract id, submitting the + /// resulting through + /// . + /// + /// The interface-typed contract id to exercise on. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> DescribeAsync( + this ContractId contractId, + ILedgerWriter client, + Describe argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.DescribeCommand(argument); + + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); + } + + /// + /// Builds the interface-typed for the Reissue choice on this contract id. + /// The wire-level template_id slot carries the interface id — Canton's + /// ledger API resolves the concrete implementing template at the participant. + /// + /// The interface-typed contract id to exercise on. + /// The choice argument. + public static ExerciseCommand ReissueCommand( + this ContractId contractId, + Reissue argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return ExerciseCommand.ForInterface(contractId, new ChoiceName("Reissue"), argument.ToRecord()); + } + + /// + /// Exercises the Reissue interface choice on this contract id, submitting the + /// resulting through + /// . + /// + /// The interface-typed contract id to exercise on. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReissueAsync( + this ContractId contractId, + ILedgerWriter client, + Reissue argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ReissueCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Marker.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Marker.cs index cd6d236..37bf9c2 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Marker.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Marker.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,13 +21,15 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml template RichTypes:Marker /// -public sealed partial record Marker([property: DamlFieldAttribute("owner")] Party Owner) : ITemplate +public sealed partial record Marker( + [property: DamlFieldAttribute("owner")] Party Owner +) : ITemplate, IDamlRecord { /// Gets the template identifier. - public static Identifier TemplateId { get; } = new("8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef", "RichTypes", "Marker"); + public static Identifier TemplateId { get; } = new("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "Marker"); /// Gets the package ID. - public static string PackageId => "8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef"; + public static string PackageId => "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; /// Gets the package name. public static string PackageName => "richtypes"; @@ -60,6 +63,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Marker. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -70,7 +74,7 @@ public sealed record Contract(ContractId Id, Marker Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Marker.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Daml.Codegen.Testing.Conformance.Richtypes.Marker.FromRecord(@event.CreateArguments)); } } @@ -88,25 +92,23 @@ public static class MarkerSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, Marker payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Owner; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -115,7 +117,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class MarkerNonContractExtensions @@ -142,15 +144,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -160,14 +162,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Outcome_Win.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Outcome_Win.cs index 3dc13a3..df74f0f 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Outcome_Win.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Outcome_Win.cs @@ -12,7 +12,10 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml record Outcome.Win /// -public sealed record Outcome_Win([property: DamlFieldAttribute("prize")] decimal Prize, [property: DamlFieldAttribute("tier")] string Tier) : IDamlRecord +public sealed record Outcome_Win( + [property: DamlFieldAttribute("prize")] decimal Prize, + [property: DamlFieldAttribute("tier")] string Tier +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Profile.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Profile.cs index c785778..bc4d8b7 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Profile.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Profile.cs @@ -12,7 +12,10 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml record Profile /// -public sealed record Profile([property: DamlFieldAttribute("nickname")] string Nickname, [property: DamlFieldAttribute("level")] long Level) : IDamlRecord +public sealed record Profile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("level")] long Level +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Reissue.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Reissue.cs new file mode 100644 index 0000000..bfd7e1d --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Reissue.cs @@ -0,0 +1,29 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml record Reissue +/// +public sealed record Reissue( + [property: DamlFieldAttribute("newAmount")] decimal NewAmount +) : IDamlRecord +{ + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("newAmount", new DamlNumeric(NewAmount)) + ); + + /// Creates an instance from a DamlRecord. + public static Reissue FromRecord(DamlRecord record) => new Reissue( + NewAmount: record.GetRequiredField("newAmount").As().Value + ); + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.Relabel.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.Relabel.cs index dbe21d5..63cdb76 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.Relabel.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.Relabel.cs @@ -14,7 +14,9 @@ public sealed partial record RichRecord /// /// Choice argument type for Relabel. /// - public sealed record Relabel([property: DamlFieldAttribute("newLabel")] string NewLabel) : IDamlRecord + public sealed record Relabel( + [property: DamlFieldAttribute("newLabel")] string NewLabel + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.cs index eca1a4b..dc7829a 100644 --- a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.cs +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/RichRecord.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -22,13 +23,31 @@ namespace Daml.Codegen.Testing.Conformance.Richtypes; /// /// Generated from Daml template RichTypes:RichRecord /// -public sealed partial record RichRecord([property: DamlFieldAttribute("owner")] Party Owner, [property: DamlFieldAttribute("count")] long Count, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("label")] string Label, [property: DamlFieldAttribute("active")] bool Active, [property: DamlFieldAttribute("asOf")] DateOnly AsOf, [property: DamlFieldAttribute("observedAt")] DateTimeOffset ObservedAt, [property: DamlFieldAttribute("note")] string? Note, [property: DamlFieldAttribute("tags")] IReadOnlyList Tags, [property: DamlFieldAttribute("attributes")] IReadOnlyDictionary Attributes, [property: DamlFieldAttribute("marker")] ContractId Marker, [property: DamlFieldAttribute("holdingCid")] ContractId HoldingCid, [property: DamlFieldAttribute("holdingCids")] IReadOnlyList> HoldingCids, [property: DamlFieldAttribute("profile")] Profile Profile, [property: DamlFieldAttribute("outcome")] Outcome Outcome, [property: DamlFieldAttribute("suit")] Suit Suit, [property: DamlFieldAttribute("fee")] decimal Fee) : ITemplate +public sealed partial record RichRecord( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("count")] long Count, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("label")] string Label, + [property: DamlFieldAttribute("active")] bool Active, + [property: DamlFieldAttribute("asOf")] DateOnly AsOf, + [property: DamlFieldAttribute("observedAt")] DateTimeOffset ObservedAt, + [property: DamlFieldAttribute("note")] string? Note, + [property: DamlFieldAttribute("tags")] IReadOnlyList Tags, + [property: DamlFieldAttribute("attributes")] IReadOnlyDictionary Attributes, + [property: DamlFieldAttribute("marker")] ContractId Marker, + [property: DamlFieldAttribute("holdingCid")] ContractId HoldingCid, + [property: DamlFieldAttribute("holdingCids")] IReadOnlyList> HoldingCids, + [property: DamlFieldAttribute("profile")] Profile Profile, + [property: DamlFieldAttribute("outcome")] Outcome Outcome, + [property: DamlFieldAttribute("suit")] Suit Suit, + [property: DamlFieldAttribute("fee")] decimal Fee +) : ITemplate, IDamlRecord { /// Gets the template identifier. - public static Identifier TemplateId { get; } = new("8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef", "RichTypes", "RichRecord"); + public static Identifier TemplateId { get; } = new("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "RichRecord"); /// Gets the package ID. - public static string PackageId => "8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef"; + public static string PackageId => "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; /// Gets the package name. public static string PackageName => "richtypes"; @@ -105,6 +124,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for RichRecord. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -115,7 +135,7 @@ public sealed record Contract(ContractId Id, RichRecord Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), RichRecord.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Daml.Codegen.Testing.Conformance.Richtypes.RichRecord.FromRecord(@event.CreateArguments)); } } @@ -124,7 +144,9 @@ public static Contract FromCreatedEvent(CreatedEvent @event) => /// One field per template the choice creates; cardinality follows the choice's /// return type (single, optional, list). /// -public sealed record RelabelResult(ContractId RichRecord) +public sealed record RelabelResult( + ContractId RichRecord +) { /// /// Projects an upstream transaction's created contracts to a typed . @@ -181,7 +203,7 @@ public static ExerciseOutcome FromCreatedContracts(IEnumerable /// Static <Choice>Async extension methods for . /// One method per create-bearing choice; each delegates to -/// +/// /// and projects success via <Choice>Result.FromCreatedContracts. /// public static class RichRecordExtensions @@ -215,10 +237,10 @@ public static ExerciseCommand RelabelCommand( /// The choice argument. /// Controller party from the Daml controller clause, routed into the submission's actAs set. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> RelabelAsync( + public static Task> RelabelAsync( this ContractId contractId, ILedgerWriter client, RichRecord.Relabel argument, @@ -232,18 +254,14 @@ public static async Task> RelabelAsync( SubmitterInfo submitter = owner; - var command = contractId.RelabelCommand(argument); - - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); - - return outcome.ProjectCommitted(tx => RelabelResult.FromCreatedContracts(tx.CreatedContracts)); + return contractId.RelabelAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); } /// @@ -257,8 +275,8 @@ public static async Task> RelabelAsync( /// The choice argument. /// The submitter party set (actAs + optional readAs). /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> RelabelAsync( this ContractId contractId, @@ -274,14 +292,7 @@ public static async Task> RelabelAsync( var command = contractId.RelabelCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => RelabelResult.FromCreatedContracts(tx.CreatedContracts)); } @@ -296,8 +307,8 @@ public static async Task> RelabelAsync( /// The ledger client. /// The choice argument. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static Task> RelabelAsync( this RichRecord.Contract contract, @@ -321,6 +332,45 @@ public static Task> RelabelAsync( timeout, cancellationToken); } + + /// + /// Exercises the Relabel choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<RichRecord> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> RelabelAsync( + this RichRecord.Contract contract, + ILedgerWriter client, + RichRecord.Relabel argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.RelabelAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } } /// @@ -336,25 +386,23 @@ public static class RichRecordSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, RichRecord payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Owner; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -363,7 +411,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class RichRecordNonContractExtensions @@ -390,15 +438,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -408,14 +456,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Slot.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Slot.cs new file mode 100644 index 0000000..eb34b96 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/Slot.cs @@ -0,0 +1,56 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; +using Daml.Runtime.Stdlib; +using System; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml variant Slot +/// Type parameters: a +/// +/// Type parameter a +public abstract record Slot where TA : notnull +{ + /// Gets the variant constructor name. + public abstract string Tag { get; } + + /// Converts to a DamlVariant. + public abstract DamlVariant ToVariant(Func convertTA); + + /// Reconstructs a Slot by dispatching on the DamlVariant constructor tag. + public static Slot FromVariant(DamlVariant variant, Func convertTA) => + variant.Constructor switch + { + "Filled" => new Filled(convertTA(variant.Value)), + "Vacant" => new Vacant(), + _ => throw new ArgumentOutOfRangeException(nameof(variant), variant.Constructor, "Unknown Slot constructor") + }; + + /// Filled constructor. + public sealed record Filled(TA Value) : Slot + { + /// + public override string Tag => "Filled"; + + /// + public override DamlVariant ToVariant(Func convertTA) => DamlVariant.Create("Filled", convertTA(Value)); + } + + /// Vacant constructor (no arguments). + public sealed record Vacant() : Slot + { + /// + public override string Tag => "Vacant"; + + /// + public override DamlVariant ToVariant(Func convertTA) => DamlVariant.Create("Vacant", DamlUnit.Instance); + } + +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.Rebox.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.Rebox.cs new file mode 100644 index 0000000..7f82bb4 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.Rebox.cs @@ -0,0 +1,32 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Runtime.Data; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +public sealed partial record TypeCorners +{ + /// + /// Choice argument type for Rebox. + /// + public sealed record Rebox( + [property: DamlFieldAttribute("replacement")] Box Replacement + ) : IDamlRecord + { + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("replacement", Replacement.ToRecord(__t0 => (DamlValue)(new DamlText(__t0)))) + ); + + /// Creates an instance from a DamlRecord. + public static Rebox FromRecord(DamlRecord record) => new Rebox( + Replacement: Box.FromRecord(record.GetRequiredField("replacement").As(), __v0 => __v0.As().Value) + ); + + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.cs b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.cs new file mode 100644 index 0000000..dd1b8f0 --- /dev/null +++ b/src/Daml.Codegen.Testing.Conformance/Generated/Richtypes/TypeCorners.cs @@ -0,0 +1,481 @@ +// +// This code was generated by daml-codegen-csharp. +// Do not edit this file manually. +// + +#nullable enable + +using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Stdlib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Daml.Codegen.Testing.Conformance.Richtypes; + +/// +/// Generated from Daml template RichTypes:TypeCorners +/// +public sealed partial record TypeCorners( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("boxedText")] Box BoxedText, + [property: DamlFieldAttribute("boxedProfile")] Box BoxedProfile, + [property: DamlFieldAttribute("slot")] Slot Slot, + [property: DamlFieldAttribute("nestedNote")] Box>? NestedNote, + [property: DamlFieldAttribute("maybeMaybeNote")] Optional> MaybeMaybeNote, + [property: DamlFieldAttribute("crate")] Crate Crate, + [property: DamlFieldAttribute("quotaByParty")] IReadOnlyDictionary QuotaByParty, + [property: DamlFieldAttribute("labelByRank")] IReadOnlyDictionary LabelByRank, + [property: DamlFieldAttribute("rankOrLabel")] Either RankOrLabel, + [property: DamlFieldAttribute("noteOrRank")] Either, long> NoteOrRank, + [property: DamlFieldAttribute("pair")] Tuple2 Pair, + [property: DamlFieldAttribute("triple")] Tuple3 Triple, + [property: DamlFieldAttribute("branch")] Branch Branch, + [property: DamlFieldAttribute("whole")] decimal Whole, + [property: DamlFieldAttribute("finest")] decimal Finest +) : ITemplate, IDamlRecord +{ + /// Gets the template identifier. + public static Identifier TemplateId { get; } = new("1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38", "RichTypes", "TypeCorners"); + + /// Gets the package ID. + public static string PackageId => "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; + + /// Gets the package name. + public static string PackageName => "richtypes"; + + /// Gets the package version. + public static Version PackageVersion { get; } = new(0, 0, 1); + + /// Gets the compile-time Daml type descriptor. + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + /// Converts this value to a DamlRecord. + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("owner", Owner.ToDamlValue()), + DamlField.Create("boxedText", BoxedText.ToRecord(__t0 => (DamlValue)(new DamlText(__t0)))), + DamlField.Create("boxedProfile", BoxedProfile.ToRecord(__t0 => (DamlValue)(__t0.ToRecord()))), + DamlField.Create("slot", Slot.ToVariant(__t0 => (DamlValue)(new DamlInt64(__t0)))), + DamlField.Create("nestedNote", NestedNote is { } __NestedNote ? new DamlOptional(__NestedNote.ToRecord(__t0 => (DamlValue)(__t0.ToValue(__optional2 => new DamlText(__optional2))))) : DamlOptional.None), + DamlField.Create("maybeMaybeNote", MaybeMaybeNote.ToChainValue(__optional0 => __optional0.ToChainValue(__optional1 => new DamlText(__optional1)))), + DamlField.Create("crate", Crate.ToRecord(__t0 => (DamlValue)(new DamlText(__t0)))), + DamlField.Create("quotaByParty", new DamlGenMap(QuotaByParty.Select(kv => ((DamlValue)kv.Key.ToDamlValue(), (DamlValue)new DamlInt64(kv.Value))).ToList())), + DamlField.Create("labelByRank", new DamlGenMap(LabelByRank.Select(kv => ((DamlValue)new DamlInt64(kv.Key), (DamlValue)new DamlText(kv.Value))).ToList())), + DamlField.Create("rankOrLabel", RankOrLabel.ToValue(__t0 => (DamlValue)(new DamlInt64(__t0)), __t1 => (DamlValue)(new DamlText(__t1)))), + DamlField.Create("noteOrRank", NoteOrRank.ToValue(__t0 => (DamlValue)(__t0.ToValue(__optional1 => new DamlText(__optional1))), __t1 => (DamlValue)(new DamlInt64(__t1)))), + DamlField.Create("pair", Pair.ToRecord(__t0 => (DamlValue)(new DamlText(__t0)), __t1 => (DamlValue)(new DamlInt64(__t1)))), + DamlField.Create("triple", Triple.ToRecord(__t0 => (DamlValue)(new DamlText(__t0)), __t1 => (DamlValue)(new DamlInt64(__t1)), __t2 => (DamlValue)(new DamlBool(__t2)))), + DamlField.Create("branch", Branch.ToRecord()), + DamlField.Create("whole", new DamlNumeric(Whole)), + DamlField.Create("finest", new DamlNumeric(Finest)) + ); + + /// Creates an instance from a DamlRecord. + public static TypeCorners FromRecord(DamlRecord record) => new TypeCorners( + Owner: Party.FromDamlValue(record.GetRequiredField("owner").As()), + BoxedText: Box.FromRecord(record.GetRequiredField("boxedText").As(), __v0 => __v0.As().Value), + BoxedProfile: Box.FromRecord(record.GetRequiredField("boxedProfile").As(), __v0 => Profile.FromRecord(__v0.As())), + Slot: Slot.FromVariant(record.GetRequiredField("slot").As(), __v0 => __v0.As().Value), + NestedNote: record.GetRequiredField("nestedNote").AsOptional().HasValue ? Box>.FromRecord(record.GetRequiredField("nestedNote").AsOptional().Value!.As(), __v0 => Optional.FromValue(__v0, __optional2 => __optional2.As().Value)) : null, + MaybeMaybeNote: Optional>.FromChainValue(record.GetRequiredField("maybeMaybeNote"), __optional0 => Optional.FromChainValue(__optional0, __optional1 => __optional1.As().Value)), + Crate: Crate.FromRecord(record.GetRequiredField("crate").As(), __v0 => __v0.As().Value), + QuotaByParty: (IReadOnlyDictionary)record.GetRequiredField("quotaByParty").As().Entries.ToDictionary(kv => Party.FromDamlValue(kv.Key.As()), kv => kv.Value.As().Value), + LabelByRank: (IReadOnlyDictionary)record.GetRequiredField("labelByRank").As().Entries.ToDictionary(kv => kv.Key.As().Value, kv => kv.Value.As().Value), + RankOrLabel: Either.FromValue(record.GetRequiredField("rankOrLabel"), __v0 => __v0.As().Value, __v1 => __v1.As().Value), + NoteOrRank: Either, long>.FromValue(record.GetRequiredField("noteOrRank"), __v0 => Optional.FromValue(__v0, __optional1 => __optional1.As().Value), __v1 => __v1.As().Value), + Pair: Tuple2.FromRecord(record.GetRequiredField("pair").As(), __v0 => __v0.As().Value, __v1 => __v1.As().Value), + Triple: Tuple3.FromRecord(record.GetRequiredField("triple").As(), __v0 => __v0.As().Value, __v1 => __v1.As().Value, __v2 => __v2.As().Value), + Branch: Branch.FromRecord(record.GetRequiredField("branch").As()), + Whole: record.GetRequiredField("whole").As().Value, + Finest: record.GetRequiredField("finest").As().Value + ); + + /// + /// Exercise the Archive choice. + /// This choice is consuming and will archive the contract. + /// + public static Choice ChoiceArchive { get; } = new() + { + Name = new ChoiceName("Archive"), + Consuming = true, + ArgumentEncoder = _ => DamlRecord.Create(), + ResultDecoder = _ => DamlUnit.Instance + }; + + /// + /// Exercise the Rebox choice. + /// + public static Choice> ChoiceRebox { get; } = new() + { + Name = new ChoiceName("Rebox"), + Consuming = false, + ArgumentEncoder = arg => arg.ToRecord(), + ResultDecoder = val => new ContractId(val.As().Value) + }; + + /// Contract ID for TypeCorners. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) : ContractId(Value), IExercises + { + ContractId IExercises.ContractId => this; + } + + /// Active contract for TypeCorners. + public sealed record Contract(ContractId Id, TypeCorners Data) : IContract + { + /// Creates a Contract from a CreatedEvent. + public static Contract FromCreatedEvent(CreatedEvent @event) => + new(new ContractId(@event.ContractId), global::Daml.Codegen.Testing.Conformance.Richtypes.TypeCorners.FromRecord(@event.CreateArguments)); + } +} + +/// +/// Typed projection of the contracts created by the Rebox choice. +/// One field per template the choice creates; cardinality follows the choice's +/// return type (single, optional, list). +/// +public sealed record ReboxResult( + ContractId TypeCorners +) +{ + /// + /// Projects an upstream transaction's created contracts to a typed . + /// Returns: + /// + /// when every expected template's cardinality matches. + /// when at least one required slot's template is missing from the transaction. + /// when a single-cardinality slot has more than one created contract of its template, or an optional-cardinality slot has more than one. + /// + /// Cardinality is matched by template ID's (module, entity) pair only — package upgrades that share the same logical template name match cleanly. + /// + public static ExerciseOutcome FromCreatedContracts(IEnumerable created) + { + ArgumentNullException.ThrowIfNull(created); + var templateMatches0 = new List(); + foreach (var item in created) + { + if (string.Equals(item.TemplateId.ModuleName, global::Daml.Codegen.Testing.Conformance.Richtypes.TypeCorners.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(item.TemplateId.EntityName, global::Daml.Codegen.Testing.Conformance.Richtypes.TypeCorners.TemplateId.EntityName, StringComparison.Ordinal)) + { + templateMatches0.Add(item.ContractId); + } + } + var matches0 = new List(); + var templateMatchIndex0 = 0; + if (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + while (templateMatchIndex0 < templateMatches0.Count) + { + matches0.Add(templateMatches0[templateMatchIndex0]); + templateMatchIndex0++; + } + + if (matches0.Count == 0) + { + return new ExerciseOutcome.None(); + } + if (matches0.Count > 1) + { + return new ExerciseOutcome.Many(matches0.Count, matches0); + } + + return new ExerciseOutcome.One(new ReboxResult( + TypeCorners: new ContractId(matches0[0]) + )); + } + +} + + +/// +/// Static <Choice>Async extension methods for . +/// One method per create-bearing choice; each delegates to +/// +/// and projects success via <Choice>Result.FromCreatedContracts. +/// +public static class TypeCornersExtensions +{ + /// + /// Builds the for the Rebox choice on this contract id. + /// + /// The contract on which to exercise the choice. + /// The choice argument. + public static ExerciseCommand ReboxCommand( + this ContractId contractId, + TypeCorners.Rebox argument) + { + ArgumentNullException.ThrowIfNull(contractId); + ArgumentNullException.ThrowIfNull(argument); + return new ExerciseCommand( + TypeCorners.TemplateId, + contractId, + new ChoiceName("Rebox"), + argument.ToRecord()); + } + + /// + /// Exercises the Rebox choice and projects the resulting transaction's created contracts to a typed . + /// One Party parameter is emitted per Daml controller (declaration order). + /// The wrapper builds a from those parties before + /// dispatching to ILedgerWriter. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Controller party from the Daml controller clause, routed into the submission's actAs set. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReboxAsync( + this ContractId contractId, + ILedgerWriter client, + TypeCorners.Rebox argument, + Party owner, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + SubmitterInfo submitter = owner; + + return contractId.ReboxAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Rebox choice with an explicit and projects the resulting transaction's created contracts to a typed . + /// Companion to the named-Party overload for the case where the submitter must + /// read contracts it does not act as — the choice's created contracts are visible to an + /// observer but not to the submitter, so the caller supplies the readAs parties. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ReboxAsync( + this ContractId contractId, + ILedgerWriter client, + TypeCorners.Rebox argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ReboxCommand(argument); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ReboxResult.FromCreatedContracts(tx.CreatedContracts)); + } + + /// + /// Exercises the Rebox choice on a fetched contract, + /// reading every controller and observer party off the contract payload so the + /// caller passes no parties. Delegates to the + /// ContractId<TypeCorners> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReboxAsync( + this TypeCorners.Contract contract, + ILedgerWriter client, + TypeCorners.Rebox argument, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ReboxAsync( + client, + argument, + contract.Data.Owner, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Rebox choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<TypeCorners> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> ReboxAsync( + this TypeCorners.Contract contract, + ILedgerWriter client, + TypeCorners.Rebox argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.ReboxAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } +} + +/// +/// Typed-submitter extensions for . The +/// overload derives the actAs parties +/// from the template payload (for signatories that are payload-field +/// references) and from the caller's explicit arguments (for signatories +/// the static analyzer could not resolve). When the template's +/// observer clause is statically resolvable, an +/// Observers(payload) documentation helper is also emitted. +/// +public static class TypeCornersSubmissionExtensions +{ + /// + /// Creates a new contract on the ledger. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. + /// + /// The ledger client. + /// The contract payload. + /// Cancellation token. + public static Task>> CreateAsync( + this ILedgerWriter client, + TypeCorners payload, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(payload); + + SubmitterInfo submitter = payload.Owner; + + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); + } +} + +/// +/// Async exerciser extensions for contract IDs whose choices +/// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). +/// Each method submits the choice via +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result +/// into ExerciseOutcome<TReturn>. +/// +public static class TypeCornersNonContractExtensions +{ + /// + /// Builds the for the Archive choice on this contract id. + /// + /// The contract on which to exercise the choice. + public static ExerciseCommand ArchiveCommand( + this ContractId contractId) + { + ArgumentNullException.ThrowIfNull(contractId); + return new ExerciseCommand( + TypeCorners.TemplateId, + contractId, + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + /// + /// Exercises the Archive choice and lifts the choice's exercise result to + /// over Unit. Structured Canton/Daml errors + /// and infrastructure/transport errors pass through unchanged. + /// + /// The contract on which to exercise the choice. + /// The ledger client. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static async Task> ArchiveAsync( + this ContractId contractId, + ILedgerWriter client, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + var command = contractId.ArchiveCommand(); + + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); + + return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); + } + + private static ExerciseOutcome ProjectArchiveResult(TransactionResult tx, string contractId) + { + foreach (var exercised in tx.ExercisedEvents) + { + if (string.Equals(exercised.ContractId, contractId, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.ModuleName, TypeCorners.TemplateId.ModuleName, StringComparison.Ordinal) + && string.Equals(exercised.TemplateId.EntityName, TypeCorners.TemplateId.EntityName, StringComparison.Ordinal) + && string.Equals(exercised.ChoiceName, "Archive", StringComparison.Ordinal)) + { + return new ExerciseOutcome.One(Unit.Value); + } + } + + throw new InvalidOperationException( + $"Submission succeeded but no 'Archive' exercise on contract '{contractId}' was recorded on transaction {tx.UpdateId}. " + + "This is most often caused by the ILedgerWriter implementation not populating TransactionResult.ExercisedEvents — " + + "your ILedgerWriter implementation must project the transaction's exercised events into TransactionResult.ExercisedEvents. " + + "If your implementation does populate ExercisedEvents, ensure the participant is configured to return " + + "LedgerEffects with verbose events so the exercise event survives projection."); + } +} diff --git a/src/Daml.Codegen.Testing.Conformance/README.md b/src/Daml.Codegen.Testing.Conformance/README.md index e94600b..7b016f5 100644 --- a/src/Daml.Codegen.Testing.Conformance/README.md +++ b/src/Daml.Codegen.Testing.Conformance/README.md @@ -1,8 +1,38 @@ # Daml.Codegen.Testing.Conformance -Compiled C# generated by `daml-codegen-csharp` from the conformance corpus, -plus the corpus DAR embedded as a resource. Consumers (typically ledger-client +Compiled C# generated by `daml-codegen-csharp` from the conformance corpora, +plus each corpus DAR embedded as a resource. Consumers (typically ledger-client integration test suites) round-trip these types against a real Canton participant to prove codegen output works end-to-end. Not for production use. -`ConformanceCorpus.OpenDar()` returns the embedded DAR to upload to a participant. +`ConformanceCorpus.OpenDar()` returns the embedded rich-types DAR to upload to a +participant; `ConformanceCorpus.OpenDar(ConformancePackage.ContractKeys)` returns +the contract-key one, and `ConformanceCorpus.OpenDar(ConformancePackage.DefaultTarget)` +the one built with no Daml-LF target requested. + +`RichRecord` carries the primitive, collection and nominal shapes. `TypeCorners` +carries the harder ones: parameterized records and variants (`Box`, `Slot`), +`GenMap` keyed by `Party` and by `Int`, `Either`, `Tuple2`/`Tuple3`, a recursive +record (`Branch`), an `Optional` nested directly inside an `Optional` +(`maybeMaybeNote`, held as `Optional>`) and one separated from +its outer `Optional` by an intervening record (`nestedNote`), an `Optional` over +a record's own type variable (`Crate`), held as `Optional` because C# +nullable syntax cannot spell that position, an `Optional` carried by an `Either` +arm (`noteOrRank`, held as `Either, long>` for the same reason), +and the `Numeric` scale extremes 0 +and 37 — the last of which the generated code exposes as `decimal`, so values +needing more than 28 fractional digits are not representable. The `Holding` +interface carries choices as well as a view. Keyed templates live in the second +corpus, `ContractKeys`, which is built at Daml-LF 2.3 because no earlier version +can express a contract key: `Account` keys on a record of two payload fields, +`Holiday` on a record whose field comes from a nested projection, `Schedule` on a +record built by a helper in another module, and `Steward` on a bare `Party`. Each +generated active contract carries a `Key` slot read off the created event, and +each choice has a `ByKeyCommand` builder beside its `Command` +one. + +A third corpus, `DefaultTarget`, carries a single template (`Note`) compiled with +no Daml-LF target requested, so it ships the version damlc picks for a project +that asks for none — the version a scaffolded project actually produces. Its +generated types are compiled and shipped like the other two, so that version is +covered on the code-generation path and not only on the reading one. diff --git a/src/Daml.Ledger.Abstractions.Testing.Conformance/CommandIdConformanceFixture.cs b/src/Daml.Ledger.Abstractions.Testing.Conformance/CommandIdConformanceFixture.cs new file mode 100644 index 0000000..84eefce --- /dev/null +++ b/src/Daml.Ledger.Abstractions.Testing.Conformance/CommandIdConformanceFixture.cs @@ -0,0 +1,57 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime.Commands; + +namespace Daml.Ledger.Abstractions.Testing.Conformance; + +/// +/// A write-capable client and the two single-command submissions it accepts, used to prove that an +/// / +/// +/// implementation honors the deduplication contract both document: the commandId argument +/// reaches the participant verbatim, and a fresh id is minted only when the caller omits it — +/// never leaving the participant's command_id unset. +/// +/// +/// A fresh client that accepts both submissions below. Each conformance check takes its own +/// fixture and submits exactly once against it, so the id read back by +/// can only have come from that one submission — a +/// leftover id from an earlier check cannot masquerade as a minted one. +/// +/// +/// Submits one exercise the accepts, forwarding the supplied +/// to the commandId parameter of +/// +/// verbatim — null included, since passing it through unchanged is what asks the +/// implementation to mint. Adopters bind the choice's result type here, so the kit need not guess +/// a TResult the transport can decode. +/// +/// +/// Submits one create the accepts, forwarding the supplied +/// to the commandId parameter of +/// +/// verbatim, null included. Adopters bind the template type here. +/// +/// +/// Reads back the command_id the participant recorded for the submission just dispatched, +/// or null when it recorded none. A raw string rather than a : an +/// implementation that leaves the field unset is precisely what the mint check must catch, and +/// cannot represent that state — it rejects empty input at construction, +/// and a default instance still reads as present through a while +/// throwing on . +/// +public sealed record CommandIdConformanceFixture( + ILedgerClient Client, + Func Exercise, + Func Create, + Func> ReadRecordedCommandId) : IAsyncDisposable +{ + /// Delegates to today; gives an adopter that seeds + /// supplementary resources alongside the client (e.g. a pre-created contract handle) + /// a place to add their cleanup later. + public ValueTask DisposeAsync() => Client.DisposeAsync(); +} diff --git a/src/Daml.Ledger.Abstractions.Testing.Conformance/Daml.Ledger.Abstractions.Testing.Conformance.csproj b/src/Daml.Ledger.Abstractions.Testing.Conformance/Daml.Ledger.Abstractions.Testing.Conformance.csproj index 599534b..5e7c1f4 100644 --- a/src/Daml.Ledger.Abstractions.Testing.Conformance/Daml.Ledger.Abstractions.Testing.Conformance.csproj +++ b/src/Daml.Ledger.Abstractions.Testing.Conformance/Daml.Ledger.Abstractions.Testing.Conformance.csproj @@ -5,7 +5,6 @@ Behavioral conformance kit for Daml.Ledger.Abstractions: an abstract xUnit base any ILedgerClient implementation subclasses to verify the documented behavioral contract (cancellation, unclassified-row surfacing, terminal checkpoint, seeded-row ordering). daml;canton;blockchain;smart-contracts;ledger;abstractions;conformance;testing README.md - true diff --git a/src/Daml.Ledger.Abstractions.Testing.Conformance/LedgerClientConformanceTests.cs b/src/Daml.Ledger.Abstractions.Testing.Conformance/LedgerClientConformanceTests.cs index 55b1313..8ba07ea 100644 --- a/src/Daml.Ledger.Abstractions.Testing.Conformance/LedgerClientConformanceTests.cs +++ b/src/Daml.Ledger.Abstractions.Testing.Conformance/LedgerClientConformanceTests.cs @@ -10,6 +10,7 @@ using Daml.Runtime; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; +using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using Daml.Runtime.Streams; using Xunit; @@ -30,9 +31,13 @@ namespace Daml.Ledger.Abstractions.Testing.Conformance; /// ), honored (fromOffset, toOffset] /// bounds, and cancellation-honoring streams. /// -/// The Daml marker the seeded snapshot/stream is filtered to. +/// The Daml template the seeded snapshot/stream is filtered to. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1707:Identifiers should not contain underscores", + Justification = "These are xUnit test methods shipped as an abstract conformance base; they follow the repo-wide Subject_scenario_expectation naming that test readers and failure output depend on.")] public abstract class LedgerClientConformanceTests - where TProbe : IDamlType + where TProbe : ITemplate, IDamlRecord { /// Creates a client seeded with the canonical conformance scenario. protected abstract ILedgerClient CreateClient(); @@ -75,6 +80,17 @@ public abstract class LedgerClientConformanceTests /// protected virtual WriteConformanceFixture? CreateWriteFixture() => null; + /// + /// A write-capable client and the submissions proving the commandId deduplication + /// contract of + /// / + /// , + /// or null if the adopter cannot read back the command_id the participant + /// recorded for a submission. Defaults to null, which skips the command-id checks + /// below. Called afresh by each check, which then submits exactly once. + /// + protected virtual CommandIdConformanceFixture? CreateCommandIdFixture() => null; + /// A cancelled live subscription surfaces cancellation, not an in-band error. [Fact] public async Task Cancelling_a_live_subscription_throws_OperationCanceledException() @@ -172,12 +188,12 @@ public async Task Subscribing_from_an_offset_excludes_the_event_at_that_offset() var all = await CollectBounded(client, LedgerOffset.Begin, end); all.Should().NotBeEmpty( "the conformance scenario must seed at least one event on the subscription stream"); - var resumeFrom = OffsetOf(all[0]); + var resumeFrom = FirstPosition(all); var resumed = await CollectBounded(client, resumeFrom, end); resumed.Should().NotContain( - e => OffsetOf(e) == resumeFrom, + e => PositionOf(e) == resumeFrom, "fromOffset is exclusive: resuming from an offset must not re-deliver the event at it"); } @@ -191,16 +207,17 @@ public async Task Bounded_subscription_delivers_the_event_at_toOffset_then_compl var all = await CollectBounded(client, LedgerOffset.Begin, end); all.Should().NotBeEmpty( "the conformance scenario must seed at least one event on the subscription stream"); - var boundary = OffsetOf(all[0]); + var boundary = FirstPosition(all); var bounded = await CollectBounded(client, LedgerOffset.Begin, boundary); bounded.Should().Contain( - e => OffsetOf(e) == boundary, + e => PositionOf(e) == boundary, "toOffset is inclusive: the event at toOffset must be delivered"); bounded.Should().OnlyContain( - e => OffsetOf(e).Value <= boundary.Value, - "a bounded subscription must complete at toOffset and deliver nothing past it"); + e => SitsAtOrBefore(e, boundary), + "a bounded subscription must complete at toOffset and deliver nothing past it; an event " + + "carrying no ledger position cannot sit past the boundary because it sits nowhere"); } /// @@ -346,6 +363,107 @@ await act.Should().ThrowAsync( "authorized pre-set ActAs rescue an unauthorized submitter"); } + /// + /// + /// must dispatch a caller-supplied commandId to the participant verbatim, never + /// substituting one of its own — a caller replaying a lost-but-accepted submission under the + /// same id gets deduplication only if the id survives the call. Opt-in: skipped unless the + /// adopter overrides . + /// + [Fact] + public async Task TryExerciseAsync_dispatches_the_caller_supplied_commandId_verbatim() + { + var maybeFixture = CreateCommandIdFixture(); + Assert.SkipWhen(maybeFixture is null, CommandIdFixtureSkipReason); + await using var fixture = maybeFixture!; + + await fixture.Exercise(fixture.Client, CallerSuppliedCommandId); + + var recorded = await fixture.ReadRecordedCommandId(); + + recorded.Should().Be( + CallerSuppliedCommandId.Value, + "the caller-supplied commandId must reach the participant unchanged; an implementation " + + "that mints its own id anyway silently breaks deduplication across a retry that reuses the id"); + } + + /// + /// + /// must mint a command id when the caller omits one, rather than leaving the participant's + /// command_id unset — an unset id is not deduplicable and leaves the completion + /// uncorrelatable. Opt-in: skipped unless the adopter overrides + /// . Takes its own fresh fixture and submits once, so a + /// leftover id recorded by the sibling check cannot pass for a minted one. + /// + [Fact] + public async Task TryExerciseAsync_mints_a_command_id_when_the_caller_omits_one() + { + var maybeFixture = CreateCommandIdFixture(); + Assert.SkipWhen(maybeFixture is null, CommandIdFixtureSkipReason); + await using var fixture = maybeFixture!; + + await fixture.Exercise(fixture.Client, null); + + var recorded = await fixture.ReadRecordedCommandId(); + + recorded.Should().NotBeNullOrWhiteSpace( + "an omitted commandId obliges the implementation to mint one; an implementation that " + + "forwards the omission and leaves the participant's command_id unset yields a " + + "submission that can neither be deduplicated nor correlated to its completion"); + } + + /// + /// + /// must dispatch a caller-supplied commandId to the participant verbatim, never + /// substituting one of its own. Opt-in: skipped unless the adopter overrides + /// . + /// + [Fact] + public async Task TryCreateAsync_dispatches_the_caller_supplied_commandId_verbatim() + { + var maybeFixture = CreateCommandIdFixture(); + Assert.SkipWhen(maybeFixture is null, CommandIdFixtureSkipReason); + await using var fixture = maybeFixture!; + + await fixture.Create(fixture.Client, CallerSuppliedCommandId); + + var recorded = await fixture.ReadRecordedCommandId(); + + recorded.Should().Be( + CallerSuppliedCommandId.Value, + "the caller-supplied commandId must reach the participant unchanged; an implementation " + + "that mints its own id anyway silently breaks deduplication across a retry that reuses the id"); + } + + /// + /// + /// must mint a command id when the caller omits one, rather than leaving the participant's + /// command_id unset. Opt-in: skipped unless the adopter overrides + /// . Takes its own fresh fixture and submits once, so a + /// leftover id recorded by the sibling check cannot pass for a minted one. + /// + [Fact] + public async Task TryCreateAsync_mints_a_command_id_when_the_caller_omits_one() + { + var maybeFixture = CreateCommandIdFixture(); + Assert.SkipWhen(maybeFixture is null, CommandIdFixtureSkipReason); + await using var fixture = maybeFixture!; + + await fixture.Create(fixture.Client, null); + + var recorded = await fixture.ReadRecordedCommandId(); + + recorded.Should().NotBeNullOrWhiteSpace( + "an omitted commandId obliges the implementation to mint one; an implementation that " + + "forwards the omission and leaves the participant's command_id unset yields a " + + "submission that can neither be deduplicated nor correlated to its completion"); + } + + private static readonly CommandId CallerSuppliedCommandId = new("conformance-caller-supplied-command-id"); + + private const string CommandIdFixtureSkipReason = + "adopter opted out of the command-id check: CreateCommandIdFixture() returned null"; + private const string WriteFixtureSkipReason = "adopter opted out of the submitter-authority check: CreateWriteFixture() returned null"; @@ -400,7 +518,7 @@ private async Task> CollectWithinBudget( return items; } - private static LedgerOffset OffsetOf(ContractStreamEvent e) => e switch + private static LedgerOffset? PositionOf(ContractStreamEvent e) => e switch { ContractStreamEvent.Created c => c.Offset, ContractStreamEvent.Archived a => a.Offset, @@ -413,4 +531,19 @@ private async Task> CollectWithinBudget( "a bounded conformance subscription must not surface a transport StreamError"), _ => throw new InvalidOperationException("unrecognized ContractStreamEvent variant"), }; + + private static bool SitsAtOrBefore(ContractStreamEvent e, LedgerOffset boundary) => + PositionOf(e) is not { } position || position.Value <= boundary.Value; + + private static LedgerOffset FirstPosition(IReadOnlyList> events) + { + var positioned = events.Select(PositionOf).OfType().ToList(); + + positioned.Should().NotBeEmpty( + "the conformance scenario must seed at least one event that carries a ledger position " + + "(Created, Archived, Checkpoint, …); an Unclassified event may legitimately carry none, " + + "so it alone cannot anchor the offset-boundary checks"); + + return positioned[0]; + } } diff --git a/src/Daml.Ledger.Abstractions.Testing.Conformance/README.md b/src/Daml.Ledger.Abstractions.Testing.Conformance/README.md index bba544f..64a174e 100644 --- a/src/Daml.Ledger.Abstractions.Testing.Conformance/README.md +++ b/src/Daml.Ledger.Abstractions.Testing.Conformance/README.md @@ -5,8 +5,18 @@ xUnit base class, `LedgerClientConformanceTests`, that verifies the behavioral contract `Daml.Ledger.Abstractions` documents but cannot enforce by itself. +This kit is for *implementers* of `ILedgerClient`. To unit-test +*application code* that consumes an `ILedgerClient`, don't hand-roll a fake +— use `Canton.Ledger.Testing` (published from +[`canton-ledger-api-csharp`](https://github.com/peacefulstudio/canton-ledger-api-csharp)): +its `FakeLedgerClient` is a stageable in-memory implementation, with +builders for the fiddly event/result types and no mocking framework +required. + A transport package subclasses it, supplying a client factory and the -submitter whose visibility scopes the reads: +submitter whose visibility scopes the reads. `TProbe` is a Daml template in the +template family (`ITemplate, IDamlRecord`) — a generated template already +carries both facets: ```csharp public class MyClientConformanceTests : LedgerClientConformanceTests @@ -17,6 +27,17 @@ public class MyClientConformanceTests : LedgerClientConformanceTests`, +`SubscribeActiveAsync`, `SubscribeLedgerEffectsAsync`. The interface-family +overloads that take a marker's `View` witness are **not exercised**: the kit would need +an interface marker and view record from the adopter's own corpus to drive them, which is +a fixture this kit does not yet ask for. An implementation whose +`SubscribeAsync` gets its offset bounds, cancellation or view-absent +downgrade wrong will still pass this suite. Until that fixture exists, hold those three +members to the contract `ILedgerStreamer` documents by your own tests. + ## Covered contracts - **Cancellation** — a cancelled live subscription surfaces @@ -49,6 +70,13 @@ public class MyClientConformanceTests : LedgerClientConformanceTests Daml.Ledger.Abstractions - Transport-agnostic abstractions for Daml ledger clients. Defines ILedgerClient, the contract any Daml ledger transport (gRPC Canton, planned HTTP REST, in-memory test fake) implements. + Transport-agnostic abstractions for Daml ledger clients. Defines ILedgerClient, the contract any Daml ledger transport (gRPC Canton, HTTP REST, in-memory test fake) implements. daml;canton;blockchain;smart-contracts;ledger;abstractions README.md - true diff --git a/src/Daml.Ledger.Abstractions/Extensions/CreateByExercise.cs b/src/Daml.Ledger.Abstractions/Extensions/CreateByExercise.cs index dac8bc4..eaa19eb 100644 --- a/src/Daml.Ledger.Abstractions/Extensions/CreateByExercise.cs +++ b/src/Daml.Ledger.Abstractions/Extensions/CreateByExercise.cs @@ -33,7 +33,7 @@ public static class CreateByExercise /// carrying every created contract id. /// A writer-level (multiple root transactions) /// is propagated faithfully rather than discarded. Use - /// + /// /// when the choice is expected to create any number of contracts. /// public static async Task>> TryCreateOneByExerciseAsync( @@ -41,38 +41,20 @@ public static async Task>> TryCreateOneByE ExerciseCommand choice, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : IDamlType { ArgumentNullException.ThrowIfNull(writer); - var submission = CommandsSubmission.Single(choice).WithOptionalWorkflowId(workflowId); var outcome = await writer - .TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout, cancellationToken) + .TrySubmitSingleAsync(choice, submitter, workflowId, commandId, timeout, cancellationToken) .ConfigureAwait(false); return RemapExerciseOutcome(outcome, ProjectSingleCreated); } - /// - /// Exercises as a single party and lifts the single created - /// . See - /// . - /// - public static Task>> TryCreateOneByExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand choice, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - where TTemplate : IDamlType - { - SubmitterInfo submitter = actAs; - return writer.TryCreateOneByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken); - } - /// /// Exercises and lifts every created /// into an over a @@ -90,15 +72,15 @@ public static async Task>>> ExerciseCommand choice, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : IDamlType { ArgumentNullException.ThrowIfNull(writer); - var submission = CommandsSubmission.Single(choice).WithOptionalWorkflowId(workflowId); var outcome = await writer - .TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout, cancellationToken) + .TrySubmitSingleAsync(choice, submitter, workflowId, commandId, timeout, cancellationToken) .ConfigureAwait(false); return RemapExerciseOutcome( @@ -106,78 +88,35 @@ public static async Task>>> result => new ExerciseOutcome>>.One(result.All())); } - /// - /// Exercises as a single party and lifts every created - /// . See - /// . - /// - public static Task>>> TryCreateManyByExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand choice, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - where TTemplate : IDamlType - { - SubmitterInfo submitter = actAs; - return writer.TryCreateManyByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken); - } - /// /// Exercises and returns the single created /// contract id, throwing on any other outcome. /// Throws when the choice created no /// (expected exactly one), when it created more than /// one (use - /// - /// instead), or on a Daml or infrastructure error. For structured handling or a - /// per-call deadline, use - /// . + /// + /// instead), or on a Daml or infrastructure error. For structured handling, use + /// . /// public static async Task> CreateOneByExerciseAsync( this ILedgerWriter writer, ExerciseCommand choice, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : IDamlType { var outcome = await writer - .TryCreateOneByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken) + .TryCreateOneByExerciseAsync(choice, submitter, workflowId, commandId, timeout, cancellationToken) .ConfigureAwait(false); - return outcome switch - { - ExerciseOutcome>.One one => one.Result, - ExerciseOutcome>.None => throw new LedgerOperationException( - $"Exercising the choice created no {typeof(TTemplate).Name}; expected exactly one."), - ExerciseOutcome>.Many many => throw new LedgerOperationException( - $"Exercising the choice created {many.Count} {typeof(TTemplate).Name} contracts; expected exactly one. Use CreateManyByExerciseAsync to collect them."), - ExerciseOutcome>.DamlError e => throw e.ToException(), - ExerciseOutcome>.InfraError e => e.ThrowAsCancellationOrException(cancellationToken), - _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from TryCreateOneByExerciseAsync."), - }; - } - - /// - /// Exercises as a single party and returns the single - /// created contract id, throwing on any other - /// outcome. See - /// . - /// - public static Task> CreateOneByExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand choice, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - where TTemplate : IDamlType - { - SubmitterInfo submitter = actAs; - return writer.CreateOneByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken); + return outcome.ResultOrThrow( + "TryCreateOneByExerciseAsync", + static () => $"Exercising the choice created no {typeof(TTemplate).Name}; expected exactly one.", + static count => $"Exercising the choice created {count} {typeof(TTemplate).Name} contracts; expected exactly one. Use CreateManyByExerciseAsync to collect them.", + cancellationToken); } /// @@ -186,52 +125,28 @@ public static Task> CreateOneByExerciseAsync( /// error. The returned list may be empty when the choice created no /// . Throws on /// a Daml or infrastructure error, or when a non-conforming writer yields a - /// None / Many outcome. For structured handling or a per-call deadline, - /// use - /// . + /// None / Many outcome. For structured handling, use + /// . /// public static async Task>> CreateManyByExerciseAsync( this ILedgerWriter writer, ExerciseCommand choice, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : IDamlType { var outcome = await writer - .TryCreateManyByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken) + .TryCreateManyByExerciseAsync(choice, submitter, workflowId, commandId, timeout, cancellationToken) .ConfigureAwait(false); - return outcome switch - { - ExerciseOutcome>>.One one => one.Result, - ExerciseOutcome>>.None => throw new LedgerOperationException( - $"Exercising the choice yielded no result (None) for {typeof(TTemplate).Name}; a conforming writer returns One carrying every created contract."), - ExerciseOutcome>>.Many many => throw new LedgerOperationException( - $"Exercising the choice yielded Many ({many.Count}) for {typeof(TTemplate).Name}; a conforming writer returns One carrying every created contract."), - ExerciseOutcome>>.DamlError e => throw e.ToException(), - ExerciseOutcome>>.InfraError e => e.ThrowAsCancellationOrException(cancellationToken), - _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from TryCreateManyByExerciseAsync."), - }; - } - - /// - /// Exercises as a single party and returns every created - /// contract id, throwing on error. See - /// . - /// - public static Task>> CreateManyByExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand choice, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - where TTemplate : IDamlType - { - SubmitterInfo submitter = actAs; - return writer.CreateManyByExerciseAsync(choice, submitter, workflowId, timeout, cancellationToken); + return outcome.ResultOrThrow( + "TryCreateManyByExerciseAsync", + static () => $"Exercising the choice yielded no result (None) for {typeof(TTemplate).Name}; a conforming writer returns One carrying every created contract.", + static count => $"Exercising the choice yielded Many ({count}) for {typeof(TTemplate).Name}; a conforming writer returns One carrying every created contract.", + cancellationToken); } private static ExerciseOutcome RemapExerciseOutcome( @@ -244,8 +159,8 @@ private static ExerciseOutcome RemapExerciseOutcome( ExerciseOutcome.None => new ExerciseOutcome.None(), ExerciseOutcome.Many many => new ExerciseOutcome.Many(many.Count, many.ContractIds), ExerciseOutcome.DamlError e => new ExerciseOutcome.DamlError(e.Category, e.ErrorId, e.Message, e.Metadata), - ExerciseOutcome.InfraError e => new ExerciseOutcome.InfraError(e.StatusCode, e.Message, e.SourceException), - _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from TrySubmitAndWaitForTransactionAsync."), + ExerciseOutcome.InfraError e => new ExerciseOutcome.InfraError(e.StatusCode, e.Message, e.Category, e.SourceException), + _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from TrySubmitSingleAsync."), }; } diff --git a/src/Daml.Ledger.Abstractions/Extensions/PartyOverloads.cs b/src/Daml.Ledger.Abstractions/Extensions/PartyOverloads.cs deleted file mode 100644 index 96ff0f1..0000000 --- a/src/Daml.Ledger.Abstractions/Extensions/PartyOverloads.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Daml.Runtime; -using Daml.Runtime.Commands; -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using Daml.Runtime.Outcomes; -using Daml.Runtime.Streams; - -namespace Daml.Ledger.Abstractions.Extensions; - -/// Single- convenience overloads over the ledger capabilities. -public static class PartyOverloads -{ - /// Exercises a choice acting as a single party. - public static Task> TryExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand command, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(writer); - SubmitterInfo submitter = actAs; - return writer.TryExerciseAsync(command, submitter, workflowId, timeout, cancellationToken); - } - - /// Creates a contract acting as a single party. - public static Task>> TryCreateAsync( - this ILedgerWriter writer, - TTemplate payload, - Party actAs, - string? workflowId = null, - TimeSpan? timeout = null, - CancellationToken cancellationToken = default) - where TTemplate : ITemplate - { - ArgumentNullException.ThrowIfNull(writer); - SubmitterInfo submitter = actAs; - return writer.TryCreateAsync(payload, submitter, workflowId, timeout, cancellationToken); - } - - /// Subscribes to contract events acting as a single party. - public static IAsyncEnumerable> SubscribeAsync( - this ILedgerStreamer streamer, - Party actAs, - LedgerOffset? fromOffset = null, - LedgerOffset? toOffset = null, - CancellationToken cancellationToken = default) - where T : IDamlType - { - ArgumentNullException.ThrowIfNull(streamer); - SubmitterInfo submitter = actAs; - return streamer.SubscribeAsync(submitter, fromOffset, toOffset, cancellationToken); - } - - /// - /// Subscribes to contract events acting as a single party, resuming a - /// ticket from a prior snapshot's - /// . - /// - public static IAsyncEnumerable> SubscribeAsync( - this ILedgerStreamer streamer, - Party actAs, - StakeholderResume resumeFrom, - LedgerOffset? toOffset = null, - CancellationToken cancellationToken = default) - where T : IDamlType - { - ArgumentNullException.ThrowIfNull(streamer); - SubmitterInfo submitter = actAs; - return streamer.SubscribeAsync(submitter, resumeFrom, toOffset, cancellationToken); - } - - /// Subscribes to the ACS snapshot acting as a single party. - public static IAsyncEnumerable> SubscribeActiveAsync( - this ILedgerStreamer streamer, - Party actAs, - LedgerOffset? activeAtOffset = null, - CancellationToken cancellationToken = default) - where T : IDamlType - { - ArgumentNullException.ThrowIfNull(streamer); - SubmitterInfo submitter = actAs; - return streamer.SubscribeActiveAsync(submitter, activeAtOffset, cancellationToken); - } - - /// Subscribes to ledger-effects events acting as a single party. - public static IAsyncEnumerable> SubscribeLedgerEffectsAsync( - this ILedgerStreamer streamer, - Party actAs, - LedgerOffset? fromOffset = null, - LedgerOffset? toOffset = null, - CancellationToken cancellationToken = default) - where T : IDamlType - { - ArgumentNullException.ThrowIfNull(streamer); - SubmitterInfo submitter = actAs; - return streamer.SubscribeLedgerEffectsAsync(submitter, fromOffset, toOffset, cancellationToken); - } -} diff --git a/src/Daml.Ledger.Abstractions/Extensions/SingleCommandExtensions.cs b/src/Daml.Ledger.Abstractions/Extensions/SingleCommandExtensions.cs new file mode 100644 index 0000000..3099c46 --- /dev/null +++ b/src/Daml.Ledger.Abstractions/Extensions/SingleCommandExtensions.cs @@ -0,0 +1,71 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Outcomes; + +namespace Daml.Ledger.Abstractions.Extensions; + +/// +/// The single-command submission path shared by generated choice exercisers and by the +/// hand-written write-path extensions, so the submission a single command is wrapped in +/// is built in one place rather than at every call site. +/// +public static class SingleCommandExtensions +{ + /// + /// Submits as a single-command transaction and returns the + /// submission task, minting a command id when the caller supplies none so every + /// submission carries one rather than leaving command_id unset, where + /// deduplicability would depend on the transport. + /// + /// + /// Thrown synchronously when or is null. + /// + /// + /// Thrown synchronously when is a default (uninitialized) value. + /// + /// The ledger writer. + /// The single command to submit. + /// The submitter party set (actAs + optional readAs). + /// + /// Optional workflow id; passed through to the ledger when non-empty. A null or empty + /// value leaves workflow_id unset, because it is a correlation key and an empty one + /// correlates nothing. + /// + /// + /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the + /// same id across a retry of a lost-but-accepted submission so the ledger deduplicates the + /// resubmission instead of re-executing the command. A minted id is not returned on a failed + /// submission, so only a caller-supplied id makes an application-level retry deduplicable. + /// + /// + /// Optional per-call deadline, applied best-effort by the transport; transports without a + /// server-side deadline apply a client-side bound only. null applies no deadline. + /// + /// Cancellation token. + /// The outcome of the submission, carrying the resulting transaction on success. + public static Task> TrySubmitSingleAsync( + this ILedgerWriter writer, + ICommand command, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(writer); + ArgumentNullException.ThrowIfNull(command); + WriterExtensionHelpers.ThrowIfDefault(commandId); + + var submission = CommandsSubmission.Single(command) + .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())) + .WithOptionalWorkflowId(workflowId); + + return writer.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout, cancellationToken); + } +} diff --git a/src/Daml.Ledger.Abstractions/Extensions/StreamerSnapshot.cs b/src/Daml.Ledger.Abstractions/Extensions/StreamerSnapshot.cs new file mode 100644 index 0000000..80a4b6c --- /dev/null +++ b/src/Daml.Ledger.Abstractions/Extensions/StreamerSnapshot.cs @@ -0,0 +1,229 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Streams; + +namespace Daml.Ledger.Abstractions.Extensions; + +/// +/// Throwing convenience over for the +/// common case: a caller who wants the active contracts and nothing else. +/// +public static class StreamerSnapshot +{ + /// + /// Drains an active-contract-set snapshot into a materialized list of typed + /// contracts. + /// + /// + /// + /// A snapshot that faults, carries a row the projector could not classify, or ends + /// without its terminal checkpoint throws + /// rather than shortening the returned list: a terminal + /// means the snapshot is incomplete, an + /// row means a row was not projected, + /// and a stream that ends without the documented terminal + /// means the transport truncated it. + /// rows are collected, and the checkpoint + /// terminates the drain. + /// A short list that looks complete is the failure mode this convenience exists to + /// prevent; use directly for + /// value-shaped handling of those cases. + /// + /// + /// A create row whose payload did not decode into never reaches + /// here as a create row: the projector surfaces it as + /// with + /// , which this method reports as + /// along with every other unclassified row. + /// + /// + /// Cancelling surfaces as + /// even when the transport reports the cancelled + /// call in-band as a or by ending the stream + /// short, matching the write-path conveniences in this namespace. + /// + /// + /// The terminal checkpoint's ticket is consumed and + /// discarded, so this method cannot feed the gapless snapshot-to-stream handover. + /// Stay on when the resume + /// offset matters. + /// + /// + /// The returned contracts carry no contract key. A keyed template's snapshot goes through + /// the + /// overload, which yields with the key decoded. + /// + /// + /// The Daml template the snapshot is filtered to. + /// The streaming capability. + /// The submitter authorization whose combined parties scope visibility. + /// Snapshot offset; null means the current ledger end. + /// Cancels the underlying stream cleanly. + /// is null. + /// + /// The snapshot faulted, carried an unclassified row, or ended without its terminal + /// checkpoint. + /// + /// + /// was cancelled. + /// + public static async Task>> SnapshotAsync( + this ILedgerStreamer streamer, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord + { + ArgumentNullException.ThrowIfNull(streamer); + + var rows = await DrainAsync(streamer, submitter, activeAtOffset, cancellationToken) + .ConfigureAwait(false); + + var contracts = new List>(rows.Count); + foreach (var row in rows) + { + contracts.Add(row.ToContract()); + } + + return contracts; + } + + /// + /// Drains an active-contract-set snapshot of a keyed template into a materialized list of + /// typed contracts, each carrying its contract key decoded into + /// . + /// + /// + /// Behaves exactly as + /// + /// for every fault, unclassified row and truncation case, and additionally throws + /// when a create row of a keyed template arrives + /// without the key the keyed shape requires. + /// + /// The keyed Daml template the snapshot is filtered to. + /// The template's contract key type. + /// The streaming capability. + /// The template's key witness. It is the type argument carrier — passing + /// Account.Key infers both and + /// from one argument, which C# cannot do from a partial type-argument list. The decode is + /// taken from the template's own witness, so this + /// overload and decode identically. + /// The submitter authorization whose combined parties scope visibility. + /// Snapshot offset; null means the current ledger end. + /// Cancels the underlying stream cleanly. + /// or + /// is null. + /// + /// The snapshot faulted, carried an unclassified row, ended without its terminal + /// checkpoint, or carried a create row with no contract key. + /// + /// + /// was cancelled. + /// + public static async Task>> SnapshotAsync( + this ILedgerStreamer streamer, + KeyDescriptor key, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord, IHasKey + { + ArgumentNullException.ThrowIfNull(streamer); + ArgumentNullException.ThrowIfNull(key); + _ = key; // type-inference carrier only; decode uses T.Key per IHasKey + + var rows = await DrainAsync(streamer, submitter, activeAtOffset, cancellationToken) + .ConfigureAwait(false); + + var contracts = new List>(rows.Count); + foreach (var row in rows) + { + contracts.Add(ToKeyedContract(row)); + } + + return contracts; + } + + private static Contract ToKeyedContract(AcsSnapshotEntry.Created created) + where T : ITemplate, IDamlRecord, IHasKey + { + if (created.Key is null) + throw new LedgerOperationException( + $"The active-contract-set snapshot for keyed template {typeof(T).Name} carried a " + + $"create row for contract '{created.ContractId.Value}' with no contract key, so " + + "the keyed contracts would be incomplete. Use the keyless SnapshotAsync overload " + + "when the transport does not supply keys."); + return created.ToContract(); + } + + private static async Task.Created>> DrainAsync( + ILedgerStreamer streamer, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset, + CancellationToken cancellationToken) + where T : ITemplate, IDamlRecord + { + var rows = new List.Created>(); + var reachedCheckpoint = false; + + await foreach (var entry in streamer + .SubscribeActiveAsync(submitter, activeAtOffset, cancellationToken) + .ConfigureAwait(false)) + { + switch (entry) + { + case AcsSnapshotEntry.Created created: + rows.Add(created); + break; + case AcsSnapshotEntry.Checkpoint: + reachedCheckpoint = true; + goto done; + case AcsSnapshotEntry.StreamError error: + cancellationToken.ThrowIfCancellationRequested(); + throw new LedgerOperationException( + $"The active-contract-set snapshot for {typeof(T).Name} faulted after {rows.Count} " + + $"contract(s): {error.Message}. Use SubscribeActiveAsync for value-shaped fault handling.", + error.StatusCode, + error.Category, + error.SourceException); + case AcsSnapshotEntry.Unclassified unclassified: + throw new LedgerOperationException( + $"The active-contract-set snapshot for {typeof(T).Name} carried an unclassified row " + + $"({DescribeKind(unclassified)}) {DescribePosition(unclassified.Offset)}, so the returned " + + "contracts would be incomplete. Use SubscribeActiveAsync to handle it as a value."); + default: + throw new LedgerOperationException( + $"Unexpected snapshot entry {entry.GetType().Name} for {typeof(T).Name}."); + } + } + done: + + if (!reachedCheckpoint) + { + cancellationToken.ThrowIfCancellationRequested(); + throw new LedgerOperationException( + $"The active-contract-set snapshot for {typeof(T).Name} ended after {rows.Count} contract(s) " + + "without its terminal checkpoint, so the returned contracts would be incomplete."); + } + + return rows; + } + + private static string DescribeKind(AcsSnapshotEntry.Unclassified unclassified) + where T : ITemplate, IDamlRecord => + unclassified.RawKind is null + ? unclassified.Kind.ToString() + : $"{unclassified.Kind}: '{unclassified.RawKind}'"; + + private static string DescribePosition(LedgerOffset? offset) => + offset is { } at ? $"at offset {at}" : "with no ledger offset"; +} diff --git a/src/Daml.Ledger.Abstractions/Extensions/ThrowingExercise.cs b/src/Daml.Ledger.Abstractions/Extensions/ThrowingExercise.cs index 69b46eb..4f7cc1e 100644 --- a/src/Daml.Ledger.Abstractions/Extensions/ThrowingExercise.cs +++ b/src/Daml.Ledger.Abstractions/Extensions/ThrowingExercise.cs @@ -16,110 +16,81 @@ namespace Daml.Ledger.Abstractions.Extensions; /// public static class ThrowingExercise { - /// - /// Exercises a choice and returns the typed result. Throws - /// on Daml or infrastructure errors. - /// For structured error handling or a per-call deadline, use - /// instead. - /// - public static async Task ExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand command, - Party actAs, - string? workflowId = null, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(writer); - var outcome = await writer.TryExerciseAsync(command, actAs, workflowId, cancellationToken: cancellationToken).ConfigureAwait(false); - return outcome.GetResultOrThrow(cancellationToken); - } - /// /// Exercises a choice using a multi-party and returns /// the typed result. Throws on error. - /// For structured error handling or a per-call deadline, use - /// instead. + /// For structured error handling, use + /// instead. /// public static async Task ExerciseAsync( this ILedgerWriter writer, ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(writer); - var outcome = await writer.TryExerciseAsync(command, submitter, workflowId, cancellationToken: cancellationToken).ConfigureAwait(false); + WriterExtensionHelpers.ThrowIfDefault(commandId); + var outcome = await writer.TryExerciseAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.GetResultOrThrow(cancellationToken); } - /// - /// Exercises a void choice acting as a single party, throwing on failure. - /// Submits the exercise as a single-command transaction and discards the result: - /// One, None, and Many outcomes are all treated as success — - /// a void caller has discarded the result and no distinction between them is needed. - /// Only structured Daml errors and infrastructure errors throw - /// . - /// - public static async Task ExerciseAsync( - this ILedgerWriter writer, - ExerciseCommand command, - Party actAs, - string? workflowId = null, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(writer); - var submission = CommandsSubmission.Single(command).WithOptionalWorkflowId(workflowId); - var outcome = await writer - .TrySubmitAndWaitForTransactionAsync(submission, actAs, cancellationToken: cancellationToken) - .ConfigureAwait(false); - outcome.ThrowIfError(cancellationToken); - } - /// /// Exercises a void choice with an explicit submitter, throwing on failure. - /// Submits the exercise as a single-command transaction and discards the result: - /// One, None, and Many outcomes are all treated as success. - /// Only structured Daml errors and infrastructure errors throw + /// Submits the exercise as a single-command transaction and discards the result. + /// A None outcome throws : a method whose + /// contract is to throw on failure cannot return normally when the writer reports that + /// the submission produced no transaction. + /// A Many outcome succeeds: the transaction committed, so reporting it as a + /// submission failure would risk a resubmission of already-accepted work. + /// Structured Daml errors and infrastructure errors also throw /// . + /// For structured error handling, use + /// instead. /// public static async Task ExerciseAsync( this ILedgerWriter writer, ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(writer); - var submission = CommandsSubmission.Single(command).WithOptionalWorkflowId(workflowId); var outcome = await writer - .TrySubmitAndWaitForTransactionAsync(submission, submitter, cancellationToken: cancellationToken) + .TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken) .ConfigureAwait(false); outcome.ThrowIfError(cancellationToken); } private static T GetResultOrThrow(this ExerciseOutcome outcome, CancellationToken cancellationToken) => - outcome switch - { - ExerciseOutcome.One success => success.Result, - ExerciseOutcome.None => throw new LedgerOperationException( - "TryExerciseAsync returned no result (None); expected exactly one. Use TryExerciseAsync for structured handling."), - ExerciseOutcome.Many m => throw new LedgerOperationException( - $"TryExerciseAsync returned Many ({m.Count} results); use TryCreateManyByExerciseAsync to collect created contract ids, or inspect the TransactionResult directly."), - ExerciseOutcome.DamlError e => throw e.ToException(), - ExerciseOutcome.InfraError e => e.ThrowAsCancellationOrException(cancellationToken), - _ => throw new LedgerOperationException( - $"Unexpected outcome {outcome.GetType().Name} from TryExerciseAsync."), - }; + outcome.ResultOrThrow( + "TryExerciseAsync", + static () => "TryExerciseAsync returned no result (None); expected exactly one. Use TryExerciseAsync for structured handling.", + static count => $"TryExerciseAsync returned Many ({count} results); use TryCreateManyByExerciseAsync to collect created contract ids, or inspect the TransactionResult directly.", + cancellationToken); private static void ThrowIfError(this ExerciseOutcome outcome, CancellationToken cancellationToken) { switch (outcome) { + case ExerciseOutcome.One: + case ExerciseOutcome.Many: + break; + case ExerciseOutcome.None: + throw new LedgerOperationException( + "TrySubmitSingleAsync returned no transaction (None); nothing committed. Use TrySubmitSingleAsync for the non-throwing path."); case ExerciseOutcome.DamlError e: throw e.ToException(); case ExerciseOutcome.InfraError e: e.ThrowAsCancellationOrException(cancellationToken); break; + default: + throw new LedgerOperationException( + $"Unexpected outcome {outcome.GetType().Name} from TrySubmitSingleAsync."); } } } diff --git a/src/Daml.Ledger.Abstractions/Extensions/WriterExtensionHelpers.cs b/src/Daml.Ledger.Abstractions/Extensions/WriterExtensionHelpers.cs index 1e7810c..f052318 100644 --- a/src/Daml.Ledger.Abstractions/Extensions/WriterExtensionHelpers.cs +++ b/src/Daml.Ledger.Abstractions/Extensions/WriterExtensionHelpers.cs @@ -1,6 +1,8 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using Daml.Runtime.Commands; @@ -10,15 +12,39 @@ namespace Daml.Ledger.Abstractions.Extensions; internal static class WriterExtensionHelpers { - internal static CommandsSubmission WithOptionalWorkflowId(this CommandsSubmission submission, string? workflowId) => - workflowId is null ? submission : submission.WithWorkflowId(new WorkflowId(workflowId)); + internal static void ThrowIfDefault(CommandId? commandId) + { + if (commandId == default(CommandId)) + { + throw new ArgumentException( + "A default (uninitialized) CommandId cannot be submitted. Construct one from a non-empty string, or omit the argument to have a fresh id minted.", + nameof(commandId)); + } + } + + internal static T ResultOrThrow( + this ExerciseOutcome outcome, + string operation, + Func describeNone, + Func describeMany, + CancellationToken cancellationToken) => + outcome switch + { + ExerciseOutcome.One one => one.Result, + ExerciseOutcome.None => throw new LedgerOperationException(describeNone()), + ExerciseOutcome.Many many => throw new LedgerOperationException(describeMany(many.Count)), + ExerciseOutcome.DamlError e => throw e.ToException(), + ExerciseOutcome.InfraError e => e.ThrowAsCancellationOrException(cancellationToken), + _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from {operation}."), + }; internal static LedgerOperationException ToException(this ExerciseOutcome.DamlError error) => new($"Daml error [{error.Category}/{error.ErrorId}]: {error.Message}", error.Category, error.ErrorId, error.Metadata); internal static LedgerOperationException ToException(this ExerciseOutcome.InfraError error) => - new($"Infrastructure error [{error.StatusCode}]: {error.Message}", error.StatusCode, error.SourceException); + new($"Infrastructure error [{error.StatusCode}]: {error.Message}", + error.StatusCode, error.Category, error.SourceException); [DoesNotReturn] internal static T ThrowAsCancellationOrException(this ExerciseOutcome.InfraError error, CancellationToken cancellationToken) diff --git a/src/Daml.Ledger.Abstractions/ILedgerClient.cs b/src/Daml.Ledger.Abstractions/ILedgerClient.cs index 4bd3740..0a88859 100644 --- a/src/Daml.Ledger.Abstractions/ILedgerClient.cs +++ b/src/Daml.Ledger.Abstractions/ILedgerClient.cs @@ -8,7 +8,7 @@ namespace Daml.Ledger.Abstractions; /// /// A ledger client: the composition of the write, read, and streaming capabilities. -/// Derived transports (gRPC today, JSON/REST planned) implement this whole surface. +/// Derived transports (gRPC, JSON/REST) implement this whole surface. /// public interface ILedgerClient : ILedgerWriter, ILedgerReader, ILedgerStreamer, IDisposable, IAsyncDisposable { diff --git a/src/Daml.Ledger.Abstractions/ILedgerReader.cs b/src/Daml.Ledger.Abstractions/ILedgerReader.cs index c9d8cca..11654fb 100644 --- a/src/Daml.Ledger.Abstractions/ILedgerReader.cs +++ b/src/Daml.Ledger.Abstractions/ILedgerReader.cs @@ -14,7 +14,7 @@ public interface ILedgerReader /// Gets the current end of the participant's ledger. /// /// Optional per-call deadline, applied best-effort by the transport — see - /// + /// /// for the deadline contract. /// /// Cancels the call. diff --git a/src/Daml.Ledger.Abstractions/ILedgerStreamer.cs b/src/Daml.Ledger.Abstractions/ILedgerStreamer.cs index fc04912..717766f 100644 --- a/src/Daml.Ledger.Abstractions/ILedgerStreamer.cs +++ b/src/Daml.Ledger.Abstractions/ILedgerStreamer.cs @@ -5,6 +5,8 @@ using System.Threading; using Daml.Runtime; using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; using Daml.Runtime.Streams; namespace Daml.Ledger.Abstractions; @@ -40,8 +42,14 @@ public interface ILedgerStreamer /// shape. For an event boundary, break out of the enumeration; for an /// offset boundary, pass . /// - /// A Daml template or interface marker. - /// The submitter authorization whose combined parties scope visibility. + /// The Daml template the stream is filtered to. Subscribing to a + /// Daml interface goes through the witness overload of the same name, which yields the + /// interface's view record as the payload. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// /// /// Exclusive lower bound: the stream resumes strictly after this offset, so the /// event at is never delivered. null means @@ -63,7 +71,7 @@ IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType; + where T : ITemplate, IDamlRecord; /// /// Resumes the stakeholder-based live stream from a @@ -77,8 +85,14 @@ IAsyncEnumerable> SubscribeAsync( /// via ; implementations may override for a more /// direct path. /// - /// A Daml template or interface marker. - /// The submitter authorization whose combined parties scope visibility. + /// The Daml template the stream is filtered to. Subscribing to a + /// Daml interface goes through the witness overload of the same name, which yields the + /// interface's view record as the payload. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// /// The resume ticket from a /// snapshot's terminal . /// @@ -92,7 +106,7 @@ IAsyncEnumerable> SubscribeAsync( StakeholderResume resumeFrom, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => SubscribeAsync(submitter, resumeFrom.Offset, toOffset, cancellationToken); /// Streams the active-contract-set snapshot for . @@ -109,19 +123,27 @@ IAsyncEnumerable> SubscribeAsync( /// that , so a caller draining the /// snapshot handles faults as values rather than exceptions — the same fault /// contract as . + /// An active contract arrives as an row + /// carrying its create-arguments already decoded into . /// See Canton /// Ledger API — active contracts snapshot and its active-at offset for the snapshot→stream /// handover this mirrors. /// - /// A Daml template or interface marker. - /// The submitter authorization whose combined parties scope visibility. + /// The Daml template the stream is filtered to. Subscribing to a + /// Daml interface goes through the witness overload of the same name, which yields the + /// interface's view record as the payload. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// /// Snapshot offset; null means the current ledger end. /// Cancels the underlying stream cleanly. IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType; + where T : ITemplate, IDamlRecord; /// /// Streams ledger-effects events for over the half-open @@ -158,8 +180,14 @@ IAsyncEnumerable> SubscribeActiveAsync( /// event), break out of the /// enumeration; for an offset boundary, pass . /// - /// A Daml template or interface marker. - /// The submitter authorization whose combined parties scope visibility. + /// The Daml template the stream is filtered to. Subscribing to a + /// Daml interface goes through the witness overload of the same name, which yields the + /// interface's view record as the payload. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// /// /// Exclusive lower bound: the stream resumes strictly after this offset, so the /// event at is never delivered. null means @@ -179,5 +207,175 @@ IAsyncEnumerable> SubscribeLedgerEffectsAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType; + where T : ITemplate, IDamlRecord; + + /// + /// Streams active-contract-set delta events for the Daml interface + /// over the half-open offset window + /// (fromOffset, toOffset] — lower bound exclusive, upper bound inclusive. + /// + /// + /// The interface-family counterpart of + /// , + /// with the same shape contract: it emits + /// and + /// and never + /// . The payload is the + /// participant-computed interface view rather than the implementing template's record, + /// which an interface subscription never sees; a matching event delivered without a view + /// is surfaced as + /// with + /// . + /// + /// The Daml interface marker the stream is filtered to. + /// The interface's view record type. + /// The marker's generated View witness — e.g. + /// SubscribeAsync(IHolding.View, parties). C# performs no partial type-argument + /// inference, so the pair travels as one argument and both type parameters are inferred + /// from it. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// + /// + /// Exclusive lower bound: the stream resumes strictly after this offset, so the + /// event at is never delivered. null means + /// . + /// + /// + /// Inclusive, terminal upper bound: the event at is + /// delivered and then the bounded stream completes. null follows the live + /// stream, which does not complete on its own. + /// + /// Cancels the underlying stream cleanly. + IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord; + + /// + /// Resumes the stakeholder-based live interface stream from a + /// snapshot's terminal checkpoint — + /// the interface-family counterpart of + /// . + /// + /// + /// Default implementation forwards to + /// + /// via ; implementations may override for a more + /// direct path. + /// + /// The Daml interface marker the stream is filtered to. + /// The interface's view record type. + /// The marker's generated View witness. + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// + /// The resume ticket from a + /// snapshot's terminal + /// . + /// + /// Inclusive, terminal upper bound: the event at is + /// delivered and then the bounded stream completes. null follows the live + /// stream, which does not complete on its own. + /// + /// Cancels the underlying stream cleanly. + IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + StakeholderResume resumeFrom, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + SubscribeAsync(view, submitter, resumeFrom.Offset, toOffset, cancellationToken); + + /// + /// Streams the active-contract-set snapshot for the Daml interface + /// . + /// + /// + /// The interface-family counterpart of , with the + /// same terminal contract: a successful snapshot ends with a single + /// carrying a + /// ticket — emitted even when the snapshot is empty — and + /// a mid-snapshot transport fault ends it with a terminal + /// instead. Each + /// active contract arrives as an + /// row carrying the + /// participant-computed interface view decoded into ; a row + /// delivered without a view is surfaced as + /// with + /// . + /// + /// The Daml interface marker the snapshot is filtered to. + /// The interface's view record type. + /// The marker's generated View witness — e.g. + /// SubscribeActiveAsync(IHolding.View, parties). + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// + /// Snapshot offset; null means the current ledger end. + /// Cancels the underlying stream cleanly. + IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord; + + /// + /// Streams ledger-effects events for the Daml interface + /// over the half-open offset window (fromOffset, toOffset] — lower bound + /// exclusive, upper bound inclusive. + /// + /// + /// The interface-family counterpart of , with + /// the same shape contract: a consuming + /// is a contract's + /// archival on this shape, so it never emits + /// . As on every interface + /// subscription, the payload is the participant-computed view and a matching event + /// delivered without one is surfaced as + /// with + /// . + /// + /// The Daml interface marker the stream is filtered to. + /// The interface's view record type. + /// The marker's generated View witness — e.g. + /// SubscribeLedgerEffectsAsync(IHolding.View, parties). + /// + /// The submitter authorization whose combined parties scope visibility. A single + /// converts implicitly, so a one-party + /// subscription passes the party itself here. + /// + /// + /// Exclusive lower bound: the stream resumes strictly after this offset, so the + /// event at is never delivered. null means + /// . + /// + /// + /// Inclusive, terminal upper bound: the event at is + /// delivered and then the bounded stream completes. null follows the live + /// stream, which does not complete on its own. + /// + /// Cancels the underlying stream cleanly. + IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord; } diff --git a/src/Daml.Ledger.Abstractions/ILedgerWriter.cs b/src/Daml.Ledger.Abstractions/ILedgerWriter.cs index f3e529e..d802653 100644 --- a/src/Daml.Ledger.Abstractions/ILedgerWriter.cs +++ b/src/Daml.Ledger.Abstractions/ILedgerWriter.cs @@ -21,19 +21,30 @@ public interface ILedgerWriter /// primary authorization-carrying overload: the /// carries the act-as parties and any optional read-as parties through to the /// implementation. - /// Use + /// Use /// for the throwing convenience overload. /// /// The result type of the choice. /// The exercise command. - /// The submitter authorization (act-as parties and optional read-as parties). + /// + /// The submitter authorization (act-as parties and optional read-as parties). A single + /// converts implicitly, so a one-party submission + /// passes the party itself here. + /// /// Optional workflow identifier. + /// + /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the + /// same id across a retry of a lost-but-accepted submission so the ledger deduplicates the + /// resubmission instead of re-executing the command. + /// /// - /// Optional per-call deadline, applied best-effort by the transport (for gRPC - /// transports, mapped to CallOptions.Deadline): the transport bounds - /// server-side call duration, but this is not a hard guarantee that participant-side - /// work stops the instant the deadline elapses. An overrun is a transport failure and - /// surfaces as an outcome. null + /// Optional per-call deadline, applied best-effort by the transport. gRPC transports + /// map it to CallOptions.Deadline, which bounds server-side call duration — + /// still not a hard guarantee that participant-side work stops the instant the + /// deadline elapses. Transports without a server-side deadline, such as JSON/REST, + /// apply a client-side bound only and cannot stop the participant working on the + /// call. An overrun is a transport failure and surfaces as an + /// outcome. null /// applies no deadline; then remains the only bound. /// /// @@ -49,6 +60,7 @@ Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default); @@ -64,7 +76,7 @@ Task> TryExerciseAsync( /// /// /// Optional per-call deadline, applied best-effort by the transport — see - /// + /// /// for the deadline contract. An overrun surfaces as a transport failure. /// /// @@ -107,7 +119,7 @@ Task SubmitAndWaitAsync( /// /// /// Optional per-call deadline, applied best-effort by the transport — see - /// + /// /// for the deadline contract. An overrun surfaces as an /// outcome. /// @@ -116,7 +128,7 @@ Task SubmitAndWaitAsync( /// , not as an /// outcome — see the /// contract documented on - /// . + /// . /// /// The outcome of the submission. Task> TrySubmitAndWaitForTransactionAsync( @@ -134,11 +146,20 @@ Task> TrySubmitAndWaitForTransactionAsync( /// /// The template type expected to be created. /// The template payload. - /// The submitter authorization (act-as parties and optional read-as parties). + /// + /// The submitter authorization (act-as parties and optional read-as parties). A single + /// converts implicitly, so a one-party submission + /// passes the party itself here. + /// /// Optional workflow identifier. + /// + /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the + /// same id across a retry of a lost-but-accepted submission so the ledger deduplicates the + /// resubmission instead of re-executing the command. + /// /// /// Optional per-call deadline, applied best-effort by the transport — see - /// + /// /// for the deadline contract. An overrun surfaces as an /// outcome. /// @@ -146,12 +167,13 @@ Task> TrySubmitAndWaitForTransactionAsync( /// Cancellation token. Cancelling it must surface as /// , not as an /// outcome — see the contract - /// documented on . + /// documented on . /// Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate; diff --git a/src/Daml.Ledger.Abstractions/LedgerNamespaces.cs b/src/Daml.Ledger.Abstractions/LedgerNamespaces.cs index 515d476..6cb7b9f 100644 --- a/src/Daml.Ledger.Abstractions/LedgerNamespaces.cs +++ b/src/Daml.Ledger.Abstractions/LedgerNamespaces.cs @@ -10,4 +10,7 @@ public static class LedgerNamespaces { /// The Daml.Ledger.Abstractions namespace. public const string Abstractions = "Daml.Ledger.Abstractions"; + + /// The Daml.Ledger.Abstractions.Extensions namespace. + public const string AbstractionsExtensions = "Daml.Ledger.Abstractions.Extensions"; } diff --git a/src/Daml.Ledger.Abstractions/LedgerOperationException.cs b/src/Daml.Ledger.Abstractions/LedgerOperationException.cs index cfbb1b7..72d8ecf 100644 --- a/src/Daml.Ledger.Abstractions/LedgerOperationException.cs +++ b/src/Daml.Ledger.Abstractions/LedgerOperationException.cs @@ -10,7 +10,8 @@ namespace Daml.Ledger.Abstractions; /// when the underlying Try* method yields a non-success outcome. Carries the /// structured data of a / /// outcome so catch sites keep access to -/// the detail the structured API exposes. Derives from +/// the detail the structured API exposes; a classified infrastructure failure carries its +/// alongside its . Derives from /// because the convenience wrappers /// previously threw directly; the base /// type preserves that catch contract. @@ -19,7 +20,9 @@ public sealed class LedgerOperationException : InvalidOperationException { /// /// Canton error category when the failed outcome was a - /// ; otherwise null. + /// , or when it was an + /// the transport classified without a + /// structured Canton error attached; null when neither applies. /// public DamlErrorCategory? Category { get; } @@ -76,21 +79,18 @@ public LedgerOperationException( } /// - /// Creates an exception carrying an outcome. + /// Creates an exception carrying an outcome, + /// its classification when the transport determined one, and the transport exception that + /// caused it when available. /// - public LedgerOperationException(string message, int statusCode) - : base(message) - { - StatusCode = statusCode; - } - - /// - /// Creates an exception carrying an outcome - /// and preserving the transport exception that caused it. - /// - public LedgerOperationException(string message, int statusCode, Exception? innerException) + public LedgerOperationException( + string message, + int statusCode, + DamlErrorCategory? category = null, + Exception? innerException = null) : base(message, innerException) { StatusCode = statusCode; + Category = category; } } diff --git a/src/Daml.Ledger.Abstractions/README.md b/src/Daml.Ledger.Abstractions/README.md index dbc86be..9c0bb0f 100644 --- a/src/Daml.Ledger.Abstractions/README.md +++ b/src/Daml.Ledger.Abstractions/README.md @@ -12,12 +12,15 @@ covering one slice of ledger interaction: A transport implements the split interfaces (and therefore `ILedgerClient`); a consumer that only needs to read or stream can depend on `ILedgerReader` or -`ILedgerStreamer` alone. Implementations live in their own transport -packages: +`ILedgerStreamer` alone. Implementations live in their own packages, +published to NuGet.org from +[`canton-ledger-api-csharp`](https://github.com/peacefulstudio/canton-ledger-api-csharp): -- (planned, not yet published) gRPC client for Canton participants -- (planned) HTTP REST client for the Daml JSON Ledger API -- in-memory test fakes for application testing +- `Canton.Ledger.Grpc.Client` — gRPC client for Canton participants +- `Canton.Ledger.Rest.Client` — HTTP client for the Canton JSON Ledger API +- `Canton.Ledger.Testing` — in-memory fakes (`FakeLedgerClient` and friends) + for unit-testing application code without a live participant, no mocking + framework required Derived convenience methods — single-`Party` overloads, the throwing `ExerciseAsync` wrappers, and the create-by-exercise helpers @@ -28,13 +31,52 @@ Daml.Ledger.Abstractions.Extensions;`. The capability interfaces stay free of this sugar, so a consumer that only needs the `SubmitterInfo`-based primitives never has it in scope. +Two interfaces, one client. The transport packages above register the same +client instance under two interfaces: `ILedgerClient` (this package) and +`Canton.Ledger.Abstractions.ICantonLedgerClient` (the ledger repo), where +`ICantonLedgerClient : ILedgerClient` adds the operations specific to a +Canton participant — fire-and-forget submission, the command completion +stream, connected-synchronizer and Ledger API version discovery, and +offset/id point reads. Picture the surface as a stack: at the bottom the +three capability slices (`ILedgerWriter`, `ILedgerReader`, +`ILedgerStreamer`); above them `ILedgerClient`, which is nothing but the +three combined; above that `ICantonLedgerClient`, which is `ILedgerClient` +plus the Canton-only operations; and at the top the concrete transport +clients, which implement `ICantonLedgerClient` and add nothing public of +their own. Depend on the lowest layer that has what you need: a capability +slice when you only read or stream, `ILedgerClient` for portable +application code (it is all this codegen's generated extensions ever +require), and `ICantonLedgerClient` only where you call a Canton-specific +operation. Never downcast to a concrete client class — every method the +concretes expose is already on one of the registered interfaces, and +staying on the interface keeps your code mockable, decoratable, and +transport-swappable. + +Cancellation is never an error outcome. When the caller's +`CancellationToken` fires, every operation on these interfaces — the +structured `Try*` primitives, the throwing wrappers in +`Daml.Ledger.Abstractions.Extensions`, and the streaming methods — must +surface `OperationCanceledException`; cancellation is never wrapped in an +`ExerciseOutcome.InfraError` outcome or a `LedgerOperationException`. +Previews before `0.4.0-preview.1` masked cancellation of the throwing +convenience wrappers as `LedgerOperationException`, so consumers added +call-site guards re-checking the token after a failed call; on +`0.4.0-preview.1` and later those guards are dead code and can be deleted +(see the CHANGELOG entry for that release). + Ledger positions are the `LedgerOffset` value type, never a raw `long`. Generated codegen output (`Async` extensions, projector helpers) references this package — never a transport-specific one — so consumers that only need projectors do not transitively depend on a gRPC stack. -Versioned in lockstep with `Daml.Runtime` and `Daml.Codegen.CSharp`. +Versioned in lockstep with `Daml.Runtime` and `Daml.Codegen.CSharp`. The +`Canton.Ledger.*` transport packages track this line by minor: a client +`0.N.x` embeds `Daml.* 0.N.x`, so the package version alone tells you which +Daml line a transport carries, while patch and `-preview.N` counters evolve +independently per repo. The convention is recorded in the ledger repo's +ADR 0013 (`docs/adr/0013-package-version-tracks-daml-line.md` in +[`canton-ledger-api-csharp`](https://github.com/peacefulstudio/canton-ledger-api-csharp)). Interface-only package. `Daml.Ledger.Abstractions.Testing.Conformance` ships the shared behavioral conformance kit — a transport implementation subclasses it diff --git a/src/Daml.Runtime/Commands/CommandId.cs b/src/Daml.Runtime/Commands/CommandId.cs index 02f327c..8ef9f26 100644 --- a/src/Daml.Runtime/Commands/CommandId.cs +++ b/src/Daml.Runtime/Commands/CommandId.cs @@ -8,10 +8,18 @@ namespace Daml.Runtime.Commands; /// onto the Ledger API command_id field. /// /// +/// /// Conversions to and from are both explicit, so a command id can /// never be silently mistaken for an arbitrary string (or vice versa) — in particular it /// cannot be transposed with a at a call site; use /// or for logging and interpolation. +/// +/// +/// default(CommandId) is never a valid representation of an absent id — it throws +/// on any access to , as does the explicit conversion to +/// ; project an optional wire field through , +/// which returns for an absent value. +/// /// public readonly record struct CommandId { @@ -40,6 +48,20 @@ public static explicit operator string(CommandId id) => /// Parses a command id; explicit so arbitrary strings never silently become command ids. public static explicit operator CommandId(string value) => new(value); + /// + /// Projects an optional wire field: when + /// is null, empty, or whitespace; a constructed + /// otherwise. + /// + /// + /// The supported projection for an optional wire field — it never produces a default + /// instance, so the absent case is observable as rather than + /// as a value that throws on . + /// + /// The optional wire value. + public static CommandId? FromWire(string? value) => + string.IsNullOrWhiteSpace(value) ? null : new CommandId(value); + /// /// Returns a sentinel — not a throw — for default(CommandId): logging /// frameworks may invoke ToString on a captured value during exception diff --git a/src/Daml.Runtime/Commands/CommandsSubmission.cs b/src/Daml.Runtime/Commands/CommandsSubmission.cs index 4c0eef1..2093887 100644 --- a/src/Daml.Runtime/Commands/CommandsSubmission.cs +++ b/src/Daml.Runtime/Commands/CommandsSubmission.cs @@ -19,6 +19,11 @@ namespace Daml.Runtime.Commands; /// that don't natively see them. preserves today's behaviour /// (no explicit disclosure). /// +/// +/// Optional earliest ledger effective time for this submission — the participant must not +/// commit it before the bound. preserves today's behaviour (no bound: +/// the participant assigns the ledger time itself). +/// public sealed record CommandsSubmission( IReadOnlyList Commands, WorkflowId? WorkflowId = null, @@ -26,11 +31,16 @@ public sealed record CommandsSubmission( IReadOnlyList? ActAs = null, IReadOnlyList? ReadAs = null, SynchronizerId? SynchronizerId = null, - IReadOnlyList? DisclosedContracts = null) + IReadOnlyList? DisclosedContracts = null, + MinLedgerTime? MinLedgerTime = null) { /// /// Creates a submission with a single command. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1720:Identifier contains type name", + Justification = "Single and Multiple name the command cardinality of the submission, paired as a factory vocabulary; neither refers to System.Single.")] public static CommandsSubmission Single(ICommand command, Party? actAs = null) => new([command], ActAs: actAs is not null ? [actAs.Value] : null); @@ -46,6 +56,24 @@ public static CommandsSubmission Multiple(params ICommand[] commands) => public CommandsSubmission WithWorkflowId(WorkflowId workflowId) => this with { WorkflowId = workflowId }; + /// + /// Adds a workflow ID to this submission when carries one, + /// and returns the submission unchanged when it is null, empty or whitespace-only. + /// A blank workflow id is treated as absent rather than stored, because workflow_id + /// is a correlation key and a blank one correlates nothing — which is as true of a + /// whitespace-only value as it is of an empty one. + /// + /// + /// Only this convenience overload decides that blank means absent. + /// itself stays permissive — its constructor accepts empty and + /// whitespace because the Ledger API puts no non-empty constraint on workflow_id — + /// so a caller who genuinely wants to send a blank one still can, by saying + /// WithWorkflowId(new WorkflowId(" ")) explicitly. + /// + /// The workflow id, or null/blank to leave the submission unchanged. + public CommandsSubmission WithOptionalWorkflowId(string? workflowId) => + string.IsNullOrWhiteSpace(workflowId) ? this : WithWorkflowId(new WorkflowId(workflowId)); + /// /// Adds a command ID to this submission. /// @@ -78,6 +106,17 @@ public CommandsSubmission WithReadAs(params Party[] parties) => public CommandsSubmission WithDisclosedContracts(params DisclosedContract[]? disclosedContracts) => this with { DisclosedContracts = disclosedContracts is { Length: > 0 } ? disclosedContracts : null }; + /// + /// Sets the earliest ledger effective time this submission may be assigned. Passing + /// clears the bound, leaving the ledger time to the participant. + /// + /// + /// The bound — or + /// — or to impose none. + /// + public CommandsSubmission WithMinLedgerTime(MinLedgerTime? minLedgerTime) => + this with { MinLedgerTime = minLedgerTime }; + /// /// Applies a — sets both and /// from the submitter's party sets in a single call. The @@ -85,12 +124,13 @@ public CommandsSubmission WithDisclosedContracts(params DisclosedContract[]? dis /// submitter onto a submission; preserves the property that the wire format /// reflects exactly the parties carried by . /// + /// + /// Both projections are overwritten, so a submitter carrying no readAs parties clears + /// any already on the submission. + /// public CommandsSubmission WithSubmitter(SubmitterInfo submitter) { var withActAs = this with { ActAs = [.. submitter.ActAs] }; - // When the submitter carries no readAs parties, clear any pre-existing - // ReadAs on the submission so the wire shape reflects exactly the - // parties carried by the submitter — both projections fully overwritten. return submitter.ReadAs.Count == 0 ? withActAs with { ReadAs = null } : withActAs with { ReadAs = [.. submitter.ReadAs] }; diff --git a/src/Daml.Runtime/Commands/DisclosedContract.cs b/src/Daml.Runtime/Commands/DisclosedContract.cs index 4e52517..5549b61 100644 --- a/src/Daml.Runtime/Commands/DisclosedContract.cs +++ b/src/Daml.Runtime/Commands/DisclosedContract.cs @@ -16,12 +16,27 @@ namespace Daml.Runtime.Commands; /// /// The raw created_event_blob bytes from the gRPC CreatedEvent, /// carried verbatim and opaque to this library — no encoding is imposed on callers. +/// Copied into this record, which then owns its bytes. /// public sealed record DisclosedContract( string ContractId, Identifier TemplateId, ReadOnlyMemory CreatedEventBlob) { + private readonly ReadOnlyMemory _createdEventBlob = CreatedEventBlob.ToArray(); + + /// + /// The raw created_event_blob bytes carried by this disclosed contract. Copied + /// at construction and on init, so a caller that retains the array it supplied + /// cannot change the disclosed payload, this value's equality, or its hash code after + /// the contract has been attached to a submission. + /// + public ReadOnlyMemory CreatedEventBlob + { + get => _createdEventBlob; + init => _createdEventBlob = value.ToArray(); + } + /// /// Compares byte-for-byte, unlike the synthesized /// record equality, which compares only the memory segment's reference, offset, diff --git a/src/Daml.Runtime/Commands/MinLedgerTime.cs b/src/Daml.Runtime/Commands/MinLedgerTime.cs new file mode 100644 index 0000000..e8ac5ca --- /dev/null +++ b/src/Daml.Runtime/Commands/MinLedgerTime.cs @@ -0,0 +1,117 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +namespace Daml.Runtime.Commands; + +/// +/// The earliest ledger effective time a submission may be assigned — "do not commit this +/// before T". A transport projects it onto Commands.min_ledger_time_abs or +/// Commands.min_ledger_time_rel in the Ledger API payload, one field per arm. +/// +/// +/// +/// Omitting the bound — leaving at +/// — imposes no constraint at all: the participant assigns the ledger +/// time itself, which is the behaviour of every submission that does not carry one. A bound is +/// a floor, not a schedule: the participant may assign a later ledger time, and a bound that +/// has already passed by the time the submission is processed delays nothing. +/// +/// +/// The two wire fields are mutually exclusive — a participant rejects a submission carrying +/// both — so the bound is modelled as a closed hierarchy of the two forms rather than as a +/// pair of nullable members, and a submission carrying both is unrepresentable rather than +/// rejected at dispatch. Consumers project a bound by matching the arms: +/// and are the only ones. +/// +/// +public abstract record MinLedgerTime +{ + /// Sealed; new arms live alongside the existing ones. + private protected MinLedgerTime() { } + + /// + /// Applies the handler for this bound's arm and returns its result — the projection a + /// transport uses to reach the one wire field this arm maps to. + /// + /// + /// Every arm is a parameter, so adding one changes this signature and a consumer that + /// projects through it stops compiling instead of falling through a default branch. A + /// switch cannot offer that: C# treats a class hierarchy as open, so a switch + /// expression covering both arms is still non-exhaustive (CS8509) and needs a discard + /// arm that would silently swallow a new one. + /// + /// The projection's result type. + /// Handler for , receiving its instant. + /// Handler for , receiving its delay. + /// Either handler is . + public abstract TResult Match( + Func absolute, + Func relative); + + /// + /// A bound expressed as an instant, projected onto Commands.min_ledger_time_abs. + /// The instant is carried verbatim, including its UTC offset; converting it to the wire + /// clock is the transport's business. + /// + public sealed record Absolute : MinLedgerTime + { + /// + /// Creates an absolute bound. + /// + /// The instant before which the submission must not be committed. + public Absolute(DateTimeOffset value) => Value = value; + + /// The instant before which the submission must not be committed. + public DateTimeOffset Value { get; } + + /// + public override TResult Match( + Func absolute, + Func relative) + { + ArgumentNullException.ThrowIfNull(absolute); + ArgumentNullException.ThrowIfNull(relative); + return absolute(Value); + } + } + + /// + /// A bound expressed as a delay from the moment the participant receives the submission, + /// projected onto Commands.min_ledger_time_rel. Preferred over + /// when the submitting application's clock is not known to agree with the participant's, + /// because a relative bound is resolved against the participant's own clock. + /// + public sealed record Relative : MinLedgerTime + { + /// + /// Creates a relative bound. + /// + /// + /// The delay from submission. is accepted and means "no + /// earlier than now". + /// + /// + /// is negative. A negative delay has no wire representation: + /// the field is a duration into the future, and a bound in the past is expressed by + /// omitting the bound. + /// + public Relative(TimeSpan value) + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, TimeSpan.Zero); + Value = value; + } + + /// The delay from submission before which the submission must not be committed. + public TimeSpan Value { get; } + + /// + public override TResult Match( + Func absolute, + Func relative) + { + ArgumentNullException.ThrowIfNull(absolute); + ArgumentNullException.ThrowIfNull(relative); + return relative(Value); + } + } +} diff --git a/src/Daml.Runtime/Commands/SubmitterInfo.cs b/src/Daml.Runtime/Commands/SubmitterInfo.cs index 6df3f37..81c240c 100644 --- a/src/Daml.Runtime/Commands/SubmitterInfo.cs +++ b/src/Daml.Runtime/Commands/SubmitterInfo.cs @@ -38,9 +38,11 @@ namespace Daml.Runtime.Commands; /// public readonly record struct SubmitterInfo { - // FrozenSet.Empty is genuinely immutable — there is no underlying mutable - // collection a consumer could cast back to and mutate, unlike a HashSet - // typed as IReadOnlySet. + /// + /// A frozen set is genuinely immutable: unlike a HashSet exposed as + /// , there is no underlying mutable collection a consumer can + /// cast back to and mutate. + /// private static readonly IReadOnlySet EmptyParties = FrozenSet.Empty; private readonly IReadOnlySet? _actAs; @@ -76,9 +78,6 @@ public SubmitterInfo(IReadOnlySet actAs, IReadOnlySet? readAs = nu "SubmitterInfo.ActAs must contain at least one party.", nameof(actAs)); } - // Snapshot into a FrozenSet so the stored backing collection is genuinely - // immutable. Callers that cast ActAs/ReadAs back to a concrete type still - // can't mutate, and SubmitterInfo's value semantics survive any such cast. _actAs = ValidatedFrozenSet(actAs, nameof(actAs)); _readAs = readAs is null || readAs.Count == 0 ? null @@ -94,9 +93,6 @@ public SubmitterInfo(IReadOnlySet actAs, IReadOnlySet? readAs = nu /// public SubmitterInfo(Party singleActAs, IReadOnlySet? readAs = null) { - // Touch Id so a default(Party) submitter fails at construction rather - // than later, when ToDamlValue / serialization would surface the - // uninitialized state with a much harder-to-trace stack. _ = singleActAs.Id; _actAs = FrozenSet.Create(singleActAs); _readAs = readAs is null || readAs.Count == 0 @@ -108,8 +104,6 @@ private static FrozenSet ValidatedFrozenSet(IReadOnlySet source, s { foreach (var party in source) { - // Touch Id on each party so default(Party) entries fail loudly at - // construction. Mirrors the single-party ctor's invariant. try { _ = party.Id; @@ -158,7 +152,6 @@ private static bool SetsEqual(IReadOnlySet? a, IReadOnlySet? b) { return true; } - // Both non-empty and same count — set-equal iff every element of `a` is in `b`. foreach (var p in a!) { if (!b!.Contains(p)) @@ -175,8 +168,6 @@ private static int SetHash(IReadOnlySet? set) { return 0; } - // Order-independent: XOR each element's hash so set equality implies - // hash equality regardless of enumeration order. var h = 0; foreach (var p in set) { diff --git a/src/Daml.Runtime/Contracts/Contract.cs b/src/Daml.Runtime/Contracts/Contract.cs index 87e9896..fac9fec 100644 --- a/src/Daml.Runtime/Contracts/Contract.cs +++ b/src/Daml.Runtime/Contracts/Contract.cs @@ -25,19 +25,91 @@ public interface IContract } /// -/// Base record for generated contracts. +/// A contract key decoded into the template's key type, paired with the ledger's hash of it. +/// +/// The template's contract key type. +/// The key, decoded through the template's +/// . +/// The ledger's hash of the key — the value Canton indexes keyed contracts +/// by, as the base64 text the JSON encoding uses. It is Canton-computed over the key and the +/// template id, so it cannot be reconstructed here; a null is a stated absence, meaning +/// the created event carried no hash. +public sealed record ContractKey(TKey Value, string? Hash); + +/// +/// Base record for generated contracts of a template that declares no contract key. A keyed +/// template's contracts use , which carries the decoded key. /// /// The template type. +/// The on-ledger contract ID. +/// The contract payload data. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Contract<...>.FromCreatedEvent, mirroring the Daml constructor it decodes.")] public sealed record Contract(ContractId Id, T Data) : IContract, T> where T : ITemplate { /// /// Creates a contract from a ledger event. /// + /// or + /// is null. public static Contract FromCreatedEvent(CreatedEvent @event, Func decoder) { + ArgumentNullException.ThrowIfNull(@event); + ArgumentNullException.ThrowIfNull(decoder); + var id = new ContractId(@event.ContractId); var data = decoder(@event.CreateArguments); return new Contract(id, data); } } + +/// +/// Base record for generated contracts of a template that declares a contract key. The key is +/// non-nullable: this type argument pair exists only for a template whose +/// facet declares the link, so every contract of that +/// template carries one. +/// +/// The keyed template type. +/// The template's contract key type. +/// The on-ledger contract ID. +/// The contract payload data. +/// The contract key, decoded into and carrying +/// the ledger's hash of it. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Contract<...>.FromCreatedEvent, mirroring the Daml constructor it decodes.")] +public sealed record Contract(ContractId Id, T Data, ContractKey Key) + : IContract, T> + where T : ITemplate, IHasKey +{ + /// + /// Creates a keyed contract from a ledger event, decoding the key through the template's + /// witness. + /// + /// or + /// is null. + /// The event carried no contract key, so the + /// keyed shape cannot be populated. + public static Contract FromCreatedEvent(CreatedEvent @event, Func decoder) + { + ArgumentNullException.ThrowIfNull(@event); + ArgumentNullException.ThrowIfNull(decoder); + + var id = new ContractId(@event.ContractId); + var data = decoder(@event.CreateArguments); + return new Contract(id, data, DecodeKey(@event.ContractKey, @event.ContractId)); + } + + internal static ContractKey DecodeKey(ContractKey? key, string contractId) => + key is null + ? throw new InvalidOperationException( + $"The created event for contract '{contractId}' of keyed template " + + $"'{typeof(T).FullName}' carried no contract key, so the keyed contract shape " + + "cannot be populated. Read this contract through the keyless overload when the " + + "transport does not supply the key.") + : new ContractKey(T.Key.KeyDecoder(key.Value), key.KeyHash); +} diff --git a/src/Daml.Runtime/Contracts/ContractEvent.cs b/src/Daml.Runtime/Contracts/ContractEvent.cs index a7badc6..a0b2b08 100644 --- a/src/Daml.Runtime/Contracts/ContractEvent.cs +++ b/src/Daml.Runtime/Contracts/ContractEvent.cs @@ -8,6 +8,19 @@ namespace Daml.Runtime.Contracts; /// /// Represents a contract creation event from the ledger. /// +/// The event's identifier within its transaction. +/// The on-ledger contract ID of the created contract. +/// The template the contract instantiates. +/// The create-arguments record. +/// Parties notified of this event. +/// Parties that authorized the create. +/// Parties the template names as observers. +/// The contract key the event carried, or null when the +/// template declares none. Mandatory rather than defaulted: this is the single entry point +/// feeding every downstream key slot, so a transport must state the absence rather than +/// inherit it and populate every consumer's key with null while compiling clean. +/// The ledger-effective time of the create, when the event +/// reported one. public sealed record CreatedEvent( string EventId, string ContractId, @@ -16,13 +29,97 @@ public sealed record CreatedEvent( IReadOnlyList WitnessParties, IReadOnlyList Signatories, IReadOnlyList Observers, - ContractKey? ContractKey = null, - DateTimeOffset? CreatedAt = null); + ContractKey? ContractKey, + DateTimeOffset? CreatedAt = null) +{ + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + private readonly IReadOnlyList _signatories = + EventCollections.Borrow(Signatories, nameof(Signatories)); + + private readonly IReadOnlyList _observers = + EventCollections.Borrow(Observers, nameof(Observers)); + + /// + /// Parties notified of this event. Held as the producer supplied it, not copied — an + /// is a read-only view, so a caller that retains its + /// backing list must not mutate it after construction. Rejected at construction and on + /// init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + + /// + /// Parties that authorized the create. Held on the same terms as + /// . + /// + public IReadOnlyList Signatories + { + get => _signatories; + init => _signatories = EventCollections.Borrow(value, nameof(Signatories)); + } + + /// + /// Parties the template names as observers. Held on the same terms as + /// . + /// + public IReadOnlyList Observers + { + get => _observers; + init => _observers = EventCollections.Borrow(value, nameof(Observers)); + } +} /// /// Represents a contract key. /// -public sealed record ContractKey(DamlValue Value, Identifier? TemplateId = null); +/// The key value, wire-level. Decode it with the same hop generated +/// code performs — TKey.FromRecord(Value.As<DamlRecord>()) — or read it +/// directly when the template's key is a bare scalar. +/// The template that declares the key, when the event named one; +/// null otherwise. +public sealed record ContractKey(DamlValue Value, Identifier? TemplateId = null) +{ + /// + /// The ledger's hash of this key — the value Canton indexes keyed contracts by. It + /// travels beside the key on both wire formats (contractKeyHash on the JSON + /// encoding, contract_key_hash on gRPC) and is held here as the base64 text the + /// JSON encoding uses. A null there is a stated absence: the created event + /// carried no hash, or the key was constructed by a caller rather than read off the + /// wire. It is never computed here — the hash is the ledger's, and a transport that + /// reads one off a created event populates it. + /// + /// + /// Deliberately excluded from and + /// : two keys are the same key when they name the same value + /// of the same template, and a key read off the wire must therefore equal the same key + /// a caller constructed to exercise by it — which carries no hash. Including it would + /// make by-key matching fail quietly depending on where the key came from. + /// + public string? KeyHash { get; init; } + + /// + /// Compares two keys by the key they name — and + /// — ignoring . + /// + /// The key to compare against. + /// true when both name the same key. + public bool Equals(ContractKey? other) => + other is not null + && EqualityComparer.Default.Equals(Value, other.Value) + && EqualityComparer.Default.Equals(TemplateId, other.TemplateId); + + /// + /// Hashes the key by the key it names, consistently with + /// . + /// + /// A hash code over and . + public override int GetHashCode() => HashCode.Combine(Value, TemplateId); +} /// /// Represents an archived (consumed) contract event. @@ -31,7 +128,23 @@ public sealed record ArchivedEvent( string EventId, string ContractId, Identifier TemplateId, - IReadOnlyList WitnessParties); + IReadOnlyList WitnessParties) +{ + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties notified of this event. Held as the producer supplied it, not copied — an + /// is a read-only view, so a caller that retains its + /// backing list must not mutate it after construction. Rejected at construction and on + /// init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } +} /// /// Represents a Daml exception caught by a try/catch block during @@ -44,10 +157,68 @@ public sealed record ArchivedEvent( /// The human-readable message carried by the exception. /// Additional key-value context associated with the /// exception, as provided by the ledger. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1711:Identifiers should not have incorrect suffix", + Justification = "This is the projected shape of a Daml exception caught by the ledger, not a throwable; the name matches the Daml vocabulary consumers already read in transaction trees.")] public sealed record CaughtException( string ErrorId, string Message, - IReadOnlyDictionary Metadata); + IReadOnlyDictionary Metadata) +{ + private readonly IReadOnlyDictionary _metadata = + EventCollections.Copy(Metadata, nameof(Metadata)); + + /// + /// Additional key-value context associated with the exception, as provided by the + /// ledger. Copied at construction and on init, so a producer that retains the + /// dictionary it supplied cannot change this value's equality or hash code afterwards. + /// + public IReadOnlyDictionary Metadata + { + get => _metadata; + init => _metadata = EventCollections.Copy(value, nameof(Metadata)); + } + + /// + /// Compares two caught exceptions by content, comparing key by + /// key and independently of insertion order. The record-synthesized equality compares + /// the backing by reference — a footgun + /// for a value type — so we override it, as already does + /// for the same shape. + /// + /// The caught exception to compare against. + /// true when both describe the same caught exception. + public bool Equals(CaughtException? other) + { + if (other is null + || ErrorId != other.ErrorId + || Message != other.Message + || Metadata.Count != other.Metadata.Count) + { + return false; + } + foreach (var (key, value) in Metadata) + { + if (!other.Metadata.TryGetValue(key, out var otherValue) || value != otherValue) + { + return false; + } + } + return true; + } + + /// + public override int GetHashCode() + { + var hash = HashCode.Combine(ErrorId, Message, Metadata.Count); + foreach (var (key, value) in Metadata) + { + hash ^= HashCode.Combine(key, value); + } + return hash; + } +} /// /// Represents a choice-exercise event observed in a transaction. Carries the @@ -80,11 +251,97 @@ public sealed record ExercisedEvent( IReadOnlyList ActingParties, IReadOnlyList WitnessParties) { + private readonly IReadOnlyList _actingParties = + EventCollections.Copy(ActingParties, nameof(ActingParties)); + + private readonly IReadOnlyList _witnessParties = + EventCollections.Copy(WitnessParties, nameof(WitnessParties)); + + private readonly IReadOnlyList _caughtExceptions = Array.Empty(); + + /// + /// Parties that exercised the choice. Copied at construction and on init, so a + /// producer that retains the list it supplied cannot change this value's equality or + /// hash code afterwards. + /// + public IReadOnlyList ActingParties + { + get => _actingParties; + init => _actingParties = EventCollections.Copy(value, nameof(ActingParties)); + } + + /// + /// Parties notified of this event. Copied on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Copy(value, nameof(WitnessParties)); + } + /// /// Daml exceptions caught by a try/catch block during this /// choice's interpretation. Defaults to an empty list — populated by /// ledger-client transport implementations from the gRPC exception - /// status on the exercise node. + /// status on the exercise node. Copied on the same terms as + /// . + /// + public IReadOnlyList CaughtExceptions + { + get => _caughtExceptions; + init => _caughtExceptions = EventCollections.Copy(value, nameof(CaughtExceptions)); + } + + /// + /// Compares two exercise events field-by-field, comparing , + /// and element by element + /// rather than by list identity. The record-synthesized equality compares the backing + /// by reference — a footgun for a value type — so we + /// override it with structural element comparison, as and + /// already do. /// - public IReadOnlyList CaughtExceptions { get; init; } = Array.Empty(); + /// The exercise event to compare against. + /// true when both describe the same exercise. + public bool Equals(ExercisedEvent? other) => + other is not null + && ContractId == other.ContractId + && TemplateId == other.TemplateId + && InterfaceId == other.InterfaceId + && ChoiceName == other.ChoiceName + && Equals(ChoiceArgument, other.ChoiceArgument) + && Equals(ExerciseResult, other.ExerciseResult) + && Consuming == other.Consuming + && ActingParties.SequenceEqual(other.ActingParties) + && WitnessParties.SequenceEqual(other.WitnessParties) + && CaughtExceptions.SequenceEqual(other.CaughtExceptions); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(ContractId); + hash.Add(TemplateId); + hash.Add(InterfaceId); + hash.Add(ChoiceName); + hash.Add(ChoiceArgument); + hash.Add(ExerciseResult); + hash.Add(Consuming); + hash.Add(ActingParties.Count); + foreach (var actingParty in ActingParties) + { + hash.Add(actingParty); + } + hash.Add(WitnessParties.Count); + foreach (var witness in WitnessParties) + { + hash.Add(witness); + } + hash.Add(CaughtExceptions.Count); + foreach (var caught in CaughtExceptions) + { + hash.Add(caught); + } + return hash.ToHashCode(); + } } diff --git a/src/Daml.Runtime/Contracts/ContractId.cs b/src/Daml.Runtime/Contracts/ContractId.cs index d328d6e..6181ffb 100644 --- a/src/Daml.Runtime/Contracts/ContractId.cs +++ b/src/Daml.Runtime/Contracts/ContractId.cs @@ -1,14 +1,17 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Text.Json.Serialization; using Daml.Runtime.Data; +using Daml.Runtime.Serialization; namespace Daml.Runtime.Contracts; /// /// Non-generic, erased contract id: a validated ledger contract-id string with no -/// static template witness. The only concrete subtype is ; -/// the base is abstract on purpose, so a typeless contract id can never be fabricated. +/// static template witness. is the only subtype declared +/// here, though codegen derives a per-template T.ContractId from it; the base is +/// abstract on purpose, so a typeless contract id can never be fabricated. /// /// /// Construction is guarded — @@ -45,6 +48,12 @@ protected ContractId(string value) /// ContractId Holding across the Splice token standard — and that flows here as /// ContractId<IHolding>. /// +/// +/// Serializes as the bare ledger contract-id string via +/// , matching how PQS rows and JSON Ledger +/// API payloads encode a contract id. +/// +[JsonConverter(typeof(ContractIdJsonConverterFactory))] public record ContractId : ContractId where T : IDamlType { /// Constructs a typed contract id from a non-empty string. diff --git a/src/Daml.Runtime/Contracts/EventCollections.cs b/src/Daml.Runtime/Contracts/EventCollections.cs new file mode 100644 index 0000000..5f2733d --- /dev/null +++ b/src/Daml.Runtime/Contracts/EventCollections.cs @@ -0,0 +1,58 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.ObjectModel; + +namespace Daml.Runtime.Contracts; + +/// +/// Null guards and defensive copies for the collection-typed members of the event records +/// in this namespace and of the Daml.Runtime.Stdlib value types. The Copy +/// overloads materialize, mirroring what +/// , and +/// already do for the value shapes; only checks, keeping the +/// producer's own instance. +/// +/// +/// and are +/// read-only views, not immutable collections: a producer that keeps its backing +/// can mutate it after handing it over. On the records whose equality +/// and hash codes read the contents, that would silently change an already-computed hash and +/// make the value unfindable in a set or dictionary that already holds it, so those copy at +/// every entry point — the primary constructor and each init accessor, so with +/// expressions are covered too. The records that keep record-synthesized equality compare +/// their collection member by reference, so no hash can be corrupted and copying would +/// instead narrow their equality to near-identity while allocating on the read path; those +/// borrow, and their doc-comments state the caller's obligation not to mutate. +/// +internal static class EventCollections +{ + internal static IReadOnlyList Borrow(IReadOnlyList values, string parameterName) + { + ArgumentNullException.ThrowIfNull(values, parameterName); + return values; + } + + internal static IReadOnlyList Copy(IReadOnlyList values, string parameterName) + { + ArgumentNullException.ThrowIfNull(values, parameterName); + return values.Count == 0 ? Array.Empty() : [.. values]; + } + + internal static IReadOnlySet Copy(IEnumerable values, string parameterName) + { + ArgumentNullException.ThrowIfNull(values, parameterName); + return values.TryGetNonEnumeratedCount(out var count) && count == 0 + ? ReadOnlySet.Empty + : values.ToHashSet(); + } + + internal static IReadOnlyDictionary Copy( + IReadOnlyDictionary values, + string parameterName) + where TKey : notnull + { + ArgumentNullException.ThrowIfNull(values, parameterName); + return values.Count == 0 ? ReadOnlyDictionary.Empty : values.ToDictionary(); + } +} diff --git a/src/Daml.Runtime/Contracts/KeyDescriptor.cs b/src/Daml.Runtime/Contracts/KeyDescriptor.cs new file mode 100644 index 0000000..bf07b0c --- /dev/null +++ b/src/Daml.Runtime/Contracts/KeyDescriptor.cs @@ -0,0 +1,34 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Data; + +namespace Daml.Runtime.Contracts; + +/// +/// Type witness pairing a keyed template with its key type and carrying the codec between the +/// wire value and that key. Generated keyed templates expose a singleton through the +/// static abstract member, so a call site passing it to a +/// generic method lets the compiler infer both type parameters from one argument — C# performs no +/// partial type-argument inference, so the pair must travel together. Because the codec rides on +/// the descriptor, needs no record constraint and a bare +/// key is admitted on the same footing as a record key. A mismatched pair is +/// unconstructible: the constraints tie to +/// through . +/// +/// The keyed template type. +/// The template's contract key type. +public sealed class KeyDescriptor + where TTemplate : ITemplate, IHasKey +{ + /// + /// Gets the function encoding a into the ledger's key value, the + /// form carries. + /// + public required Func KeyEncoder { get; init; } + + /// + /// Gets the function decoding the ledger's key value into . + /// + public required Func KeyDecoder { get; init; } +} diff --git a/src/Daml.Runtime/Contracts/Template.cs b/src/Daml.Runtime/Contracts/Template.cs index 22c72b5..54d675c 100644 --- a/src/Daml.Runtime/Contracts/Template.cs +++ b/src/Daml.Runtime/Contracts/Template.cs @@ -38,15 +38,20 @@ public interface ITemplate : IDamlRecord, IDamlType } /// -/// Interface for templates that have a contract key. +/// Facet implemented by templates that have a contract key. Generic code constrained on this +/// facet reaches the key codec through with no reflection, so +/// carries no constraint of its own and a bare +/// key is admitted alongside a record key. /// +/// The implementing template type. /// The key type. -public interface IHasKey +public interface IHasKey + where TSelf : ITemplate, IHasKey { /// - /// Gets the contract key. + /// Gets the witness pairing this template with its key type and carrying the key codec. /// - TKey Key { get; } + static abstract KeyDescriptor Key { get; } } /// @@ -82,15 +87,13 @@ public interface IDamlInterface : IDamlRecord, IDamlType } /// -/// Interface for Daml interfaces that have an associated view type. +/// Phantom link between a Daml interface marker and its view type. Carries no members: +/// views are server-computed, so the association exists purely at the type level, and +/// generic code binds the pair through . /// /// The view type. public interface IHasView { - /// - /// Gets the interface view. - /// - TView View { get; } } /// diff --git a/src/Daml.Runtime/Contracts/TransactionResult.cs b/src/Daml.Runtime/Contracts/TransactionResult.cs index 5cc0f09..6ef39ba 100644 --- a/src/Daml.Runtime/Contracts/TransactionResult.cs +++ b/src/Daml.Runtime/Contracts/TransactionResult.cs @@ -32,14 +32,46 @@ public sealed record SubmitAndWaitResult( /// typed values via . /// Raw contract IDs archived by the transaction. /// The effective command id the participant recorded for the -/// submission that produced this transaction, used for deduplication. +/// submission that produced this transaction, used for deduplication; null when +/// the participant reported none. The Ledger API omits the command id on transactions +/// this participant did not submit, so callers must treat it as optional rather than +/// assume every transaction carries one. public sealed record TransactionResult( string UpdateId, LedgerOffset CompletionOffset, IReadOnlyList CreatedContracts, IReadOnlyList ArchivedContractIds, - CommandId CommandId) + CommandId? CommandId) { + private readonly IReadOnlyList _createdContracts = + EventCollections.Copy(CreatedContracts, nameof(CreatedContracts)); + + private readonly IReadOnlyList _archivedContractIds = + EventCollections.Copy(ArchivedContractIds, nameof(ArchivedContractIds)); + + private readonly IReadOnlyList _exercisedEvents = Array.Empty(); + + /// + /// Contracts created by the transaction. Copied at construction and on init, so a + /// producer that retains the list it supplied cannot change this value's equality or + /// hash code afterwards. + /// + public IReadOnlyList CreatedContracts + { + get => _createdContracts; + init => _createdContracts = EventCollections.Copy(value, nameof(CreatedContracts)); + } + + /// + /// Raw contract IDs archived by the transaction. Copied on the same terms as + /// . + /// + public IReadOnlyList ArchivedContractIds + { + get => _archivedContractIds; + init => _archivedContractIds = EventCollections.Copy(value, nameof(ArchivedContractIds)); + } + /// /// Choice-exercise events observed in the transaction, in transaction order. /// Defaults to an empty list — populated by ledger-client transport @@ -48,27 +80,227 @@ public sealed record TransactionResult( /// through the appropriate typed /// projector to surface a typed ExerciseOutcome<TResult> for choices /// whose return type is not a contract id (e.g. choice C : Decimal). + /// Copied on the same terms as . + /// + public IReadOnlyList ExercisedEvents + { + get => _exercisedEvents; + init => _exercisedEvents = EventCollections.Copy(value, nameof(ExercisedEvents)); + } + + /// + /// Compares two transaction results field-by-field, comparing + /// , and + /// element by element rather than by list identity — + /// each element then using its own equality. The record-synthesized equality compares + /// the backing by reference — a footgun for a + /// value type — so we override it with structural element comparison, as + /// and already do. /// - public IReadOnlyList ExercisedEvents { get; init; } = Array.Empty(); + /// + /// The comparison is structural all the way down: , + /// and each compare by content + /// themselves, so two results projected from two separately-decoded trees of the same + /// transaction compare equal. + /// + public bool Equals(TransactionResult? other) => + other is not null + && UpdateId == other.UpdateId + && CompletionOffset == other.CompletionOffset + && CommandId == other.CommandId + && CreatedContracts.SequenceEqual(other.CreatedContracts) + && ArchivedContractIds.SequenceEqual(other.ArchivedContractIds) + && ExercisedEvents.SequenceEqual(other.ExercisedEvents); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(UpdateId); + hash.Add(CompletionOffset); + hash.Add(CommandId); + hash.Add(CreatedContracts.Count); + foreach (var created in CreatedContracts) + { + hash.Add(created); + } + hash.Add(ArchivedContractIds.Count); + foreach (var archivedContractId in ArchivedContractIds) + { + hash.Add(archivedContractId); + } + hash.Add(ExercisedEvents.Count); + foreach (var exercised in ExercisedEvents) + { + hash.Add(exercised); + } + return hash.ToHashCode(); + } } /// -/// Information about a contract created by a transaction. +/// Information about a contract created by a transaction. A field-for-field mirror of +/// , so +/// flattens a create node +/// losslessly. /// +/// The ledger-assigned event identifier. /// The on-ledger contract ID. /// The template identifier (package + module + entity). -/// The serialized payload of the contract. +/// The create-arguments record — +/// under the name the snapshot-side shapes use for the same slot (compare +/// AcsSnapshotEntry.Created.Payload); the mirror is field-for-field, not name-for-name +/// here. Non-nullable, so a transport +/// projecting a created event the participant sent without create arguments — the +/// interface-only case, where the contract is known only as an interface — passes an +/// empty record, which a consumer cannot distinguish from a template whose payload +/// genuinely has no fields. +/// Parties notified of this event. Required rather than +/// defaulted: an empty list must mean the event named no witnesses, not that a producer +/// never populated the slot. That is what separates these three from the two defaulted +/// list members — and +/// . Witnesses, signatories and observers +/// are always on the wire, so a silently-unpopulated one is a producer bug rather than a fact +/// about the contract — an empty list is itself meaningful, and a template that names no +/// observer clause reports empty observers on every create; interface views and exercise +/// events are populated only when the read requested that shape, so empty is both common and +/// correct for them. +/// Parties that authorized the contract's creation. Required +/// for the same reason as . +/// Parties the template names as observers. Required for the +/// same reason as . +/// The contract's key, when its template declares one; +/// null otherwise. Trailing and defaulted to mirror +/// exactly. Contrast +/// , which is mandatory: that shape is the single +/// entry point feeding the downstream key slots, whereas this one is built beside it and +/// keeps its mirror's shape. +/// Ledger-effective time at which the contract was created; +/// null when the transport does not supply it. Mirrors +/// . public sealed record CreatedContract( + string EventId, string ContractId, Identifier TemplateId, - string Payload) + DamlRecord Payload, + IReadOnlyList WitnessParties, + IReadOnlyList Signatories, + IReadOnlyList Observers, + ContractKey? ContractKey = null, + DateTimeOffset? CreatedAt = null) { + private readonly IReadOnlyList _witnessParties = + EventCollections.Copy(WitnessParties, nameof(WitnessParties)); + + private readonly IReadOnlyList _signatories = + EventCollections.Copy(Signatories, nameof(Signatories)); + + private readonly IReadOnlyList _observers = + EventCollections.Copy(Observers, nameof(Observers)); + + private readonly IReadOnlyList _interfaceIds = Array.Empty(); + + /// + /// Parties notified of this event. Copied at construction and on init, so a + /// producer that retains the list it supplied cannot change this value's equality or + /// hash code afterwards. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Copy(value, nameof(WitnessParties)); + } + + /// + /// Parties that authorized the contract's creation. Copied on the same terms as + /// . + /// + public IReadOnlyList Signatories + { + get => _signatories; + init => _signatories = EventCollections.Copy(value, nameof(Signatories)); + } + + /// + /// Parties the template names as observers. Copied on the same terms as + /// . + /// + public IReadOnlyList Observers + { + get => _observers; + init => _observers = EventCollections.Copy(value, nameof(Observers)); + } + /// /// Interface ids the participant computed for this created event /// (Canton gRPC CreatedEvent.interface_views[].interface_id). /// Defaults to an empty list — populated by ledger-client transport /// implementations for interface-only consumption, where a contract is /// known only as an interface and must be dispatched at runtime. + /// Copied on the same terms as . /// - public IReadOnlyList InterfaceIds { get; init; } = Array.Empty(); + public IReadOnlyList InterfaceIds + { + get => _interfaceIds; + init => _interfaceIds = EventCollections.Copy(value, nameof(InterfaceIds)); + } + + /// + /// Compares two created contracts field-by-field, including element-wise + /// , , + /// and content. The record-synthesized equality compares + /// the backing by reference — a footgun for a + /// value type — so we override it with structural element comparison. + /// + /// + /// One corner is not literally field-by-field: compares by the + /// key it names and deliberately ignores , so + /// a create read off the wire equals the same create rebuilt by a caller. That is the + /// intended propagation of that type's own rule, not an oversight here. + /// + public bool Equals(CreatedContract? other) => + other is not null + && EventId == other.EventId + && ContractId == other.ContractId + && TemplateId == other.TemplateId + && Equals(Payload, other.Payload) + && ContractKey == other.ContractKey + && CreatedAt == other.CreatedAt + && WitnessParties.SequenceEqual(other.WitnessParties) + && Signatories.SequenceEqual(other.Signatories) + && Observers.SequenceEqual(other.Observers) + && InterfaceIds.SequenceEqual(other.InterfaceIds); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(EventId); + hash.Add(ContractId); + hash.Add(TemplateId); + hash.Add(Payload); + hash.Add(ContractKey); + hash.Add(CreatedAt); + hash.Add(WitnessParties.Count); + foreach (var witness in WitnessParties) + { + hash.Add(witness); + } + hash.Add(Signatories.Count); + foreach (var signatory in Signatories) + { + hash.Add(signatory); + } + hash.Add(Observers.Count); + foreach (var observer in Observers) + { + hash.Add(observer); + } + hash.Add(InterfaceIds.Count); + foreach (var interfaceId in InterfaceIds) + { + hash.Add(interfaceId); + } + return hash.ToHashCode(); + } } diff --git a/src/Daml.Runtime/Contracts/TransactionTree.cs b/src/Daml.Runtime/Contracts/TransactionTree.cs index 2ad80d3..c781460 100644 --- a/src/Daml.Runtime/Contracts/TransactionTree.cs +++ b/src/Daml.Runtime/Contracts/TransactionTree.cs @@ -21,7 +21,25 @@ namespace Daml.Runtime.Contracts; public sealed record TransactionTree( string UpdateId, LedgerOffset CompletionOffset, - IReadOnlyList RootEvents); + IReadOnlyList RootEvents) +{ + private readonly IReadOnlyList _rootEvents = + EventCollections.Borrow(RootEvents, nameof(RootEvents)); + + /// + /// The transaction's top-level events, in transaction order. Events caused by an + /// exercise (its sub-creates and sub-exercises) are not repeated here — they nest under + /// that exercise's . Held as the producer + /// supplied it, not copied — an is a read-only view, so a + /// caller that retains its backing list must not mutate it after construction. Rejected + /// at construction and on init when null. + /// + public IReadOnlyList RootEvents + { + get => _rootEvents; + init => _rootEvents = EventCollections.Borrow(value, nameof(RootEvents)); + } +} /// /// A single node in a : either a contract @@ -98,6 +116,49 @@ public sealed record Created( ContractKey? ContractKey = null, DateTimeOffset? CreatedAt = null) : TreeEvent { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + private readonly IReadOnlyList _signatories = + EventCollections.Borrow(Signatories, nameof(Signatories)); + + private readonly IReadOnlyList _observers = + EventCollections.Borrow(Observers, nameof(Observers)); + + private readonly IReadOnlyList _interfaceIds = Array.Empty(); + + /// + /// Parties notified of this event. Held as the producer supplied it, not copied — an + /// is a read-only view, so a caller that retains its + /// backing list must not mutate it after construction. Rejected at construction and + /// on init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + + /// + /// Parties that authorized the contract's creation. Held on the same terms as + /// . + /// + public IReadOnlyList Signatories + { + get => _signatories; + init => _signatories = EventCollections.Borrow(value, nameof(Signatories)); + } + + /// + /// Parties with read access to the contract. Held on the same terms as + /// . + /// + public IReadOnlyList Observers + { + get => _observers; + init => _observers = EventCollections.Borrow(value, nameof(Observers)); + } + /// /// Interface ids the participant computed for this created event /// (Canton gRPC CreatedEvent.interface_views[].interface_id). @@ -106,8 +167,13 @@ public sealed record Created( /// known only as an interface and must be dispatched at runtime. Flattened /// through to by /// . + /// Held on the same terms as . /// - public IReadOnlyList InterfaceIds { get; init; } = Array.Empty(); + public IReadOnlyList InterfaceIds + { + get => _interfaceIds; + init => _interfaceIds = EventCollections.Borrow(value, nameof(InterfaceIds)); + } } /// @@ -144,5 +210,47 @@ public sealed record Exercised( bool Consuming, IReadOnlyList ActingParties, IReadOnlyList WitnessParties, - IReadOnlyList ChildEvents) : TreeEvent; + IReadOnlyList ChildEvents) : TreeEvent + { + private readonly IReadOnlyList _actingParties = + EventCollections.Borrow(ActingParties, nameof(ActingParties)); + + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + private readonly IReadOnlyList _childEvents = + EventCollections.Borrow(ChildEvents, nameof(ChildEvents)); + + /// + /// Parties that exercised the choice. Held as the producer supplied it, not copied — + /// an is a read-only view, so a caller that retains + /// its backing list must not mutate it after construction. Rejected at construction + /// and on init when null. + /// + public IReadOnlyList ActingParties + { + get => _actingParties; + init => _actingParties = EventCollections.Borrow(value, nameof(ActingParties)); + } + + /// + /// Parties notified of this event. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + + /// + /// The events this exercise directly caused — its sub-creates and sub-exercises — in + /// transaction order. Held on the same terms as . + /// + public IReadOnlyList ChildEvents + { + get => _childEvents; + init => _childEvents = EventCollections.Borrow(value, nameof(ChildEvents)); + } + } } diff --git a/src/Daml.Runtime/Contracts/TransactionTreeExtensions.cs b/src/Daml.Runtime/Contracts/TransactionTreeExtensions.cs index e07c9d9..92f98c4 100644 --- a/src/Daml.Runtime/Contracts/TransactionTreeExtensions.cs +++ b/src/Daml.Runtime/Contracts/TransactionTreeExtensions.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using Daml.Runtime.Data; -using Daml.Runtime.Serialization; namespace Daml.Runtime.Contracts; @@ -39,23 +38,36 @@ private static IEnumerable AllEventsCore(TransactionTree tree) /// Projects this to the flattened /// shape, for callers that don't need /// hierarchy. nodes become - /// entries (with a JSON-serialized payload); + /// entries carrying + /// as their payload; /// nodes become /// entries, and consuming /// exercises additionally contribute their target contract id to /// . /// /// - /// This projection is lossy: has no slot for - /// , , - /// , , - /// , or , - /// and has no slot for ; - /// its is always empty here, since - /// doesn't carry that data. Callers that need those - /// fields must walk directly instead. - /// is default here because + /// + /// nodes project losslessly: + /// mirrors that node field for field. + /// + /// + /// Exercise nodes do not. has no slot for + /// or ; + /// a caller that needs either walks + /// directly instead. Its + /// is always empty here and walking the + /// tree does not recover it, because carries no such + /// data at all — that field is populated only by a transport reading a ledger-effects + /// transaction. + /// + /// + /// Hierarchy is flattened either way: the parent/child structure of the tree is not + /// recoverable from the projected lists. + /// + /// + /// is null here because /// carries no command id to project. + /// /// public static TransactionResult ToTransactionResult(this TransactionTree tree) { @@ -71,9 +83,15 @@ public static TransactionResult ToTransactionResult(this TransactionTree tree) { case TreeEvent.Created created: createdContracts.Add(new CreatedContract( + created.EventId, created.ContractId, created.TemplateId, - DamlJsonSerializer.Serialize(created.CreateArguments)) + created.CreateArguments, + created.WitnessParties, + created.Signatories, + created.Observers, + created.ContractKey, + created.CreatedAt) { InterfaceIds = created.InterfaceIds, }); @@ -105,7 +123,7 @@ public static TransactionResult ToTransactionResult(this TransactionTree tree) tree.CompletionOffset, createdContracts, archivedContractIds, - default) + null) { ExercisedEvents = exercisedEvents, }; diff --git a/src/Daml.Runtime/Contracts/ViewDescriptor.cs b/src/Daml.Runtime/Contracts/ViewDescriptor.cs new file mode 100644 index 0000000..4bbd484 --- /dev/null +++ b/src/Daml.Runtime/Contracts/ViewDescriptor.cs @@ -0,0 +1,23 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Data; + +namespace Daml.Runtime.Contracts; + +/// +/// Pure type witness pairing a Daml interface marker with its view record. Generated +/// markers expose a singleton through a static View property, so a call site +/// passing it to a generic method lets the compiler infer both type parameters from one +/// argument — C# performs no partial type-argument inference, so the pair must travel +/// together. A mismatched pair is unconstructible: the constraints tie +/// to through +/// . +/// +/// The Daml interface marker type. +/// The interface's view record type. +public sealed class ViewDescriptor + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord +{ +} diff --git a/src/Daml.Runtime/Daml.Runtime.csproj b/src/Daml.Runtime/Daml.Runtime.csproj index 06851c1..e18bdc6 100644 --- a/src/Daml.Runtime/Daml.Runtime.csproj +++ b/src/Daml.Runtime/Daml.Runtime.csproj @@ -5,7 +5,6 @@ Runtime library for Daml-generated C# code. Provides base types and serialization support for Daml contracts. daml;canton;blockchain;smart-contracts;ledger README.md - true diff --git a/src/Daml.Runtime/Data/DamlCollections.cs b/src/Daml.Runtime/Data/DamlCollections.cs index c70ab1e..f29be05 100644 --- a/src/Daml.Runtime/Data/DamlCollections.cs +++ b/src/Daml.Runtime/Data/DamlCollections.cs @@ -28,6 +28,26 @@ public DamlValue GetValueOrThrow() => Value ?? throw new InvalidOperationException("Optional value is None."); } +/// +/// One level of a Daml Optional chain nested two or more levels deep — +/// Optional (Optional a) and beyond. Distinct from because +/// the two carry different wire encodings for the same shape: a flat optional is JSON +/// null or its bare value, while every level of a nested chain is the array form, +/// [] when absent and [v] when present. A participant rejects the flat encoding +/// in a nested position. +/// +/// The carried value, or when absent. +public sealed record DamlOptionalChain(DamlValue? Value) : DamlValue +{ + /// The absent level, mirroring Daml's None. + public static DamlOptionalChain None => new(default(DamlValue)); + + /// Wraps a value as a present level, mirroring Daml's Some. + /// The value to carry. + /// A present level carrying . + public static DamlOptionalChain Some(DamlValue value) => new(value); +} + /// /// Represents a Daml List value. /// diff --git a/src/Daml.Runtime/Data/DamlPrimitives.cs b/src/Daml.Runtime/Data/DamlPrimitives.cs index 88b081c..cb96094 100644 --- a/src/Daml.Runtime/Data/DamlPrimitives.cs +++ b/src/Daml.Runtime/Data/DamlPrimitives.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; +using Daml.Runtime.Serialization; namespace Daml.Runtime.Data; @@ -100,8 +101,10 @@ private DamlNumeric(bool isNegative, BigInteger unscaledMagnitude, int mantissaS /// /// /// The stored value cannot be represented exactly as a : its magnitude - /// exceeds , or it has more fractional digits than - /// can hold. The narrowing throws rather than silently rounding. + /// exceeds , or it still has more fractional digits than + /// can hold once trailing mantissa zeros are stripped. A participant + /// pads a Numeric out to its declared scale, so that padding never decides representability. + /// The narrowing throws rather than silently rounding. /// public decimal Value => TryToDecimal(out var value) @@ -111,16 +114,27 @@ private DamlNumeric(bool isNegative, BigInteger unscaledMagnitude, int mantissaS private bool TryToDecimal(out decimal value) { - if (_mantissaScale > MaxDecimalMantissaScale || _unscaledMagnitude > MaxDecimalUnscaledMagnitude) + if (TryNarrow(_unscaledMagnitude, _mantissaScale, out value)) + { + return true; + } + + var (strippedMantissa, strippedScale, _) = Normalize(_unscaledMagnitude, _mantissaScale, _isNegative); + return TryNarrow(strippedMantissa, strippedScale, out value); + } + + private bool TryNarrow(BigInteger unscaledMagnitude, int mantissaScale, out decimal value) + { + if (mantissaScale > MaxDecimalMantissaScale || unscaledMagnitude > MaxDecimalUnscaledMagnitude) { value = default; return false; } - var lo = unchecked((int)(uint)(_unscaledMagnitude & UInt32Mask)); - var mid = unchecked((int)(uint)((_unscaledMagnitude >> 32) & UInt32Mask)); - var hi = unchecked((int)(uint)((_unscaledMagnitude >> 64) & UInt32Mask)); - value = new decimal(lo, mid, hi, _isNegative, (byte)_mantissaScale); + var lo = unchecked((int)(uint)(unscaledMagnitude & UInt32Mask)); + var mid = unchecked((int)(uint)((unscaledMagnitude >> 32) & UInt32Mask)); + var hi = unchecked((int)(uint)((unscaledMagnitude >> 64) & UInt32Mask)); + value = new decimal(lo, mid, hi, _isNegative, (byte)mantissaScale); return true; } @@ -446,56 +460,13 @@ public static Party FromDamlValue(DamlParty value) /// so Party round-trips through JSON payloads produced by PQS and the JSON Ledger API, /// which encode parties as raw strings (e.g. "Alice::1220abcd..."). /// -internal sealed class PartyJsonConverter : JsonConverter +internal sealed class PartyJsonConverter : OpaqueStringIdJsonConverter { - // HandleNull=true so a bare `null` on a non-nullable Party field surfaces as a - // JsonException here instead of silently producing a default(Party) that later - // throws InvalidOperationException on .Id access. Party? is unaffected — STJ - // short-circuits null for Nullable before invoking the converter. - public override bool HandleNull => true; - - public override Party Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.String) - { - throw new JsonException($"Expected string token for Party, got {reader.TokenType}."); - } - - var id = reader.GetString()!; - if (string.IsNullOrWhiteSpace(id)) - { - throw new JsonException("Party id cannot be null or whitespace."); - } - - // Translate any ArgumentException the constructor might grow in the future - // into a JsonException, so callers catching serialization errors see the right type. - try - { - return new Party(id); - } - catch (ArgumentException ex) - { - throw new JsonException($"Invalid Party id '{id}'.", ex); - } - } - - public override void Write(Utf8JsonWriter writer, Party value, JsonSerializerOptions options) - { - // Mirror Read: translate the InvalidOperationException that Party.Id throws - // for default(Party) into a JsonException so callers can catch both directions - // of the round-trip uniformly. - string id; - try - { - id = value.Id; - } - catch (InvalidOperationException ex) - { - throw new JsonException("Cannot serialize an uninitialized Party.", ex); - } + /// + protected override Party Parse(string id) => new(id); - writer.WriteStringValue(id); - } + /// + protected override string Format(Party value) => value.Id; } /// diff --git a/src/Daml.Runtime/Data/DamlRecord.cs b/src/Daml.Runtime/Data/DamlRecord.cs index eb5df88..27a2f04 100644 --- a/src/Daml.Runtime/Data/DamlRecord.cs +++ b/src/Daml.Runtime/Data/DamlRecord.cs @@ -1,6 +1,8 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Runtime.Contracts; + namespace Daml.Runtime.Data; /// @@ -12,6 +14,20 @@ public sealed record DamlRecord( Identifier? RecordId, IReadOnlyList Fields) : DamlValue { + private readonly IReadOnlyList _fields = + EventCollections.Copy(Fields, nameof(Fields)); + + /// + /// The fields of the record. Copied at construction and on init, so a caller that + /// retains the list it supplied cannot change this value's equality or hash code + /// afterwards. + /// + public IReadOnlyList Fields + { + get => _fields; + init => _fields = EventCollections.Copy(value, nameof(Fields)); + } + /// /// Creates a record with the specified fields. /// diff --git a/src/Daml.Runtime/Data/DamlValue.cs b/src/Daml.Runtime/Data/DamlValue.cs index c43df27..6129467 100644 --- a/src/Daml.Runtime/Data/DamlValue.cs +++ b/src/Daml.Runtime/Data/DamlValue.cs @@ -26,6 +26,27 @@ public interface IDamlRecord : IDamlValue DamlRecord ToRecord(); } +/// +/// A Daml value whose Ledger API representation is a record and whose concrete type +/// can be reconstructed from that representation. +/// +/// +/// The factory is a static abstract, so it is called through a type parameter constrained +/// to this interface — static T Materialize<T>(DamlRecord record) +/// where T : IDamlRecord<T> => T.FromRecord(record); — never on an instance. +/// +/// The implementing type itself; the self-referential constraint +/// lets the factory return the concrete type rather than this interface. +public interface IDamlRecord : IDamlRecord + where TSelf : IDamlRecord +{ + /// + /// Creates a instance from its Ledger API record + /// representation. Implementations throw when a required field is missing. + /// + static abstract TSelf FromRecord(DamlRecord record); +} + /// /// A Daml value whose Ledger API representation is a variant. /// diff --git a/src/Daml.Runtime/Data/DamlValueExtensions.cs b/src/Daml.Runtime/Data/DamlValueExtensions.cs index e8d54b1..ea06ca7 100644 --- a/src/Daml.Runtime/Data/DamlValueExtensions.cs +++ b/src/Daml.Runtime/Data/DamlValueExtensions.cs @@ -18,9 +18,24 @@ public static class DamlValueExtensions /// schema-aware readers use this to recover the Optional wrapper that /// would reject. /// + /// The value to normalize. + /// The value as a . + /// is . + /// is a + /// . A chain level is already an Optional, in the array + /// encoding rather than the flat one, so wrapping it as Some would add a level that + /// neither the ledger nor expects. + /// Decode a chain through instead. public static DamlOptional AsOptional(this DamlValue value) { ArgumentNullException.ThrowIfNull(value); + if (value is DamlOptionalChain) + { + throw new InvalidCastException( + "Cannot normalize a DamlOptionalChain to DamlOptional. A chain level is already an " + + "Optional in the array wire encoding; wrapping it as Some would add a level. " + + "Decode a nested Optional chain through Optional.FromChainValue."); + } return value as DamlOptional ?? DamlOptional.Some(value); } @@ -45,24 +60,27 @@ public static DamlOptional AsOptional(this DamlValue value) /// via reflection. /// /// Any other combination throws . + /// + /// The assignability check runs before any unwrapping so that asking for + /// or itself returns the instance rather than + /// default. Beyond that check a target is treated as its + /// underlying type — unboxing a boxed T to T? is a well-defined CLR conversion + /// — and a nullable value type is a valid destination for because it + /// can represent "no value". + /// /// [return: MaybeNull] public static TResult FromDamlValue(this DamlValue value) { ArgumentNullException.ThrowIfNull(value); - // Assignable check runs first so that FromDamlValue(DamlUnit.Instance) - // and FromDamlValue(DamlUnit.Instance) return the instance rather than default. if (typeof(TResult).IsAssignableFrom(value.GetType())) return (TResult)(object)value; - // Treat Nullable the same as T for the primitive-unwrapping branches. - // Unboxing a boxed T to Nullable is a well-defined CLR conversion. var targetType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult); if (value is DamlUnit) { - // Nullable is a struct but can represent null, so default(T?) is a valid "no value". if (typeof(TResult).IsValueType && Nullable.GetUnderlyingType(typeof(TResult)) is null) throw new NotSupportedException( $"Cannot convert DamlUnit to value type {typeof(TResult)}. " + diff --git a/src/Daml.Runtime/Data/DamlVariant.cs b/src/Daml.Runtime/Data/DamlVariant.cs index 77bdfab..d0fdf2b 100644 --- a/src/Daml.Runtime/Data/DamlVariant.cs +++ b/src/Daml.Runtime/Data/DamlVariant.cs @@ -36,6 +36,10 @@ public T GetValue() where T : DamlValue => /// /// Optional type identifier. /// The enum constructor name. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1711:Identifiers should not have incorrect suffix", + Justification = "Daml is the naming scheme of the whole Daml value model (DamlRecord, DamlVariant, DamlEnum); the suffix names the Daml type, not a CLR enum.")] public sealed record DamlEnum(Identifier? EnumId, string Constructor) : DamlValue { /// Builds an enum value without a type identifier, e.g. when the type is implied by context. diff --git a/src/Daml.Runtime/Data/SynchronizerId.cs b/src/Daml.Runtime/Data/SynchronizerId.cs index fc2d3f2..9f50aac 100644 --- a/src/Daml.Runtime/Data/SynchronizerId.cs +++ b/src/Daml.Runtime/Data/SynchronizerId.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Daml.Runtime.Serialization; namespace Daml.Runtime.Data; @@ -27,6 +28,12 @@ namespace Daml.Runtime.Data; /// will not compare equal as raw strings. Code that needs to bridge the /// transition must handle the format-aware comparison itself. /// +/// +/// default(SynchronizerId) is never a valid representation of an absent id — it +/// throws on any access to , as does the explicit conversion to +/// ; project an optional wire field through , +/// which returns for an absent value. +/// /// [JsonConverter(typeof(SynchronizerIdJsonConverter))] public readonly record struct SynchronizerId @@ -56,6 +63,20 @@ public static explicit operator string(SynchronizerId id) => /// Parses a wire-format id; explicit so arbitrary strings never silently become synchronizer ids. public static explicit operator SynchronizerId(string id) => new(id); + /// + /// Projects an optional wire field: when + /// is null, empty, or whitespace; a constructed + /// otherwise. + /// + /// + /// The supported projection for an optional wire field — it never produces a default + /// instance, so the absent case is observable as rather than + /// as a value that throws on . + /// + /// The optional wire value. + public static SynchronizerId? FromWire(string? id) => + string.IsNullOrWhiteSpace(id) ? null : new SynchronizerId(id); + /// /// Returns a sentinel — not a throw — for default(SynchronizerId): logging /// frameworks may invoke ToString on a captured value during exception @@ -70,56 +91,11 @@ public static explicit operator string(SynchronizerId id) => /// and the JSON Ledger API, which encode synchronizer ids as raw strings /// (e.g. "global_sync::1220abcd...::35-0"). /// -internal sealed class SynchronizerIdJsonConverter : JsonConverter +internal sealed class SynchronizerIdJsonConverter : OpaqueStringIdJsonConverter { - // HandleNull=true so a bare `null` on a non-nullable SynchronizerId field surfaces - // as a JsonException here instead of silently producing a default(SynchronizerId) - // that later throws InvalidOperationException on .Id access. SynchronizerId? is - // unaffected — STJ short-circuits null for Nullable before invoking the converter. - public override bool HandleNull => true; - - public override SynchronizerId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.String) - { - throw new JsonException($"Expected string token for SynchronizerId, got {reader.TokenType}."); - } + /// + protected override SynchronizerId Parse(string id) => new(id); - var id = reader.GetString(); - if (string.IsNullOrWhiteSpace(id)) - { - throw new JsonException(id is null - ? "SynchronizerId id cannot be null." - : $"SynchronizerId id cannot be whitespace; got '{id}'."); - } - - // Translate any ArgumentException the constructor might grow in the future - // into a JsonException, so callers catching serialization errors see the right type. - try - { - return new SynchronizerId(id); - } - catch (ArgumentException ex) - { - throw new JsonException($"Invalid SynchronizerId '{id}'.", ex); - } - } - - public override void Write(Utf8JsonWriter writer, SynchronizerId value, JsonSerializerOptions options) - { - // Mirror Read: translate the InvalidOperationException that SynchronizerId.Id - // throws for default(SynchronizerId) into a JsonException so callers can catch - // both directions of the round-trip uniformly. - string id; - try - { - id = value.Id; - } - catch (InvalidOperationException ex) - { - throw new JsonException("Cannot serialize an uninitialized SynchronizerId.", ex); - } - - writer.WriteStringValue(id); - } + /// + protected override string Format(SynchronizerId value) => value.Id; } diff --git a/src/Daml.Runtime/Outcomes/ExerciseOutcome.cs b/src/Daml.Runtime/Outcomes/ExerciseOutcome.cs index 1aa5a8c..d6db82a 100644 --- a/src/Daml.Runtime/Outcomes/ExerciseOutcome.cs +++ b/src/Daml.Runtime/Outcomes/ExerciseOutcome.cs @@ -94,6 +94,12 @@ public sealed record DamlError( /// (int)Grpc.Core.StatusCode; consumers that want the typed enum cast back. Held as /// int so this type stays free of any transport-library dep. /// Status detail / message from the participant or transport. + /// Classification of the transport failure when the transport could determine + /// one without a structured Canton error attached; null when the failure was not classified. /// Transport exception that caused the infrastructure failure, when available. - public sealed record InfraError(int StatusCode, string Message, Exception? SourceException = null) : ExerciseOutcome; + public sealed record InfraError( + int StatusCode, + string Message, + DamlErrorCategory? Category = null, + Exception? SourceException = null) : ExerciseOutcome; } diff --git a/src/Daml.Runtime/Outcomes/ExerciseOutcomeProjection.cs b/src/Daml.Runtime/Outcomes/ExerciseOutcomeProjection.cs index f894d49..27fc573 100644 --- a/src/Daml.Runtime/Outcomes/ExerciseOutcomeProjection.cs +++ b/src/Daml.Runtime/Outcomes/ExerciseOutcomeProjection.cs @@ -53,7 +53,7 @@ public static ExerciseOutcome ProjectCommitted( ExerciseOutcome.None => new ExerciseOutcome.None(), ExerciseOutcome.Many many => new ExerciseOutcome.Many(many.Count, many.ContractIds), ExerciseOutcome.DamlError e => new ExerciseOutcome.DamlError(e.Category, e.ErrorId, e.Message, e.Metadata), - ExerciseOutcome.InfraError e => new ExerciseOutcome.InfraError(e.StatusCode, e.Message, e.SourceException), + ExerciseOutcome.InfraError e => new ExerciseOutcome.InfraError(e.StatusCode, e.Message, e.Category, e.SourceException), _ => throw new UnreachableException($"Unexpected outcome {outcome.GetType().Name} from TrySubmitAndWaitForTransactionAsync."), }; } diff --git a/src/Daml.Runtime/README.md b/src/Daml.Runtime/README.md index 116b897..75a2dac 100644 --- a/src/Daml.Runtime/README.md +++ b/src/Daml.Runtime/README.md @@ -57,6 +57,36 @@ var submission = CommandsSubmission.Single(createCmd) .WithCommandId(new CommandId(Guid.NewGuid().ToString())); ``` +### Projecting Transaction Results + +A committed submission yields a `TransactionResult` (for example as the +success payload of `ILedgerWriter.TrySubmitAndWaitForTransactionAsync`), +whose `CreatedContracts` list is untyped wire data. Don't re-implement the +projection back to typed contract ids — `TransactionResultExtensions` in +`Daml.Runtime.Contracts` ships it: + +```csharp +using Daml.Runtime.Contracts; +using Quickstart; + +// tx is a TransactionResult from a committed submission. + +// Every created Iou, in transaction order: +IReadOnlyList> all = tx.All(); + +// Exactly-one-or-null: null when none was created, +// throws InvalidOperationException when more than one was: +ContractId? maybe = tx.TrySingle(); + +// Exactly one: throws InvalidOperationException on zero or many: +ContractId single = tx.Single(); +``` + +Matching is by qualified name (module + entity), deliberately ignoring the +package id, so a template upgrade that changes the package hash still +resolves. When `T` is a generated Daml *interface* marker, a created +contract matches on its `InterfaceIds` instead of its `TemplateId`. + ### Manual Serialization If you need to work with Daml values directly: @@ -75,6 +105,58 @@ var json = DamlJsonSerializer.Serialize(record); var parsed = DamlJsonSerializer.DeserializeRecord(json); ``` +`DeserializeRecord` and `Deserialize` are **untyped**: with no schema to consult they +fold shapes that share a JSON form onto one Daml node. A `TextMap` comes back as a +`DamlRecord`, a `Party` as a `DamlText`, and a nested `Optional` chain — written `[]` or +`[v]` at every level — as a `DamlList`. Their output is therefore not interchangeable with +a generated `FromRecord`'s input: a record carrying a nested `Optional` field throws +`InvalidCastException: Cannot cast DamlList to DamlOptionalChain`, where every other field +type would have decoded. + +Read against the type when the distinction matters — `DamlLfJsonReader` has the schema +and produces the right node for each slot: + +```csharp +using Daml.Runtime.Serialization; +using Quickstart; + +var iou = Iou.FromRecord(DamlLfJsonReader.ReadRecord(json)); +``` + +### System.Text.Json Converters + +`Party`, `ContractId` and `SynchronizerId` each travel as a bare JSON string in PQS +rows and JSON Ledger API payloads, and each carries its converter as a `[JsonConverter]` +attribute — so plain `System.Text.Json` needs no setup: + +```csharp +var iou = JsonSerializer.Deserialize(pqsRowJson); +``` + +`System.Text.Json` reads `[JsonConverter]` off the declared type and does not walk its +base chain, so a derived contract id needs the attribute of its own. The emitted +`T.ContractId` — including `T.Contract.Id` — is given it by the codegen, so it converts +with no setup either. A hand-written type deriving from `ContractId` is not, and still +writes `{"Value":"..."}` until it is registered. + +Register explicitly for that case, and whenever a host builds its own +`JsonSerializerOptions` and wants the Daml conversions listed explicitly rather than +inherited from attributes: + +```csharp +using Daml.Runtime.Serialization; + +var options = new JsonSerializerOptions().AddDamlConverters(); +``` + +`AddDamlConverters` also sets `RespectNullableAnnotations`, which is what makes the three +identity types agree on what a JSON `null` means: rejected wherever the declared type +forbids one, read as absent wherever it permits one. `Party` and `SynchronizerId` are +structs and enforce that themselves; `ContractId` is a reference type, so it and +`ContractId?` — the C# rendering of `Optional (ContractId T)` — are the same type at +runtime and only the annotation separates a required contract id from an optional one. +The flag is serializer-wide, so it applies to a host's own types in the same options too. + ## Type Mappings | Daml Type | Generated C# Type | Runtime backing | @@ -88,9 +170,29 @@ var parsed = DamlJsonSerializer.DeserializeRecord(json); | `Time` | `DateTimeOffset` | `DamlTimestamp` | | `ContractId T` | `ContractId` | `DamlContractId` | | `Optional a` | `T?` | `DamlOptional` | +| `Optional a`, where nullable syntax cannot carry it | `Optional` (`Daml.Runtime.Stdlib`) | `DamlOptional` | +| `Optional (Optional a)` | `Optional>` | `DamlOptionalChain`, one level per `Optional` | | `List a` | `IReadOnlyList` | `DamlList` | | `TextMap a` | `IReadOnlyDictionary` | `DamlTextMap` | +`T?` is the default and covers almost every Optional field. The `Optional` wrapper +appears only where C# nullable syntax provably cannot carry the value: over a type +parameter, where the compiler erases the `?`; as a type argument to a +generated generic; and nested inside another Optional, where `T??` does not exist and +`Some None` would collapse into `None`. A chain is uniform — every level rides the +wrapper, none of it rides C# nullability — which is why `Optional (Optional Text)` is +`Optional>` rather than `Optional?`. + +The type-argument case has one exception, and it fails the run rather than emitting: where +the generic's own declaration already wraps that type parameter in an `Optional`, as +`data Crate a = Crate with item : Optional a` does, passing an `Optional` for `a` is +refused by codegen. A generic's body is emitted once from its declaration, so it cannot +also account for a level the use site substitutes in. + +The two wire nodes are not interchangeable. A flat `DamlOptional` writes JSON `null` or +its bare value; each `DamlOptionalChain` level writes the array form, `[]` when absent and +`[v]` when present, which is what a participant accepts in a nested position. + ## License Apache-2.0 diff --git a/src/Daml.Runtime/Serialization/ContractIdJsonConverter.cs b/src/Daml.Runtime/Serialization/ContractIdJsonConverter.cs new file mode 100644 index 0000000..f9d5b0c --- /dev/null +++ b/src/Daml.Runtime/Serialization/ContractIdJsonConverter.cs @@ -0,0 +1,151 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using Daml.Runtime.Contracts; + +namespace Daml.Runtime.Serialization; + +/// +/// Supplies the converter for any closed +/// and for the per-template contract-id types codegen +/// derives from it. PQS rows and the JSON Ledger API encode a contract id as a raw +/// JSON string; without this factory the default object contract writes +/// {"Value":"..."} and cannot read a bare string back, so every consumer ends up +/// hand-rolling the same reflective factory. +/// +/// +/// +/// carries this factory as a +/// , so a property declared as the closed generic — +/// which is how generated template payloads declare their contract-id fields — converts +/// with no registration. reads +/// off the declared type and does not walk its base +/// chain, so the emitted T.ContractId (including T.Contract.Id) carries the +/// attribute of its own, written by the codegen onto the generated record — it converts +/// unregistered too. A hand-written type deriving from +/// carries no such attribute and does need registration: directly, or through +/// , which is also what makes the +/// converter list self-describing. +/// +/// +/// Codegen emits a T.ContractId record deriving from +/// for every template, and that derived type — not the open generic — is what a +/// consumer-authored DTO usually declares. Matching only the exact closed generic would +/// leave those properties on the default object contract, so one value would take two +/// wire shapes depending on the type it was declared as. The match therefore walks the +/// base chain, and reads back the +/// concrete derived type rather than its base. +/// +/// +/// AOT / trimming incompatibility: uses +/// and to +/// instantiate the closed converter at runtime. These reflection-based calls are not +/// compatible with Native AOT compilation or aggressive IL trimming and will produce +/// in those environments. Use a source-generated +/// instead when targeting AOT. +/// +/// +[RequiresUnreferencedCode("ContractIdJsonConverterFactory uses MakeGenericType and Activator.CreateInstance, which are not trimming-safe.")] +[RequiresDynamicCode("ContractIdJsonConverterFactory uses MakeGenericType at runtime, which requires dynamic code generation.")] +public sealed class ContractIdJsonConverterFactory : JsonConverterFactory +{ + /// + public override bool CanConvert(Type typeToConvert) => + !IsAbstract(typeToConvert) && ClosedContractIdBaseOf(typeToConvert) is not null; + + /// + /// + /// is neither a closed + /// nor a constructible type derived from one. + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + if (!CanConvert(typeToConvert)) + { + throw new ArgumentException( + $"'{typeToConvert}' is neither a closed {typeof(ContractId<>).Name} nor a constructible type derived from one.", + nameof(typeToConvert)); + } + + return (JsonConverter)Activator.CreateInstance( + typeof(ContractIdJsonConverter<>).MakeGenericType(typeToConvert))!; + } + + private static bool IsAbstract(Type typeToConvert) => typeToConvert is { IsAbstract: true }; + + private static Type? ClosedContractIdBaseOf(Type typeToConvert) + { + for (var candidate = typeToConvert; candidate is not null; candidate = candidate.BaseType) + { + if (candidate is { IsGenericType: true } + && candidate.GetGenericTypeDefinition() == typeof(ContractId<>)) + { + return candidate; + } + } + + return null; + } +} + +internal sealed class ContractIdJsonConverter : JsonConverter + where TContractId : ContractId +{ + private static readonly ConstructorInfo FromContractIdString = + typeof(TContractId).GetConstructor([typeof(string)]) + ?? throw new InvalidOperationException( + $"'{typeof(TContractId)}' has no public constructor taking a single contract-id string."); + + private static readonly string TypeName = DescribeContractIdType(typeof(TContractId)); + + /// + /// The reference-type member of the identity-converter family cannot police null the way + /// does. ContractId<T> and + /// ContractId<T>? — the C# rendering of Optional (ContractId T) — are the + /// same runtime type, so a converter handed the null token has no way to tell a required + /// field from an optional one and would have to reject both. The family reaches its shared + /// posture instead through , + /// which enables. + /// + public override bool HandleNull => false; + + public override TContractId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException($"Expected string token for {TypeName}, got {reader.TokenType}."); + } + + var value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + throw new JsonException( + $"Invalid contract id for {TypeName}: contract ids must be non-null and non-whitespace."); + } + + try + { + return (TContractId)FromContractIdString.Invoke([value]); + } + catch (TargetInvocationException ex) when (ex.InnerException is ArgumentException inner) + { + throw new JsonException($"Invalid contract id for {TypeName}: {inner.Message}", inner); + } + } + + public override void Write(Utf8JsonWriter writer, TContractId value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Value); + } + + private static string DescribeContractIdType(Type type) => type switch + { + { IsGenericType: true } => $"ContractId<{type.GetGenericArguments()[0].Name}>", + { DeclaringType: not null } => $"{type.DeclaringType.Name}.{type.Name}", + _ => type.Name, + }; +} diff --git a/src/Daml.Runtime/Serialization/DamlJsonConverters.cs b/src/Daml.Runtime/Serialization/DamlJsonConverters.cs new file mode 100644 index 0000000..eb1e844 --- /dev/null +++ b/src/Daml.Runtime/Serialization/DamlJsonConverters.cs @@ -0,0 +1,74 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using System.Text.Json.Serialization; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; + +namespace Daml.Runtime.Serialization; + +/// +/// The converters for the scalar identity types generated +/// records carry as fields — , and +/// — each of which travels as a bare JSON string in PQS rows +/// and JSON Ledger API payloads rather than as an object. +/// +/// +/// Each of those types already carries its converter as a +/// , so plain +/// JsonSerializer.Deserialize<MyTemplate>(json) needs no registration at all. +/// exists for the hosts that build their own +/// — a ledger or PQS client's default options, say — +/// and want the Daml conversions listed explicitly rather than inherited from attributes. +/// does not walk the base chain to find an inherited +/// attribute, so a type deriving from needs one of its own. +/// The emitted T.ContractId is given that attribute by the codegen; a hand-written +/// derived contract id is not, and falls back to {"Value":"..."} unless registered +/// here. +/// +public static class DamlJsonConverters +{ + /// + /// The converters, in the order appends them. + /// Each instance is stateless and safe to share across + /// . + /// + public static IReadOnlyList All { get; } = + [ + new PartyJsonConverter(), + new ContractIdJsonConverterFactory(), + new SynchronizerIdJsonConverter(), + ]; + + /// + /// Appends every converter in to , sets + /// , and returns + /// so the call chains off an options initializer. + /// + /// + /// The three identity types share one posture on null: a JSON null is rejected wherever the + /// declared type forbids one, and read as absent wherever it permits one. + /// and are structs and their converter enforces that itself; + /// is a reference type, and ContractId<T>? — the C# + /// rendering of Optional (ContractId T) — is indistinguishable from it at runtime, so + /// only the nullability annotation separates a required contract id from an optional one. + /// Enabling is therefore not a + /// preference but the condition for the family agreeing at all; it applies to every type + /// serializes, not only the Daml ones. + /// + /// is null. + /// + /// is already read-only — that is, serialization has begun. + /// + public static JsonSerializerOptions AddDamlConverters(this JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(options); + foreach (var converter in All) + { + options.Converters.Add(converter); + } + options.RespectNullableAnnotations = true; + return options; + } +} diff --git a/src/Daml.Runtime/Serialization/DamlJsonSerializer.cs b/src/Daml.Runtime/Serialization/DamlJsonSerializer.cs index 68a8f8d..d5f17f7 100644 --- a/src/Daml.Runtime/Serialization/DamlJsonSerializer.cs +++ b/src/Daml.Runtime/Serialization/DamlJsonSerializer.cs @@ -27,16 +27,19 @@ public static class DamlJsonSerializer { private const string VariantTagKey = "tag"; private const string VariantValueKey = "value"; - private const string CanonicalDateFormat = "yyyy-MM-dd"; + internal const string CanonicalDateFormat = "yyyy-MM-dd"; private const string CanonicalTimestampEmitFormat = "yyyy-MM-dd'T'HH:mm:ss.FFFFFF'Z'"; - private const string CanonicalTimestampParseFormat = "yyyy-MM-dd'T'HH':'mm':'ss.FFFFFFFK"; - private const int MaximumNestingDepth = 128; + internal const string CanonicalTimestampParseFormat = "yyyy-MM-dd'T'HH':'mm':'ss.FFFFFFFK"; + internal const DateTimeStyles UtcNormalizingTimestampParseStyles = DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; + internal const int MaximumNestingDepth = 128; private const int JsonReaderWriterMaxDepth = 4 * MaximumNestingDepth; - // Values mirror the defaults on DamlJsonDeserializationLimits' primary constructor. - // `new()` cannot be used here: for a readonly record struct the parameterless - // constructor zeroes all fields rather than applying the primary-constructor - // default arguments, and CA1805 correctly flags it. - private static readonly DamlJsonDeserializationLimits DefaultDeserializationLimits = new( + /// + /// The values repeat the defaults on ' primary + /// constructor. new() cannot stand in: for a readonly record struct the parameterless + /// constructor zeroes every field instead of applying the primary constructor's default + /// arguments, which CA1805 correctly flags. + /// + internal static readonly DamlJsonDeserializationLimits DefaultDeserializationLimits = new( 16 * 1024 * 1024, 100_000); @@ -50,7 +53,7 @@ public static class DamlJsonSerializer Converters = { new DamlValueJsonConverter() } }; - private static readonly JsonDocumentOptions DocumentOptions = new() + internal static readonly JsonDocumentOptions DocumentOptions = new() { MaxDepth = JsonReaderWriterMaxDepth, AllowDuplicateProperties = false @@ -127,7 +130,10 @@ public static string Serialize(DamlRecord record, JsonSerializerOptions? options /// An explicit JSON null record field deserializes to /// ; a Some value is flattened to its inner value on /// write, so schema-aware readers recover the wrapper via - /// . + /// . That flattening is specific to + /// : a level writes the array + /// form instead, [] or [v], and comes back as a on + /// an untyped round-trip. /// A two-property JSON object whose properties are exactly /// tag (a string) and value is reconstructed as a /// , so a genuine two-field record with those labels is @@ -202,7 +208,7 @@ public static DamlRecord DeserializeRecord(string json, DamlJsonDeserializationL return JsonObjectToRecord(obj, limits, depth: 0); } - private static void EnsureWithinInputLimit(string json, DamlJsonDeserializationLimits limits) + internal static void ValidateLimits(DamlJsonDeserializationLimits limits) { if (limits.MaxInputCharacters < 1) { @@ -212,6 +218,11 @@ private static void EnsureWithinInputLimit(string json, DamlJsonDeserializationL { throw new ArgumentOutOfRangeException(nameof(limits), "MaxArrayElements must be non-negative."); } + } + + internal static void EnsureWithinInputLimit(string json, DamlJsonDeserializationLimits limits) + { + ValidateLimits(limits); if (json.Length > limits.MaxInputCharacters) { throw new JsonException( @@ -243,7 +254,7 @@ private static JsonObject RecordToJsonObject(DamlRecord record, int depth) private static JsonNode? ValueToJsonNode(DamlValue value, int depth) => value switch { _ when depth > MaximumNestingDepth => throw DepthBoundExceeded(), - DamlInt64 i => JsonValue.Create(i.Value), + DamlInt64 i => JsonValue.Create(i.Value.ToString(CultureInfo.InvariantCulture)), DamlNumeric n => JsonValue.Create(n.ToCanonicalString()), DamlText t => JsonValue.Create(t.Value), DamlBool b => JsonValue.Create(b.Value), @@ -253,6 +264,9 @@ private static JsonObject RecordToJsonObject(DamlRecord record, int depth) DamlParty p => JsonValue.Create(p.Value), DamlContractId c => JsonValue.Create(c.Value), DamlOptional opt => opt.Value is null ? null : ValueToJsonNode(opt.Value, depth + 1), + DamlOptionalChain chain => chain.Value is null + ? new JsonArray() + : new JsonArray(ValueToJsonNode(chain.Value, depth + 1)), DamlList list => new JsonArray(list.Values.Select(v => ValueToJsonNode(v, depth + 1)).ToArray()), DamlTextMap map => MapToJsonObject(map, depth), DamlGenMap map => GenMapToJsonArray(map, depth), @@ -262,7 +276,7 @@ private static JsonObject RecordToJsonObject(DamlRecord record, int depth) _ => throw new JsonException($"Cannot serialize {value.GetType().Name} to JSON") }; - private static JsonException DepthBoundExceeded() => + internal static JsonException DepthBoundExceeded() => new($"Value nesting exceeds the maximum supported depth of {MaximumNestingDepth}"); private static JsonObject MapToJsonObject(DamlTextMap map, int depth) @@ -325,7 +339,7 @@ JsonObject obj when IsVariantShape(obj) => _ => throw new JsonException($"Cannot deserialize {node.GetType().Name}") }; - private static JsonException ArrayBreadthExceeded(int count, int maxArrayElements) => + internal static JsonException ArrayBreadthExceeded(int count, int maxArrayElements) => new($"JSON array length {count} exceeds the maximum supported JSON array length of {maxArrayElements}"); private static bool IsVariantShape(JsonObject obj) => @@ -404,7 +418,7 @@ private static DamlValue InferStringValue(string s) return new DamlDate(date); } - if (DateTimeOffset.TryParseExact(s, CanonicalTimestampParseFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var ts)) + if (DateTimeOffset.TryParseExact(s, CanonicalTimestampParseFormat, CultureInfo.InvariantCulture, UtcNormalizingTimestampParseStyles, out var ts)) { return new DamlTimestamp(ts); } @@ -424,7 +438,7 @@ private static DamlValue InferStringValue(string s) return new DamlText(s); } - private static bool MatchesCanonicalIntegerGrammar(string s) + internal static bool MatchesCanonicalIntegerGrammar(string s) { var digitsStart = s.StartsWith('-') ? 1 : 0; if (digitsStart == s.Length) diff --git a/src/Daml.Runtime/Serialization/DamlLfJsonReader.cs b/src/Daml.Runtime/Serialization/DamlLfJsonReader.cs new file mode 100644 index 0000000..ec4d130 --- /dev/null +++ b/src/Daml.Runtime/Serialization/DamlLfJsonReader.cs @@ -0,0 +1,832 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Concurrent; +using System.Collections.Frozen; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text.Json; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Stdlib; + +namespace Daml.Runtime.Serialization; + +/// +/// Decodes LF-JSON against the CLR shape of a generated Daml record, producing a +/// whose field values carry their true Daml types — a Daml +/// Party field arrives as rather than the +/// an untyped decode would yield. Callers hand the result to the +/// generated FromRecord. +/// +/// +/// The returned record's is always : +/// LF-JSON carries no type identifier, and generated FromRecord reads fields by label. +/// +public static class DamlLfJsonReader +{ + /// + /// Decodes an already-parsed LF-JSON object against the shape of . + /// + /// The generated Daml record type describing the expected shape. + /// The LF-JSON object to decode. + /// + /// Decode limits; the shared hardened defaults apply when omitted. + /// is accepted and ignored here: + /// the caller already parsed the document, so the parse-time size, duplicate-property and + /// parse-depth caps were theirs to apply. What this overload bounds is the reader's own + /// allocation amplification and recursion depth while walking an already-materialized + /// document — decode bounds, not a boundary defence against hostile input. + /// + /// A record whose field values carry their Daml types. + /// The JSON does not match the expected shape, or a decode limit is exceeded. + /// A CLR property type lies outside the Daml type mapping, or a generated enum companion cannot name the wire constructor of one of its members. + /// is not a valid limit configuration. + /// JSON properties the target type does not declare are ignored by design, for tolerance of payloads produced by newer Daml package versions. + public static DamlRecord ReadRecord(JsonElement json, DamlJsonDeserializationLimits? limits = null) + where T : IDamlRecord => + ReadRecord(json, typeof(T), limits); + + /// + /// Parses LF-JSON under the shared hardened document options and decodes it against the + /// shape of . + /// + /// The generated Daml record type describing the expected shape. + /// The LF-JSON text to parse and decode. + /// Decode limits; the shared hardened defaults apply when omitted. + /// A record whose field values carry their Daml types. + /// The JSON is malformed, does not match the expected shape, or exceeds a limit. + /// A CLR property type lies outside the Daml type mapping, or a generated enum companion cannot name the wire constructor of one of its members. + /// is . + /// is not a valid limit configuration. + /// JSON properties the target type does not declare are ignored by design, for tolerance of payloads produced by newer Daml package versions. + public static DamlRecord ReadRecord(string json, DamlJsonDeserializationLimits? limits = null) + where T : IDamlRecord => + ReadRecord(json, typeof(T), limits); + + /// + /// Decodes an already-parsed LF-JSON object against the shape of . + /// + /// The LF-JSON object to decode. + /// The generated Daml record type describing the expected shape. + /// + /// Decode limits; the shared hardened defaults apply when omitted. + /// is accepted and ignored here: + /// the caller already parsed the document, so the parse-time size, duplicate-property and + /// parse-depth caps were theirs to apply. What this overload bounds is the reader's own + /// allocation amplification and recursion depth while walking an already-materialized + /// document — decode bounds, not a boundary defence against hostile input. + /// + /// A record whose field values carry their Daml types. + /// The JSON does not match the expected shape, or a decode limit is exceeded. + /// is not a generated Daml record, a CLR property type lies outside the Daml type mapping, or a generated enum companion cannot name the wire constructor of one of its members. + /// is . + /// is not a valid limit configuration. + /// JSON properties the target type does not declare are ignored by design, for tolerance of payloads produced by newer Daml package versions. + public static DamlRecord ReadRecord(JsonElement json, Type recordType, DamlJsonDeserializationLimits? limits = null) + { + ArgumentNullException.ThrowIfNull(recordType); + var effectiveLimits = limits ?? DamlJsonSerializer.DefaultDeserializationLimits; + DamlJsonSerializer.ValidateLimits(effectiveLimits); + return ReadRecordValue(json, recordType, effectiveLimits, depth: 0, recordType.Name); + } + + /// + /// Parses LF-JSON under the shared hardened document options and decodes it against the + /// shape of . + /// + /// The LF-JSON text to parse and decode. + /// The generated Daml record type describing the expected shape. + /// Decode limits; the shared hardened defaults apply when omitted. + /// A record whose field values carry their Daml types. + /// The JSON is malformed, does not match the expected shape, or exceeds a limit. + /// is not a generated Daml record, a CLR property type lies outside the Daml type mapping, or a generated enum companion cannot name the wire constructor of one of its members. + /// or is . + /// is not a valid limit configuration. + /// JSON properties the target type does not declare are ignored by design, for tolerance of payloads produced by newer Daml package versions. + public static DamlRecord ReadRecord(string json, Type recordType, DamlJsonDeserializationLimits? limits = null) + { + ArgumentNullException.ThrowIfNull(json); + ArgumentNullException.ThrowIfNull(recordType); + var effectiveLimits = limits ?? DamlJsonSerializer.DefaultDeserializationLimits; + DamlJsonSerializer.EnsureWithinInputLimit(json, effectiveLimits); + using var document = JsonDocument.Parse(json, DamlJsonSerializer.DocumentOptions); + return ReadRecordValue(document.RootElement, recordType, effectiveLimits, depth: 0, recordType.Name); + } + + private static DamlRecord ReadRecordValue(JsonElement json, Type recordType, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (recordType.IsInterface && typeof(IDamlInterface).IsAssignableFrom(recordType)) + { + throw InterfaceMarkerTarget(recordType, path); + } + if (recordType.IsInterface || recordType.IsAbstract || !typeof(IDamlRecord).IsAssignableFrom(recordType)) + { + throw NotAGeneratedRecord(recordType, path); + } + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var fields = new List(); + foreach (var (label, slot) in DamlFieldsOf(recordType)) + { + var fieldPath = $"{path}.{label}"; + if (!json.TryGetProperty(label, out var element)) + { + throw MissingRecordField(fieldPath); + } + fields.Add(new DamlField(label, ReadValue(element, slot, limits, depth + 1, fieldPath))); + } + return new DamlRecord(null, fields); + } + + private static readonly ConcurrentDictionary RecordFields = new(); + + private static (string Label, ValueSlot Slot)[] DamlFieldsOf(Type recordType) => + RecordFields.GetOrAdd(recordType, ResolveDamlFields); + + private static (string Label, ValueSlot Slot)[] ResolveDamlFields(Type recordType) + { + var nullability = new NullabilityInfoContext(); + var fields = new List<(string Label, ValueSlot Slot)>(); + foreach (var property in recordType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .OrderBy(DeclarationOrderWithinTheDeclaringModule)) + { + if (property.GetCustomAttribute() is { } damlField) + { + fields.Add((damlField.Name, new ValueSlot(nullability.Create(property)))); + } + } + return fields.ToArray(); + } + + private static int DeclarationOrderWithinTheDeclaringModule(PropertyInfo property) => property.MetadataToken; + + private readonly record struct ValueSlot(NullabilityInfo Nullability) + { + public Type ClrType => Nullable.GetUnderlyingType(Nullability.Type) ?? Nullability.Type; + + public bool IsOptional => Nullability.ReadState == NullabilityState.Nullable; + + public ValueSlot TypeArgument(int index) => new(Nullability.GenericTypeArguments[index]); + } + + private static DamlValue ReadValue(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (depth > DamlJsonSerializer.MaximumNestingDepth) + { + throw DamlJsonSerializer.DepthBoundExceeded(); + } + if (!slot.IsOptional) + { + return ReadPresentValue(json, slot, limits, depth, path); + } + return json.ValueKind == JsonValueKind.Null + ? DamlOptional.None + : DamlOptional.Some(ReadPresentValue(json, slot, limits, depth, path)); + } + + private static DamlValue ReadPresentValue(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + var clrType = slot.ClrType; + if (ScalarArms.TryGetValue(clrType, out var readScalar)) + { + return readScalar(json, path); + } + if (clrType.IsEnum) + { + return ReadEnum(json, clrType, path); + } + if (IsContractId(clrType)) + { + return new DamlContractId(ReadWireString(json, path)); + } + if (IsList(clrType)) + { + return ReadList(json, slot.TypeArgument(0), limits, depth, path); + } + if (IsDictionary(clrType)) + { + return ReadMap(json, slot, limits, depth, path); + } + if (IsStdlibTuple(clrType)) + { + return ReadStdlibTuple(json, slot, limits, depth, path); + } + if (IsEither(clrType)) + { + return ReadEither(json, slot, limits, depth, path); + } + if (IsWrappedOptional(clrType)) + { + return IsOptionalChainRoot(clrType) + ? ReadOptionalChain(json, slot, limits, depth, path) + : ReadWrappedOptional(json, slot, limits, depth, path); + } + if (IsStdlibSet(clrType)) + { + return ReadStdlibSet(json, slot, limits, depth, path); + } + if (IsStdlibNonEmpty(clrType)) + { + return ReadStdlibNonEmpty(json, slot, limits, depth, path); + } + if (IsStdlibMap(clrType)) + { + return ReadStdlibMap(json, slot, limits, depth, path); + } + if (typeof(IDamlVariant).IsAssignableFrom(clrType)) + { + return ReadVariant(json, clrType, limits, depth, path); + } + if (typeof(IDamlRecord).IsAssignableFrom(clrType)) + { + return ReadRecordValue(json, clrType, limits, depth, path); + } + throw UnmappedClrType(clrType, path); + } + + private static readonly FrozenDictionary> ScalarArms = + new Dictionary> + { + [typeof(Party)] = (json, path) => new DamlParty(ReadWireString(json, path)), + [typeof(string)] = (json, path) => new DamlText(ReadWireString(json, path)), + [typeof(bool)] = ReadBool, + [typeof(long)] = ReadInt64, + [typeof(decimal)] = ReadNumeric, + [typeof(DateOnly)] = ReadDate, + [typeof(DateTimeOffset)] = ReadTimestamp, + [typeof(DamlUnit)] = ReadUnit + }.ToFrozenDictionary(); + + private static string ReadWireString(JsonElement json, string path) => + json.ValueKind == JsonValueKind.String + ? json.GetString()! + : throw ShapeMismatch(path, JsonValueKind.String, json.ValueKind); + + private static DamlValue ReadBool(JsonElement json, string path) => + json.ValueKind is JsonValueKind.True or JsonValueKind.False + ? new DamlBool(json.GetBoolean()) + : throw ShapeMismatch(path, "boolean", json.ValueKind); + + private static DamlValue ReadInt64(JsonElement json, string path) + { + var raw = ReadWireString(json, path); + return DamlJsonSerializer.MatchesCanonicalIntegerGrammar(raw) + && long.TryParse(raw, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var value) + ? new DamlInt64(value) + : throw MalformedScalar(path, raw, "Int64"); + } + + private static DamlValue ReadNumeric(JsonElement json, string path) + { + var raw = ReadWireString(json, path); + return DamlNumeric.TryParseCanonical(raw, out var numeric) + ? numeric + : throw MalformedScalar(path, raw, "Numeric"); + } + + private static DamlValue ReadDate(JsonElement json, string path) + { + var raw = ReadWireString(json, path); + return DateOnly.TryParseExact(raw, DamlJsonSerializer.CanonicalDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) + ? new DamlDate(date) + : throw MalformedScalar(path, raw, "Date"); + } + + private static DamlValue ReadTimestamp(JsonElement json, string path) + { + var raw = ReadWireString(json, path); + return DateTimeOffset.TryParseExact(raw, DamlJsonSerializer.CanonicalTimestampParseFormat, CultureInfo.InvariantCulture, DamlJsonSerializer.UtcNormalizingTimestampParseStyles, out var timestamp) + ? new DamlTimestamp(timestamp) + : throw MalformedScalar(path, raw, "Timestamp"); + } + + private static DamlValue ReadUnit(JsonElement json, string path) => + json.ValueKind == JsonValueKind.Object + ? DamlUnit.Instance + : throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + + private static DamlValue ReadEnum(JsonElement json, Type enumType, string path) + { + var constructor = ReadWireString(json, path); + var (known, sorted) = WireConstructorsOf(enumType, path); + return known.Contains(constructor) + ? DamlEnum.Create(constructor) + : throw UnknownConstructor("enum constructor", constructor, path, sorted); + } + + private const string ToDamlEnumMethod = "ToDamlEnum"; + private const string GeneratedCompanionSuffix = "Extensions"; + + private readonly record struct EnumConstructors(FrozenSet Known, IReadOnlyList Sorted); + + private static readonly ConcurrentDictionary EnumWireConstructors = new(); + + private static EnumConstructors WireConstructorsOf(Type enumType, string path) => + EnumWireConstructors.GetOrAdd(enumType, ResolveWireConstructors, path); + + private static EnumConstructors ResolveWireConstructors(Type enumType, string path) + { + var names = WireConstructorNames(enumType, path); + return new EnumConstructors(names.ToFrozenSet(), names.Order(StringComparer.Ordinal).ToList()); + } + + private static IReadOnlyList WireConstructorNames(Type enumType, string path) + { + var toDamlEnum = enumType.FullName is { } fullName + ? enumType.Assembly.GetType(fullName + GeneratedCompanionSuffix) + ?.GetMethod(ToDamlEnumMethod, BindingFlags.Public | BindingFlags.Static, null, [enumType], null) + : null; + if (toDamlEnum is null) + { + return Enum.GetNames(enumType); + } + + var constructors = new List(); + foreach (var member in Enum.GetValues(enumType)) + { + try + { + constructors.Add((toDamlEnum.Invoke(null, [member]) as DamlEnum)?.Constructor ?? member.ToString()!); + } + catch (Exception exception) + when (exception is TargetInvocationException or MemberAccessException or ArgumentException) + { + throw UnmappableEnumMember(enumType, member, path, exception); + } + } + return constructors; + } + + private static NotSupportedException UnmappableEnumMember(Type enumType, object member, string path, Exception cause) => + new($"Enum '{enumType}' at '{path}' has a companion whose {ToDamlEnumMethod} fails for member '{member}', " + + "so its wire constructors cannot be determined; pass a generated Daml enum.", cause); + + private const string VariantTagKey = "tag"; + private const string VariantValueKey = "value"; + private const string VariantConstructorKind = "variant constructor"; + + private static DamlVariant ReadVariant(JsonElement json, Type variantType, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var tag = ReadVariantTag(json, path); + var arms = ArmsOf(variantType, path); + if (!arms.TryGetValue(tag, out var arm)) + { + throw UnknownConstructor(VariantConstructorKind, tag, path, arms.Keys.Order(StringComparer.Ordinal).ToList()); + } + if (!json.TryGetProperty(VariantValueKey, out var valueElement)) + { + throw MissingVariantMember(path, VariantValueKey); + } + + var payloadPath = $"{path}.{VariantValueKey}"; + return DamlVariant.Create(tag, PayloadSlotOf(arm, payloadPath) is { } payloadSlot + ? ReadValue(valueElement, payloadSlot, limits, depth + 1, payloadPath) + : ReadUnit(valueElement, payloadPath)); + } + + private static readonly ConcurrentDictionary VariantArmPayloads = new(); + + private static ValueSlot? PayloadSlotOf(Type arm, string payloadPath) => + VariantArmPayloads.GetOrAdd(arm, ResolvePayloadSlot, payloadPath); + + private static ValueSlot? ResolvePayloadSlot(Type arm, string payloadPath) + { + var constructors = arm.GetConstructors(); + if (constructors.Length != 1) + { + throw new NotSupportedException( + $"Variant arm '{arm}' at '{payloadPath}' must expose exactly one public constructor; pass a generated variant."); + } + return constructors[0].GetParameters() switch + { + [] => null, + [var payload] => new ValueSlot(new NullabilityInfoContext().Create(payload)), + var parameters => throw new NotSupportedException( + $"Variant arm '{arm}' at '{payloadPath}' constructor takes {parameters.Length} parameters; generated arms take 0 (nullary) or 1 (payload); pass a generated variant.") + }; + } + + private static string ReadVariantTag(JsonElement json, string path) + { + if (!json.TryGetProperty(VariantTagKey, out var tagElement)) + { + throw MissingVariantMember(path, VariantTagKey); + } + return tagElement.ValueKind == JsonValueKind.String + ? tagElement.GetString()! + : throw ShapeMismatch($"{path}.{VariantTagKey}", JsonValueKind.String, tagElement.ValueKind); + } + + private static readonly ConcurrentDictionary> VariantArms = new(); + + private static FrozenDictionary ArmsOf(Type variantType, string path) => + VariantArms.GetOrAdd(variantType, ResolveArms, path); + + private static FrozenDictionary ResolveArms(Type variantType, string path) + { + var arms = variantType.GetNestedTypes(BindingFlags.Public) + .Where(variantType.IsAssignableFrom) + .ToFrozenDictionary(arm => WireTagOf(arm, path)); + return arms.Count > 0 ? arms : throw ArmlessVariant(variantType, path); + } + + private const string VariantTagProperty = "Tag"; + + private static string WireTagOf(Type arm, string path) + { + var tagProperty = arm.GetProperty(VariantTagProperty, BindingFlags.Public | BindingFlags.Instance); + return tagProperty?.PropertyType == typeof(string) + ? ReadTagLiteral(tagProperty, arm) ?? throw UntaggedVariantArm(arm, path) + : throw UntaggedVariantArm(arm, path); + } + + private static string? ReadTagLiteral(PropertyInfo tagProperty, Type arm) + { + try + { + return tagProperty.GetValue(RuntimeHelpers.GetUninitializedObject(arm)) as string; + } + catch (Exception exception) + when (exception is TargetInvocationException or MemberAccessException + or NotSupportedException or ArgumentException) + { + return null; + } + } + + private static JsonException MissingVariantMember(string path, string member) => + new($"Required Daml variant field '{path}.{member}' is missing from the JSON object"); + + private static readonly string[] StdlibTupleFieldLabels = ["_1", "_2", "_3"]; + + private static bool IsStdlibTuple(Type clrType) => + clrType.IsGenericType + && (clrType.GetGenericTypeDefinition() == typeof(Tuple2<,>) + || clrType.GetGenericTypeDefinition() == typeof(Tuple3<,,>)); + + private static DamlRecord ReadStdlibTuple(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var componentCount = slot.ClrType.GetGenericArguments().Length; + var fields = new List(componentCount); + for (var component = 0; component < componentCount; component++) + { + var label = StdlibTupleFieldLabels[component]; + var componentPath = $"{path}.{label}"; + if (!json.TryGetProperty(label, out var element)) + { + throw MissingRecordField(componentPath); + } + fields.Add(new DamlField( + label, + ReadValue(element, slot.TypeArgument(component), limits, depth + 1, componentPath))); + } + return new DamlRecord(null, fields); + } + + private static readonly string[] EitherConstructors = ["Left", "Right"]; + + private static bool IsEither(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(Either<,>); + + private static bool IsWrappedOptional(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(Optional<>); + + private static DamlOptional ReadWrappedOptional( + JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) => + json.ValueKind == JsonValueKind.Null + ? DamlOptional.None + : DamlOptional.Some(ReadValue(json, slot.TypeArgument(0), limits, depth + 1, path)); + + private static bool IsOptionalChainRoot(Type wrappedOptional) + { + var carried = wrappedOptional.GetGenericArguments()[0]; + return IsWrappedOptional(carried); + } + + private static DamlValue ReadOptionalChain( + JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Array) + { + throw ShapeMismatch(path, JsonValueKind.Array, json.ValueKind); + } + + var length = json.GetArrayLength(); + if (length == 0) + { + return DamlOptionalChain.None; + } + if (length > 1) + { + throw OverfullOptionalChain(path, length); + } + + var carried = slot.TypeArgument(0); + var carriedPath = $"{path}[0]"; + return DamlOptionalChain.Some( + IsWrappedOptional(carried.ClrType) + ? ReadOptionalChain(json[0], carried, limits, depth + 1, carriedPath) + : ReadValue(json[0], carried, limits, depth + 1, carriedPath)); + } + + private static DamlVariant ReadEither(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var tag = ReadVariantTag(json, path); + var component = Array.IndexOf(EitherConstructors, tag); + if (component < 0) + { + throw UnknownConstructor(VariantConstructorKind, tag, path, EitherConstructors); + } + if (!json.TryGetProperty(VariantValueKey, out var valueElement)) + { + throw MissingVariantMember(path, VariantValueKey); + } + + var payloadPath = $"{path}.{VariantValueKey}"; + return DamlVariant.Create( + tag, + ReadValue(valueElement, slot.TypeArgument(component), limits, depth + 1, payloadPath)); + } + + private const string StdlibMapFieldLabel = "map"; + private const string NonEmptyHeadFieldLabel = "hd"; + private const string NonEmptyTailFieldLabel = "tl"; + + private static bool IsStdlibSet(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(Set<>); + + private static bool IsStdlibNonEmpty(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(NonEmpty<>); + + private static bool IsStdlibMap(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(Map<,>); + + private static DamlRecord ReadStdlibSet(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + var (entries, entriesPath) = ReadStdlibMapField(json, path); + return WrapStdlibMap( + ReadGenMapEntries( + entries, slot.TypeArgument(0), ReadUnit, StdlibSetContainerName, limits, depth + 1, entriesPath)); + } + + private static DamlRecord ReadStdlibMap(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + var (entries, entriesPath) = ReadStdlibMapField(json, path); + var valueSlot = slot.TypeArgument(1); + return WrapStdlibMap(ReadGenMapEntries( + entries, + slot.TypeArgument(0), + (value, valuePath) => ReadValue(value, valueSlot, limits, depth + 2, valuePath), + StdlibMapContainerName, + limits, + depth + 1, + entriesPath)); + } + + private static (JsonElement Entries, string Path) ReadStdlibMapField(JsonElement json, string path) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var entriesPath = $"{path}.{StdlibMapFieldLabel}"; + return json.TryGetProperty(StdlibMapFieldLabel, out var entries) + ? (entries, entriesPath) + : throw MissingRecordField(entriesPath); + } + + private static DamlRecord WrapStdlibMap(DamlGenMap entries) => + new(null, [new DamlField(StdlibMapFieldLabel, entries)]); + + private static DamlRecord ReadStdlibNonEmpty(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Object) + { + throw ShapeMismatch(path, JsonValueKind.Object, json.ValueKind); + } + + var elementSlot = slot.TypeArgument(0); + var headPath = $"{path}.{NonEmptyHeadFieldLabel}"; + if (!json.TryGetProperty(NonEmptyHeadFieldLabel, out var head)) + { + throw MissingRecordField(headPath); + } + + var tailPath = $"{path}.{NonEmptyTailFieldLabel}"; + if (!json.TryGetProperty(NonEmptyTailFieldLabel, out var tail)) + { + throw MissingRecordField(tailPath); + } + + return new DamlRecord(null, [ + new DamlField(NonEmptyHeadFieldLabel, ReadValue(head, elementSlot, limits, depth + 1, headPath)), + new DamlField(NonEmptyTailFieldLabel, ReadList(tail, elementSlot, limits, depth + 1, tailPath))]); + } + + private static bool IsContractId(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(ContractId<>); + + private static bool IsList(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(IReadOnlyList<>); + + private static bool IsDictionary(Type clrType) => + clrType.IsGenericType && clrType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>); + + private static DamlValue ReadMap(JsonElement json, ValueSlot slot, DamlJsonDeserializationLimits limits, int depth, string path) + { + var keySlot = slot.TypeArgument(0); + if (json.ValueKind == JsonValueKind.Array) + { + return ReadGenMap(json, keySlot, slot.TypeArgument(1), limits, depth, path); + } + if (json.ValueKind == JsonValueKind.Object && keySlot.ClrType == typeof(string)) + { + return ReadTextMap(json, slot.TypeArgument(1), limits, depth, path); + } + throw ShapeMismatch(path, AcceptedMapWireForms(keySlot), json.ValueKind); + } + + private static string AcceptedMapWireForms(ValueSlot keySlot) => + keySlot.ClrType == typeof(string) + ? "object (TextMap) or array of entry pairs (GenMap)" + : "array of entry pairs (GenMap)"; + + private static DamlGenMap ReadGenMap(JsonElement json, ValueSlot keySlot, ValueSlot valueSlot, DamlJsonDeserializationLimits limits, int depth, string path) => + ReadGenMapEntries( + json, + keySlot, + (value, valuePath) => ReadValue(value, valueSlot, limits, depth + 1, valuePath), + GenMapContainerName, + limits, + depth, + path); + + private const string GenMapContainerName = "GenMap"; + private const string StdlibSetContainerName = "Set"; + private const string StdlibMapContainerName = "Map"; + + private static DamlGenMap ReadGenMapEntries( + JsonElement json, + ValueSlot keySlot, + Func readEntryValue, + string containerName, + DamlJsonDeserializationLimits limits, + int depth, + string path) + { + if (json.ValueKind != JsonValueKind.Array) + { + throw ShapeMismatch(path, JsonValueKind.Array, json.ValueKind); + } + + var length = json.GetArrayLength(); + if (length > limits.MaxArrayElements) + { + throw DamlJsonSerializer.ArrayBreadthExceeded(length, limits.MaxArrayElements); + } + + var entries = new List<(DamlValue Key, DamlValue Value)>(length); + var seenKeys = new HashSet(); + foreach (var entry in json.EnumerateArray()) + { + var entryPath = $"{path}[{entries.Count}]"; + if (entry.ValueKind != JsonValueKind.Array) + { + throw ShapeMismatch(entryPath, JsonValueKind.Array, entry.ValueKind); + } + if (entry.GetArrayLength() != 2) + { + throw new JsonException( + $"Expected a two-element key/value pair at '{entryPath}' but found {entry.GetArrayLength()} element(s)"); + } + + var key = ReadValue(entry[0], keySlot, limits, depth + 1, $"{entryPath}.key"); + if (!seenKeys.Add(key)) + { + throw new JsonException($"Duplicate key at '{entryPath}' in a Daml {containerName}"); + } + entries.Add((key, readEntryValue(entry[1], $"{entryPath}.value"))); + } + return new DamlGenMap(entries); + } + + private static DamlTextMap ReadTextMap(JsonElement json, ValueSlot valueSlot, DamlJsonDeserializationLimits limits, int depth, string path) + { + var count = json.GetPropertyCount(); + if (count > limits.MaxArrayElements) + { + throw MapBreadthExceeded(count, limits.MaxArrayElements); + } + + var values = new Dictionary(count); + foreach (var entry in json.EnumerateObject()) + { + if (!values.TryAdd(entry.Name, ReadValue(entry.Value, valueSlot, limits, depth + 1, MapEntryPath(path, entry.Name)))) + { + throw new JsonException($"Duplicate key '{entry.Name}' at '{path}' in a Daml TextMap"); + } + } + return new DamlTextMap(values); + } + + private static JsonException MapBreadthExceeded(int count, int maxEntries) => + new($"JSON object property count {count} exceeds the maximum supported Daml TextMap entry count of {maxEntries}"); + + private static string MapEntryPath(string path, string key) => + $"{path}['{Elide(key).Replace("\\", "\\\\", StringComparison.Ordinal).Replace("'", "\\'", StringComparison.Ordinal)}']"; + + private static DamlList ReadList(JsonElement json, ValueSlot elementSlot, DamlJsonDeserializationLimits limits, int depth, string path) + { + if (json.ValueKind != JsonValueKind.Array) + { + throw ShapeMismatch(path, JsonValueKind.Array, json.ValueKind); + } + + var length = json.GetArrayLength(); + if (length > limits.MaxArrayElements) + { + throw DamlJsonSerializer.ArrayBreadthExceeded(length, limits.MaxArrayElements); + } + + var values = new List(length); + foreach (var element in json.EnumerateArray()) + { + values.Add(ReadValue(element, elementSlot, limits, depth + 1, $"{path}[{values.Count}]")); + } + return new DamlList(values); + } + + private static JsonException ShapeMismatch(string path, JsonValueKind expected, JsonValueKind actual) => + ShapeMismatch(path, expected.ToString(), actual); + + private static JsonException ShapeMismatch(string path, string expected, JsonValueKind actual) => + new($"Expected JSON {expected} at '{path}' but found {actual}"); + + private static JsonException OverfullOptionalChain(string path, int length) => + new($"A nested Daml Optional at '{path}' encodes as an array of at most one element " + + $"but found {length}"); + + private static JsonException MissingRecordField(string fieldPath) => + new($"Required Daml field '{fieldPath}' is missing from the JSON object"); + + private static JsonException UnknownConstructor(string kind, string name, string path, IReadOnlyList known) => + new($"Unknown Daml {kind} '{Elide(name)}' at '{path}'; expected one of {string.Join(", ", known)}"); + + private const int MaximumEchoedValueLength = 64; + + private static JsonException MalformedScalar(string path, string raw, string damlType) => + new($"Value '{Elide(raw)}' at '{path}' is not a valid Daml {damlType}"); + + private static string Elide(string raw) + { + if (raw.Length <= MaximumEchoedValueLength) + { + return raw; + } + var boundary = char.IsHighSurrogate(raw[MaximumEchoedValueLength - 1]) + ? MaximumEchoedValueLength - 1 + : MaximumEchoedValueLength; + return string.Concat(raw.AsSpan(0, boundary), "…"); + } + + private static NotSupportedException ArmlessVariant(Type variantType, string path) => + new($"Type '{variantType}' at '{path}' declares no variant arms; " + + $"pass a generated variant whose constructors are nested types carrying a {VariantTagProperty} property."); + + private static NotSupportedException UntaggedVariantArm(Type arm, string path) => + new($"Variant arm '{arm}' at '{path}' exposes no readable {VariantTagProperty} property, so its wire " + + "constructor cannot be determined; pass a generated variant."); + + private static NotSupportedException InterfaceMarkerTarget(Type interfaceType, string path) => + new($"Type '{interfaceType}' at '{path}' is a Daml interface marker, which has no wire record of its own; " + + "read the interface's view type instead."); + + private static NotSupportedException NotAGeneratedRecord(Type recordType, string path) => + new($"Type '{recordType}' at '{path}' is not a generated Daml record; " + + $"pass a concrete type implementing {nameof(IDamlRecord)} whose properties carry {nameof(DamlFieldAttribute)}."); + + private static NotSupportedException UnmappedClrType(Type clrType, string path) => + new($"CLR type '{clrType}' at '{path}' lies outside the Daml type mapping; " + + "give the property a mapped Daml type or decode this field without the reader."); +} diff --git a/src/Daml.Runtime/Serialization/OpaqueStringIdJsonConverter.cs b/src/Daml.Runtime/Serialization/OpaqueStringIdJsonConverter.cs new file mode 100644 index 0000000..aa7d279 --- /dev/null +++ b/src/Daml.Runtime/Serialization/OpaqueStringIdJsonConverter.cs @@ -0,0 +1,80 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Daml.Runtime.Serialization; + +/// +/// Shared behaviour for the Daml identity structs that travel +/// as a bare JSON string — an opaque, non-empty identifier the wrapper never decomposes. +/// One implementation so the family cannot drift apart on its null posture or on the +/// diagnostics it produces for a malformed id. +/// +/// The identity struct, constructed from and rendered as one string. +internal abstract class OpaqueStringIdJsonConverter : JsonConverter + where TId : struct +{ + private static readonly string TypeName = typeof(TId).Name; + + /// + /// The family's posture on null: a JSON null is rejected wherever the declared type forbids + /// one and accepted wherever it permits one. Handling null here is what turns a bare + /// null on a non-nullable field into a naming the type, + /// rather than a default value that only throws when something later reads it. + /// fields are unaffected: the serializer short-circuits null for + /// them before the converter runs. The reference-type member of the family, + /// , cannot mirror this and reaches the + /// same posture through . + /// + public override bool HandleNull => true; + + /// Builds the identity value from its wire string, which is never empty or whitespace. + /// The string is not a well-formed identifier. + protected abstract TId Parse(string id); + + /// Extracts the wire string from an identity value. + /// The value is uninitialized. + protected abstract string Format(TId value); + + /// + public override TId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException($"Expected string token for {TypeName}, got {reader.TokenType}."); + } + + var id = reader.GetString()!; + if (string.IsNullOrWhiteSpace(id)) + { + throw new JsonException($"{TypeName} id cannot be empty or whitespace; got '{id}'."); + } + + try + { + return Parse(id); + } + catch (ArgumentException ex) + { + throw new JsonException($"Invalid {TypeName} '{id}'.", ex); + } + } + + /// + public override void Write(Utf8JsonWriter writer, TId value, JsonSerializerOptions options) + { + string id; + try + { + id = Format(value); + } + catch (InvalidOperationException ex) + { + throw new JsonException($"Cannot serialize an uninitialized {TypeName}.", ex); + } + + writer.WriteStringValue(id); + } +} diff --git a/src/Daml.Runtime/Stdlib/Either.cs b/src/Daml.Runtime/Stdlib/Either.cs index 37395f7..5020093 100644 --- a/src/Daml.Runtime/Stdlib/Either.cs +++ b/src/Daml.Runtime/Stdlib/Either.cs @@ -10,7 +10,13 @@ namespace Daml.Runtime.Stdlib; /// /// Type carried by the constructor. /// Type carried by the constructor. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Either<...>.FromValue, mirroring the Daml constructor it decodes.")] public abstract record Either + where TL : notnull + where TR : notnull { private Either() { diff --git a/src/Daml.Runtime/Stdlib/Map.cs b/src/Daml.Runtime/Stdlib/Map.cs index ea883b4..daa87a7 100644 --- a/src/Daml.Runtime/Stdlib/Map.cs +++ b/src/Daml.Runtime/Stdlib/Map.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Runtime.Contracts; using Daml.Runtime.Data; namespace Daml.Runtime.Stdlib; @@ -31,8 +32,28 @@ namespace Daml.Runtime.Stdlib; /// /// Key type. /// Value type. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Map<...>.FromRecord, mirroring the Daml constructor it decodes.")] public sealed record Map(IReadOnlyList> Entries) + where TKey : notnull + where TValue : notnull { + private readonly IReadOnlyList> _entries = + EventCollections.Copy(Entries, nameof(Entries)); + + /// + /// The map's entries, in wire order. Copied at construction and on init, so a + /// producer that retains the list it supplied cannot change this value's equality or + /// hash code afterwards. + /// + public IReadOnlyList> Entries + { + get => _entries; + init => _entries = EventCollections.Copy(value, nameof(Entries)); + } + /// The number of entries in the map. public int Count => Entries.Count; @@ -75,4 +96,23 @@ public static Map FromRecord( .ToList(); return new Map(entries); } + + /// + /// Compares two maps entry by entry, in entry order. The record-synthesized equality + /// compares the backing by reference — a footgun for a + /// value type — so we override it with structural comparison. + /// + public bool Equals(Map? other) => + other is not null && Entries.SequenceEqual(other.Entries); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + foreach (var entry in Entries) + { + hash.Add(entry); + } + return hash.ToHashCode(); + } } diff --git a/src/Daml.Runtime/Stdlib/NonEmpty.cs b/src/Daml.Runtime/Stdlib/NonEmpty.cs index 5b63a3a..f55e412 100644 --- a/src/Daml.Runtime/Stdlib/NonEmpty.cs +++ b/src/Daml.Runtime/Stdlib/NonEmpty.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Runtime.Contracts; using Daml.Runtime.Data; namespace Daml.Runtime.Stdlib; @@ -25,8 +26,26 @@ namespace Daml.Runtime.Stdlib; /// /// /// Element type. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as NonEmpty<...>.FromRecord, mirroring the Daml constructor it decodes.")] public sealed record NonEmpty(T Hd, IReadOnlyList Tl) + where T : notnull { + private readonly IReadOnlyList _tl = EventCollections.Copy(Tl, nameof(Tl)); + + /// + /// The elements after . Copied at construction and on init, so a + /// producer that retains the list it supplied cannot change this value's equality or + /// hash code afterwards. + /// + public IReadOnlyList Tl + { + get => _tl; + init => _tl = EventCollections.Copy(value, nameof(Tl)); + } + /// /// All elements: first, followed by every element of . /// @@ -67,4 +86,28 @@ public static NonEmpty FromRecord(DamlRecord record, Func conve .ToList(); return new NonEmpty(hd, tl); } + + /// + /// Compares two non-empty lists by head and by tail content, in order. The + /// record-synthesized equality compares the backing + /// by reference — a footgun for a value type — so we override it with structural + /// comparison. + /// + public bool Equals(NonEmpty? other) => + other is not null + && EqualityComparer.Default.Equals(Hd, other.Hd) + && Tl.SequenceEqual(other.Tl); + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(Hd); + hash.Add(Tl.Count); + foreach (var item in Tl) + { + hash.Add(item); + } + return hash.ToHashCode(); + } } diff --git a/src/Daml.Runtime/Stdlib/Optional.cs b/src/Daml.Runtime/Stdlib/Optional.cs new file mode 100644 index 0000000..a36eee5 --- /dev/null +++ b/src/Daml.Runtime/Stdlib/Optional.cs @@ -0,0 +1,192 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics.CodeAnalysis; +using Daml.Runtime.Data; + +namespace Daml.Runtime.Stdlib; + +/// +/// A Daml Optional a in the positions where C# nullable syntax cannot carry it — +/// over a type parameter, as a type argument to a generated generic, as a +/// GenMap key, or nested inside another Optional. Elsewhere an Optional is emitted +/// as t?. +/// +/// The carried type. +[SuppressMessage( + "Naming", + "CA1716:Identifiers should not match keywords", + Justification = "Optional is the Daml stdlib type this represents, and the name is the user-facing half of the runtime's wire/user split alongside DamlOptional; the conflict is with a Visual Basic keyword, and no consumer of this package targets Visual Basic.")] +[SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Optional<...>.FromValue, mirroring the Daml constructor it decodes.")] +public abstract record Optional + where T : notnull +{ + private protected Optional() { } + + /// True when this optional is ; false when it is . + public abstract bool HasValue { get; } + + /// + /// Applies the handler for this optional's arm and returns its result. + /// + /// + /// Every arm is a parameter, so adding one changes this signature and a consumer that + /// projects through it stops compiling instead of falling through a default branch. A + /// switch cannot offer that: C# treats a class hierarchy as open, so a switch + /// expression covering both arms is still non-exhaustive (CS8509) and needs a discard + /// arm that would silently swallow a new one. + /// + /// The projection's result type. + /// Handler for , receiving its carried value. + /// Handler for . + /// The selected handler's result. + /// Either handler is . + public abstract TResult Match(Func some, Func none); + + /// The carried value, or when . + /// + /// A single ToNullable() returning T? is not expressible: it would need + /// where T : class and where T : struct overloads, and C# forbids overloads + /// differing only by type-parameter constraints. + /// + /// The carried value, or . + public abstract T? GetValueOrDefault(); + + /// The carried value. + /// The carried value. + /// This optional is . + public abstract T GetValueOrThrow(); + + /// Attempts to read the carried value. + /// The carried value when this optional is . + /// when this optional is . + public abstract bool TryGetValue([MaybeNullWhen(false)] out T value); + + /// + /// Converts this value to its wire representation, in the flat + /// null-or-value encoding a Daml Optional a carries wherever a is + /// not itself an Optional. + /// + /// Converter for the carried value. + /// The wire value. + /// is . + public DamlValue ToValue(Func convert) + { + ArgumentNullException.ThrowIfNull(convert); + return Match( + value => DamlOptional.Some(convert(value)), + () => DamlOptional.None); + } + + /// + /// Reconstructs an from its flat wire + /// representation. + /// + /// The wire value. + /// Converter for the carried value. + /// The reconstructed value. + /// Any argument is . + public static Optional FromValue(DamlValue value, Func convert) + { + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(convert); + var optional = value.As(); + return optional.Value is null ? new None() : new Some(convert(optional.Value)); + } + + /// + /// Converts this value to its wire representation in the array + /// encoding every level of a nested Optional chain carries. + /// + /// Converter for the carried value. + /// The wire value. + /// is . + public DamlValue ToChainValue(Func convert) + { + ArgumentNullException.ThrowIfNull(convert); + return Match( + value => DamlOptionalChain.Some(convert(value)), + () => DamlOptionalChain.None); + } + + /// + /// Reconstructs an from the array encoding a nested Optional + /// chain carries. + /// + /// The wire value. + /// Converter for the carried value. + /// The reconstructed value. + /// Any argument is . + public static Optional FromChainValue(DamlValue value, Func convert) + { + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(convert); + var chain = value.As(); + return chain.Value is null ? new None() : new Some(convert(chain.Value)); + } + + /// + /// The present arm, mirroring Daml's Some. + /// + /// The carried value. + public sealed record Some(T Value) : Optional + { + /// + public override bool HasValue => true; + + /// + public override TResult Match(Func some, Func none) + { + ArgumentNullException.ThrowIfNull(some); + ArgumentNullException.ThrowIfNull(none); + return some(Value); + } + + /// + public override T? GetValueOrDefault() => Value; + + /// + public override T GetValueOrThrow() => Value; + + /// + public override bool TryGetValue([MaybeNullWhen(false)] out T value) + { + value = Value; + return true; + } + } + + /// + /// The absent arm, mirroring Daml's None. + /// + public sealed record None : Optional + { + /// + public override bool HasValue => false; + + /// + public override TResult Match(Func some, Func none) + { + ArgumentNullException.ThrowIfNull(some); + ArgumentNullException.ThrowIfNull(none); + return none(); + } + + /// + public override T? GetValueOrDefault() => default; + + /// + public override T GetValueOrThrow() => + throw new InvalidOperationException("Optional value is None."); + + /// + public override bool TryGetValue([MaybeNullWhen(false)] out T value) + { + value = default; + return false; + } + } +} diff --git a/src/Daml.Runtime/Stdlib/RelTime.cs b/src/Daml.Runtime/Stdlib/RelTime.cs index eadb123..c035680 100644 --- a/src/Daml.Runtime/Stdlib/RelTime.cs +++ b/src/Daml.Runtime/Stdlib/RelTime.cs @@ -10,7 +10,7 @@ namespace Daml.Runtime.Stdlib; /// Hand-coded into Daml.Runtime since daml-stdlib-DA-Time-Types 1.0.0 is a frozen /// stdlib package (no NuGet equivalent) referenced by many Splice DARs. /// -public sealed record RelTime(long Microseconds) : IDamlRecord +public sealed record RelTime(long Microseconds) : IDamlRecord { /// Encodes this RelTime as the single-field microseconds wire record. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/src/Daml.Runtime/Stdlib/Set.cs b/src/Daml.Runtime/Stdlib/Set.cs index 4da39b4..bcecc9d 100644 --- a/src/Daml.Runtime/Stdlib/Set.cs +++ b/src/Daml.Runtime/Stdlib/Set.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Runtime.Contracts; using Daml.Runtime.Data; namespace Daml.Runtime.Stdlib; @@ -28,9 +29,18 @@ namespace Daml.Runtime.Stdlib; [System.Diagnostics.CodeAnalysis.SuppressMessage( "Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches the Daml stdlib type name DA.Set.Types.Set so the codegen-emitted type name resolves directly. Renaming would force the codegen to learn an additional translation.")] +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Set<...>.FromRecord, mirroring the Daml constructor it decodes.")] public sealed record Set + where T : notnull { - /// The set's elements, deduplicated under default CLR equality. + /// + /// The set's elements, deduplicated under default CLR equality. Materialized at + /// construction, so a producer that retains the collection it supplied cannot change + /// this value's equality or hash code afterwards. + /// public IReadOnlySet Elements { get; } /// @@ -41,8 +51,7 @@ public sealed record Set /// public Set(IEnumerable elements) { - ArgumentNullException.ThrowIfNull(elements); - Elements = elements as IReadOnlySet ?? elements.ToHashSet(); + Elements = EventCollections.Copy(elements, nameof(elements)); } /// The number of elements in the set. @@ -88,4 +97,24 @@ public static Set FromRecord(DamlRecord record, Func convertEle var elements = map.Entries.Select(entry => convertElement(entry.Key)); return new Set(elements); } + + /// + /// Compares two sets by element content, independent of iteration order. The + /// record-synthesized equality compares the backing + /// by reference — a footgun for a value type — so we override it with structural + /// comparison. + /// + public bool Equals(Set? other) => + other is not null && Elements.SetEquals(other.Elements); + + /// + public override int GetHashCode() + { + var hash = 0; + foreach (var element in Elements) + { + hash ^= EqualityComparer.Default.GetHashCode(element); + } + return hash; + } } diff --git a/src/Daml.Runtime/Stdlib/Tuple.cs b/src/Daml.Runtime/Stdlib/Tuple.cs index b4e22e3..182dab5 100644 --- a/src/Daml.Runtime/Stdlib/Tuple.cs +++ b/src/Daml.Runtime/Stdlib/Tuple.cs @@ -27,7 +27,17 @@ namespace Daml.Runtime.Stdlib; /// /// Type of the first component. /// Type of the second component. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Tuple2<...>.FromRecord.")] +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1707:Identifiers should not contain underscores", + Justification = "_1, _2 and _3 are the field names Daml-LF gives tuple components; renaming them would break the record round-trip the wire format is keyed on.")] public sealed record Tuple2(T1 _1, T2 _2) + where T1 : notnull + where T2 : notnull { /// /// Converts this tuple to its Ledger API record representation. The supplied @@ -71,7 +81,18 @@ public static Tuple2 FromRecord( /// Type of the first component. /// Type of the second component. /// Type of the third component. +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1000:Do not declare static members on generic types", + Justification = "The static factory is the wire-decoding entry point for this Daml stdlib shape; generated code calls it as Tuple3<...>.FromRecord.")] +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Naming", + "CA1707:Identifiers should not contain underscores", + Justification = "_1, _2 and _3 are the field names Daml-LF gives tuple components; renaming them would break the record round-trip the wire format is keyed on.")] public sealed record Tuple3(T1 _1, T2 _2, T3 _3) + where T1 : notnull + where T2 : notnull + where T3 : notnull { /// /// Converts this tuple to its Ledger API record representation. diff --git a/src/Daml.Runtime/Streams/AcsSnapshotEntry.cs b/src/Daml.Runtime/Streams/AcsSnapshotEntry.cs index 000cc90..f8b74c9 100644 --- a/src/Daml.Runtime/Streams/AcsSnapshotEntry.cs +++ b/src/Daml.Runtime/Streams/AcsSnapshotEntry.cs @@ -3,23 +3,26 @@ using Daml.Runtime.Contracts; using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; namespace Daml.Runtime.Streams; /// /// One entry in an active-contract-set snapshot. The snapshot yields -/// rows and an row for anything -/// the projector cannot classify, then ends with a single terminal -/// — emitted even when the snapshot is empty — or, when -/// the transport faults mid-snapshot, a terminal in -/// place of that . +/// rows and an row for anything the +/// projector cannot classify, then ends with a single terminal +/// — emitted even when the snapshot is empty — or, when the +/// transport faults mid-snapshot, a terminal in place of that +/// . /// /// -/// The Daml marker the snapshot is filtered to: a template (matched by -/// TemplateId) or a Daml interface marker (matched by interface id). +/// The Daml template the snapshot is filtered to, matched by TemplateId. A +/// snapshot filtered to a Daml interface marker yields +/// instead, whose payload is +/// the interface's view record. /// public abstract record AcsSnapshotEntry - where T : IDamlType + where T : ITemplate, IDamlRecord { /// Sealed; new variants live alongside the existing ones. private protected AcsSnapshotEntry() { } @@ -28,25 +31,95 @@ private protected AcsSnapshotEntry() { } /// An active contract in the snapshot. /// /// The on-ledger contract ID. - /// The deserialized create-arguments record. - /// The ledger offset at which the contract was created. + /// The create-arguments, decoded into . + /// The contract key read off the created event, or null when the + /// event carried none. Stays wire-level even though is decoded: + /// the key's type is the template's key type, not , so decoding it + /// is the consumer's TKey.FromRecord(Key.Value.As<DamlRecord>()) hop. + /// The ledger offset at which the contract was created — a + /// per-contract fact, not the snapshot's position. It is not a resume point: a consumer + /// persisting resume state must take it from the terminal 's + /// ticket, never from a per-row offset. /// The synchronizer the contract is active on. /// Parties that witnessed the create event. public sealed record Created( ContractId ContractId, - DamlRecord Payload, + T Payload, + ContractKey? Key, LedgerOffset Offset, SynchronizerId SynchronizerId, - IReadOnlyList WitnessParties) : AcsSnapshotEntry; + IReadOnlyList WitnessParties) : AcsSnapshotEntry + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the create event. Held as the producer supplied it, not + /// copied — an is a read-only view, so a caller that + /// retains its backing list must not mutate it after construction. Rejected at + /// construction and on init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// A snapshot row the projector could not classify; surfaced, never dropped. + /// Carries the same discriminator pair as + /// , so a consumer handling both the + /// snapshot and the live stream switches on one + /// vocabulary rather than on magic strings here and an enum there. /// - /// The ledger offset at which the unrecognized row occurred. - /// A short description of the unrecognized row, for logging/diagnostics. + /// The ledger offset at which the unrecognized row occurred, or + /// null when the row could not be placed on the ledger at all — typically because + /// the wire offset itself was absent or unparseable. A consumer persisting resume state + /// must not checkpoint a null offset: has no absent value + /// and its default is , a genuine ledger position, + /// so substituting one resumes from the beginning of the ledger and re-reads the whole + /// stream. Skip the row and keep the last offset that was real. + /// Why the row could not be classified, as a strongly-typed + /// discriminator consumers switch on. means + /// the transport delivered a row this layer does not recognise; the raw descriptor is + /// then on . + /// The transport's raw descriptor for the unrecognized row. + /// Non-null exactly when is + /// , and null for every enumerated reason, so + /// a consumer never sees a stale descriptor attached to a named kind. + /// is + /// with a null , or + /// an enumerated with a non-null . public sealed record Unclassified( - LedgerOffset Offset, - string Kind) : AcsSnapshotEntry; + LedgerOffset? Offset, + UnclassifiedKind Kind, + string? RawKind = null) : AcsSnapshotEntry + { + /// + /// Why the row could not be classified, as a strongly-typed discriminator consumers + /// switch on. Get-only, so a with expression cannot reassign it + /// independently of . + /// + public UnclassifiedKind Kind { get; } = Kind; + + /// + /// The transport's raw descriptor for the unrecognized row — non-null exactly + /// when is , and null + /// otherwise. Get-only, so the invariant validated at construction cannot be bypassed + /// by a with expression. + /// + public string? RawKind { get; } = (Kind, RawKind) switch + { + (UnclassifiedKind.Unknown, null) => throw new ArgumentException( + "An Unclassified snapshot row with Kind Unknown must carry the transport's raw descriptor in RawKind.", + nameof(RawKind)), + (not UnclassifiedKind.Unknown, not null) => throw new ArgumentException( + $"An Unclassified snapshot row with the enumerated Kind '{Kind}' must not carry a RawKind; RawKind is populated only for Unknown.", + nameof(RawKind)), + _ => RawKind, + }; + } /// /// The single terminal marker that always ends the snapshot stream — emitted @@ -78,7 +151,12 @@ public sealed record Checkpoint(StakeholderResume Resume) : AcsSnapshotEntry; /// want the typed enum cast back. Held as int so this type stays free /// of any transport-library dep. /// Status detail / message from the participant or transport. + /// Classification of the transport failure when the transport could determine + /// one without a structured Canton error attached; null when the failure was not classified. + /// Transport exception that caused the stream failure, when available. public sealed record StreamError( int StatusCode, - string Message) : AcsSnapshotEntry; + string Message, + DamlErrorCategory? Category = null, + Exception? SourceException = null) : AcsSnapshotEntry; } diff --git a/src/Daml.Runtime/Streams/AcsSnapshotEntryExtensions.cs b/src/Daml.Runtime/Streams/AcsSnapshotEntryExtensions.cs new file mode 100644 index 0000000..3441bc4 --- /dev/null +++ b/src/Daml.Runtime/Streams/AcsSnapshotEntryExtensions.cs @@ -0,0 +1,47 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; + +namespace Daml.Runtime.Streams; + +/// +/// Projects an active-contract-set snapshot row into the shape the rest of the runtime +/// speaks. A snapshot row's create-arguments arrive already decoded into the template +/// type, so no decoding helper is needed here. +/// +public static class AcsSnapshotEntryExtensions +{ + /// + /// Projects a snapshot row to the keyless shape — the same + /// pairing Contract<T>.FromCreatedEvent produces on the write path. The row's + /// wire-level key is not carried: a keyed template's snapshot goes through + /// , which decodes it. + /// + /// is null. + public static Contract ToContract(this AcsSnapshotEntry.Created created) + where T : ITemplate, IDamlRecord + { + ArgumentNullException.ThrowIfNull(created); + return new Contract(created.ContractId, created.Payload); + } + + /// + /// Projects a snapshot row to the keyed shape, decoding the + /// row's wire-level key through the template's + /// witness and carrying the ledger's hash of it. + /// + /// is null. + /// The row carried no contract key, so the keyed + /// shape cannot be populated. + public static Contract ToContract(this AcsSnapshotEntry.Created created) + where T : ITemplate, IDamlRecord, IHasKey + { + ArgumentNullException.ThrowIfNull(created); + return new Contract( + created.ContractId, + created.Payload, + Contract.DecodeKey(created.Key, created.ContractId.Value)); + } +} diff --git a/src/Daml.Runtime/Streams/ContractStreamEvent.cs b/src/Daml.Runtime/Streams/ContractStreamEvent.cs index 86ac199..a90a756 100644 --- a/src/Daml.Runtime/Streams/ContractStreamEvent.cs +++ b/src/Daml.Runtime/Streams/ContractStreamEvent.cs @@ -3,6 +3,7 @@ using Daml.Runtime.Contracts; using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; namespace Daml.Runtime.Streams; @@ -14,8 +15,10 @@ namespace Daml.Runtime.Streams; /// these without dragging the consumer into a specific transport dependency. /// /// -/// The Daml marker the stream is filtered to: a template (matched by -/// TemplateId) or a Daml interface marker (matched by interface id). +/// The Daml template the stream is filtered to, matched by TemplateId. A +/// subscription filtered to a Daml interface marker yields +/// instead, whose payload is the +/// interface's view record. /// /// /// @@ -41,7 +44,13 @@ namespace Daml.Runtime.Streams; /// consumers persist via to advance the /// resume offset during quiet periods (no template-matching transactions /// arriving), avoiding the re-process-from-stale-offset failure mode after -/// a crash. Active-contract-set snapshots stream +/// a crash. Not every variant's offset is safe to persist: an +/// event carries a nullable +/// , and null there means the event +/// has no ledger position at all. Skip it and keep the last offset that was +/// real — substituting checkpoints at the +/// beginning of the ledger and re-reads the entire stream. +/// Active-contract-set snapshots stream /// instead of this type; their terminal /// marker is . /// — the transport stream failed mid-flight. @@ -89,7 +98,7 @@ namespace Daml.Runtime.Streams; /// /// public abstract record ContractStreamEvent - where T : IDamlType + where T : ITemplate, IDamlRecord { /// Sealed; new variants live alongside the existing ones. private protected ContractStreamEvent() { } @@ -98,7 +107,11 @@ private protected ContractStreamEvent() { } /// A contract of type was created. /// /// The on-ledger contract ID. - /// The deserialized create-arguments record. + /// The create-arguments, decoded into . + /// The contract key read off the created event, or null when the + /// event carried none. Separate from because the key is its own + /// wire field, not a projection of the create-arguments, and its type is the template's key + /// type rather than . /// The ledger offset at which the contract was /// created. Strictly increasing per synchronizer; suitable for use as /// the resume offset on a subsequent subscription (exclusive). @@ -106,10 +119,27 @@ private protected ContractStreamEvent() { } /// Parties that witnessed the create event. public sealed record Created( ContractId ContractId, - DamlRecord Payload, + T Payload, + ContractKey? Key, LedgerOffset Offset, SynchronizerId SynchronizerId, - IReadOnlyList WitnessParties) : ContractStreamEvent; + IReadOnlyList WitnessParties) : ContractStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the create event. Held as the producer supplied it, not + /// copied — an is a read-only view, so a caller that + /// retains its backing list must not mutate it after construction. Rejected at + /// construction and on init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// A contract of type was archived. Emitted only on @@ -127,7 +157,21 @@ public sealed record Archived( ContractId ContractId, LedgerOffset Offset, SynchronizerId SynchronizerId, - IReadOnlyList WitnessParties) : ContractStreamEvent; + IReadOnlyList WitnessParties) : ContractStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the archive event. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// A choice was exercised on a contract of type . @@ -154,7 +198,21 @@ public sealed record Exercised( bool Consuming, LedgerOffset Offset, SynchronizerId SynchronizerId, - IReadOnlyList WitnessParties) : ContractStreamEvent; + IReadOnlyList WitnessParties) : ContractStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the exercise event. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// A contract of type was assigned to a @@ -164,7 +222,12 @@ public sealed record Exercised( /// rebuilding state from a single stream stay correct. /// /// The on-ledger contract ID. - /// The contract's create-arguments, re-emitted on assignment. + /// The contract's create-arguments, re-emitted on assignment and + /// decoded into . + /// The contract key read off the assigned event's created contract, or + /// null when it carried none. Canton's assignment wraps the whole created contract, + /// so the key is on the wire here exactly as it is on a create; a consumer rebuilding + /// state from one stream would otherwise lose the key at every reassignment. /// The ledger offset of the assignment. /// The synchronizer the contract was reassigned from. /// The synchronizer the contract was reassigned to. @@ -176,13 +239,28 @@ public sealed record Exercised( /// Parties that witnessed the assignment. public sealed record Assigned( ContractId ContractId, - DamlRecord Payload, + T Payload, + ContractKey? Key, LedgerOffset Offset, SynchronizerId Source, SynchronizerId Target, string ReassignmentId, long ReassignmentCounter, - IReadOnlyList WitnessParties) : ContractStreamEvent; + IReadOnlyList WitnessParties) : ContractStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the assignment. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// A contract of type was unassigned from a @@ -205,7 +283,21 @@ public sealed record Unassigned( SynchronizerId Target, string ReassignmentId, long ReassignmentCounter, - IReadOnlyList WitnessParties) : ContractStreamEvent; + IReadOnlyList WitnessParties) : ContractStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the unassignment. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } /// /// An offset checkpoint with no contract payload: on a live update @@ -247,9 +339,14 @@ public sealed record Checkpoint(LedgerOffset Offset) : ContractStreamEvent; /// that want the typed enum cast back. Held as int so this type /// stays free of any transport-library dep. /// Status detail / message from the participant or transport. + /// Classification of the transport failure when the transport could determine + /// one without a structured Canton error attached; null when the failure was not classified. + /// Transport exception that caused the stream failure, when available. public sealed record StreamError( int StatusCode, - string Message) : ContractStreamEvent; + string Message, + DamlErrorCategory? Category = null, + Exception? SourceException = null) : ContractStreamEvent; /// /// An event the transport delivered but this layer could not map to any @@ -258,7 +355,13 @@ public sealed record StreamError( /// transport-agnostic Daml.Runtime layer, so no raw wire bytes /// are available to attach here. /// - /// The ledger offset at which the unrecognized event occurred. + /// The ledger offset at which the unrecognized event occurred, or + /// null when the event could not be placed on the ledger at all — typically because + /// the wire offset itself was absent or unparseable. A consumer persisting resume state + /// must not checkpoint a null offset: has no absent value + /// and its default is , a genuine ledger position, + /// so substituting one resumes from the beginning of the ledger and re-reads the whole + /// stream. Skip the event and keep the last offset that was real. /// Why the event could not be mapped to a typed variant, as a /// strongly-typed discriminator consumers switch on. /// means the transport delivered a variant this layer does not recognise; the raw @@ -274,7 +377,7 @@ public sealed record StreamError( /// with a null , or an /// enumerated with a non-null . public sealed record Unclassified( - LedgerOffset Offset, + LedgerOffset? Offset, UnclassifiedKind Kind, string? RawKind = null) : ContractStreamEvent { diff --git a/src/Daml.Runtime/Streams/InterfaceAcsSnapshotEntry.cs b/src/Daml.Runtime/Streams/InterfaceAcsSnapshotEntry.cs new file mode 100644 index 0000000..d025513 --- /dev/null +++ b/src/Daml.Runtime/Streams/InterfaceAcsSnapshotEntry.cs @@ -0,0 +1,167 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; + +namespace Daml.Runtime.Streams; + +/// +/// One entry in an active-contract-set snapshot filtered to the Daml interface +/// . The interface-family counterpart of +/// : identical variants and fields, but the payload is +/// the interface's server-computed view record rather than +/// the implementing template's own record, which an interface subscription never sees. +/// The snapshot yields rows and an row +/// for anything the projector cannot classify, then ends with a single terminal +/// — emitted even when the snapshot is empty — or, when the +/// transport faults mid-snapshot, a terminal in place of that +/// . +/// +/// The Daml interface marker the snapshot is filtered to, +/// matched by interface id. +/// The interface's view record type, carried as the payload. +/// +/// Call sites do not name the pair: they pass the marker's generated +/// witness to +/// ILedgerStreamer.SubscribeActiveAsync and both type parameters are inferred from +/// it. A row that arrives without an interface view is surfaced as +/// with +/// . +/// +public abstract record InterfaceAcsSnapshotEntry + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord +{ + /// Sealed; new variants live alongside the existing ones. + private protected InterfaceAcsSnapshotEntry() { } + + /// + /// An active contract implementing in the snapshot. + /// + /// The on-ledger contract ID, interface-typed. + /// The interface view, decoded into . + /// The contract key read off the created event, or null when the + /// event carried none. Stays wire-level even though is decoded: + /// the key's type is the implementing template's key type, not + /// , so decoding it is the consumer's + /// TKey.FromRecord(Key.Value.As<DamlRecord>()) hop. + /// The ledger offset at which the contract was created — a + /// per-contract fact, not the snapshot's position. It is not a resume point: a consumer + /// persisting resume state must take it from the terminal 's + /// ticket, never from a per-row offset. + /// The synchronizer the contract is active on. + /// Parties that witnessed the create event. + public sealed record Created( + ContractId ContractId, + TView Payload, + ContractKey? Key, + LedgerOffset Offset, + SynchronizerId SynchronizerId, + IReadOnlyList WitnessParties) : InterfaceAcsSnapshotEntry + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the create event. Held as the producer supplied it, not + /// copied — an is a read-only view, so a caller that + /// retains its backing list must not mutate it after construction. Rejected at + /// construction and on init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// A snapshot row the projector could not classify; surfaced, never dropped. Carries the + /// same discriminator pair as + /// , so a consumer + /// handling both the snapshot and the live stream switches on one + /// vocabulary. + /// + /// The ledger offset at which the unrecognized row occurred, or + /// null when the row could not be placed on the ledger at all. A consumer + /// persisting resume state must not checkpoint a null offset: + /// has no absent value and its default is + /// , a genuine ledger position, so substituting one + /// resumes from the beginning of the ledger and re-reads the whole stream. Skip the row + /// and keep the last offset that was real. + /// Why the row could not be classified, as a strongly-typed + /// discriminator consumers switch on. + /// The transport's raw descriptor for the unrecognized row — + /// non-null exactly when is + /// , and null for every enumerated reason. + /// is + /// with a null , or + /// an enumerated with a non-null . + public sealed record Unclassified( + LedgerOffset? Offset, + UnclassifiedKind Kind, + string? RawKind = null) : InterfaceAcsSnapshotEntry + { + /// + /// Why the row could not be classified, as a strongly-typed discriminator consumers + /// switch on. Get-only, so a with expression cannot reassign it + /// independently of . + /// + public UnclassifiedKind Kind { get; } = Kind; + + /// + /// The transport's raw descriptor for the unrecognized row — non-null exactly + /// when is , and null + /// otherwise. Get-only, so the invariant validated at construction cannot be bypassed + /// by a with expression. + /// + public string? RawKind { get; } = (Kind, RawKind) switch + { + (UnclassifiedKind.Unknown, null) => throw new ArgumentException( + "An Unclassified snapshot row with Kind Unknown must carry the transport's raw descriptor in RawKind.", + nameof(RawKind)), + (not UnclassifiedKind.Unknown, not null) => throw new ArgumentException( + $"An Unclassified snapshot row with the enumerated Kind '{Kind}' must not carry a RawKind; RawKind is populated only for Unknown.", + nameof(RawKind)), + _ => RawKind, + }; + } + + /// + /// The single terminal marker that always ends the snapshot stream — emitted even when + /// the snapshot is empty — carrying the snapshot's effective offset as a + /// ticket. + /// + /// The resume ticket for the snapshot's effective offset — pass it to + /// ILedgerStreamer.SubscribeAsync for a gapless, duplicate-free handover; that + /// subscription's lower bound is exclusive, so the event at this offset is not re-delivered. + /// The raw offset is reachable via . + public sealed record Checkpoint(StakeholderResume Resume) : InterfaceAcsSnapshotEntry; + + /// + /// The transport stream failed mid-snapshot. Surfaced in-band rather than thrown so a + /// caller draining the snapshot with await foreach can decide policy. + /// + /// + /// Terminal, and mutually exclusive with : a faulted snapshot ends + /// with this entry instead of the a successful snapshot ends + /// with, so no snapshot offset is available to hand over to a live subscription and the + /// caller must treat the snapshot as incomplete. + /// + /// Transport status code from the failed call. For gRPC streams + /// this is (int)Grpc.Core.StatusCode; consumers that want the typed enum cast back. + /// Held as int so this type stays free of any transport-library dep. + /// Status detail / message from the participant or transport. + /// Classification of the transport failure when the transport could + /// determine one without a structured Canton error attached; null when the failure + /// was not classified. + /// Transport exception that caused the stream failure, when + /// available. + public sealed record StreamError( + int StatusCode, + string Message, + DamlErrorCategory? Category = null, + Exception? SourceException = null) : InterfaceAcsSnapshotEntry; +} diff --git a/src/Daml.Runtime/Streams/InterfaceStreamEvent.cs b/src/Daml.Runtime/Streams/InterfaceStreamEvent.cs new file mode 100644 index 0000000..6d12d2e --- /dev/null +++ b/src/Daml.Runtime/Streams/InterfaceStreamEvent.cs @@ -0,0 +1,315 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; + +namespace Daml.Runtime.Streams; + +/// +/// A typed event observed on a subscription stream over the Daml interface +/// . The interface-family counterpart of +/// : identical variants and fields, but the payload +/// is the interface's server-computed view record rather +/// than the implementing template's own record, which an interface subscription never +/// sees. +/// +/// The Daml interface marker the stream is filtered to, +/// matched by interface id. +/// The interface's view record type, carried as the payload. +/// +/// +/// Call sites do not name the pair: they pass the marker's generated +/// witness to +/// ILedgerStreamer.SubscribeAsync and both type parameters are inferred from it. +/// +/// +/// A matching event that arrives without an interface view — the view is computed by the +/// participant, so it can be absent — is surfaced as +/// with +/// rather than dropped, since +/// there is no view record to decode into . +/// +/// +/// Which variants a stream yields is a property of its transaction shape, exactly as for +/// : the ACS-delta shape emits +/// and never , the ledger-effects shape +/// emits (a consuming exercise being the archival signal) and +/// never , and an active-contract-set snapshot streams +/// instead of this type. +/// +/// +public abstract record InterfaceStreamEvent + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord +{ + /// Sealed; new variants live alongside the existing ones. + private protected InterfaceStreamEvent() { } + + /// + /// A contract implementing was created. + /// + /// The on-ledger contract ID, interface-typed. + /// The interface view, decoded into . + /// The contract key read off the created event, or null when the + /// event carried none. Separate from because the key is its own + /// wire field, not a projection of the view, and its type is the implementing template's + /// key type rather than . + /// The ledger offset at which the contract was created. Strictly + /// increasing per synchronizer; suitable for use as the resume offset on a subsequent + /// subscription (exclusive). + /// The synchronizer the contract was created on. + /// Parties that witnessed the create event. + public sealed record Created( + ContractId ContractId, + TView Payload, + ContractKey? Key, + LedgerOffset Offset, + SynchronizerId SynchronizerId, + IReadOnlyList WitnessParties) : InterfaceStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the create event. Held as the producer supplied it, not + /// copied — an is a read-only view, so a caller that + /// retains its backing list must not mutate it after construction. Rejected at + /// construction and on init when null. + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// A contract implementing was archived. Emitted only + /// on ACS-delta-shaped streams; the ledger-effects subscription yields a consuming + /// instead. + /// + /// The on-ledger contract ID, interface-typed. + /// The ledger offset at which the contract was archived. + /// The synchronizer the contract was archived on. + /// Parties that witnessed the archive event. + public sealed record Archived( + ContractId ContractId, + LedgerOffset Offset, + SynchronizerId SynchronizerId, + IReadOnlyList WitnessParties) : InterfaceStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the archive event. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// A choice was exercised on a contract implementing . + /// Emitted only on the ledger-effects shape, where a consuming exercise + /// ( is true) is the contract's archival signal. + /// + /// The on-ledger contract ID the choice was exercised on. + /// The choice name. + /// The argument value passed to the choice. + /// The result returned by the choice. + /// Whether the exercise consumed (archived) the contract. + /// The ledger offset of the exercise. + /// The synchronizer the exercise occurred on. + /// Parties that witnessed the exercise event. + public sealed record Exercised( + ContractId ContractId, + string ChoiceName, + DamlValue ChoiceArgument, + DamlValue ExerciseResult, + bool Consuming, + LedgerOffset Offset, + SynchronizerId SynchronizerId, + IReadOnlyList WitnessParties) : InterfaceStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the exercise event. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// A contract implementing was assigned to a + /// synchronizer. The interface view is re-emitted so consumers rebuilding state from a + /// single stream stay correct. + /// + /// The on-ledger contract ID, interface-typed. + /// The interface view, re-emitted on assignment and decoded into + /// . + /// The contract key read off the assigned event's created contract, or + /// null when it carried none. + /// The ledger offset of the assignment. + /// The synchronizer the contract was reassigned from. + /// The synchronizer the contract was reassigned to. + /// The reassignment's unique id — the same value on the + /// paired unassignment and assignment, and the input to the completing assign command. + /// The reassignment counter shared by the paired + /// unassignment and assignment; consumers pair the two events (and dedup replays) by + /// matching this value. + /// Parties that witnessed the assignment. + public sealed record Assigned( + ContractId ContractId, + TView Payload, + ContractKey? Key, + LedgerOffset Offset, + SynchronizerId Source, + SynchronizerId Target, + string ReassignmentId, + long ReassignmentCounter, + IReadOnlyList WitnessParties) : InterfaceStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the assignment. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// A contract implementing was unassigned from a + /// synchronizer (the start of a reassignment). + /// + /// The on-ledger contract ID, interface-typed. + /// The ledger offset of the unassignment. + /// The synchronizer the contract is leaving. + /// The synchronizer the contract is moving to. + /// The reassignment's unique id — the same value on the + /// paired assignment, and the input to the assign command that completes the move. + /// The reassignment counter shared by the paired + /// assignment; consumers pair the two events (and dedup replays) by matching this value. + /// Parties that witnessed the unassignment. + public sealed record Unassigned( + ContractId ContractId, + LedgerOffset Offset, + SynchronizerId Source, + SynchronizerId Target, + string ReassignmentId, + long ReassignmentCounter, + IReadOnlyList WitnessParties) : InterfaceStreamEvent + { + private readonly IReadOnlyList _witnessParties = + EventCollections.Borrow(WitnessParties, nameof(WitnessParties)); + + /// + /// Parties that witnessed the unassignment. Held on the same terms as + /// . + /// + public IReadOnlyList WitnessParties + { + get => _witnessParties; + init => _witnessParties = EventCollections.Borrow(value, nameof(WitnessParties)); + } + } + + /// + /// An offset checkpoint with no contract payload: a participant-emitted marker with no + /// matching activity to surface, so consumers can advance their persisted resume offset + /// during quiet periods. + /// + /// The participant's current ledger offset; persist it as the resume + /// offset for a subsequent subscription. That subscription treats its lower bound as + /// exclusive, so resuming from this offset does not re-deliver any event already seen up + /// to it. + public sealed record Checkpoint(LedgerOffset Offset) : InterfaceStreamEvent; + + /// + /// The transport stream failed mid-flight. Surfaced in-band rather than thrown so callers + /// can decide policy — log and continue with a fresh stream from the last good offset, + /// terminate, etc. + /// + /// Transport status code from the failed call. For gRPC streams + /// this is (int)Grpc.Core.StatusCode; consumers that want the typed enum cast back. + /// Held as int so this type stays free of any transport-library dep. + /// Status detail / message from the participant or transport. + /// Classification of the transport failure when the transport could + /// determine one without a structured Canton error attached; null when the failure + /// was not classified. + /// Transport exception that caused the stream failure, when + /// available. + public sealed record StreamError( + int StatusCode, + string Message, + DamlErrorCategory? Category = null, + Exception? SourceException = null) : InterfaceStreamEvent; + + /// + /// An event the transport delivered but this layer could not map to any of the other + /// variants. Surfaced rather than silently dropped so consumers can honour a + /// no-silent-drop invariant. A matching event carrying no interface view lands here as + /// . + /// + /// The ledger offset at which the unrecognized event occurred, or + /// null when the event could not be placed on the ledger at all. A consumer + /// persisting resume state must not checkpoint a null offset: + /// has no absent value and its default is + /// , a genuine ledger position, so substituting one + /// resumes from the beginning of the ledger and re-reads the whole stream. Skip the event + /// and keep the last offset that was real. + /// Why the event could not be mapped to a typed variant, as a + /// strongly-typed discriminator consumers switch on. + /// The transport's raw descriptor for the unrecognized event — + /// non-null exactly when is + /// , and null for every enumerated reason. + /// is + /// with a null , or an + /// enumerated with a non-null . + public sealed record Unclassified( + LedgerOffset? Offset, + UnclassifiedKind Kind, + string? RawKind = null) : InterfaceStreamEvent + { + /// + /// Why the event could not be mapped to a typed variant, as a strongly-typed + /// discriminator consumers switch on. Get-only, so a with expression + /// cannot reassign it independently of . + /// + public UnclassifiedKind Kind { get; } = Kind; + + /// + /// The transport's raw descriptor for the unrecognized event — non-null exactly + /// when is , and null + /// otherwise. Get-only, so the invariant validated at construction cannot be bypassed by + /// a with expression. + /// + public string? RawKind { get; } = (Kind, RawKind) switch + { + (UnclassifiedKind.Unknown, null) => throw new ArgumentException( + "An Unclassified event with Kind Unknown must carry the transport's raw descriptor in RawKind.", + nameof(RawKind)), + (not UnclassifiedKind.Unknown, not null) => throw new ArgumentException( + $"An Unclassified event with the enumerated Kind '{Kind}' must not carry a RawKind; RawKind is populated only for Unknown.", + nameof(RawKind)), + _ => RawKind, + }; + } +} diff --git a/tests/Daml.Codegen.CSharp.Cli.Tests/CliErrorReportingTests.cs b/tests/Daml.Codegen.CSharp.Cli.Tests/CliErrorReportingTests.cs index 0409c7b..2cefcfa 100644 --- a/tests/Daml.Codegen.CSharp.Cli.Tests/CliErrorReportingTests.cs +++ b/tests/Daml.Codegen.CSharp.Cli.Tests/CliErrorReportingTests.cs @@ -29,7 +29,7 @@ public void Dispose() Path.Combine(AppContext.BaseDirectory, "Snapshots", FixtureSnapshotName, "intermediate.binpb"); [Fact] - public async Task failure_at_default_verbosity_also_logs_the_root_cause_when_it_differs_from_the_top_message() + public async Task CliErrorReporting_failure_at_default_verbosity_also_logs_the_root_cause_when_it_differs_from_the_top_message() { var corruptCounters = Path.Combine(_workspace, "release-counters.json"); await File.WriteAllTextAsync(corruptCounters, "{not json", TestContext.Current.CancellationToken); @@ -49,7 +49,7 @@ public async Task failure_at_default_verbosity_also_logs_the_root_cause_when_it_ } [Fact] - public async Task cancellation_warns_that_partially_written_files_may_remain_in_the_output_directory() + public async Task CliErrorReporting_cancellation_warns_that_partially_written_files_may_remain_in_the_output_directory() { using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -81,7 +81,7 @@ public async Task cancellation_warns_that_partially_written_files_may_remain_in_ } [Fact] - public async Task invalid_version_suffix_is_rejected_at_the_cli_boundary_with_a_clean_error() + public async Task CliErrorReporting_invalid_version_suffix_is_rejected_at_the_cli_boundary_with_a_clean_error() { var (exit, stderr) = await RunCapturingStdErr(() => Program.Main( [ diff --git a/tests/Daml.Codegen.CSharp.Cli.Tests/CliExitCodeTests.cs b/tests/Daml.Codegen.CSharp.Cli.Tests/CliExitCodeTests.cs index 6975e2b..ba3dbf6 100644 --- a/tests/Daml.Codegen.CSharp.Cli.Tests/CliExitCodeTests.cs +++ b/tests/Daml.Codegen.CSharp.Cli.Tests/CliExitCodeTests.cs @@ -26,6 +26,18 @@ public async Task Program_no_args_returns_exit_code_one() "--intermediate is a required option, so a no-args invocation must fail at parse time; an exact-value assertion catches SetAction overload misbinding (Task -> Task) that the looser !=0 check would miss if the int got coerced to 0"); } + [Theory] + [InlineData("--target-framework", "", "--target-framework rejects empty/whitespace-only strings at the CLI boundary; an empty TFM produces a broken .csproj that fails late with a confusing dotnet error")] + [InlineData("--target-framework", " ", "--target-framework rejects whitespace-only values at the CLI boundary")] + [InlineData("--runtime-version", "", "--runtime-version rejects empty/whitespace-only strings when explicitly supplied; an empty version string breaks the generated PackageReference attribute")] + [InlineData("--runtime-version", " ", "--runtime-version rejects whitespace-only values when explicitly supplied")] + public async Task Program_blank_option_value_returns_nonzero_exit_code( + string option, string value, string because) + { + var exit = await Program.Main([option, value]); + exit.Should().NotBe(0, because); + } + [Fact] public void Program_is_not_part_of_the_public_API_surface() { diff --git a/tests/Daml.Codegen.CSharp.Cli.Tests/CliReleaseCountersTests.cs b/tests/Daml.Codegen.CSharp.Cli.Tests/CliReleaseCountersTests.cs index 2b450ae..d655157 100644 --- a/tests/Daml.Codegen.CSharp.Cli.Tests/CliReleaseCountersTests.cs +++ b/tests/Daml.Codegen.CSharp.Cli.Tests/CliReleaseCountersTests.cs @@ -41,7 +41,7 @@ private static string FixtureIntermediate() => Path.Combine(AppContext.BaseDirectory, "Snapshots", FixtureSnapshotName, "intermediate.binpb"); [Fact] - public async Task release_counters_flag_stamps_generation_ordinal_into_generated_csproj() + public async Task CliReleaseCounters_release_counters_flag_stamps_generation_ordinal_into_generated_csproj() { var intermediate = FixtureIntermediate(); File.Exists(intermediate).Should().BeTrue($"fixture proto must ship at {intermediate}"); @@ -70,7 +70,7 @@ public async Task release_counters_flag_stamps_generation_ordinal_into_generated } [Fact] - public async Task release_counters_flag_holds_generation_ordinal_steady_on_re_emission_of_the_same_codegen_version() + public async Task CliReleaseCounters_release_counters_flag_holds_generation_ordinal_steady_on_re_emission_of_the_same_codegen_version() { var intermediate = FixtureIntermediate(); var counters = Path.Combine(_workspace, "release-counters.json"); @@ -105,7 +105,7 @@ public async Task release_counters_flag_holds_generation_ordinal_steady_on_re_em } [Fact] - public async Task codegen_version_flag_drives_the_generation_key() + public async Task CliReleaseCounters_codegen_version_flag_drives_the_generation_key() { var intermediate = FixtureIntermediate(); var counters = Path.Combine(_workspace, "release-counters.json"); @@ -140,7 +140,7 @@ public async Task codegen_version_flag_drives_the_generation_key() } [Fact] - public async Task release_counters_flag_defaults_the_generation_key_to_the_emitter_version_when_codegen_version_is_omitted() + public async Task CliReleaseCounters_release_counters_flag_defaults_the_generation_key_to_the_emitter_version_when_codegen_version_is_omitted() { var intermediate = FixtureIntermediate(); var counters = Path.Combine(_workspace, "release-counters.json"); @@ -167,7 +167,7 @@ public async Task release_counters_flag_defaults_the_generation_key_to_the_emitt [Theory] [InlineData("")] [InlineData(" ")] - public async Task codegen_version_flag_fails_loudly_when_blank(string blankCodegenVersion) + public async Task CliReleaseCounters_codegen_version_flag_fails_loudly_when_blank(string blankCodegenVersion) { var intermediate = FixtureIntermediate(); var counters = Path.Combine(_workspace, "release-counters.json"); @@ -185,7 +185,7 @@ public async Task codegen_version_flag_fails_loudly_when_blank(string blankCodeg } [Fact] - public async Task release_counters_flag_fails_loudly_when_intermediate_is_not_provided() + public async Task CliReleaseCounters_release_counters_flag_fails_loudly_when_intermediate_is_not_provided() { var counters = Path.Combine(_workspace, "release-counters.json"); @@ -200,7 +200,7 @@ public async Task release_counters_flag_fails_loudly_when_intermediate_is_not_pr } [Fact] - public async Task codegen_version_flag_fails_loudly_when_release_counters_is_not_provided() + public async Task CliReleaseCounters_codegen_version_flag_fails_loudly_when_release_counters_is_not_provided() { var intermediate = FixtureIntermediate(); @@ -215,6 +215,51 @@ public async Task codegen_version_flag_fails_loudly_when_release_counters_is_not "--codegen-version only keys the release-counter store, so supplying it without --release-counters fails loudly rather than being silently ignored"); } + [Fact] + public async Task CliReleaseCounters_release_counters_flag_pins_dependency_references_to_the_co_produced_version() + { + var intermediate = FixtureIntermediate(); + var counters = Path.Combine(_workspace, "release-counters.json"); + + (await Program.Main( + [ + "--intermediate", intermediate, + "-o", _workspace, + "--release-counters", counters, + "--codegen-version", "0.2.0-preview.3", + "--generate-project" + ])).Should().Be(0); + + DependencyReferenceVersion(_workspace, "Splice.Api.Token.Metadata.V1").Should().Be("1.0.0.0", + "--release-counters keys one generation ordinal across a family the release publishes together, so the dependency is co-produced by this run at a version it knows exactly"); + } + + [Fact] + public async Task CliReleaseCounters_omitting_release_counters_floats_dependency_references_over_published_generations() + { + var intermediate = FixtureIntermediate(); + + (await Program.Main( + [ + "--intermediate", intermediate, + "-o", _workspace, + "--generate-project" + ])).Should().Be(0); + + DependencyReferenceVersion(_workspace, "Splice.Api.Token.Metadata.V1").Should().Be("1.0.0.*-*", + "without --release-counters this run publishes nothing, so it cannot know the dependency's generation ordinal or prerelease tag; pinning the default 1.0.0.0 named a version nobody published and failed restore with NU1103"); + } + + private static string DependencyReferenceVersion(string workspace, string packageId) + { + var csproj = Directory.GetFiles(workspace, "*.csproj", SearchOption.TopDirectoryOnly).Single(); + return XDocument.Load(csproj) + .Descendants("PackageReference") + .Single(reference => reference.Attribute("Include")?.Value == packageId) + .Attribute("Version")! + .Value; + } + private static string FourthSegmentOfGeneratedVersion(string workspace) { var csproj = Directory.GetFiles(workspace, "*.csproj", SearchOption.TopDirectoryOnly).Single(); diff --git a/tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollection.cs b/tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollectionDefinition.cs similarity index 90% rename from tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollection.cs rename to tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollectionDefinition.cs index 7232883..3241304 100644 --- a/tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollection.cs +++ b/tests/Daml.Codegen.CSharp.Cli.Tests/ConsoleRedirectionCollectionDefinition.cs @@ -12,4 +12,4 @@ namespace Daml.Codegen.CSharp.Cli.Tests; /// classes are held out of the assembly's parallel run; everything else parallelizes. /// [CollectionDefinition("ConsoleRedirection", DisableParallelization = true)] -public sealed class ConsoleRedirectionCollection; +public sealed class ConsoleRedirectionCollectionDefinition; diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceAsyncExerciserTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceAsyncExerciserTests.cs index ba90908..b096cae 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceAsyncExerciserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceAsyncExerciserTests.cs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; namespace Daml.Codegen.CSharp.Tests; @@ -45,7 +46,6 @@ private static DamlTemplate Template(string name, DamlType returnType, string ch new() { Name = name, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -83,7 +83,6 @@ private static DamlModule ModuleWith(DamlTemplate template, params string[] sibl templates.Add(new DamlTemplate { Name = sibling, - Fields = [], Choices = [] }); } @@ -174,18 +173,13 @@ public void Generate_should_not_emit_default_workflow_id() code.Should().NotContain("\"exercise-renew\""); code.Should().NotContain("workflowId ??"); - code.Should().Contain("if (!string.IsNullOrEmpty(workflowId))"); - code.Should().Contain("submission = submission.WithWorkflowId(new WorkflowId(workflowId));"); + code.Should().NotContain(".WithWorkflowId("); + code.Should().Contain("client." + TrySubmitSingleArgumentOrder); } [Fact] - public void Generate_should_skip_async_extension_when_argument_type_is_fallback() + public void Generate_should_fail_loudly_when_a_choice_argument_type_is_unmappable() { - // B3: when the codegen can't resolve the choice argument to a known same-module - // record / Unit / external ref, GetChoiceArgumentInfo returns IsFallback=true and - // emits a field-less Arg stub with no ToRecord(). The Async exerciser's - // `argument.ToRecord()` call would then fail to compile. Skip emission for that - // case so generated code always compiles. var unresolvedArgType = new DamlTypeApp( new DamlTypeRef("", "Test.Module", "Unresolved"), [new DamlPrimitiveType(DamlPrimitive.Int64)]); @@ -196,9 +190,9 @@ public void Generate_should_skip_async_extension_when_argument_type_is_fallback( argType: unresolvedArgType); var module = ModuleWith(template); - var code = GenerateAndReadTemplate(module, "Agreement"); - - code.Should().NotContain("WeirdAsync"); + FluentActions.Invoking(() => GenerateAndReadTemplate(module, "Agreement")) + .Should().Throw() + .WithMessage("*Weird*"); } [Fact] @@ -209,7 +203,7 @@ public void Generate_should_call_TrySubmitAndWaitForTransactionAsync_in_emitted_ var code = GenerateAndReadTemplate(module, "Agreement"); - code.Should().Contain("client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken)"); + code.Should().Contain("client." + TrySubmitSingleArgumentOrder); } [Fact] @@ -253,7 +247,6 @@ public void Generate_should_emit_async_extension_for_each_create_bearing_choice( var template = new DamlTemplate { Name = "Agreement", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceCodeGenTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceCodeGenTests.cs index d352782..0d2dfb1 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceCodeGenTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceCodeGenTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; using System.IO; @@ -41,11 +41,6 @@ public void Generate_should_reference_existing_type_for_local_data_type_argument new DamlTemplate { Name = "Asset", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Int64)) - ], Choices = [ new DamlChoice @@ -116,8 +111,8 @@ public void GenerateNestedChoiceArgumentType_emits_indented_signature_without_st nestedFile.Should().NotBeNull("the choice argument record is emitted as a nested type in a partial template file"); nestedFile!.Content.Should().Contain( - "\n public sealed record Transfer([property: DamlFieldAttribute(\"newOwner\")] Party NewOwner, [property: DamlFieldAttribute(\"amount\")] decimal Amount) : IDamlRecord", - "the nested record signature must be indented one level and the parameter list must close without trailing whitespace"); + "\n public sealed record Transfer(\n [property: DamlFieldAttribute(\"newOwner\")] Party NewOwner,\n [property: DamlFieldAttribute(\"amount\")] decimal Amount\n ) : IDamlRecord", + "the nested record signature must be indented one level, carry one parameter per line indented one level further, and close on its own line with the base list"); } private static DamlModule NestedTransferArgumentModule() @@ -136,7 +131,6 @@ private static DamlModule NestedTransferArgumentModule() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -177,8 +171,7 @@ public void Generate_should_use_DamlUnit_for_Unit_argument() [ new DamlTemplate { - Name = "Contract", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], + Name = "MyTemplate", Choices = [ new DamlChoice @@ -195,7 +188,7 @@ public void Generate_should_use_DamlUnit_for_Unit_argument() [ new DamlDataType { - Name = "Contract", + Name = "MyTemplate", Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) } ], @@ -207,13 +200,13 @@ public void Generate_should_use_DamlUnit_for_Unit_argument() // Act var files = generator.Generate(dar); - var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Contract.cs", StringComparison.Ordinal)); + var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("MyTemplate.cs", StringComparison.Ordinal)); // Assert contractFile.Should().NotBeNull(); var code = contractFile!.Content; - code.Should().Contain("Choice"); + code.Should().Contain("Choice"); code.Should().Contain("ArgumentEncoder = _ => DamlUnit.Instance"); code.Should().Contain("ResultDecoder = _ => DamlUnit.Instance"); } @@ -230,7 +223,6 @@ public void Generate_should_encode_Archive_choice_argument_as_empty_record() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("data", new DamlPrimitiveType(DamlPrimitive.Text))], Choices = [ new DamlChoice @@ -282,7 +274,6 @@ public void Generate_should_decode_ContractId_result_correctly() new DamlTemplate { Name = "Factory", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -345,7 +336,6 @@ public void Generate_should_decode_primitive_results_correctly() new DamlTemplate { Name = "Counter", - Fields = [new DamlFieldDefinition("count", new DamlPrimitiveType(DamlPrimitive.Int64))], Choices = [ new DamlChoice @@ -417,7 +407,6 @@ public void Generate_should_handle_multiple_choices_with_different_arg_types() new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("amount", new DamlPrimitiveType(DamlPrimitive.Numeric))], Choices = [ // Local data type argument @@ -511,7 +500,7 @@ public void Generate_should_keep_choice_argument_fields_when_simple_names_collid agreementExpire.Should().NotBeNull(); agreementExpire!.Content.Should().Contain( - "public sealed record Expire([property: DamlFieldAttribute(\"actor\")] Party Actor) : IDamlRecord", + "public sealed record Expire(\n [property: DamlFieldAttribute(\"actor\")] Party Actor\n ) : IDamlRecord", "the Agreement module's Expire argument record must keep its own fields instead of the Offer module's"); offerExpire.Should().NotBeNull(); offerExpire!.Content.Should().Contain( @@ -532,7 +521,6 @@ private static DamlModule ModuleWithExpireChoice( new DamlTemplate { Name = templateName, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceCreatedSlotsTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceCreatedSlotsTests.cs index 78162fa..d256853 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceCreatedSlotsTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceCreatedSlotsTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -58,7 +58,7 @@ private static DamlTypeApp Tuple(params DamlType[] components) => new(new DamlTypeRef(LocalPackageId, "DA.Types", $"Tuple{components.Length}"), components); [Fact] - public void single_contract_id_yields_one_single_slot() + public void ChoiceCreatedSlots_single_contract_id_yields_one_single_slot() { var slots = Extract(ContractIdOf(Ref("Agreement"))); @@ -68,7 +68,7 @@ public void single_contract_id_yields_one_single_slot() } [Fact] - public void template_typed_contract_id_has_no_interface_matcher() + public void ChoiceCreatedSlots_template_typed_contract_id_has_no_interface_matcher() { var slots = Extract(ContractIdOf(Ref("Agreement"))); @@ -76,7 +76,7 @@ public void template_typed_contract_id_has_no_interface_matcher() } [Fact] - public void interface_typed_contract_id_yields_an_interface_matcher_with_the_interface_module_and_entity() + public void ChoiceCreatedSlots_interface_typed_contract_id_yields_an_interface_matcher_with_the_interface_module_and_entity() { var module = new DamlModule { @@ -101,11 +101,11 @@ public void interface_typed_contract_id_yields_an_interface_matcher_with_the_int var slots = ChoiceCreatedSlots.Extract(context, resolver, mapper, ContractIdOf(Ref("Holdable"))); slots.Should().ContainSingle(); - slots[0].Interface.Should().Be(new InterfaceMatcher("Main", "Holdable", IsPlaceholder: true)); + slots[0].Interface.Should().Be(new InterfaceMatcher("Main", "Holdable")); } [Fact] - public void interface_typed_contract_id_from_a_foreign_package_yields_an_interface_matcher() + public void ChoiceCreatedSlots_interface_typed_contract_id_from_a_foreign_package_yields_an_interface_matcher() { const string ForeignPackageId = "foreign-pkg-id"; var foreignModule = new DamlModule @@ -132,11 +132,11 @@ public void interface_typed_contract_id_from_a_foreign_package_yields_an_interfa var slots = ChoiceCreatedSlots.Extract(context, resolver, mapper, ContractIdOf(foreignRef)); slots.Should().ContainSingle(); - slots[0].Interface.Should().Be(new InterfaceMatcher("Foreign.Module", "Holdable", IsPlaceholder: false)); + slots[0].Interface.Should().Be(new InterfaceMatcher("Foreign.Module", "Holdable")); } [Fact] - public void optional_contract_id_yields_an_optional_slot() + public void ChoiceCreatedSlots_optional_contract_id_yields_an_optional_slot() { var slots = Extract(OptionalOf(ContractIdOf(Ref("Agreement")))); @@ -145,7 +145,7 @@ public void optional_contract_id_yields_an_optional_slot() } [Fact] - public void list_of_contract_id_yields_a_list_slot() + public void ChoiceCreatedSlots_list_of_contract_id_yields_a_list_slot() { var slots = Extract(ListOf(ContractIdOf(Ref("Agreement")))); @@ -154,7 +154,7 @@ public void list_of_contract_id_yields_a_list_slot() } [Fact] - public void tuple_is_flattened_across_components() + public void ChoiceCreatedSlots_tuple_is_flattened_across_components() { var slots = Extract(Tuple(ContractIdOf(Ref("Buyer")), ContractIdOf(Ref("Seller")))); @@ -164,7 +164,7 @@ public void tuple_is_flattened_across_components() } [Fact] - public void same_template_twice_disambiguates_field_names() + public void ChoiceCreatedSlots_same_template_twice_disambiguates_field_names() { var slots = Extract(Tuple(ContractIdOf(Ref("Half")), ContractIdOf(Ref("Half")))); @@ -174,7 +174,7 @@ public void same_template_twice_disambiguates_field_names() } [Fact] - public void base_name_ending_in_digit_is_left_untouched_when_unique() + public void ChoiceCreatedSlots_base_name_ending_in_digit_is_left_untouched_when_unique() { var slots = Extract(Tuple(ContractIdOf(Ref("Half2")), ContractIdOf(Ref("Whole")))); @@ -184,7 +184,7 @@ public void base_name_ending_in_digit_is_left_untouched_when_unique() } [Fact] - public void synthesized_suffix_does_not_steal_a_later_real_name() + public void ChoiceCreatedSlots_synthesized_suffix_does_not_steal_a_later_real_name() { var slots = Extract(Tuple( ContractIdOf(Ref("Half")), @@ -195,7 +195,7 @@ public void synthesized_suffix_does_not_steal_a_later_real_name() } [Fact] - public void cascading_collisions_each_get_a_distinct_free_suffix() + public void ChoiceCreatedSlots_cascading_collisions_each_get_a_distinct_free_suffix() { var slots = Extract(Tuple( ContractIdOf(Ref("Half")), @@ -208,7 +208,7 @@ public void cascading_collisions_each_get_a_distinct_free_suffix() } [Fact] - public void non_contract_return_yields_no_slots() + public void ChoiceCreatedSlots_non_contract_return_yields_no_slots() { Extract(new DamlPrimitiveType(DamlPrimitive.Int64)).Should().BeEmpty(); Extract(new DamlPrimitiveType(DamlPrimitive.Unit)).Should().BeEmpty(); diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterArchiveChoiceTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterArchiveChoiceTests.cs index 6207c88..0f49347 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterArchiveChoiceTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterArchiveChoiceTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -79,7 +79,6 @@ private static DamlTemplate ItemTemplate(string archiveArgPackageId) => new() { Name = "Item", - Fields = [], Choices = [ArchiveChoice(archiveArgPackageId)], }; @@ -93,6 +92,16 @@ private static string EmitNonContract(DamlTemplate template, StubResolver resolv return sb.ToString(); } + private static string EmitContractIdExercisers(DamlTemplate template, StubResolver resolver) + { + var package = Package(new DamlModule { Name = "Main", Templates = [template], DataTypes = [], Interfaces = [] }); + var context = PackageEmitContext.ForPackage(package, new CodeGenOptions { RootNamespace = "Test.Package" }); + var sb = new StringBuilder(); + var indent = new IndentWriter(sb) { CurrentTypeName = template.Name }; + Emitter(context, resolver).WriteChoiceAsyncExercisersClass(indent, template, template.Name, [], context.DataTypes); + return sb.ToString(); + } + private static string EmitDescriptors(DamlTemplate template, StubResolver resolver) { var package = Package(new DamlModule { Name = "Main", Templates = [template], DataTypes = [], Interfaces = [] }); @@ -114,7 +123,7 @@ private static string EmitInterfaceExtensions(DamlInterface iface, string interf } [Fact] - public void archive_choice_emits_synthetic_stdlib_archive_in_non_contract_wrappers() + public void ArchiveChoice_emits_synthetic_stdlib_archive_in_non_contract_wrappers() { var resolver = new StubResolver(packages: Packages(StdlibPackage)); @@ -127,7 +136,7 @@ public void archive_choice_emits_synthetic_stdlib_archive_in_non_contract_wrappe } [Fact] - public void archive_choice_keeps_user_archive_choice_from_non_stdlib_package() + public void ArchiveChoice_keeps_user_archive_choice_from_non_stdlib_package() { var resolver = new StubResolver(packages: Packages(UserPackage)); @@ -138,7 +147,7 @@ public void archive_choice_keeps_user_archive_choice_from_non_stdlib_package() } [Fact] - public void archive_choice_uses_actual_argument_type_not_damlunit_for_user_archive() + public void ArchiveChoice_uses_actual_argument_type_not_damlunit_for_user_archive() { var resolver = new StubResolver("User.Package.Archive", Packages(UserPackage)); var template = ItemTemplate(UserPackageId); @@ -155,7 +164,7 @@ public void archive_choice_uses_actual_argument_type_not_damlunit_for_user_archi } [Fact] - public void archive_choice_uses_actual_argument_type_not_damlunit_for_user_archive_interface_choice() + public void ArchiveChoice_uses_actual_argument_type_not_damlunit_for_user_archive_interface_choice() { var iface = new DamlInterface { @@ -174,7 +183,7 @@ public void archive_choice_uses_actual_argument_type_not_damlunit_for_user_archi } [Fact] - public void archive_choice_encodes_synthetic_stdlib_archive_argument_as_empty_record_in_descriptor() + public void ArchiveChoice_encodes_synthetic_stdlib_archive_argument_as_empty_record_in_descriptor() { var resolver = new StubResolver(packages: Packages(StdlibPackage)); var template = ItemTemplate(StdlibPackageId); @@ -186,8 +195,57 @@ public void archive_choice_encodes_synthetic_stdlib_archive_argument_as_empty_re descriptor.Should().NotContain("ArgumentEncoder = _ => DamlUnit.Instance"); } + public static TheoryData ArgumentlessContractIdCommandEncodings => new() + { + { + "Archive", + new DamlTypeRef(StdlibPackageId, "DA.Internal.Template", "Archive"), + "DamlRecord.Create());", + "DamlUnit.Instance" + }, + { + "Accept", + new DamlPrimitiveType(DamlPrimitive.Unit), + "DamlUnit.Instance);", + "DamlRecord.Create()" + }, + }; + + [Theory] + [MemberData(nameof(ArgumentlessContractIdCommandEncodings))] + public void ArchiveChoice_contract_id_command_builder_encodes_argument_like_the_descriptor( + string choiceName, + DamlType argumentType, + string encodedArgument, + string rejectedArgument) + { + var resolver = new StubResolver(packages: Packages(StdlibPackage)); + var template = new DamlTemplate + { + Name = "Item", + Choices = + [ + new DamlChoice + { + Name = choiceName, + Consuming = true, + ArgumentType = argumentType, + ReturnType = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.ContractId), + [new DamlTypeRef(LocalPackageId, "Main", "Item")]), + } + ], + }; + + var output = EmitContractIdExercisers(template, resolver); + + output.Should().Contain($"public static ExerciseCommand {choiceName}Command("); + output.Should().Contain(encodedArgument); + output.Should().NotContain(rejectedArgument); + } + [Fact] - public void archive_choice_encodes_synthetic_stdlib_interface_archive_argument_as_empty_record() + public void ArchiveChoice_encodes_synthetic_stdlib_interface_archive_argument_as_empty_record() { var iface = new DamlInterface { diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterByKeyCommandTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterByKeyCommandTests.cs new file mode 100644 index 0000000..89ffcff --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterByKeyCommandTests.cs @@ -0,0 +1,220 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text; +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Codegen.CSharp.Tests; + +public class ChoiceEmitterByKeyCommandTests +{ + private const string LocalPackageId = "pkg-id"; + + private sealed class StubResolver : ICrossPackageResolver + { + public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) => Identifiers.Sanitize(typeRef.Name); + + public IReadOnlySet DiscoveredExternalPackageIds => new HashSet(); + + public DamlPackage? LookupPackage(string packageId) => null; + } + + private static DamlChoice Choice(string name, DamlType returnType) => + new() + { + Name = name, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = returnType, + Consuming = false, + Controllers = DamlPartyAnalysis.Dynamic, + Observers = DamlPartyAnalysis.Dynamic, + }; + + private static readonly IReadOnlyList VaultFields = + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]; + + private static DamlTemplate Template(DamlType? key, params DamlChoice[] choices) => + new() + { + Name = "Vault", + Choices = choices, + Key = key, + Signatories = DamlPartyAnalysis.Dynamic, + Observers = DamlPartyAnalysis.Dynamic, + }; + + private static DamlTypeApp ContractIdOf(string templateName) => + new(new DamlPrimitiveType(DamlPrimitive.ContractId), [new DamlTypeRef(LocalPackageId, "Main", templateName)]); + + /// + /// The emitted template record body, and everything emitted beside it at namespace + /// level — the choice-result structs and the Extensions / + /// NonContractExtensions classes. The template record is the first type + /// written, so it is the text up to the first line closing a type at column 0. + /// + private sealed record EmittedTemplate(string RecordBody, string SiblingTypes); + + private static EmittedTemplate Emit(DamlTemplate template) + { + var module = new DamlModule + { + Name = "Main", + Templates = [template], + DataTypes = [], + Interfaces = [], + }; + var package = new DamlPackage + { + PackageId = LocalPackageId, + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.3", + Modules = [module], + DependencyReferences = [], + }; + var options = new CodeGenOptions { RootNamespace = "Test.Package" }; + var context = PackageEmitContext.ForPackage(package, options); + var resolver = new StubResolver(); + var mapper = new DamlTypeMapper(context, resolver); + var party = new PartyAnalysis(); + var emitter = new TemplateEmitter( + context, + resolver, + new RecordSerializationEmitter(context, resolver, options, mapper), + new ChoiceEmitter(context, resolver, options, mapper, party), + new SubmissionExtensionsEmitter(context, options, party), + options); + + var emitted = new StringBuilder(); + emitter.WriteTemplateType(new IndentWriter(emitted), package, module, template, VaultFields); + + var lines = emitted.ToString().ReplaceLineEndings("\n").Split('\n'); + var recordClose = Array.IndexOf(lines, "}"); + recordClose.Should().BePositive("the template record is the first type emitted and closes at column 0"); + + return new EmittedTemplate( + string.Join('\n', lines[..recordClose]), + string.Join('\n', lines[recordClose..])); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_create_bearing_choice_gets_a_by_key_twin_of_its_command_builder() + { + var template = Template(new DamlTypeRef(LocalPackageId, "Main", "VaultKey"), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.SiblingTypes.Should().Contain("public static ExerciseCommand SpawnCommand("); + emitted.RecordBody.Should().Contain("public static ExerciseByKeyCommand SpawnByKeyCommand("); + emitted.RecordBody.Should().Contain("global::Test.Package.VaultKey key)"); + emitted.RecordBody.Should().Contain("key.ToRecord(),"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_value_returning_choice_gets_a_by_key_twin_of_its_command_builder() + { + var template = Template(new DamlTypeRef(LocalPackageId, "Main", "VaultKey"), Choice("Peek", new DamlPrimitiveType(DamlPrimitive.Int64))); + + var emitted = Emit(template); + + emitted.SiblingTypes.Should().Contain("public static ExerciseCommand PeekCommand("); + emitted.RecordBody.Should().Contain("public static ExerciseByKeyCommand PeekByKeyCommand("); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_lives_on_the_template_record_not_the_extensions_classes() + { + var template = Template( + new DamlTypeRef(LocalPackageId, "Main", "VaultKey"), + Choice("Spawn", ContractIdOf("Token")), + Choice("Peek", new DamlPrimitiveType(DamlPrimitive.Int64))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("public static ExerciseByKeyCommand SpawnByKeyCommand("); + emitted.RecordBody.Should().Contain("public static ExerciseByKeyCommand PeekByKeyCommand("); + emitted.SiblingTypes.Should().Contain("public static class VaultExtensions"); + emitted.SiblingTypes.Should().Contain("public static class VaultNonContractExtensions"); + emitted.SiblingTypes.Should().NotContain("ByKeyCommand", + "the key-addressed builders are reached through the template record, not through an extensions class"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_is_not_an_extension_method_on_the_key_type() + { + var template = Template(new DamlPrimitiveType(DamlPrimitive.Text), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("string key)"); + emitted.RecordBody.Should().NotContain("this string key", + "extending string would put the method on every string in the consuming project"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_guards_a_reference_typed_key_against_null() + { + var template = Template(new DamlTypeRef(LocalPackageId, "Main", "VaultKey"), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("ArgumentNullException.ThrowIfNull(key);"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_leaves_a_value_typed_key_unguarded() + { + var template = Template(new DamlPrimitiveType(DamlPrimitive.Party), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("Party key)"); + emitted.RecordBody.Should().NotContain("ArgumentNullException.ThrowIfNull(key);", + "Party is a struct, so the guard would only box the argument"); + emitted.RecordBody.Should().Contain("key.ToDamlValue(),"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_guards_a_nested_optional_key_against_null() + { + var template = Template(OptionalOf(OptionalOf(new DamlPrimitiveType(DamlPrimitive.Text))), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("Optional> key)"); + emitted.RecordBody.Should().Contain("ArgumentNullException.ThrowIfNull(key);", + "the wrapper is a non-nullable reference type, so without the guard an absent key " + + "surfaces as a NullReferenceException inside ToValue rather than at the boundary"); + } + + [Fact] + public void ChoiceEmitterByKeyCommand_leaves_a_flat_optional_key_unguarded() + { + var template = Template(OptionalOf(new DamlPrimitiveType(DamlPrimitive.Text)), Choice("Spawn", ContractIdOf("Token"))); + + var emitted = Emit(template); + + emitted.RecordBody.Should().Contain("string? key)"); + emitted.RecordBody.Should().NotContain("ArgumentNullException.ThrowIfNull(key);", + "a flat Optional stays on C# nullable syntax, so null is the absent key rather than a bug"); + } + + private static DamlTypeApp OptionalOf(DamlType argument) => + new(new DamlPrimitiveType(DamlPrimitive.Optional), [argument]); + + [Fact] + public void ChoiceEmitterByKeyCommand_is_absent_from_a_key_less_template() + { + var template = Template(key: null, Choice("Spawn", ContractIdOf("Token")), Choice("Peek", new DamlPrimitiveType(DamlPrimitive.Int64))); + + var emitted = Emit(template); + + emitted.SiblingTypes.Should().Contain("public static ExerciseCommand SpawnCommand("); + emitted.SiblingTypes.Should().Contain("public static ExerciseCommand PeekCommand("); + emitted.RecordBody.Should().NotContain("ByKeyCommand"); + emitted.SiblingTypes.Should().NotContain("ByKeyCommand"); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdExerciserTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdExerciserTests.cs index 1f5fcc8..a3dbf5f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdExerciserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdExerciserTests.cs @@ -3,9 +3,10 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; namespace Daml.Codegen.CSharp.Tests; @@ -56,21 +57,25 @@ private static DamlChoice Choice(string name, DamlType returnType, DamlPartyAnal Observers = DamlPartyAnalysis.Dynamic, }; - private static DamlTemplate Template(IReadOnlyList fields, params DamlChoice[] choices) => - new() - { - Name = "Vault", - Fields = fields, - Choices = choices, - Signatories = DamlPartyAnalysis.Dynamic, - Observers = DamlPartyAnalysis.Dynamic, - }; + private sealed record TemplateFixture(DamlTemplate Template, IReadOnlyList Fields); + + private static TemplateFixture Template(IReadOnlyList fields, params DamlChoice[] choices) => + new( + new DamlTemplate + { + Name = "Vault", + Choices = choices, + Signatories = DamlPartyAnalysis.Dynamic, + Observers = DamlPartyAnalysis.Dynamic, + }, + fields); private static DamlTypeApp ContractIdOf(string templateName) => new(new DamlPrimitiveType(DamlPrimitive.ContractId), [new DamlTypeRef(LocalPackageId, "Main", templateName)]); - private static (string ResultStructs, string Exercisers) Emit(DamlTemplate template) + private static (string ResultStructs, string Exercisers) Emit(TemplateFixture fixture) { + var template = fixture.Template; var package = Package(template); var context = PackageEmitContext.ForPackage(package, new CodeGenOptions { RootNamespace = "Test.Package" }); var resolver = new StubResolver(); @@ -82,44 +87,44 @@ private static (string ResultStructs, string Exercisers) Emit(DamlTemplate templ var exerciserSb = new StringBuilder(); var exerciserIndent = new IndentWriter(exerciserSb) { CurrentTypeName = template.Name }; - emitter.WriteChoiceAsyncExercisersClass(exerciserIndent, template, template.Name, template.Fields, context.DataTypes); + emitter.WriteChoiceAsyncExercisersClass(exerciserIndent, template, template.Name, fixture.Fields, context.DataTypes); return (structsSb.ToString(), exerciserSb.ToString()); } [Fact] - public void single_contract_id_choice_emits_a_single_cardinality_slot_property() + public void ChoiceEmitterContractIdExerciser_single_contract_id_choice_emits_a_single_cardinality_slot_property() { var template = Template([], Choice("Spawn", ContractIdOf("Token"))); var (structs, _) = Emit(template); - structs.Should().Contain("public sealed record SpawnResult(ContractId Token)"); + structs.Should().Contain("public sealed record SpawnResult(\n ContractId Token\n)"); structs.Should().Contain("public static ExerciseOutcome FromCreatedContracts"); } [Fact] - public void optional_contract_id_choice_emits_a_nullable_slot_property() + public void ChoiceEmitterContractIdExerciser_optional_contract_id_choice_emits_a_nullable_slot_property() { var template = Template([], Choice("Spawn", new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [ContractIdOf("Token")]))); var (structs, _) = Emit(template); - structs.Should().Contain("public sealed record SpawnResult(ContractId? Token)"); + structs.Should().Contain("public sealed record SpawnResult(\n ContractId? Token\n)"); } [Fact] - public void list_contract_id_choice_emits_a_list_slot_property() + public void ChoiceEmitterContractIdExerciser_list_contract_id_choice_emits_a_list_slot_property() { var template = Template([], Choice("Spawn", new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.List), [ContractIdOf("Token")]))); var (structs, _) = Emit(template); - structs.Should().Contain("public sealed record SpawnResult(IReadOnlyList> Token)"); + structs.Should().Contain("public sealed record SpawnResult(\n IReadOnlyList> Token\n)"); } [Fact] - public void create_bearing_choice_emits_a_typed_async_exerciser() + public void ChoiceEmitterContractIdExerciser_create_bearing_choice_emits_a_typed_async_exerciser() { var template = Template( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], @@ -129,11 +134,12 @@ public void create_bearing_choice_emits_a_typed_async_exerciser() exercisers.Should().Contain("public static class VaultExtensions"); exercisers.Should().Contain("public static async Task> SpawnAsync("); + exercisers.Should().Contain("public static Task> SpawnAsync("); exercisers.Should().Contain("this ContractId contractId,"); } [Fact] - public void non_creating_choice_emits_no_exerciser_class() + public void ChoiceEmitterContractIdExerciser_non_creating_choice_emits_no_exerciser_class() { var template = Template([], Choice("Touch", new DamlPrimitiveType(DamlPrimitive.Unit))); @@ -144,7 +150,7 @@ public void non_creating_choice_emits_no_exerciser_class() } [Fact] - public void create_bearing_choice_exerciser_accepts_optional_command_id_override() + public void ChoiceEmitterContractIdExerciser_create_bearing_choice_exerciser_accepts_optional_command_id_override() { var template = Template( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], @@ -153,7 +159,7 @@ public void create_bearing_choice_exerciser_accepts_optional_command_id_override var (_, exercisers) = Emit(template); exercisers.Should().Contain("CommandId? commandId = null,"); - exercisers.Should().Contain(".WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString()));"); + exercisers.Should().Contain(TrySubmitSingleArgumentOrder); var idxWorkflowId = exercisers.IndexOf("string? workflowId = null,", StringComparison.Ordinal); var idxCommandId = exercisers.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); @@ -163,7 +169,7 @@ public void create_bearing_choice_exerciser_accepts_optional_command_id_override } [Fact] - public void create_bearing_choice_exerciser_forwards_optional_timeout() + public void ChoiceEmitterContractIdExerciser_create_bearing_choice_exerciser_forwards_optional_timeout() { var template = Template( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], @@ -172,7 +178,7 @@ public void create_bearing_choice_exerciser_forwards_optional_timeout() var (_, exercisers) = Emit(template); exercisers.Should().Contain("TimeSpan? timeout = null,"); - exercisers.Should().Contain("client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken)"); + exercisers.Should().Contain("client." + TrySubmitSingleArgumentOrder); var idxCommandId = exercisers.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); var idxTimeout = exercisers.IndexOf("TimeSpan? timeout = null,", StringComparison.Ordinal); @@ -182,7 +188,7 @@ public void create_bearing_choice_exerciser_forwards_optional_timeout() } [Fact] - public void create_bearing_choice_emits_a_command_builder_shared_by_both_async_overloads() + public void ChoiceEmitterContractIdExerciser_create_bearing_choice_emits_one_submission_body_reached_by_both_overloads() { var template = Template( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], @@ -201,13 +207,14 @@ public void create_bearing_choice_emits_a_command_builder_shared_by_both_async_o const string commandCallMarker = "var command = contractId.SpawnCommand();"; var firstCall = exercisers.IndexOf(commandCallMarker, StringComparison.Ordinal); firstCall.Should().BeGreaterThanOrEqualTo(0); - var secondCall = exercisers.IndexOf(commandCallMarker, firstCall + 1, StringComparison.Ordinal); - secondCall.Should().BeGreaterThan(firstCall); - exercisers.IndexOf(commandCallMarker, secondCall + 1, StringComparison.Ordinal).Should().Be(-1); + exercisers.IndexOf(commandCallMarker, firstCall + 1, StringComparison.Ordinal).Should().Be(-1); + + exercisers.Should().Contain("SubmitterInfo submitter = owner;"); + exercisers.Should().Contain("return contractId.SpawnAsync("); } [Fact] - public void contract_overload_forwards_timeout_positionally_to_the_contract_id_overload() + public void ChoiceEmitterContractIdExerciser_contract_overload_forwards_timeout_positionally_to_the_contract_id_overload() { var template = Template( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdFilteringTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdFilteringTests.cs index 1408f72..d225da3 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdFilteringTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterContractIdFilteringTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -55,7 +55,6 @@ private static DamlTemplate Template(params DamlChoice[] choices) => new() { Name = "Factory", - Fields = [], Choices = choices, }; @@ -75,13 +74,13 @@ private static (string NonContract, string Exercisers) Emit(DamlTemplate templat var exercisersSb = new StringBuilder(); var exercisersIndent = new IndentWriter(exercisersSb) { CurrentTypeName = template.Name }; - emitter.WriteChoiceAsyncExercisersClass(exercisersIndent, template, template.Name, template.Fields, context.DataTypes); + emitter.WriteChoiceAsyncExercisersClass(exercisersIndent, template, template.Name, [], context.DataTypes); return (nonContractSb.ToString(), exercisersSb.ToString()); } [Fact] - public void contract_id_return_routes_bare_contract_id_choice_to_slot_path_not_non_contract_path() + public void ChoiceEmitterContractIdFiltering_contract_id_return_routes_bare_contract_id_choice_to_slot_path_not_non_contract_path() { var (nonContract, exercisers) = Emit(Template(Choice("Mint", ContractIdOf("Coin")))); @@ -92,7 +91,7 @@ public void contract_id_return_routes_bare_contract_id_choice_to_slot_path_not_n } [Fact] - public void contract_id_return_routes_optional_contract_id_choice_to_slot_path_not_non_contract_path() + public void ChoiceEmitterContractIdFiltering_contract_id_return_routes_optional_contract_id_choice_to_slot_path_not_non_contract_path() { var optionalCid = new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [ContractIdOf("Coin")]); diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterCrossPackageChoiceTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterCrossPackageChoiceTests.cs index 7fe12e1..836fffb 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterCrossPackageChoiceTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterCrossPackageChoiceTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; @@ -62,12 +62,11 @@ private static DamlRecordDefinition SingleTextField(string fieldName) => new([new DamlFieldDefinition(fieldName, new DamlPrimitiveType(DamlPrimitive.Text))]); [Fact] - public void non_contract_exerciser_resolves_cross_package_argument_type() + public void ChoiceEmitterCrossPackageChoice_non_contract_exerciser_resolves_cross_package_argument_type() { var template = new DamlTemplate { Name = "Trader", - Fields = [], Choices = [ new DamlChoice @@ -89,12 +88,11 @@ public void non_contract_exerciser_resolves_cross_package_argument_type() } [Fact] - public void non_contract_exerciser_resolves_cross_package_argument_when_simple_name_collides_with_local_record() + public void ChoiceEmitterCrossPackageChoice_non_contract_exerciser_resolves_cross_package_argument_when_simple_name_collides_with_local_record() { var template = new DamlTemplate { Name = "Trader", - Fields = [], Choices = [ new DamlChoice @@ -115,7 +113,7 @@ public void non_contract_exerciser_resolves_cross_package_argument_when_simple_n } [Fact] - public void interface_choice_resolves_cross_package_argument_type() + public void ChoiceEmitterCrossPackageChoice_interface_choice_resolves_cross_package_argument_type() { var iface = new DamlInterface { @@ -136,13 +134,13 @@ public void interface_choice_resolves_cross_package_argument_type() var output = EmitInterfaceExtensions(iface, "ITransferable", new StubResolver("Other.Pkg.TransferRequest")); output.Should().Contain("public static class ITransferableExtensions"); - output.Should().Contain("public static async Task> TransferAsync("); + output.Should().Contain("public static Task> TransferAsync("); output.Should().Contain("Other.Pkg.TransferRequest argument,"); output.Should().Contain("argument.ToRecord()"); } [Fact] - public void interface_choice_emits_primitive_argument_without_fallback_arg_type() + public void ChoiceEmitterCrossPackageChoice_interface_choice_emits_primitive_argument_without_fallback_arg_type() { var iface = new DamlInterface { @@ -163,7 +161,7 @@ public void interface_choice_emits_primitive_argument_without_fallback_arg_type( var output = EmitInterfaceExtensions(iface, "IQuotable", new StubResolver()); output.Should().Contain("public static class IQuotableExtensions"); - output.Should().Contain("public static async Task> QuoteAsync("); + output.Should().Contain("public static Task> QuoteAsync("); output.Should().Contain("string argument,"); output.Should().NotContain("QuoteArg argument,"); } @@ -194,7 +192,7 @@ private static DarModel CreateDar(DamlModule module) => }; [Fact] - public void generate_throws_at_codegen_time_for_unresolvable_cross_package_ref() + public void Generate_throws_at_codegen_time_for_unresolvable_cross_package_ref() { var module = new DamlModule { @@ -204,7 +202,6 @@ public void generate_throws_at_codegen_time_for_unresolvable_cross_package_ref() new DamlTemplate { Name = "Trader", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterDescriptorTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterDescriptorTests.cs index 1f5b526..d3ac0f9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterDescriptorTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterDescriptorTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -80,14 +80,13 @@ private static DamlTemplate Template(params DamlChoice[] choices) => new() { Name = "Asset", - Fields = [], Choices = choices, Signatories = DamlPartyAnalysis.Dynamic, Observers = DamlPartyAnalysis.Dynamic, }; [Fact] - public void emits_a_choice_descriptor_property_for_a_unit_returning_choice() + public void ChoiceEmitterDescriptor_emits_a_choice_descriptor_property_for_a_unit_returning_choice() { var choice = Choice("Accept", new DamlPrimitiveType(DamlPrimitive.Unit), new DamlPrimitiveType(DamlPrimitive.Unit)); @@ -101,46 +100,66 @@ public void emits_a_choice_descriptor_property_for_a_unit_returning_choice() } [Fact] - public void emits_a_fallback_arg_record_for_an_unresolvable_argument_type() + public void ChoiceEmitterDescriptor_decodes_an_optional_type_variable_return_through_the_wrapper() { - var choice = Choice("Mystery", new DamlTypeVar("a"), new DamlPrimitiveType(DamlPrimitive.Unit)); + var choice = Choice( + "MaybeOf", + new DamlPrimitiveType(DamlPrimitive.Unit), + new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [new DamlTypeVar("a")])); var output = EmitDescriptors(Template(choice), Package()); - output.Should().Contain("public sealed record MysteryArg"); - output.Should().NotContain("ChoiceMystery"); + output.Should().Contain("ResultDecoder = val => Optional.FromValue("); + output.Should().NotContain(".AsOptional().HasValue"); } [Fact] - public void get_choice_argument_info_classifies_unit_as_non_fallback_damlunit() + public void WriteChoiceDescriptors_throws_instead_of_emitting_a_stub_arg_record() + { + var choice = Choice("Mystery", new DamlTypeVar("a"), new DamlPrimitiveType(DamlPrimitive.Unit)); + + FluentActions.Invoking(() => EmitDescriptors(Template(choice), Package())) + .Should().Throw() + .WithMessage("*Mystery*"); + } + + [Fact] + public void GetChoiceArgumentInfo_classifies_unit_as_damlunit() { var context = Context(Package()); var resolver = new StubResolver(); var emitter = Emitter(context, resolver); var choice = Choice("Accept", new DamlPrimitiveType(DamlPrimitive.Unit), new DamlPrimitiveType(DamlPrimitive.Unit)); - var (typeName, _, isFallback, _) = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); + var argument = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); - typeName.Should().Be("DamlUnit"); - isFallback.Should().BeFalse(); + argument.TypeName.Should().Be("DamlUnit"); } - [Fact] - public void get_choice_argument_info_flags_an_unresolvable_argument_as_fallback() + public static TheoryData UnmappableChoiceArgumentTypes() => new() + { + { "a bare Daml type variable", new DamlTypeVar("a") }, + { "a non-Unit primitive", new DamlPrimitiveType(DamlPrimitive.Text) }, + { "a generic type application", new DamlTypeApp(new DamlTypeRef("", "Main", "Unresolved"), [new DamlPrimitiveType(DamlPrimitive.Int64)]) }, + }; + + [Theory] + [MemberData(nameof(UnmappableChoiceArgumentTypes))] + public void GetChoiceArgumentInfo_throws_codegen_exception_for_an_unmappable_argument_type(string label, DamlType argumentType) { var context = Context(Package()); var resolver = new StubResolver(); var emitter = Emitter(context, resolver); - var choice = Choice("Mystery", new DamlTypeVar("a"), new DamlPrimitiveType(DamlPrimitive.Unit)); - - var (typeName, _, isFallback, _) = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); + var choice = Choice("Mystery", argumentType, new DamlPrimitiveType(DamlPrimitive.Unit)); - typeName.Should().Be("MysteryArg"); - isFallback.Should().BeTrue(); + emitter.Invoking(e => e.GetChoiceArgumentInfo(choice, context.DataTypes)) + .Should().Throw( + "{0} cannot back an exercisable choice-argument record, so generation must fail loudly", label) + .WithMessage("*Mystery*"); } [Fact] - public void get_choice_argument_info_resolves_a_local_record_argument_to_a_nested_type() + public void GetChoiceArgumentInfo_resolves_a_local_record_argument_to_a_nested_type() { var argRecord = new DamlDataType { @@ -153,16 +172,15 @@ public void get_choice_argument_info_resolves_a_local_record_argument_to_a_neste var emitter = Emitter(context, resolver); var choice = Choice("Transfer", new DamlTypeRef(LocalPackageId, "Main", "TransferArg"), new DamlPrimitiveType(DamlPrimitive.Unit)); - var (typeName, fields, isFallback, isNested) = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); + var argument = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); - typeName.Should().Be("Transfer"); - isFallback.Should().BeFalse(); - isNested.Should().BeTrue(); - fields.Should().NotBeNull(); + argument.TypeName.Should().Be("Transfer"); + argument.IsNestedTemplateArg.Should().BeTrue(); + argument.Fields.Should().NotBeNull(); } [Fact] - public void get_choice_argument_info_resolves_the_argument_from_its_own_module_when_simple_names_collide() + public void GetChoiceArgumentInfo_resolves_the_argument_from_its_own_module_when_simple_names_collide() { var package = new DamlPackage { @@ -183,10 +201,9 @@ public void get_choice_argument_info_resolves_the_argument_from_its_own_module_w var emitter = Emitter(context, new StubResolver()); var choice = Choice("Expire", new DamlTypeRef(LocalPackageId, "Agreement", "Expire"), new DamlPrimitiveType(DamlPrimitive.Unit)); - var (_, fields, isFallback, _) = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); + var argument = emitter.GetChoiceArgumentInfo(choice, context.DataTypes); - isFallback.Should().BeFalse(); - fields.Should().ContainSingle(field => field.Name == "actor"); + argument.Fields.Should().ContainSingle(field => field.Name == "actor"); } private static DamlModule ModuleWithExpireRecord(string moduleName, DamlFieldDefinition[] expireFields) => diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterInterfaceExtensionTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterInterfaceExtensionTests.cs index e2525b6..cb4c2fa 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterInterfaceExtensionTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterInterfaceExtensionTests.cs @@ -3,9 +3,10 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; namespace Daml.Codegen.CSharp.Tests; @@ -67,20 +68,36 @@ private static DamlInterface Interface(params DamlChoice[] choices) => }; [Fact] - public void emits_an_extensions_class_with_one_async_method_per_interface_choice() + public void ChoiceEmitterInterfaceExtension_emits_an_extensions_class_with_one_async_method_per_interface_choice() { var output = EmitExtensions(Interface( Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)), Choice("Freeze", new DamlPrimitiveType(DamlPrimitive.Unit)))); output.Should().Contain("public static class IAssetExtensions"); - output.Should().Contain("public static async Task> TransferAsync("); - output.Should().Contain("public static async Task> FreezeAsync("); + output.Should().Contain("public static Task> TransferAsync("); + output.Should().Contain("public static Task> FreezeAsync("); output.Should().Contain("this ContractId contractId,"); } [Fact] - public void interface_exerciser_builds_an_interface_typed_exercise_command() + public void ChoiceEmitterInterfaceExtension_returns_the_submission_task_without_awaiting_it() + { + var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); + + output.Should().Contain( + "public static Task> TransferAsync(\n" + + " this ContractId contractId,\n" + + " ILedgerWriter client,\n" + + " SubmitterInfo submitter,"); + output.Should().Contain("return client." + TrySubmitSingleArgumentOrder + ";"); + output.Should().NotContain( + "public static async Task> TransferAsync(", + "awaiting the submission would force the exerciser to be declared async, and scoping the check to that declaration keeps an unrelated emitted member — or the word appearing in a doc comment — from failing this test"); + } + + [Fact] + public void ChoiceEmitterInterfaceExtension_interface_exerciser_builds_an_interface_typed_exercise_command() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); @@ -88,18 +105,18 @@ public void interface_exerciser_builds_an_interface_typed_exercise_command() } [Fact] - public void interface_with_no_choices_emits_no_extensions_class() + public void ChoiceEmitterInterfaceExtension_interface_with_no_choices_emits_no_extensions_class() { EmitExtensions(Interface()).Should().NotContain("public static class"); } [Fact] - public void interface_exerciser_accepts_optional_command_id_override() + public void ChoiceEmitterInterfaceExtension_interface_exerciser_accepts_optional_command_id_override() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); output.Should().Contain("CommandId? commandId = null,"); - output.Should().Contain(".WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString()));"); + output.Should().Contain(TrySubmitSingleArgumentOrder); var idxWorkflowId = output.IndexOf("string? workflowId = null,", StringComparison.Ordinal); var idxCommandId = output.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); @@ -109,12 +126,12 @@ public void interface_exerciser_accepts_optional_command_id_override() } [Fact] - public void interface_exerciser_forwards_optional_timeout() + public void ChoiceEmitterInterfaceExtension_interface_exerciser_forwards_optional_timeout() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); output.Should().Contain("TimeSpan? timeout = null,"); - output.Should().Contain("client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken)"); + output.Should().Contain("client." + TrySubmitSingleArgumentOrder); var idxCommandId = output.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); var idxTimeout = output.IndexOf("TimeSpan? timeout = null,", StringComparison.Ordinal); @@ -124,7 +141,7 @@ public void interface_exerciser_forwards_optional_timeout() } [Fact] - public void interface_choice_emits_a_command_builder_that_returns_an_exercise_command() + public void ChoiceEmitterInterfaceExtension_interface_choice_emits_a_command_builder_that_returns_an_exercise_command() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); @@ -134,17 +151,17 @@ public void interface_choice_emits_a_command_builder_that_returns_an_exercise_co } [Fact] - public void interface_choice_async_method_delegates_to_the_command_builder_instead_of_building_inline() + public void ChoiceEmitterInterfaceExtension_interface_choice_async_method_delegates_to_the_command_builder_instead_of_building_inline() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlPrimitiveType(DamlPrimitive.Unit)))); output.Should().Contain("var command = contractId.TransferCommand();"); - output.Should().Contain("var submission = CommandsSubmission.Single(command)"); + output.Should().Contain("client." + TrySubmitSingleArgumentOrder); output.Should().NotContain("var command = ExerciseCommand.ForInterface(contractId, new ChoiceName(\"Transfer\")"); } [Fact] - public void interface_choice_command_builder_accepts_the_typed_argument_when_the_choice_has_one() + public void ChoiceEmitterInterfaceExtension_interface_choice_command_builder_accepts_the_typed_argument_when_the_choice_has_one() { var output = EmitExtensions(Interface(Choice("Transfer", new DamlTypeRef(LocalPackageId, "Main", "TransferArg")))); diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterNonContractExerciserTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterNonContractExerciserTests.cs index 845d883..9fbfb64 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterNonContractExerciserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterNonContractExerciserTests.cs @@ -3,9 +3,10 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; namespace Daml.Codegen.CSharp.Tests; @@ -57,7 +58,6 @@ private static DamlTemplate Template(params DamlChoice[] choices) => new() { Name = "Vault", - Fields = [], Choices = choices, Signatories = DamlPartyAnalysis.Dynamic, Observers = DamlPartyAnalysis.Dynamic, @@ -76,7 +76,7 @@ private static string Emit(DamlTemplate template) } [Fact] - public void value_returning_choice_emits_a_typed_async_exerciser_over_the_return_type() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_emits_a_typed_async_exerciser_over_the_return_type() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); @@ -86,7 +86,7 @@ public void value_returning_choice_emits_a_typed_async_exerciser_over_the_return } [Fact] - public void value_returning_choice_emits_an_exercised_events_projector() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_emits_an_exercised_events_projector() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); @@ -95,7 +95,7 @@ public void value_returning_choice_emits_an_exercised_events_projector() } [Fact] - public void unit_returning_choice_uses_the_stdlib_unit_decoder_path() + public void ChoiceEmitterNonContractExerciser_unit_returning_choice_uses_the_stdlib_unit_decoder_path() { var output = Emit(Template(Choice("Touch", new DamlPrimitiveType(DamlPrimitive.Unit)))); @@ -104,7 +104,7 @@ public void unit_returning_choice_uses_the_stdlib_unit_decoder_path() } [Fact] - public void contract_id_returning_choice_is_not_handled_here() + public void ChoiceEmitterNonContractExerciser_contract_id_returning_choice_is_not_handled_here() { var cidReturn = new DamlTypeApp( new DamlPrimitiveType(DamlPrimitive.ContractId), @@ -116,12 +116,12 @@ public void contract_id_returning_choice_is_not_handled_here() } [Fact] - public void value_returning_choice_exerciser_accepts_optional_command_id_override() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_exerciser_accepts_optional_command_id_override() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); output.Should().Contain("CommandId? commandId = null,"); - output.Should().Contain(".WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString()));"); + output.Should().Contain(TrySubmitSingleArgumentOrder); var idxWorkflowId = output.IndexOf("string? workflowId = null,", StringComparison.Ordinal); var idxCommandId = output.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); @@ -131,12 +131,12 @@ public void value_returning_choice_exerciser_accepts_optional_command_id_overrid } [Fact] - public void value_returning_choice_exerciser_forwards_optional_timeout() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_exerciser_forwards_optional_timeout() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); output.Should().Contain("TimeSpan? timeout = null,"); - output.Should().Contain("client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken)"); + output.Should().Contain("client." + TrySubmitSingleArgumentOrder); var idxCommandId = output.IndexOf("CommandId? commandId = null,", StringComparison.Ordinal); var idxTimeout = output.IndexOf("TimeSpan? timeout = null,", StringComparison.Ordinal); @@ -146,7 +146,7 @@ public void value_returning_choice_exerciser_forwards_optional_timeout() } [Fact] - public void value_returning_choice_emits_a_command_builder_that_returns_an_exercise_command() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_emits_a_command_builder_that_returns_an_exercise_command() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); @@ -156,12 +156,12 @@ public void value_returning_choice_emits_a_command_builder_that_returns_an_exerc } [Fact] - public void value_returning_choice_async_method_delegates_to_the_command_builder_instead_of_building_inline() + public void ChoiceEmitterNonContractExerciser_value_returning_choice_async_method_delegates_to_the_command_builder_instead_of_building_inline() { var output = Emit(Template(Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Numeric)))); output.Should().Contain("var command = contractId.QuoteCommand();"); - output.Should().Contain("var submission = CommandsSubmission.Single(command)"); + output.Should().Contain("client." + TrySubmitSingleArgumentOrder); const string inlineConstructionMarker = "var command = new ExerciseCommand("; output.Should().NotContain(inlineConstructionMarker); diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterUnitReturnExerciserTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterUnitReturnExerciserTests.cs index a5f1b98..85fe84f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterUnitReturnExerciserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterUnitReturnExerciserTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -56,7 +56,6 @@ private static DamlTemplate Template(params DamlChoice[] choices) => new() { Name = "Sink", - Fields = [], Choices = choices, }; @@ -73,7 +72,7 @@ private static (string Code, IReadOnlyCollection Usings) EmitNonContract } [Fact] - public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_unit_returning_choice() + public void ChoiceEmitterUnitReturnExerciser_non_contract_exerciser_emits_stdlib_unit_wrapper_for_unit_returning_choice() { var (code, usings) = EmitNonContract(Template(Choice("DoNothing", new DamlPrimitiveType(DamlPrimitive.Unit)))); @@ -83,7 +82,7 @@ public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_unit_returning_ } [Fact] - public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_optional_unit_in_signature_and_decoder() + public void ChoiceEmitterUnitReturnExerciser_non_contract_exerciser_emits_stdlib_unit_wrapper_for_optional_unit_in_signature_and_decoder() { var optionalUnit = new DamlTypeApp( new DamlPrimitiveType(DamlPrimitive.Optional), @@ -99,7 +98,7 @@ public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_optional_unit_i } [Fact] - public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_list_of_unit() + public void ChoiceEmitterUnitReturnExerciser_non_contract_exerciser_emits_stdlib_unit_wrapper_for_list_of_unit() { var listOfUnit = new DamlTypeApp( new DamlPrimitiveType(DamlPrimitive.List), @@ -114,7 +113,7 @@ public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_list_of_unit() } [Fact] - public void non_contract_exerciser_emits_stdlib_unit_wrapper_for_textmap_of_unit() + public void ChoiceEmitterUnitReturnExerciser_non_contract_exerciser_emits_stdlib_unit_wrapper_for_textmap_of_unit() { var mapOfUnit = new DamlTypeApp( new DamlPrimitiveType(DamlPrimitive.TextMap), diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterValueReturnExerciserTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterValueReturnExerciserTests.cs index 1cd9286..b742f5c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterValueReturnExerciserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceEmitterValueReturnExerciserTests.cs @@ -3,9 +3,10 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; namespace Daml.Codegen.CSharp.Tests; @@ -55,7 +56,6 @@ private static DamlTemplate Template(string name, params DamlChoice[] choices) = new() { Name = name, - Fields = [], Choices = choices, }; @@ -72,7 +72,7 @@ private static string EmitNonContract(DamlTemplate template) } [Fact] - public void non_contract_exerciser_emits_typed_wrapper_delegating_to_ProjectCommitted_with_filtering_projector_for_decimal_return() + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_emits_typed_wrapper_delegating_to_ProjectCommitted_with_filtering_projector_for_decimal_return() { var template = Template( "Oracle", @@ -84,7 +84,8 @@ public void non_contract_exerciser_emits_typed_wrapper_delegating_to_ProjectComm output.Should().Contain("public static async Task> GetTrailingTwapAsync("); output.Should().Contain("this ContractId contractId,"); output.Should().Contain("ILedgerWriter client,"); - output.Should().Contain(".TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken)"); + output.Should().Contain("SubmitterInfo submitter,"); + output.Should().Contain("client." + TrySubmitSingleArgumentOrder); output.Should().Contain("return outcome.ProjectCommitted(tx => ProjectGetTrailingTwapResult(tx, contractId.Value));"); output.Should().NotContain("Unhandled outcome"); output.Should().NotContain("ExerciseOutcome.DamlError"); @@ -95,7 +96,7 @@ public void non_contract_exerciser_emits_typed_wrapper_delegating_to_ProjectComm } [Fact] - public void non_contract_exerciser_emits_async_wrapper_for_record_returning_choice() + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_emits_async_wrapper_for_record_returning_choice() { var template = Template( "Reporter", @@ -108,7 +109,7 @@ public void non_contract_exerciser_emits_async_wrapper_for_record_returning_choi } [Fact] - public void non_contract_exerciser_emits_async_wrapper_for_list_returning_choice() + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_emits_async_wrapper_for_list_returning_choice() { var template = Template( "Oracle", @@ -123,20 +124,19 @@ public void non_contract_exerciser_emits_async_wrapper_for_list_returning_choice } [Fact] - public void non_contract_exerciser_skips_choice_with_fallback_argument_shape() + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_throws_for_an_unmappable_choice_argument_shape() { var template = Template( "Trader", Choice("Quote", new DamlPrimitiveType(DamlPrimitive.Text), new DamlPrimitiveType(DamlPrimitive.Numeric))); - var output = EmitNonContract(template); - - output.Should().NotContain("TraderNonContractExtensions"); - output.Should().NotContain("QuoteAsync("); + FluentActions.Invoking(() => EmitNonContract(template)) + .Should().Throw() + .WithMessage("*Quote*"); } [Fact] - public void non_contract_exerciser_throws_for_nested_optional_return_type() + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_emits_the_wrapper_for_a_nested_optional_return_type() { var template = Template( "Sink", @@ -147,9 +147,40 @@ public void non_contract_exerciser_throws_for_nested_optional_return_type() new DamlPrimitiveType(DamlPrimitive.Optional), [new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [new DamlPrimitiveType(DamlPrimitive.Text)])]))); - var act = () => EmitNonContract(template); + var emitted = EmitNonContract(template); + + emitted.Should().Contain("Optional>"); + emitted.Should().NotContain("string??"); + } + + [Fact] + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_decodes_a_nested_optional_of_unit_return() + { + var template = Template( + "Sink", + Choice( + "MaybeMaybeUnit", + new DamlPrimitiveType(DamlPrimitive.Unit), + new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.Optional), + [new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [new DamlPrimitiveType(DamlPrimitive.Unit)])]))); + + var emitted = EmitNonContract(template); + + emitted.Should().Contain("FromChainValue("); + emitted.Should().NotContain(".AsOptional().Value!.AsOptional()"); + } + + [Fact] + public void ChoiceEmitterValueReturnExerciser_non_contract_exerciser_emits_the_wrapper_for_an_optional_type_variable_return() + { + var template = Template( + "Sink", + Choice( + "MaybeOf", + new DamlPrimitiveType(DamlPrimitive.Unit), + new DamlTypeApp(new DamlPrimitiveType(DamlPrimitive.Optional), [new DamlTypeVar("a")]))); - act.Should().Throw() - .WithMessage("*nested Optional*Optional (Optional t)*"); + EmitNonContract(template).Should().Contain("Optional"); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/ChoiceResultStructTests.cs b/tests/Daml.Codegen.CSharp.Tests/ChoiceResultStructTests.cs index fe41a2c..223cfb8 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ChoiceResultStructTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ChoiceResultStructTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; using Xunit; @@ -63,7 +63,6 @@ private static DamlTemplate Template( new() { Name = name, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -103,7 +102,6 @@ private static DamlModule ModuleWith( templates.Add(new DamlTemplate { Name = sibling, - Fields = [], Choices = [] }); } @@ -140,7 +138,7 @@ public void Generate_should_emit_result_struct_for_single_create_tuple_return() var code = GenerateAndReadTemplate(module, "Agreement"); - code.Should().Contain("public sealed record ExecuteSwapResult(ContractId Agreement, ContractId SwapRecord)"); + code.Should().Contain("public sealed record ExecuteSwapResult(\n ContractId Agreement,\n ContractId SwapRecord\n)"); code.Should().Contain("public static ExerciseOutcome FromCreatedContracts(IEnumerable created)"); } @@ -157,7 +155,7 @@ public void Generate_should_emit_optional_field_for_optional_contract_id() var code = GenerateAndReadTemplate(module, "Agreement"); - code.Should().Contain("public sealed record ExecuteSwapResult(ContractId Agreement, ContractId? AgreementRecord)"); + code.Should().Contain("public sealed record ExecuteSwapResult(\n ContractId Agreement,\n ContractId? AgreementRecord\n)"); } [Fact] @@ -170,7 +168,7 @@ public void Generate_should_emit_list_field_for_list_of_contract_ids() var code = GenerateAndReadTemplate(module, "Factory"); - code.Should().Contain("public sealed record BulkCreateResult(IReadOnlyList> Item)"); + code.Should().Contain("public sealed record BulkCreateResult(\n IReadOnlyList> Item\n)"); } [Fact] @@ -183,7 +181,7 @@ public void Generate_should_emit_single_create_alias_for_bare_contract_id_return var code = GenerateAndReadTemplate(module, "Parent"); - code.Should().Contain("public sealed record CreateChildResult(ContractId Child)"); + code.Should().Contain("public sealed record CreateChildResult(\n ContractId Child\n)"); code.Should().Contain("public static ExerciseOutcome FromCreatedContracts(IEnumerable created)"); } @@ -231,7 +229,7 @@ public void Generate_should_disambiguate_field_names_for_duplicate_templates_in_ var code = GenerateAndReadTemplate(module, "Splitter"); // First slot keeps the bare name; second gets a numeric suffix. - code.Should().Contain("public sealed record SplitResult(ContractId Half, ContractId Half2)"); + code.Should().Contain("public sealed record SplitResult(\n ContractId Half,\n ContractId Half2\n)"); } [Fact] @@ -250,10 +248,11 @@ public void Generate_should_emit_three_field_struct_for_three_tuple_with_optiona var code = GenerateAndReadTemplate(module, "Agreement"); code.Should().Contain( - "public sealed record ExecuteSwapResult(" - + "ContractId Agreement, " - + "ContractId SwapRecord, " - + "ContractId? AgreementRecord)"); + "public sealed record ExecuteSwapResult(\n" + + " ContractId Agreement,\n" + + " ContractId SwapRecord,\n" + + " ContractId? AgreementRecord\n" + + ")"); } [Fact] @@ -368,13 +367,11 @@ public void Generate_should_not_share_a_bucket_between_a_template_and_an_interfa new DamlTemplate { Name = "IFactory", - Fields = [], Choices = [], }, new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -395,8 +392,8 @@ public void Generate_should_not_share_a_bucket_between_a_template_and_an_interfa Name = "Vault", Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), }, - // Interface marker `Factory` also surfaces as a serializable placeholder - // record of the same name — this is what flags the type as an interface. + // The LF record LF emits beside interface `Factory` — this is what flags + // the type as an interface, even though no C# is emitted for it. new DamlDataType { Name = "Factory", Definition = new DamlRecordDefinition([]) }, ], Interfaces = [new DamlInterface { Name = "Factory", Choices = [], ViewType = null }], @@ -412,24 +409,18 @@ public void Generate_should_not_share_a_bucket_between_a_template_and_an_interfa // One branch matches the template by TemplateId, the other matches the // interface by InterfaceIds — neither slot silently inherits the other's branch. - // `Factory` is a local interface ref, so RecordEmitter's throwing placeholder - // stub (not a fully-emitted marker) backs it — the interface branch must match - // via string literals, not a generated InterfaceId symbol. code.Should().Contain("item.InterfaceIds.Any(interfaceId =>"); - code.Should().Contain("string.Equals(interfaceId.ModuleName, \"Test.Module\", StringComparison.Ordinal)"); - code.Should().Contain("string.Equals(interfaceId.EntityName, \"Factory\", StringComparison.Ordinal)"); + code.Should().Contain("string.Equals(interfaceId.ModuleName, global::Test.Package.IFactory_.InterfaceId.ModuleName, StringComparison.Ordinal)"); + code.Should().Contain("string.Equals(interfaceId.EntityName, global::Test.Package.IFactory_.InterfaceId.EntityName, StringComparison.Ordinal)"); code.Should().Contain("string.Equals(item.TemplateId.ModuleName, global::Test.Package.IFactory.TemplateId.ModuleName, StringComparison.Ordinal)"); - code.Should().NotContain("InterfaceId.ModuleName"); - code.Should().NotContain("InterfaceId.EntityName"); } [Fact] public void Generate_should_match_a_foreign_interface_typed_slot_via_its_generated_InterfaceId_symbol() { - // Unlike a local interface (matched via string literals, see the test above — - // RecordEmitter's placeholder stub for local interfaces has no InterfaceId - // member), a foreign interface's marker is a fully-emitted symbol carrying a - // public InterfaceId. The projector must match on that symbol, not literals. + // A foreign interface's marker is emitted by its own package and carries a + // public InterfaceId, exactly as a local one does. The projector must match on + // that symbol, qualified into the foreign namespace. const string ForeignPackageId = "foreign-pkg-id"; var foreignModule = new DamlModule { diff --git a/tests/Daml.Codegen.CSharp.Tests/CliExitCodeTests.cs b/tests/Daml.Codegen.CSharp.Tests/CliExitCodeTests.cs deleted file mode 100644 index bfd961f..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/CliExitCodeTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -using Daml.Codegen.CSharp.Cli; -using AwesomeAssertions; -using Xunit; - -namespace Daml.Codegen.CSharp.Tests; - -public class CliExitCodeTests -{ - [Fact] - public async Task no_args_returns_nonzero_exit_code() - { - var exit = await Program.Main([]); - exit.Should().NotBe(0, - "the CLI must fail loudly when given neither --intermediate nor any positional dar-files"); - } - - [Fact] - public async Task only_unknown_args_returns_nonzero_exit_code() - { - var exit = await Program.Main(["--unknown-flag"]); - exit.Should().NotBe(0, - "System.CommandLine should surface a parse error and the action must not paper over it"); - } - - [Fact] - public async Task no_args_returns_exit_code_one() - { - var exit = await Program.Main([]); - exit.Should().Be(1, - "RunCodegen returns 1 on the missing-args path; an exact-value assertion catches SetAction overload misbinding (Task -> Task) that the looser !=0 check would miss if the int got coerced to 0"); - } - - [Fact] - public async Task empty_target_framework_returns_nonzero_exit_code() - { - var exit = await Program.Main(["--target-framework", ""]); - exit.Should().NotBe(0, - "--target-framework rejects empty/whitespace-only strings at the CLI boundary; an empty TFM produces a broken .csproj that fails late with a confusing dotnet error"); - } - - [Fact] - public async Task whitespace_target_framework_returns_nonzero_exit_code() - { - var exit = await Program.Main(["--target-framework", " "]); - exit.Should().NotBe(0, - "--target-framework rejects whitespace-only values at the CLI boundary"); - } - - [Fact] - public async Task empty_runtime_version_returns_nonzero_exit_code() - { - var exit = await Program.Main(["--runtime-version", ""]); - exit.Should().NotBe(0, - "--runtime-version rejects empty/whitespace-only strings when explicitly supplied; an empty version string breaks the generated PackageReference attribute"); - } - - [Fact] - public async Task whitespace_runtime_version_returns_nonzero_exit_code() - { - var exit = await Program.Main(["--runtime-version", " "]); - exit.Should().NotBe(0, - "--runtime-version rejects whitespace-only values when explicitly supplied"); - } -} diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Choices.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Choices.cs index 98460c6..70196c8 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Choices.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Choices.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -13,58 +13,6 @@ public partial class CodeGenEdgeCaseTests { #region Choice with Various Argument Types - [Fact] - public void Generate_should_handle_choice_with_nested_argument_fallback() - { - // Arrange - A choice with an argument type that's not found in data types - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "Contract", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], - Choices = - [ - new DamlChoice - { - Name = "Process", - Consuming = false, - // Primitive type that's not Unit - fallback to nested type - ArgumentType = new DamlPrimitiveType(DamlPrimitive.Text), - ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) - } - ] - } - ], - DataTypes = - [ - new DamlDataType - { - Name = "Contract", - Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) - } - ], - Interfaces = [] - }; - - var dar = CreateTestDar(module); - var generator = CreateGenerator(); - - // Act - var files = generator.Generate(dar); - var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Contract.cs", StringComparison.Ordinal)); - - // Assert - contractFile.Should().NotBeNull(); - var code = contractFile!.Content; - - // Should generate a fallback nested argument type - code.Should().Contain("ProcessArg"); - } - [Fact] public void Generate_should_handle_choice_with_party_return_type() { @@ -76,8 +24,7 @@ public void Generate_should_handle_choice_with_party_return_type() [ new DamlTemplate { - Name = "Contract", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], + Name = "Asset", Choices = [ new DamlChoice @@ -94,7 +41,7 @@ public void Generate_should_handle_choice_with_party_return_type() [ new DamlDataType { - Name = "Contract", + Name = "Asset", Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) } ], @@ -106,13 +53,13 @@ public void Generate_should_handle_choice_with_party_return_type() // Act var files = generator.Generate(dar); - var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Contract.cs", StringComparison.Ordinal)); + var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Asset.cs", StringComparison.Ordinal)); // Assert contractFile.Should().NotBeNull(); var code = contractFile!.Content; - code.Should().Contain("Choice ChoiceGetOwner"); + code.Should().Contain("Choice ChoiceGetOwner"); code.Should().Contain("ResultDecoder = val => Party.FromDamlValue(val.As())"); } @@ -127,8 +74,7 @@ public void Generate_should_handle_choice_with_complex_return_type() [ new DamlTemplate { - Name = "Contract", - Fields = [new DamlFieldDefinition("data", new DamlPrimitiveType(DamlPrimitive.Text))], + Name = "Asset", Choices = [ new DamlChoice @@ -148,7 +94,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)]) [ new DamlDataType { - Name = "Contract", + Name = "Asset", Definition = new DamlRecordDefinition([new DamlFieldDefinition("data", new DamlPrimitiveType(DamlPrimitive.Text))]) } ], @@ -160,7 +106,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)]) // Act var files = generator.Generate(dar); - var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Contract.cs", StringComparison.Ordinal)); + var contractFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Asset.cs", StringComparison.Ordinal)); // Assert contractFile.Should().NotBeNull(); @@ -169,7 +115,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)]) // Complex types decode element-by-element through the field-conversion helper. // The previous expectation `IReadOnlyList.FromRecord(...)` was a known // codegen bug — IReadOnlyList has no FromRecord, so it never compiled. - code.Should().Contain("Choice>"); + code.Should().Contain("Choice>"); code.Should().Contain("ResultDecoder = val => (IReadOnlyList)val.As().Values.Select(x => x.As().Value).ToList()"); } @@ -178,9 +124,9 @@ [new DamlPrimitiveType(DamlPrimitive.Text)]) #region Template Gets Fields from Data Type [Fact] - public void Generate_should_get_fields_from_data_type_when_template_has_none() + public void Generate_should_get_fields_from_the_same_named_data_type() { - // Arrange - Template with no fields, fields come from corresponding data type + // Arrange - fields come from the corresponding data type var module = new DamlModule { Name = "Test.Module", @@ -189,7 +135,6 @@ public void Generate_should_get_fields_from_data_type_when_template_has_none() new DamlTemplate { Name = "Asset", - Fields = [], // Empty fields Choices = [] } ], @@ -240,7 +185,6 @@ public void Generate_should_not_create_separate_data_type_for_template() new DamlTemplate { Name = "Token", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], @@ -276,7 +220,7 @@ public void Generate_should_not_create_separate_data_type_for_template() // OtherType should exist as a data type var otherTypeFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("OtherType.cs", StringComparison.Ordinal)); otherTypeFile.Should().NotBeNull(); - otherTypeFile!.Content.Should().Contain(": IDamlRecord"); + otherTypeFile!.Content.Should().Contain(": IDamlRecord"); } #endregion diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Conversions.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Conversions.cs index ce57b0e..0b3ca6b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Conversions.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Conversions.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -113,11 +113,8 @@ public void Generate_dispatches_same_named_token_field_by_module_qualifier( #region Optional Field With C-Sharp Keyword Name [Fact] - public void Generate_should_strip_at_prefix_in_Optional_temporary_when_field_name_is_csharp_keyword() + public void Generate_should_pascal_case_an_Optional_field_named_after_a_csharp_keyword() { - // Arrange — `lock` is a C# keyword. The sanitizer escapes the field name to - // `@lock`, but the local-variable binding `__@lock` is invalid; the codegen - // must trim the `@` for the local name only. var module = new DamlModule { Name = "App.Module", @@ -141,14 +138,19 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])) var dar = CreateTestDar(module); var generator = CreateGenerator(); - // Act var files = generator.Generate(dar); var locked = files.FirstOrDefault(f => f.RelativePath.EndsWith("Locked.cs", StringComparison.Ordinal)); - // Assert — local variable in the `is { } __X` pattern is `__lock`, not `__@lock`. locked.Should().NotBeNull(); - locked!.Content.Should().Contain("@lock is { } __lock ?"); - locked.Content.Should().NotContain("__@lock"); + locked!.Content.Should().Contain( + "Lock is { } __Lock ?", + "the member and the local it binds both PascalCase, so neither carries an escape"); + locked.Content.Should().NotContain( + "@lock", + "PascalCasing lifts the name clear of the keyword before the escape is considered"); + locked.Content.Should().Contain( + "DamlFieldAttribute(\"lock\")", + "the Daml wire name keeps the keyword spelling the ledger uses"); } #endregion diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.ForeignPackages.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.ForeignPackages.cs index fb8f961..5bb8118 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.ForeignPackages.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.ForeignPackages.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; @@ -138,8 +138,8 @@ public void Generate_should_emit_PackageReference_for_each_foreign_package_refer // Assert csproj.Should().NotBeNull(); - csproj!.Content.Should().Contain(""); - csproj.Content.Should().Contain(""); + csproj!.Content.Should().Contain(""); + csproj.Content.Should().Contain(""); } [Fact] @@ -232,7 +232,6 @@ public void Generate_should_map_Archive_choice_to_DamlUnit_when_template_package new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfacePlaceholders.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfaceShadowedRecords.cs similarity index 67% rename from tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfacePlaceholders.cs rename to tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfaceShadowedRecords.cs index 440060d..9dfa036 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfacePlaceholders.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.InterfaceShadowedRecords.cs @@ -1,30 +1,28 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; namespace Daml.Codegen.CSharp.Tests; public partial class CodeGenEdgeCaseTests { - #region Interface Placeholder Tests + #region Interface-shadowed record tests // Daml-LF emits a same-named empty record for every `interface I where ...` - // declaration. Those records are the phantom type parameter for `ContractId I`, - // and the codegen emits them as `: ITemplate` with throwing static metadata so - // `ContractId` keeps satisfying the runtime's `where T : ITemplate` constraint - // while loudly failing any caller that tries to read template metadata directly. - // See WriteInterfacePlaceholderRecord in CSharpCodeGenerator for the rationale. + // declaration. The codegen emits no C# for those records: the interface's marker + // carries the type's identity, and `ContractId` serves the contract-id + // fields and choice extensions that would otherwise need one. [Fact] - public void Generate_should_emit_interface_placeholder_record_as_ITemplate_with_throwing_stubs() + public void Generate_should_not_emit_a_record_for_an_interface_shadowed_declaration() { - // Arrange — declare an interface and the LF placeholder record that always - // accompanies it. + // Arrange — declare an interface and the LF record that always accompanies it. var module = new DamlModule { Name = "Test.Holding", @@ -52,69 +50,18 @@ public void Generate_should_emit_interface_placeholder_record_as_ITemplate_with_ var generator = CreateGenerator(); // Act - var files = generator.Generate(dar); - var holdingRecord = files.FirstOrDefault(f => - f.RelativePath.EndsWith("Holding.cs", StringComparison.Ordinal) - && !f.RelativePath.Contains("IHolding", StringComparison.Ordinal)); + var files = generator.Generate(dar).ToList(); // Assert - holdingRecord.Should().NotBeNull("the LF placeholder record should be emitted alongside the interface"); - var code = holdingRecord!.Content; - - // Sealed record implementing ITemplate (NOT just IDamlRecord) - code.Should().Contain("public sealed record Holding : ITemplate"); - // Throwing static metadata — InvalidOperationException with the qualified Daml name in the message - code.Should().Contain("public static Identifier TemplateId =>"); - code.Should().Contain("throw new InvalidOperationException(\"'Holding' is the C# placeholder for the Daml interface 'Test.Holding:Holding'"); - code.Should().Contain("public static string PackageId =>"); - code.Should().Contain("public static string PackageName =>"); - code.Should().Contain("public static Version PackageVersion =>"); - // Empty ToRecord/FromRecord — placeholders carry no data - code.Should().Contain("public DamlRecord ToRecord() => DamlRecord.Create();"); - code.Should().Contain("public static Holding FromRecord(DamlRecord record) => new Holding();"); - } - - [Fact] - public void Generate_emits_throwing_daml_type_descriptor_for_placeholder() - { - var module = new DamlModule - { - Name = "Test.Holding", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Holding", - Definition = new DamlRecordDefinition([]) - } - ], - Interfaces = - [ - new DamlInterface - { - Name = "Holding", - Choices = [], - ViewType = null - } - ] - }; - - var dar = CreateTestDar(module); - var generator = CreateGenerator(); - - var files = generator.Generate(dar); - var holdingRecord = files.FirstOrDefault(f => - f.RelativePath.EndsWith("Holding.cs", StringComparison.Ordinal) - && !f.RelativePath.Contains("IHolding", StringComparison.Ordinal)); - - holdingRecord.Should().NotBeNull("the LF placeholder record should be emitted alongside the interface"); - var code = holdingRecord!.Content; - - code.Should().Contain("public static DamlTypeDescriptor DamlTypeId =>"); - code.Should().Contain( - "public static DamlTypeDescriptor DamlTypeId =>\n throw new InvalidOperationException(\"'Holding' is the C# placeholder for the Daml interface 'Test.Holding:Holding'"); - code.Should().NotContain("public static DamlTypeDescriptor DamlTypeId { get; }"); + files.Should().NotContain( + f => f.RelativePath.EndsWith("/Holding.cs", StringComparison.Ordinal), + "the marker carries the interface's identity, so the LF record beside it has nothing left to emit"); + files.Should().Contain( + f => f.RelativePath.EndsWith("/IHolding.cs", StringComparison.Ordinal), + "dropping the record must not drop the marker with it"); + files.Should().NotContain( + f => f.Content.Contains("record Holding :", StringComparison.Ordinal), + "no other file may pick the declaration up either"); } [Fact] @@ -152,17 +99,17 @@ public void Generate_should_emit_regular_record_when_no_matching_interface_in_sa // Assert holdingFile.Should().NotBeNull(); var code = holdingFile!.Content; - code.Should().Contain("public sealed record Holding([property: DamlFieldAttribute(\"amount\")] decimal Amount) : IDamlRecord"); + code.Should().Contain("public sealed record Holding(\n [property: DamlFieldAttribute(\"amount\")] decimal Amount\n) : IDamlRecord"); code.Should().NotContain(": ITemplate"); code.Should().NotContain("InvalidOperationException"); } [Fact] - public void Generate_should_distinguish_interface_placeholders_across_modules_with_same_name() + public void Generate_should_distinguish_interface_shadowed_records_across_modules_with_same_name() { - // Arrange — module A has an interface `Token` (so the same-named record is a - // placeholder); module B has an unrelated record `Token`. Each module must be - // emitted with its own treatment. + // Arrange — module A has an interface `Token` (so the same-named record is + // shadowed and not emitted); module B has an unrelated record `Token` that + // must still be emitted. var modA = new DamlModule { Name = "App.A", @@ -208,21 +155,19 @@ public void Generate_should_distinguish_interface_placeholders_across_modules_wi // Act var files = generator.Generate(dar).ToList(); - // The codegen flattens all modules into a single namespace derived from the - // package name, so file paths collide on simple names. Both `Token.cs` files - // share a path; the latter overwrites the former. Verify by content instead: - // the two Token records produce different bodies, and only one of them - // becomes the placeholder in the emitted set. - var tokenFiles = files.Where(f => f.RelativePath.EndsWith("Token.cs", StringComparison.Ordinal)).ToList(); - - // Assert — both Token records exist (last-wins file path is acceptable here: - // the regular record keeps its IDamlRecord shape, and the placeholder keeps - // its ITemplate shape, with their qualifying logic running independently). - tokenFiles.Should().NotBeEmpty(); - var hasPlaceholder = tokenFiles.Any(f => f.Content.Contains("public sealed record Token : ITemplate", StringComparison.Ordinal)); - var hasRegular = tokenFiles.Any(f => f.Content.Contains("public sealed record Token([property: DamlFieldAttribute(\"symbol\")] string Symbol) : IDamlRecord", StringComparison.Ordinal)); - hasPlaceholder.Should().BeTrue("module A's Token must be emitted as the interface placeholder"); - hasRegular.Should().BeTrue("module B's Token must keep its IDamlRecord regular-record shape"); + var tokenFiles = files.Where(f => f.RelativePath.EndsWith("/Token.cs", StringComparison.Ordinal)).ToList(); + + // Assert — the codegen flattens all modules into one namespace, so the two + // Token declarations would collide on a file path. Skipping the shadowed one + // leaves exactly module B's regular record. + tokenFiles.Should().ContainSingle( + "module A's Token is shadowed by its interface and emits nothing, so only module B's is left"); + tokenFiles[0].Content.Should().Contain( + "public sealed record Token(\n [property: DamlFieldAttribute(\"symbol\")] string Symbol\n) : IDamlRecord", + "module B's Token must keep its IDamlRecord regular-record shape"); + files.Should().Contain( + f => f.RelativePath.EndsWith("/IToken.cs", StringComparison.Ordinal), + "module A's interface marker is still emitted"); } // ------------------------------------------------------------------- @@ -307,26 +252,26 @@ public void Generate_should_emit_extension_class_for_interface_choices() // Sibling extensions class with one method per choice code.Should().Contain("public static class IHoldingExtensions"); - // Record-argument choice: async signature returning ExerciseOutcome + // Record-argument choice: signature returning ExerciseOutcome // (mirrors the concrete-template Async shape). Interface choices // surface the raw ExerciseOutcome because the implementing // template — and therefore any typed Result projection — is unknown at // the call site. - code.Should().Contain("public static async Task> TransferAsync("); + code.Should().Contain("public static Task> TransferAsync("); code.Should().Contain("this ContractId contractId,"); code.Should().Contain("ILedgerWriter client,"); code.Should().Contain("Transfer argument,"); - code.Should().Contain("Party actAs,"); + code.Should().Contain("SubmitterInfo submitter,"); // Internally builds the command via the runtime ForInterface helper — the // wire-level template_id slot carries IHolding.InterfaceId, and the choice // argument is serialised via argument.ToRecord(). code.Should().Contain("ExerciseCommand.ForInterface(contractId, new ChoiceName(\"Transfer\"), argument.ToRecord())"); - // Submission is funnelled through ILedgerWriter.TrySubmitAndWaitForTransactionAsync - // — same submission path as concrete-template Async. - code.Should().Contain("await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken)"); + code.Should().Contain("client." + TrySubmitSingleArgumentOrder); // Unit-argument choice: no `argument` parameter, DamlUnit.Instance is passed - code.Should().Contain("public static async Task> LockAsync("); + code.Should().Contain( + "public static Task> LockAsync(\n this ContractId contractId,\n ILedgerWriter client,\n SubmitterInfo submitter,", + "a single-line signature assertion cannot tell one emitted parameter list from another, so it is pinned verbatim — a failure here means the exerciser signature changed or the emitter's indentation did, not that the assertion is wrong"); code.Should().Contain("ExerciseCommand.ForInterface(contractId, new ChoiceName(\"Lock\"), DamlUnit.Instance)"); } @@ -376,11 +321,12 @@ public void Generate_should_map_interface_typed_contract_id_fields_to_the_marker Name = "Test.Holding", Templates = [ - new DamlTemplate { Name = "Account", Fields = [], Choices = [] } + new DamlTemplate { Name = "Account", Choices = [] } ], DataTypes = [ new DamlDataType { Name = "Holding", Definition = new DamlRecordDefinition([]) }, + new DamlDataType { Name = "Account", Definition = new DamlRecordDefinition([]) }, new DamlDataType { Name = "Wallet", @@ -425,7 +371,7 @@ [new DamlTypeRef("", "Test.Holding", "Account")])) code.Should().Contain("ContractId"); code.Should().Contain("IReadOnlyList>"); code.Should().Contain("ContractId?", "an Optional interface contract id maps to the nullable marker"); - code.Should().NotContain("ContractId", "the interface placeholder must never appear as a contract-id type argument"); + code.Should().NotContain("ContractId", "the interface-shadowed record name must never appear as a contract-id type argument"); code.Should().Contain("ContractId", "contract ids to a real template are unchanged"); } diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Serialization.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Serialization.cs index 6b69750..9c863ee 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Serialization.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.Serialization.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -207,7 +207,7 @@ public void Generate_should_handle_empty_record() var code = emptyFile!.Content; // Record with no primary constructor parameters - code.Should().Contain("public sealed record EmptyRecord : IDamlRecord"); + code.Should().Contain("public sealed record EmptyRecord : IDamlRecord"); code.Should().Contain("DamlRecord.Create("); } diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.StdlibRouting.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.StdlibRouting.cs index 3cf4699..c0e6a4c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.StdlibRouting.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.StdlibRouting.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; @@ -11,377 +11,216 @@ namespace Daml.Codegen.CSharp.Tests; public partial class CodeGenEdgeCaseTests { - #region Stdlib Type Routing Tests - - [Fact] - public void Generate_should_route_stdlib_RelTime_through_Daml_Runtime_Stdlib_namespace() - { - // Arrange — daml-stdlib-DA-Time-Types is a stdlib package whose `RelTime` - // type the codegen maps to the hand-coded Daml.Runtime.Stdlib.RelTime. - // Stdlib refs must NOT produce a on the generated csproj. - const string StdlibTimePackageId = "stdlib-time-id"; - - var stdlibModule = new DamlModule + private sealed record StdlibRoutingCase( + string StdlibPackageId, + string StdlibPackageName, + string StdlibModuleName, + DamlDataType StdlibDataType, + string ConsumerTypeName, + string ConsumerFieldName, + DamlType ConsumerFieldType, + bool GenerateProjectFile, + IReadOnlyList ExpectedInConsumer, + IReadOnlyList ExpectedAbsentFromConsumer, + IReadOnlyList ExpectedAbsentFromProjectFile); + + private static DamlDataType Tuple2DataType() => + new() { - Name = "DA.Time.Types", - Templates = [], - DataTypes = + Name = "Tuple2", + TypeParams = ["a", "b"], + Definition = new DamlRecordDefinition( [ - new DamlDataType - { - Name = "RelTime", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("microseconds", new DamlPrimitiveType(DamlPrimitive.Int64)) - ]) - } - ], - Interfaces = [] + new DamlFieldDefinition("_1", new DamlTypeVar("a")), + new DamlFieldDefinition("_2", new DamlTypeVar("b")) + ]) }; - var stdlibPkg = CreateTestPackage(StdlibTimePackageId, "daml-stdlib-DA-Time-Types", stdlibModule); - var mainModule = new DamlModule - { - Name = "App.Module", - Templates = [], - DataTypes = + private static DamlType Tuple2OfIntAndText(string packageId) => + new DamlTypeApp( + new DamlTypeRef(packageId, "DA.Types", "Tuple2"), [ - new DamlDataType - { - Name = "Timer", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("d", new DamlTypeRef(StdlibTimePackageId, "DA.Time.Types", "RelTime")) - ]) - } - ], - Interfaces = [] - }; - var mainPkg = CreateTestPackage("main-pkg-id", "main-pkg", mainModule); - var dar = CreateMultiPackageDar(mainPkg, stdlibPkg); - - var options = new CodeGenOptions - { - EnableNullableReferenceTypes = true, - UseFileScopedNamespaces = true, - UseRecordTypes = true, - UsePrimaryConstructors = true, - GenerateXmlDocs = true, - GenerateProjectFile = true - }; - var generator = CreateGenerator(options); - - // Act - var files = generator.Generate(dar).ToList(); - var timer = files.FirstOrDefault(f => f.RelativePath.EndsWith("Timer.cs", StringComparison.Ordinal)); - var csproj = files.FirstOrDefault(f => f.RelativePath.EndsWith(".csproj", StringComparison.Ordinal)); + new DamlPrimitiveType(DamlPrimitive.Int64), + new DamlPrimitiveType(DamlPrimitive.Text) + ]); - // Assert - timer.Should().NotBeNull(); - timer!.Content.Should().Contain("using Daml.Runtime.Stdlib;"); - timer.Content.Should().Contain("RelTime D"); - timer.Content.Should().Contain("RelTime.FromRecord"); - // No for stdlib packages — they're served by the runtime stub. - csproj.Should().NotBeNull(); - csproj!.Content.Should().NotContain("Daml.Stdlib"); - csproj.Content.Should().NotContain("daml-stdlib"); - } - - [Fact] - public void Generate_should_route_stdlib_Tuple2_through_Daml_Runtime_Stdlib_namespace() + private static readonly Dictionary StdlibRoutingCasesByName = new() { - // Arrange — Tuple2 lives in daml-prim's DA.Types module. The codegen must - // route it to Daml.Runtime.Stdlib.Tuple2 with the parameterised arguments - // preserved, and emit delegate-based ToRecord / FromRecord. - const string DamlPrimPackageId = "daml-prim-id"; - - var stdlibModule = new DamlModule - { - Name = "DA.Types", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Tuple2", - TypeParams = ["a", "b"], - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("_1", new DamlTypeVar("a")), - new DamlFieldDefinition("_2", new DamlTypeVar("b")) - ]) - } - ], - Interfaces = [] - }; - var stdlibPkg = CreateTestPackage(DamlPrimPackageId, "daml-prim-DA-Types", stdlibModule); - - var mainModule = new DamlModule - { - Name = "App.Module", - Templates = [], - DataTypes = + ["RelTime from daml-stdlib-DA-Time-Types"] = new StdlibRoutingCase( + StdlibPackageId: "stdlib-time-id", + StdlibPackageName: "daml-stdlib-DA-Time-Types", + StdlibModuleName: "DA.Time.Types", + StdlibDataType: new DamlDataType + { + Name = "RelTime", + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition("microseconds", new DamlPrimitiveType(DamlPrimitive.Int64)) + ]) + }, + ConsumerTypeName: "Timer", + ConsumerFieldName: "d", + ConsumerFieldType: new DamlTypeRef("stdlib-time-id", "DA.Time.Types", "RelTime"), + GenerateProjectFile: true, + ExpectedInConsumer: ["using Daml.Runtime.Stdlib;", "RelTime D", "RelTime.FromRecord"], + ExpectedAbsentFromConsumer: [], + ExpectedAbsentFromProjectFile: ["Daml.Stdlib", "daml-stdlib"]), + + ["Tuple2 from daml-prim-DA-Types"] = new StdlibRoutingCase( + StdlibPackageId: "daml-prim-id", + StdlibPackageName: "daml-prim-DA-Types", + StdlibModuleName: "DA.Types", + StdlibDataType: Tuple2DataType(), + ConsumerTypeName: "Pair", + ConsumerFieldName: "p", + ConsumerFieldType: Tuple2OfIntAndText("daml-prim-id"), + GenerateProjectFile: false, + ExpectedInConsumer: [ - new DamlDataType - { - Name = "Pair", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("p", new DamlTypeApp( - new DamlTypeRef(DamlPrimPackageId, "DA.Types", "Tuple2"), - [ - new DamlPrimitiveType(DamlPrimitive.Int64), - new DamlPrimitiveType(DamlPrimitive.Text) - ])) - ]) - } + "using Daml.Runtime.Stdlib;", + "Tuple2", + "Tuple2.FromRecord(" ], - Interfaces = [] - }; - var mainPkg = CreateTestPackage("main-pkg-id", "main-pkg", mainModule); - var dar = CreateMultiPackageDar(mainPkg, stdlibPkg); - - var options = new CodeGenOptions - { - EnableNullableReferenceTypes = true, - UseFileScopedNamespaces = true, - UseRecordTypes = true, - UsePrimaryConstructors = true, - GenerateXmlDocs = true, - GenerateProjectFile = false - }; - var generator = CreateGenerator(options); - - // Act - var files = generator.Generate(dar).ToList(); - var pair = files.FirstOrDefault(f => f.RelativePath.EndsWith("Pair.cs", StringComparison.Ordinal)); - - // Assert — type uses the stdlib name, FromRecord uses delegate-based decoder. - pair.Should().NotBeNull(); - pair!.Content.Should().Contain("using Daml.Runtime.Stdlib;"); - pair.Content.Should().Contain("Tuple2"); - pair.Content.Should().Contain("Tuple2.FromRecord("); - } - - [Fact] - public void Generate_should_route_stdlib_Set_through_Daml_Runtime_Stdlib_namespace() - { - // Arrange — Set lives in daml-stdlib's DA.Set.Types module. Wire shape is - // a record wrapping `Map k ()`, exposed as Daml.Runtime.Stdlib.Set. - const string SetPackageId = "stdlib-set-types"; - - var stdlibModule = new DamlModule - { - Name = "DA.Set.Types", - Templates = [], - DataTypes = + ExpectedAbsentFromConsumer: [], + ExpectedAbsentFromProjectFile: []), + + ["Set from daml-stdlib-DA-Set-Types"] = new StdlibRoutingCase( + StdlibPackageId: "stdlib-set-types", + StdlibPackageName: "daml-stdlib-DA-Set-Types", + StdlibModuleName: "DA.Set.Types", + StdlibDataType: new DamlDataType + { + Name = "Set", + TypeParams = ["k"], + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition("map", new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.GenMap), + [new DamlTypeVar("k"), new DamlPrimitiveType(DamlPrimitive.Unit)])) + ]) + }, + ConsumerTypeName: "Roster", + ConsumerFieldName: "members", + ConsumerFieldType: new DamlTypeApp( + new DamlTypeRef("stdlib-set-types", "DA.Set.Types", "Set"), + [new DamlPrimitiveType(DamlPrimitive.Party)]), + GenerateProjectFile: false, + ExpectedInConsumer: [ - new DamlDataType - { - Name = "Set", - TypeParams = ["k"], - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("map", new DamlTypeApp( - new DamlPrimitiveType(DamlPrimitive.GenMap), - [new DamlTypeVar("k"), new DamlPrimitiveType(DamlPrimitive.Unit)])) - ]) - } + "using Daml.Runtime.Stdlib;", + "Set", + "Set.FromRecord(" ], - Interfaces = [] - }; - var stdlibPkg = CreateTestPackage(SetPackageId, "daml-stdlib-DA-Set-Types", stdlibModule); - - var mainModule = new DamlModule - { - Name = "App.Module", - Templates = [], - DataTypes = + ExpectedAbsentFromConsumer: [], + ExpectedAbsentFromProjectFile: []), + + ["Tuple2 from a user package that squats on DA.Types"] = new StdlibRoutingCase( + StdlibPackageId: "user-pkg-id", + StdlibPackageName: "my-cheeky-package", + StdlibModuleName: "DA.Types", + StdlibDataType: Tuple2DataType(), + ConsumerTypeName: "Pair", + ConsumerFieldName: "p", + ConsumerFieldType: Tuple2OfIntAndText("user-pkg-id"), + GenerateProjectFile: false, + ExpectedInConsumer: [], + ExpectedAbsentFromConsumer: ["Daml.Runtime.Stdlib.Tuple2", "using Daml.Runtime.Stdlib;"], + ExpectedAbsentFromProjectFile: []), + + ["Tuple2 from a placeholder-named prim package"] = new StdlibRoutingCase( + StdlibPackageId: "lf1x-prim-types-id", + StdlibPackageName: "-no-package-metadata", + StdlibModuleName: "DA.Types", + StdlibDataType: Tuple2DataType(), + ConsumerTypeName: "Pair", + ConsumerFieldName: "p", + ConsumerFieldType: Tuple2OfIntAndText("lf1x-prim-types-id"), + GenerateProjectFile: true, + ExpectedInConsumer: [ - new DamlDataType - { - Name = "Roster", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("members", new DamlTypeApp( - new DamlTypeRef(SetPackageId, "DA.Set.Types", "Set"), - [new DamlPrimitiveType(DamlPrimitive.Party)])) - ]) - } + "using Daml.Runtime.Stdlib;", + "Tuple2", + "Tuple2.FromRecord(" ], - Interfaces = [] - }; - var mainPkg = CreateTestPackage("main-pkg-id", "main-pkg", mainModule); - var dar = CreateMultiPackageDar(mainPkg, stdlibPkg); + ExpectedAbsentFromConsumer: ["No.Package.Metadata"], + ExpectedAbsentFromProjectFile: ["No.Package.Metadata", "PackageReference Include=\"."]), + }; - var options = new CodeGenOptions - { - EnableNullableReferenceTypes = true, - UseFileScopedNamespaces = true, - UseRecordTypes = true, - UsePrimaryConstructors = true, - GenerateXmlDocs = true, - GenerateProjectFile = false - }; - var generator = CreateGenerator(options); - - // Act - var files = generator.Generate(dar).ToList(); - var roster = files.FirstOrDefault(f => f.RelativePath.EndsWith("Roster.cs", StringComparison.Ordinal)); - - // Assert - roster.Should().NotBeNull(); - roster!.Content.Should().Contain("using Daml.Runtime.Stdlib;"); - roster.Content.Should().Contain("Set"); - roster.Content.Should().Contain("Set.FromRecord("); - } + public static TheoryData StdlibRoutingCaseNames => new(StdlibRoutingCasesByName.Keys); - [Fact] - public void Generate_should_not_route_user_defined_DA_Types_Tuple2_through_Daml_Runtime_Stdlib() + [Theory] + [MemberData(nameof(StdlibRoutingCaseNames))] + public void Generate_routes_a_referenced_type_through_Daml_Runtime_Stdlib_only_when_its_package_is_stdlib( + string caseName) { - const string UserPackageId = "user-pkg-id"; - - var userTuplesModule = new DamlModule - { - Name = "DA.Types", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Tuple2", - TypeParams = ["a", "b"], - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("_1", new DamlTypeVar("a")), - new DamlFieldDefinition("_2", new DamlTypeVar("b")) - ]) - } - ], - Interfaces = [] - }; - var userTuplesPkg = CreateTestPackage(UserPackageId, "my-cheeky-package", userTuplesModule); - - var mainModule = new DamlModule - { - Name = "App.Module", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Pair", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("p", new DamlTypeApp( - new DamlTypeRef(UserPackageId, "DA.Types", "Tuple2"), - [ - new DamlPrimitiveType(DamlPrimitive.Int64), - new DamlPrimitiveType(DamlPrimitive.Text) - ])) - ]) - } - ], - Interfaces = [] - }; - var mainPkg = CreateTestPackage("main-pkg-id", "main-pkg", mainModule); - var dar = CreateMultiPackageDar(mainPkg, userTuplesPkg); - - var options = new CodeGenOptions + var routing = StdlibRoutingCasesByName[caseName]; + + var stdlibPackage = CreateTestPackage( + routing.StdlibPackageId, + routing.StdlibPackageName, + new DamlModule + { + Name = routing.StdlibModuleName, + Templates = [], + DataTypes = [routing.StdlibDataType], + Interfaces = [] + }); + + var consumerPackage = CreateTestPackage( + "main-pkg-id", + "main-pkg", + new DamlModule + { + Name = "App.Module", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = routing.ConsumerTypeName, + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition(routing.ConsumerFieldName, routing.ConsumerFieldType) + ]) + } + ], + Interfaces = [] + }); + + var generator = CreateGenerator(new CodeGenOptions { EnableNullableReferenceTypes = true, UseFileScopedNamespaces = true, UseRecordTypes = true, UsePrimaryConstructors = true, GenerateXmlDocs = true, - GenerateProjectFile = false - }; - var generator = CreateGenerator(options); - - var files = generator.Generate(dar).ToList(); - var pair = files.FirstOrDefault(f => f.RelativePath.EndsWith("Pair.cs", StringComparison.Ordinal)); - - pair.Should().NotBeNull(); - pair!.Content.Should().NotContain("Daml.Runtime.Stdlib.Tuple2"); - pair.Content.Should().NotContain("using Daml.Runtime.Stdlib;"); - } + GenerateProjectFile = routing.GenerateProjectFile + }); - [Fact] - public void Generate_should_route_stdlib_Tuple2_to_runtime_when_package_is_placeholder_named() - { - const string PlaceholderPackageId = "lf1x-prim-types-id"; + var files = generator.Generate(CreateMultiPackageDar(consumerPackage, stdlibPackage)).ToList(); + var consumer = files.FirstOrDefault(f => + f.RelativePath.EndsWith($"{routing.ConsumerTypeName}.cs", StringComparison.Ordinal)); - var stdlibModule = new DamlModule + consumer.Should().NotBeNull(); + foreach (var expected in routing.ExpectedInConsumer) { - Name = "DA.Types", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Tuple2", - TypeParams = ["a", "b"], - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("_1", new DamlTypeVar("a")), - new DamlFieldDefinition("_2", new DamlTypeVar("b")) - ]) - } - ], - Interfaces = [] - }; - var stdlibPkg = CreateTestPackage(PlaceholderPackageId, "-no-package-metadata", stdlibModule); - - var mainModule = new DamlModule + consumer!.Content.Should().Contain(expected); + } + foreach (var absent in routing.ExpectedAbsentFromConsumer) { - Name = "App.Module", - Templates = [], - DataTypes = - [ - new DamlDataType - { - Name = "Pair", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("p", new DamlTypeApp( - new DamlTypeRef(PlaceholderPackageId, "DA.Types", "Tuple2"), - [ - new DamlPrimitiveType(DamlPrimitive.Int64), - new DamlPrimitiveType(DamlPrimitive.Text) - ])) - ]) - } - ], - Interfaces = [] - }; - var mainPkg = CreateTestPackage("main-pkg-id", "main-pkg", mainModule); - var dar = CreateMultiPackageDar(mainPkg, stdlibPkg); + consumer!.Content.Should().NotContain(absent); + } - var options = new CodeGenOptions + if (!routing.GenerateProjectFile) { - EnableNullableReferenceTypes = true, - UseFileScopedNamespaces = true, - UseRecordTypes = true, - UsePrimaryConstructors = true, - GenerateXmlDocs = true, - GenerateProjectFile = true - }; - var generator = CreateGenerator(options); - - var files = generator.Generate(dar).ToList(); - var pair = files.FirstOrDefault(f => f.RelativePath.EndsWith("Pair.cs", StringComparison.Ordinal)); - var csproj = files.FirstOrDefault(f => f.RelativePath.EndsWith(".csproj", StringComparison.Ordinal)); - - pair.Should().NotBeNull(); - pair!.Content.Should().Contain("using Daml.Runtime.Stdlib;"); - pair.Content.Should().Contain("Tuple2"); - pair.Content.Should().Contain("Tuple2.FromRecord("); - pair.Content.Should().NotContain("No.Package.Metadata"); + return; + } - csproj.Should().NotBeNull(); - csproj!.Content.Should().NotContain("No.Package.Metadata"); - csproj.Content.Should().NotContain("PackageReference Include=\"."); + var projectFile = files.FirstOrDefault(f => f.RelativePath.EndsWith(".csproj", StringComparison.Ordinal)); + projectFile.Should().NotBeNull(); + foreach (var absent in routing.ExpectedAbsentFromProjectFile) + { + projectFile!.Content.Should().NotContain(absent); + } } - - #endregion } diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.cs index 7b3c191..8bc6031 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenEdgeCaseTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; diff --git a/tests/Daml.Codegen.CSharp.Tests/CodeGenOptionsTests.cs b/tests/Daml.Codegen.CSharp.Tests/CodeGenOptionsTests.cs index 1d6e06c..f7dc831 100644 --- a/tests/Daml.Codegen.CSharp.Tests/CodeGenOptionsTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/CodeGenOptionsTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; using Xunit; @@ -71,7 +71,6 @@ private static DamlModule CreateSimpleModule(string moduleName = "Test.Module", new DamlTemplate { Name = templateName, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], diff --git a/tests/Daml.Codegen.CSharp.Tests/CodegenLoggingTests.cs b/tests/Daml.Codegen.CSharp.Tests/CodegenLoggingTests.cs new file mode 100644 index 0000000..4769640 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/CodegenLoggingTests.cs @@ -0,0 +1,98 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.CSharp.Tests.TestHelpers; +using AwesomeAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Daml.Codegen.CSharp.Tests; + +public class CodegenLoggingTests +{ + [Fact] + public void CSharpCodeGenerator_accepts_any_ilogger_implementation() + { + var captured = new CapturingLogger(); + var loggerFactory = new SingleLoggerFactory(captured); + + var generator = new CSharpCodeGenerator( + new CodeGenOptions(), loggerFactory.CreateLogger()); + + generator.Should().NotBeNull( + "CSharpCodeGenerator must accept any ILogger, not only a provider the library ships"); + } + + [Fact] + public void CSharpCodeGenerator_defaults_to_the_null_logger() + { + var generator = new CSharpCodeGenerator(new CodeGenOptions()); + + generator.Should().NotBeNull( + "omitting the logger must leave the emitter silent rather than force a host to supply one"); + } + + [Fact] + public void VerbosityConsoleLoggerProvider_gates_records_on_verbosity() + { + using var errorsOnly = new VerbosityConsoleLoggerProvider(0); + using var debug = new VerbosityConsoleLoggerProvider(3); + + var quiet = errorsOnly.CreateLogger("test"); + quiet.IsEnabled(LogLevel.Error).Should().BeTrue(); + quiet.IsEnabled(LogLevel.Warning).Should().BeFalse(); + quiet.IsEnabled(LogLevel.Information).Should().BeFalse(); + + var loud = debug.CreateLogger("test"); + loud.IsEnabled(LogLevel.Debug).Should().BeTrue(); + loud.IsEnabled(LogLevel.Trace).Should().BeFalse(); + } + + [Fact] + public void VerbosityConsoleLoggerProvider_verbosity_1_enables_warning_but_not_debug() + { + using var warningsAndAbove = new VerbosityConsoleLoggerProvider(1); + var logger = warningsAndAbove.CreateLogger("test"); + + logger.IsEnabled(LogLevel.Error).Should().BeTrue(); + logger.IsEnabled(LogLevel.Warning).Should().BeTrue(); + logger.IsEnabled(LogLevel.Information).Should().BeFalse(); + logger.IsEnabled(LogLevel.Debug).Should().BeFalse(); + } + + [Fact] + public void VerbosityConsoleLoggerProvider_verbosity_2_enables_information_but_not_debug() + { + using var informationAndAbove = new VerbosityConsoleLoggerProvider(2); + var logger = informationAndAbove.CreateLogger("test"); + + logger.IsEnabled(LogLevel.Error).Should().BeTrue(); + logger.IsEnabled(LogLevel.Warning).Should().BeTrue(); + logger.IsEnabled(LogLevel.Information).Should().BeTrue(); + logger.IsEnabled(LogLevel.Debug).Should().BeFalse(); + } + + [Fact] + public void NullLogger_swallows_every_severity() + { + var logger = NullLogger.Instance; + + logger.IsEnabled(LogLevel.Error).Should().BeFalse(); + logger.IsEnabled(LogLevel.Debug).Should().BeFalse(); + } + + private sealed class SingleLoggerFactory(ILogger logger) : ILoggerFactory + { + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) => logger; + + public void Dispose() + { + } + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/ConformancePackageContentTests.cs b/tests/Daml.Codegen.CSharp.Tests/ConformancePackageContentTests.cs deleted file mode 100644 index bbc73d7..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/ConformancePackageContentTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -using Daml.Codegen.Testing.Conformance; -using Xunit; - -namespace Daml.Codegen.CSharp.Tests; - -public class ConformancePackageContentTests -{ - [Fact] - public void OpenDar_returns_a_valid_DAR_stream() - { - using var dar = ConformanceCorpus.OpenDar(); - Assert.True(dar.Length > 1000); - - var zipLocalFileHeaderMagic = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; - Span header = stackalloc byte[4]; - Assert.Equal(4, dar.Read(header)); - Assert.Equal(zipLocalFileHeaderMagic, header.ToArray()); - } - - [Fact] - public void Embedded_resource_is_named_richtypes_dar() - { - var names = typeof(ConformanceCorpus).Assembly.GetManifestResourceNames(); - Assert.Contains("richtypes.dar", names); - } -} diff --git a/tests/Daml.Codegen.CSharp.Tests/ContractIdentifiersTests.cs b/tests/Daml.Codegen.CSharp.Tests/ContractIdentifiersTests.cs index 167aed4..a79d430 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ContractIdentifiersTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ContractIdentifiersTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -15,7 +15,7 @@ public class ContractIdentifiersTests #region Basic Generation Tests [Fact] - public void generate_should_create_contract_identifiers_file() + public void Generate_should_create_contract_identifiers_file() { // Arrange var module = new DamlModule @@ -26,7 +26,6 @@ public void generate_should_create_contract_identifiers_file() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], @@ -53,7 +52,7 @@ public void generate_should_create_contract_identifiers_file() } [Fact] - public void generate_should_create_file_in_correct_path() + public void Generate_should_create_file_in_correct_path() { // Arrange var module = new DamlModule @@ -64,11 +63,18 @@ public void generate_should_create_file_in_correct_path() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -86,7 +92,7 @@ public void generate_should_create_file_in_correct_path() } [Fact] - public void generate_should_include_all_templates_as_properties() + public void Generate_should_include_all_templates_as_properties() { // Arrange var module = new DamlModule @@ -97,17 +103,29 @@ public void generate_should_include_all_templates_as_properties() new DamlTemplate { Name = "FirstTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] }, new DamlTemplate { Name = "SecondTemplate", - Fields = [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "FirstTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + }, + new DamlDataType + { + Name = "SecondTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))]) + } + ], Interfaces = [] }; @@ -127,7 +145,7 @@ public void generate_should_include_all_templates_as_properties() } [Fact] - public void generate_should_include_templates_from_multiple_modules() + public void Generate_should_include_templates_from_multiple_modules() { // Arrange var module1 = new DamlModule @@ -138,11 +156,18 @@ public void generate_should_include_templates_from_multiple_modules() new DamlTemplate { Name = "TemplateA", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "TemplateA", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -154,11 +179,18 @@ public void generate_should_include_templates_from_multiple_modules() new DamlTemplate { Name = "TemplateB", - Fields = [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "TemplateB", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))]) + } + ], Interfaces = [] }; @@ -182,7 +214,7 @@ public void generate_should_include_templates_from_multiple_modules() #region Helper Method Tests [Fact] - public void generate_should_use_template_extensions_get_template_id() + public void Generate_should_use_template_extensions_get_template_id() { // Arrange var module = new DamlModule @@ -193,11 +225,18 @@ public void generate_should_use_template_extensions_get_template_id() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -224,7 +263,7 @@ public void generate_should_use_template_extensions_get_template_id() #region Code Structure Tests [Fact] - public void generate_should_include_auto_generated_header() + public void Generate_should_include_auto_generated_header() { // Arrange var module = new DamlModule @@ -235,11 +274,18 @@ public void generate_should_include_auto_generated_header() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -259,7 +305,7 @@ public void generate_should_include_auto_generated_header() } [Fact] - public void generate_should_include_nullable_enable_directive() + public void Generate_should_include_nullable_enable_directive() { // Arrange var module = new DamlModule @@ -270,11 +316,18 @@ public void generate_should_include_nullable_enable_directive() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -293,7 +346,7 @@ public void generate_should_include_nullable_enable_directive() } [Fact] - public void generate_should_include_required_usings() + public void Generate_should_include_required_usings() { // Arrange var module = new DamlModule @@ -304,11 +357,18 @@ public void generate_should_include_required_usings() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -328,7 +388,7 @@ public void generate_should_include_required_usings() } [Fact] - public void generate_should_use_file_scoped_namespace() + public void Generate_should_use_file_scoped_namespace() { // Arrange var module = new DamlModule @@ -339,11 +399,18 @@ public void generate_should_use_file_scoped_namespace() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -362,7 +429,7 @@ public void generate_should_use_file_scoped_namespace() } [Fact] - public void generate_should_include_xml_docs() + public void Generate_should_include_xml_docs() { // Arrange var module = new DamlModule @@ -373,11 +440,18 @@ public void generate_should_include_xml_docs() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -398,7 +472,7 @@ public void generate_should_include_xml_docs() } [Fact] - public void generate_should_create_static_class() + public void Generate_should_create_static_class() { // Arrange var module = new DamlModule @@ -409,11 +483,18 @@ public void generate_should_create_static_class() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -436,7 +517,7 @@ public void generate_should_create_static_class() #region Option Tests [Fact] - public void generate_should_not_create_file_when_option_disabled() + public void Generate_should_not_create_file_when_option_disabled() { // Arrange var options = new CodeGenOptions @@ -452,11 +533,18 @@ public void generate_should_not_create_file_when_option_disabled() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -472,7 +560,7 @@ public void generate_should_not_create_file_when_option_disabled() } [Fact] - public void generate_should_not_create_file_when_no_templates() + public void Generate_should_not_create_file_when_no_templates() { // Arrange var module = new DamlModule @@ -502,7 +590,7 @@ public void generate_should_not_create_file_when_no_templates() } [Fact] - public void generate_should_use_block_scoped_namespace_when_option_disabled() + public void Generate_should_use_block_scoped_namespace_when_option_disabled() { // Arrange var options = new CodeGenOptions @@ -519,11 +607,18 @@ public void generate_should_use_block_scoped_namespace_when_option_disabled() new DamlTemplate { Name = "MyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "MyTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -546,7 +641,7 @@ public void generate_should_use_block_scoped_namespace_when_option_disabled() #region Filter Tests [Fact] - public void generate_should_respect_root_filter() + public void Generate_should_respect_root_filter() { // Arrange var options = new CodeGenOptions @@ -563,17 +658,29 @@ public void generate_should_respect_root_filter() new DamlTemplate { Name = "IncludedTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] }, new DamlTemplate { Name = "ExcludedTemplate", - Fields = [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "IncludedTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + }, + new DamlDataType + { + Name = "ExcludedTemplate", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text))]) + } + ], Interfaces = [] }; diff --git a/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj b/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj index f64d629..f688731 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj +++ b/tests/Daml.Codegen.CSharp.Tests/Daml.Codegen.CSharp.Tests.csproj @@ -15,7 +15,6 @@ - diff --git a/tests/Daml.Codegen.CSharp.Tests/DamlFieldAttributeEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/DamlFieldAttributeEmitterTests.cs index a7f81b3..5bb7c19 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DamlFieldAttributeEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DamlFieldAttributeEmitterTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -60,11 +60,18 @@ public void Required_property_carries_daml_field_attribute_on_its_own_line() new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = "Vault", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; diff --git a/tests/Daml.Codegen.CSharp.Tests/DamlModelTests.cs b/tests/Daml.Codegen.CSharp.Tests/DamlModelTests.cs index 6447431..28d0675 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DamlModelTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DamlModelTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -263,7 +263,6 @@ public void DamlFieldDefinition_should_store_name_and_type() public void DamlTemplate_should_store_all_properties() { // Arrange - var fields = new[] { new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)) }; var choices = new[] { new DamlChoice @@ -279,7 +278,6 @@ public void DamlTemplate_should_store_all_properties() var template = new DamlTemplate { Name = "MyTemplate", - Fields = fields, Choices = choices, Key = new DamlPrimitiveType(DamlPrimitive.Party), Implements = @@ -291,7 +289,6 @@ public void DamlTemplate_should_store_all_properties() // Assert template.Name.Should().Be("MyTemplate"); - template.Fields.Should().HaveCount(1); template.Choices.Should().HaveCount(1); template.Key.Should().NotBeNull(); template.Implements.Should().HaveCount(2); @@ -304,7 +301,6 @@ public void DamlTemplate_Key_should_be_optional() var template = new DamlTemplate { Name = "KeylessTemplate", - Fields = [], Choices = [] }; @@ -319,7 +315,6 @@ public void DamlTemplate_Implements_should_default_to_empty() var template = new DamlTemplate { Name = "SimpleTemplate", - Fields = [], Choices = [] }; @@ -463,7 +458,7 @@ public void DamlModule_should_store_all_collections() // Arrange var templates = new[] { - new DamlTemplate { Name = "Template1", Fields = [], Choices = [] } + new DamlTemplate { Name = "Template1", Choices = [] } }; var dataTypes = new[] { diff --git a/tests/Daml.Codegen.CSharp.Tests/DamlTypeMapperTests.cs b/tests/Daml.Codegen.CSharp.Tests/DamlTypeMapperTests.cs index b2a8e74..0d1dc6f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DamlTypeMapperTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DamlTypeMapperTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -62,7 +62,7 @@ private static DamlPackage StdlibPackage() => }; [Fact] - public void map_type_maps_text_primitive_to_string() + public void MapType_maps_text_primitive_to_string() { Mapper().MapType(Prim(DamlPrimitive.Text)).Should().Be("string"); } @@ -73,41 +73,41 @@ public void map_type_maps_text_primitive_to_string() [InlineData(DamlPrimitive.Numeric, "decimal")] [InlineData(DamlPrimitive.Date, "DateOnly")] [InlineData(DamlPrimitive.Timestamp, "DateTimeOffset")] - public void map_type_maps_primitives_to_their_clr_types(DamlPrimitive primitive, string expected) + public void MapType_maps_primitives_to_their_clr_types(DamlPrimitive primitive, string expected) { Mapper().MapType(Prim(primitive)).Should().Be(expected); } [Fact] - public void map_type_wraps_list_argument_in_ireadonlylist() + public void MapType_wraps_list_argument_in_ireadonlylist() { Mapper().MapType(App(DamlPrimitive.List, Prim(DamlPrimitive.Int64))) .Should().Be("IReadOnlyList"); } [Fact] - public void map_type_renders_optional_argument_as_nullable() + public void MapType_renders_optional_argument_as_nullable() { Mapper().MapType(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))) .Should().Be("string?"); } [Fact] - public void map_type_renders_genmap_as_ireadonlydictionary() + public void MapType_renders_genmap_as_ireadonlydictionary() { Mapper().MapType(App(DamlPrimitive.GenMap, Prim(DamlPrimitive.Text), Prim(DamlPrimitive.Int64))) .Should().Be("IReadOnlyDictionary"); } [Fact] - public void map_type_renders_contract_id_argument() + public void MapType_renders_contract_id_argument() { Mapper().MapType(App(DamlPrimitive.ContractId, Prim(DamlPrimitive.Party))) .Should().Be("ContractId"); } [Fact] - public void map_type_resolves_cross_package_type_ref_through_the_resolver() + public void MapType_resolves_cross_package_type_ref_through_the_resolver() { var mapper = Mapper(new StubResolver(resolvedName: "Acme.Widget")); @@ -116,21 +116,21 @@ public void map_type_resolves_cross_package_type_ref_through_the_resolver() } [Fact] - public void map_type_nests_optional_inside_list() + public void MapType_nests_optional_inside_list() { Mapper().MapType(App(DamlPrimitive.List, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))) .Should().Be("IReadOnlyList"); } [Fact] - public void map_type_nests_list_inside_optional() + public void MapType_nests_list_inside_optional() { Mapper().MapType(App(DamlPrimitive.Optional, App(DamlPrimitive.List, Prim(DamlPrimitive.Int64)))) .Should().Be("IReadOnlyList?"); } [Fact] - public void map_type_rejects_excessively_deep_types_before_managed_stack_overflow() + public void MapType_rejects_excessively_deep_types_before_managed_stack_overflow() { var type = Enumerable.Range(0, 300) .Aggregate((DamlType)Prim(DamlPrimitive.Text), (inner, _) => App(DamlPrimitive.List, inner)); @@ -141,14 +141,14 @@ public void map_type_rejects_excessively_deep_types_before_managed_stack_overflo } [Fact] - public void map_type_nests_optional_inside_a_genmap_value() + public void MapType_nests_optional_inside_a_genmap_value() { Mapper().MapType(App(DamlPrimitive.GenMap, Prim(DamlPrimitive.Text), App(DamlPrimitive.Optional, Prim(DamlPrimitive.Int64)))) .Should().Be("IReadOnlyDictionary"); } [Fact] - public void map_type_resolves_a_cross_package_argument_inside_a_contract_id() + public void MapType_resolves_a_cross_package_argument_inside_a_contract_id() { var mapper = Mapper(new StubResolver(resolvedName: "Acme.Widget")); @@ -157,37 +157,223 @@ public void map_type_resolves_a_cross_package_argument_inside_a_contract_id() } [Fact] - public void map_type_rejects_nested_optional_with_an_actionable_message() + public void MapType_emits_the_wrapper_at_both_levels_of_a_nested_optional() { - Mapper().Invoking(m => m.MapType( - App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))))) - .Should().Throw() - .WithMessage("*nested Optional*"); + Mapper().MapType(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))) + .Should().Be("Optional>"); } [Fact] - public void to_value_serializes_int64_primitive() + public void MapType_emits_the_wrapper_at_both_levels_of_a_nested_optional_over_a_type_variable() + { + Mapper().MapType(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, new DamlTypeVar("a")))) + .Should().Be("Optional>"); + } + + [Fact] + public void MapType_emits_the_wrapper_for_a_nested_optional_passed_to_an_emitted_generic() + { + Mapper(BoxResolver()).MapType( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))])) + .Should().Be("Acme.Box>>"); + } + + [Fact] + public void ToValue_emits_the_chain_for_a_nested_optional_passed_to_an_emitted_generic() + { + Mapper(BoxResolver()).ToValue( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))]), + "Field") + .Should().Be( + "Field.ToRecord(__t0 => (DamlValue)(__t0.ToChainValue(__optional1 => " + + "__optional1.ToChainValue(__optional2 => new DamlText(__optional2)))))"); + } + + [Fact] + public void MapType_refuses_a_nested_optional_passed_to_a_generic_that_wraps_the_parameter() + { + var act = () => Mapper(CrateResolver()).MapType( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Crate"), + [App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))])); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*") + .WithMessage("*one array level short*"); + } + + [Fact] + public void ToValue_refuses_a_single_optional_passed_to_a_generic_that_wraps_the_parameter() + { + var act = () => Mapper(CrateResolver()).ToValue( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Crate"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]), + "Field"); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*"); + } + + [Fact] + public void FromValue_refuses_a_single_optional_passed_to_a_generic_that_wraps_the_parameter() + { + var act = () => Mapper(CrateResolver()).FromValue( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Crate"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]), + "value"); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*"); + } + + [Fact] + public void MapType_keeps_a_single_optional_passed_to_a_generic_that_does_not_wrap_the_parameter() + { + Mapper(BoxResolver()).MapType( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))])) + .Should().Be("Acme.Box>"); + } + + [Fact] + public void ToValue_serializes_every_level_of_a_nested_optional_through_the_chain_encoding() + { + Mapper().ToValue(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), "MaybeMaybeNote") + .Should().Be("MaybeMaybeNote.ToChainValue(__optional0 => __optional0.ToChainValue(__optional1 => new DamlText(__optional1)))"); + } + + [Fact] + public void FromValue_deserializes_every_level_of_a_nested_optional_through_the_chain_encoding() + { + Mapper().FromValue(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), "value") + .Should().Contain("Optional>.FromChainValue(") + .And.Contain("Optional.FromChainValue("); + } + + [Fact] + public void MapType_emits_the_wrapper_for_an_optional_genmap_key() + { + Mapper().MapType(App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64))) + .Should().Be("IReadOnlyDictionary, long>"); + } + + [Fact] + public void FromValue_deserializes_an_optional_genmap_key_through_the_wrapper() + { + Mapper().FromValue( + App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64)), + "value") + .Should().Be( + "(IReadOnlyDictionary, long>)value.As().Entries.ToDictionary(" + + "kv => Optional.FromValue(kv.Key, __optional1 => __optional1.As().Value), " + + "kv => kv.Value.As().Value)"); + } + + [Fact] + public void FromValue_never_emits_a_nullable_key_selector_for_an_optional_genmap_key() + { + Mapper().FromValue( + App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64)), + "value") + .Should().NotContain(": null"); + } + + [Fact] + public void ToValue_serializes_an_optional_genmap_key_through_the_flat_wrapper() + { + Mapper().ToValue( + App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64)), + "Registry") + .Should().Be( + "new DamlGenMap(Registry.Select(kv => (" + + "(DamlValue)kv.Key.ToValue(__optional1 => new DamlText(__optional1)), " + + "(DamlValue)new DamlInt64(kv.Value))).ToList())"); + } + + [Fact] + public void FromValue_deserializes_a_nested_optional_genmap_key_through_the_chain_encoding() + { + Mapper().FromValue( + App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), + Prim(DamlPrimitive.Int64)), + "value") + .Should().Be( + "(IReadOnlyDictionary>, long>)value.As().Entries.ToDictionary(" + + "kv => Optional>.FromChainValue(kv.Key, __optional1 => " + + "Optional.FromChainValue(__optional1, __optional2 => __optional2.As().Value)), " + + "kv => kv.Value.As().Value)"); + } + + [Fact] + public void FromValue_keeps_an_optional_genmap_value_nullable() + { + Mapper().FromValue( + App( + DamlPrimitive.GenMap, + Prim(DamlPrimitive.Text), + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Int64))), + "value") + .Should().Be( + "(IReadOnlyDictionary)value.As().Entries.ToDictionary(" + + "kv => kv.Key.As().Value, " + + "kv => kv.Value.AsOptional().HasValue ? kv.Value.AsOptional().Value!.As().Value : null)"); + } + + [Fact] + public void DamlTypeMapper_handles_the_nested_chain_encoding_in_all_three_methods() + { + var mapper = Mapper(); + var chained = new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain); + + mapper.MapType(chained).Should().Be("Optional"); + mapper.ToValue(chained, "Note").Should().Contain("ToChainValue("); + mapper.FromValue(chained, "value").Should().Contain("FromChainValue("); + } + + [Fact] + public void ToValue_serializes_int64_primitive() { Mapper().ToValue(Prim(DamlPrimitive.Int64), "Amount") .Should().Be("new DamlInt64(Amount)"); } [Fact] - public void to_value_serializes_list_container() + public void ToValue_serializes_list_container() { Mapper().ToValue(App(DamlPrimitive.List, Prim(DamlPrimitive.Text)), "Items") .Should().Be("new DamlList(Items.Select(x => (DamlValue)new DamlText(x)).ToList())"); } [Fact] - public void to_value_serializes_optional_container() + public void ToValue_serializes_optional_container() { Mapper().ToValue(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Int64)), "Maybe") .Should().Be("Maybe is { } __Maybe ? new DamlOptional(new DamlInt64(__Maybe)) : DamlOptional.None"); } [Fact] - public void to_value_rejects_excessively_deep_types_before_managed_stack_overflow() + public void ToValue_rejects_excessively_deep_types_before_managed_stack_overflow() { var type = Enumerable.Range(0, 300) .Aggregate((DamlType)Prim(DamlPrimitive.Text), (inner, _) => App(DamlPrimitive.List, inner)); @@ -198,7 +384,7 @@ public void to_value_rejects_excessively_deep_types_before_managed_stack_overflo } [Fact] - public void to_value_serializes_parametric_stdlib_type_through_the_stub() + public void ToValue_serializes_parametric_stdlib_type_through_the_stub() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var either = new DamlTypeApp( @@ -210,42 +396,42 @@ public void to_value_serializes_parametric_stdlib_type_through_the_stub() } [Fact] - public void from_value_deserializes_int64_primitive() + public void FromValue_deserializes_int64_primitive() { Mapper().FromValue(Prim(DamlPrimitive.Int64), "value") .Should().Be("value.As().Value"); } [Fact] - public void from_value_deserializes_optional_container() + public void FromValue_deserializes_optional_container() { Mapper().FromValue(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), "value") .Should().Be("value.AsOptional().HasValue ? value.AsOptional().Value!.As().Value : null"); } [Fact] - public void from_value_deserializes_a_list_of_optionals() + public void FromValue_deserializes_a_list_of_optionals() { Mapper().FromValue(App(DamlPrimitive.List, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), "value") .Should().Be("(IReadOnlyList)value.As().Values.Select(x => x.AsOptional().HasValue ? x.AsOptional().Value!.As().Value : null).ToList()"); } [Fact] - public void from_value_casts_a_nested_genmap_of_genmap_to_the_declared_ireadonlydictionary() + public void FromValue_casts_a_nested_genmap_of_genmap_to_the_declared_ireadonlydictionary() { Mapper().FromValue(App(DamlPrimitive.GenMap, Prim(DamlPrimitive.Party), App(DamlPrimitive.GenMap, Prim(DamlPrimitive.Text), Prim(DamlPrimitive.Int64))), "value") .Should().Be("(IReadOnlyDictionary>)value.As().Entries.ToDictionary(kv => Party.FromDamlValue(kv.Key.As()), kv => (IReadOnlyDictionary)kv.Value.As().Entries.ToDictionary(kv => kv.Key.As().Value, kv => kv.Value.As().Value))"); } [Fact] - public void from_value_casts_a_nested_textmap_of_textmap_to_the_declared_ireadonlydictionary() + public void FromValue_casts_a_nested_textmap_of_textmap_to_the_declared_ireadonlydictionary() { Mapper().FromValue(App(DamlPrimitive.TextMap, App(DamlPrimitive.TextMap, Prim(DamlPrimitive.Int64))), "value") .Should().Be("(IReadOnlyDictionary>)value.As().Values.ToDictionary(kv => kv.Key, kv => (IReadOnlyDictionary)kv.Value.As().Values.ToDictionary(kv => kv.Key, kv => kv.Value.As().Value))"); } [Fact] - public void from_value_rejects_excessively_deep_types_before_managed_stack_overflow() + public void FromValue_rejects_excessively_deep_types_before_managed_stack_overflow() { var type = Enumerable.Range(0, 300) .Aggregate((DamlType)Prim(DamlPrimitive.Text), (inner, _) => App(DamlPrimitive.List, inner)); @@ -256,7 +442,7 @@ public void from_value_rejects_excessively_deep_types_before_managed_stack_overf } [Fact] - public void from_value_deserializes_parametric_stdlib_type_through_the_stub() + public void FromValue_deserializes_parametric_stdlib_type_through_the_stub() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var either = new DamlTypeApp( @@ -268,7 +454,7 @@ public void from_value_deserializes_parametric_stdlib_type_through_the_stub() } [Fact] - public void to_value_serializes_set_through_the_conversion_table() + public void ToValue_serializes_set_through_the_conversion_table() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var set = new DamlTypeApp( @@ -280,7 +466,7 @@ public void to_value_serializes_set_through_the_conversion_table() } [Fact] - public void from_value_deserializes_set_through_the_conversion_table() + public void FromValue_deserializes_set_through_the_conversion_table() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var set = new DamlTypeApp( @@ -292,7 +478,7 @@ public void from_value_deserializes_set_through_the_conversion_table() } [Fact] - public void to_value_serializes_nonempty_through_the_conversion_table() + public void ToValue_serializes_nonempty_through_the_conversion_table() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var nonEmpty = new DamlTypeApp( @@ -304,7 +490,7 @@ public void to_value_serializes_nonempty_through_the_conversion_table() } [Fact] - public void from_value_deserializes_nonempty_through_the_conversion_table() + public void FromValue_deserializes_nonempty_through_the_conversion_table() { var resolver = new StubResolver(packages: new Dictionary { [StdlibPackageId] = StdlibPackage() }); var nonEmpty = new DamlTypeApp( @@ -316,7 +502,7 @@ public void from_value_deserializes_nonempty_through_the_conversion_table() } [Fact] - public void from_value_handles_type_var_with_a_runtime_stub() + public void FromValue_handles_type_var_with_a_runtime_stub() { Mapper().FromValue(new DamlTypeVar("a"), "value") .Should().Be("GenericStub.NotImplemented(\"a\")"); @@ -353,11 +539,28 @@ private static DamlPackage PackageWithGenericType(string module, string name, Da private static StubResolver ResolverWith(string resolvedName, DamlPackage package) => new(resolvedName, new Dictionary { [CrossPackageId] = package }); + private static StubResolver CrateResolver() => + ResolverWith( + "Acme.Crate", + PackageWithGenericType( + "Acme.Shapes", + "Crate", + new DamlRecordDefinition( + [new DamlFieldDefinition("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))]))); + + private static StubResolver BoxResolver() => + ResolverWith( + "Acme.Box", + PackageWithGenericType( + "Acme.Shapes", + "Box", + new DamlRecordDefinition([new DamlFieldDefinition("item", new DamlTypeVar("a"))]))); + private static DamlTypeApp GenericAppOfText(string module, string name) => new(new DamlTypeRef(CrossPackageId, module, name), [Prim(DamlPrimitive.Text)]); [Fact] - public void to_value_serializes_a_user_generic_record_through_converter_lambdas() + public void ToValue_serializes_a_user_generic_record_through_converter_lambdas() { var record = new DamlRecordDefinition([new DamlFieldDefinition("value", new DamlTypeVar("a"))]); var resolver = ResolverWith("Acme.Box", PackageWithGenericType("Acme.Boxes", "Box", record)); @@ -367,7 +570,7 @@ public void to_value_serializes_a_user_generic_record_through_converter_lambdas( } [Fact] - public void from_value_deserializes_a_user_generic_record_through_converter_lambdas() + public void FromValue_deserializes_a_user_generic_record_through_converter_lambdas() { var record = new DamlRecordDefinition([new DamlFieldDefinition("value", new DamlTypeVar("a"))]); var resolver = ResolverWith("Acme.Box", PackageWithGenericType("Acme.Boxes", "Box", record)); @@ -377,7 +580,7 @@ public void from_value_deserializes_a_user_generic_record_through_converter_lamb } [Fact] - public void to_value_serializes_a_user_generic_variant_through_converter_lambdas() + public void ToValue_serializes_a_user_generic_variant_through_converter_lambdas() { var variant = new DamlVariantDefinition([new DamlVariantConstructor("Wrap", new DamlTypeVar("a"))]); var resolver = ResolverWith("Acme.Choice", PackageWithGenericType("Acme.Choices", "Choice", variant)); @@ -387,7 +590,7 @@ public void to_value_serializes_a_user_generic_variant_through_converter_lambdas } [Fact] - public void from_value_deserializes_a_user_generic_variant_through_converter_lambdas() + public void FromValue_deserializes_a_user_generic_variant_through_converter_lambdas() { var variant = new DamlVariantDefinition([new DamlVariantConstructor("Wrap", new DamlTypeVar("a"))]); var resolver = ResolverWith("Acme.Choice", PackageWithGenericType("Acme.Choices", "Choice", variant)); @@ -397,7 +600,7 @@ public void from_value_deserializes_a_user_generic_variant_through_converter_lam } [Fact] - public void to_value_maps_a_type_var_field_to_its_injected_converter_delegate() + public void ToValue_maps_a_type_var_field_to_its_injected_converter_delegate() { var delegates = new Dictionary { ["a"] = "convertTA" }; @@ -406,7 +609,7 @@ public void to_value_maps_a_type_var_field_to_its_injected_converter_delegate() } [Fact] - public void from_value_maps_a_type_var_field_to_its_injected_converter_delegate() + public void FromValue_maps_a_type_var_field_to_its_injected_converter_delegate() { var delegates = new Dictionary { ["a"] = "convertTA" }; @@ -415,7 +618,7 @@ public void from_value_maps_a_type_var_field_to_its_injected_converter_delegate( } [Fact] - public void from_value_falls_back_to_the_stub_for_a_type_var_absent_from_the_delegate_map() + public void FromValue_falls_back_to_the_stub_for_a_type_var_absent_from_the_delegate_map() { var delegates = new Dictionary { ["b"] = "convertTB" }; @@ -423,13 +626,39 @@ public void from_value_falls_back_to_the_stub_for_a_type_var_absent_from_the_del .Should().Be("GenericStub.NotImplemented(\"a\")"); } - private static readonly IReadOnlyDictionary SubtypeRepresentatives = - new Dictionary + [Fact] + public void FromValue_emits_the_throwing_stub_for_a_higher_kinded_object_mapped_type() + { + var higherKinded = new DamlTypeApp(new DamlTypeVar("f"), [new DamlTypeVar("a")]); + + Mapper().FromValue(higherKinded, "value") + .Should().Be("GenericStub.NotImplemented(\"value\")"); + } + + [Fact] + public void FromValue_throws_codegen_exception_for_an_unclassifiable_type_ref_application() + { + var unresolvable = new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Widgets", "Widget"), + [Prim(DamlPrimitive.Text)]); + + Mapper().Invoking(m => m.FromValue(unresolvable, "value")) + .Should().Throw() + .WithMessage("*Widget*"); + } + + private static readonly IReadOnlyDictionary> SubtypeRepresentatives = + new Dictionary> { - [typeof(DamlPrimitiveType)] = Prim(DamlPrimitive.Text), - [typeof(DamlTypeApp)] = App(DamlPrimitive.List, Prim(DamlPrimitive.Int64)), - [typeof(DamlTypeRef)] = new DamlTypeRef(LocalPackageId, "Test.Module", "Widget"), - [typeof(DamlTypeVar)] = new DamlTypeVar("a") + [typeof(DamlPrimitiveType)] = [Prim(DamlPrimitive.Text)], + [typeof(DamlTypeApp)] = [App(DamlPrimitive.List, Prim(DamlPrimitive.Int64))], + [typeof(DamlTypeRef)] = [new DamlTypeRef(LocalPackageId, "Test.Module", "Widget")], + [typeof(DamlTypeVar)] = [new DamlTypeVar("a")], + [typeof(DamlWrappedOptional)] = + [ + .. Enum.GetValues() + .Select(encoding => new DamlWrappedOptional(Prim(DamlPrimitive.Text), encoding)), + ] }; private static IEnumerable ConcreteDamlTypeSubtypes() => @@ -437,16 +666,16 @@ private static IEnumerable ConcreteDamlTypeSubtypes() => .Where(t => t is { IsAbstract: false, IsGenericTypeDefinition: false } && typeof(DamlType).IsAssignableFrom(t)); public static IEnumerable EveryDamlTypeSubtype() => - SubtypeRepresentatives.Values.Select(type => new object[] { type }); + SubtypeRepresentatives.Values.SelectMany(types => types).Select(type => new object[] { type }); [Fact] - public void every_parametric_stdlib_type_has_a_conversion_table_entry() + public void DamlTypeMapper_every_parametric_stdlib_type_has_a_conversion_table_entry() { Mapper().StdlibConversionKeys.Should().BeEquivalentTo(StdlibPackages.ParametricStdlibTypes); } [Fact] - public void every_parametric_stdlib_type_has_a_stdlib_mapping() + public void DamlTypeMapper_every_parametric_stdlib_type_has_a_stdlib_mapping() { foreach (var (module, name) in StdlibPackages.ParametricStdlibTypes) { @@ -480,7 +709,7 @@ private static string StripGenericArity(string typeName) } [Fact] - public void every_stdlib_mapping_return_theory_has_cases() + public void DamlTypeMapper_every_stdlib_mapping_return_theory_has_cases() { EveryStdlibMappingReturn().Should().NotBeEmpty( "a wholesale null regression in MapStdlibType would otherwise empty the theory and pass silently"); @@ -493,7 +722,7 @@ public void every_stdlib_mapping_return_theory_has_cases() [Theory] [MemberData(nameof(EveryStdlibMappingReturn))] - public void every_stdlib_mapping_return_resolves_to_a_public_runtime_type(string? returnedTypeName) + public void DamlTypeMapper_every_stdlib_mapping_return_resolves_to_a_public_runtime_type(string? returnedTypeName) { returnedTypeName.Should().NotBeNull( "a key in StdlibMappingKeys returned null from MapStdlibType, so the switch and the guarded key set have drifted apart"); @@ -509,16 +738,47 @@ public void every_stdlib_mapping_return_resolves_to_a_public_runtime_type(string } [Fact] - public void drift_guard_covers_every_concrete_subtype_discovered_by_reflection() + public void DamlTypeMapper_drift_guard_covers_every_concrete_subtype_discovered_by_reflection() { ConcreteDamlTypeSubtypes() .Should().BeEquivalentTo(SubtypeRepresentatives.Keys, "every concrete DamlType subtype needs a representative so the mapper drift-guard exercises it"); } + [Fact] + public void DamlTypeMapper_drift_guard_theory_exercises_every_optional_encoding() + { + WrappedOptionalTheoryCases().Select(wrapped => wrapped.Encoding) + .Should().BeEquivalentTo( + Enum.GetValues(), + "the encoding axis is invisible to the reflection drift guard, which walks DamlType " + + "subtypes only, so an encoding the theory omits reaches no mapper method at all"); + } + + [Fact] + public void DamlTypeMapper_emits_a_distinct_converter_pair_for_every_optional_encoding() + { + var wrappers = WrappedOptionalTheoryCases().ToList(); + wrappers.Should().HaveCountGreaterThan(1, + "one representative would make the uniqueness assertions below hold for free"); + + var mapper = Mapper(); + wrappers.Select(wrapped => mapper.ToValue(wrapped, "Field")) + .Should().OnlyHaveUniqueItems( + "two encodings sharing a serializer means one of them is being written in the other's " + + "wire form, which compiles and is only rejected by the participant"); + wrappers.Select(wrapped => mapper.FromValue(wrapped, "value")) + .Should().OnlyHaveUniqueItems( + "two encodings sharing a deserializer means one of them is being read in the other's " + + "wire form, which compiles and silently yields a different Optional"); + } + + private static IEnumerable WrappedOptionalTheoryCases() => + EveryDamlTypeSubtype().Select(row => row[0]).OfType(); + [Theory] [MemberData(nameof(EveryDamlTypeSubtype))] - public void every_subtype_is_handled_by_all_three_methods_without_throwing(DamlType type) + public void DamlTypeMapper_every_subtype_is_handled_by_all_three_methods_without_throwing(DamlType type) { var mapper = Mapper(); @@ -526,4 +786,93 @@ public void every_subtype_is_handled_by_all_three_methods_without_throwing(DamlT mapper.Invoking(m => m.ToValue(type, "field")).Should().NotThrow(); mapper.Invoking(m => m.FromValue(type, "value")).Should().NotThrow(); } + + [Fact] + public void MapType_emits_the_wrapper_for_an_optional_over_a_type_variable() + { + Mapper().MapType(App(DamlPrimitive.Optional, new DamlTypeVar("a"))) + .Should().Be("Optional"); + } + + [Fact] + public void MapsToReferenceType_places_an_optional_the_wrapper_carries_as_a_reference_type() + { + var mapper = Mapper(); + var nested = App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + mapper.MapType(nested).Should().Be("Optional>"); + mapper.MapsToReferenceType(nested).Should().BeTrue(); + } + + [Fact] + public void MapsToReferenceType_places_an_optional_nullable_syntax_carries_as_a_non_reference_type() + { + var mapper = Mapper(); + var flat = App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)); + + mapper.MapType(flat).Should().Be("string?"); + mapper.MapsToReferenceType(flat).Should().BeFalse(); + } + + [Fact] + public void MapType_emits_the_wrapper_for_an_optional_argument_to_an_emitted_generic() + { + var mapper = Mapper(new StubResolver(resolvedName: "Acme.Box")); + + mapper.MapType(new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))])) + .Should().Be("Acme.Box>"); + } + + [Fact] + public void MapType_keeps_a_flat_optional_on_nullable_syntax() + { + Mapper().MapType(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))) + .Should().Be("string?"); + } + + [Fact] + public void ToValue_serializes_a_wrapped_optional_through_the_runtime_wrapper() + { + Mapper().ToValue(App(DamlPrimitive.Optional, new DamlTypeVar("a")), "Note") + .Should().Be("Note.ToValue(__optional0 => GenericStub.NotImplemented(\"__optional0\"))"); + } + + [Fact] + public void FromValue_deserializes_a_wrapped_optional_through_the_runtime_wrapper() + { + Mapper(BoxResolver()).FromValue( + new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]), + "value") + .Should().Contain("Optional.FromValue(") + .And.Contain("__optional"); + } + + [Fact] + public void MapType_ToValue_and_FromValue_agree_on_which_optionals_are_wrapped() + { + var damlType = new DamlTypeApp( + new DamlTypeRef(CrossPackageId, "Acme.Shapes", "Box"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]); + var mapper = Mapper(BoxResolver()); + + mapper.MapType(damlType).Should().Contain("Optional"); + mapper.ToValue(damlType, "Boxed").Should().Contain("ToValue("); + mapper.FromValue(damlType, "value").Should().Contain("Optional.FromValue("); + } + + [Fact] + public void DamlTypeMapper_emits_the_wrapper_rather_than_the_object_fallback_for_a_wrapped_optional_node() + { + var wrapped = new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat); + var mapper = Mapper(); + + mapper.MapType(wrapped).Should().Be("Optional"); + mapper.ToValue(wrapped, "Note").Should().Be("Note.ToValue(__optional0 => new DamlText(__optional0))"); + mapper.FromValue(wrapped, "value") + .Should().Be("Optional.FromValue(value, __optional0 => __optional0.As().Value)"); + } } diff --git a/tests/Daml.Codegen.CSharp.Tests/DamlTypeTests.cs b/tests/Daml.Codegen.CSharp.Tests/DamlTypeTests.cs index f115513..ab0e417 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DamlTypeTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DamlTypeTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -45,6 +45,32 @@ public void DamlTypeApp_List_should_not_be_Optional() listType.IsOptional.Should().BeFalse(); } + [Fact] + public void DamlWrappedOptional_should_identify_Optional() + { + // Arrange + var wrapped = new DamlWrappedOptional( + new DamlPrimitiveType(DamlPrimitive.Text), OptionalEncoding.Flat); + + // Assert + wrapped.IsOptional.Should().BeTrue(); + } + + [Fact] + public void DamlTypeApp_should_hash_structurally_identical_values_alike() + { + // Arrange + var built = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.Optional), + [new DamlPrimitiveType(DamlPrimitive.Text)]); + var rebuilt = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.Optional), + [new DamlPrimitiveType(DamlPrimitive.Text)]); + + // Assert + rebuilt.GetHashCode().Should().Be(built.GetHashCode()); + } + [Fact] public void DamlTypeRef_should_store_type_reference() { diff --git a/tests/Daml.Codegen.CSharp.Tests/DarCrossPackageResolverTests.cs b/tests/Daml.Codegen.CSharp.Tests/DarCrossPackageResolverTests.cs index 94c0ed6..9c4f555 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DarCrossPackageResolverTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DarCrossPackageResolverTests.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; -using NSubstitute; using Xunit; namespace Daml.Codegen.CSharp.Tests; @@ -16,15 +16,6 @@ private sealed class FakeDarSource(DamlPackage main, params DamlPackage[] deps) public DamlPackage MainPackage => main; public IReadOnlyList Dependencies => deps; - - public DamlPackage? GetPackageById(string packageId) - { - if (packageId == main.PackageId) - { - return main; - } - return deps.FirstOrDefault(d => d.PackageId == packageId); - } } private sealed class CountingModules(IReadOnlyList inner) : IReadOnlyList @@ -75,7 +66,7 @@ private static DamlModule InterfaceModuleWithMarkerReservingTemplate(string modu { Name = moduleName, DataTypes = [Record(interfaceName), Record("I" + interfaceName)], - Templates = [new DamlTemplate { Name = "I" + interfaceName, Fields = [], Choices = [] }], + Templates = [new DamlTemplate { Name = "I" + interfaceName, Choices = [] }], Interfaces = [new DamlInterface { Name = interfaceName, Choices = [], ViewType = null }] }; @@ -83,10 +74,10 @@ private static PackageEmitContext ContextFor(DamlPackage main) => PackageEmitContext.ForPackage(main, new CodeGenOptions()); [Fact] - public void resolve_returns_the_bare_name_for_a_local_ref() + public void Resolve_returns_the_bare_name_for_a_local_ref() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var result = resolver.Resolve(new DamlTypeRef("main-id", "M", "Widget"), ContextFor(main)); @@ -94,10 +85,10 @@ public void resolve_returns_the_bare_name_for_a_local_ref() } [Fact] - public void resolve_returns_the_interface_marker_for_a_local_interface_ref() + public void Resolve_returns_the_interface_marker_for_a_local_interface_ref() { var main = Package("main-id", "my-pkg", InterfaceModule("M", "Holding")); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var result = resolver.Resolve(new DamlTypeRef("main-id", "M", "Holding"), ContextFor(main)); @@ -105,12 +96,12 @@ public void resolve_returns_the_interface_marker_for_a_local_interface_ref() } [Fact] - public void resolve_returns_the_qualified_interface_marker_for_a_cross_package_interface_ref() + public void Resolve_returns_the_qualified_interface_marker_for_a_cross_package_interface_ref() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var foreign = Package("foreign-id", "foreign-pkg", InterfaceModule("Splice.Holding", "Holding")); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, foreign), Substitute.For()); + new FakeDarSource(main, foreign)); var result = resolver.Resolve(new DamlTypeRef("foreign-id", "Splice.Holding", "Holding"), ContextFor(main)); @@ -119,10 +110,10 @@ public void resolve_returns_the_qualified_interface_marker_for_a_cross_package_i } [Fact] - public void resolve_disambiguates_a_local_interface_marker_reserved_by_a_template() + public void Resolve_disambiguates_a_local_interface_marker_reserved_by_a_template() { var main = Package("main-id", "my-pkg", InterfaceModuleWithMarkerReservingTemplate("M", "Holding")); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var result = resolver.Resolve(new DamlTypeRef("main-id", "M", "Holding"), ContextFor(main)); @@ -130,13 +121,13 @@ public void resolve_disambiguates_a_local_interface_marker_reserved_by_a_templat } [Fact] - public void resolve_disambiguates_a_cross_package_interface_marker_reserved_by_a_foreign_template() + public void Resolve_disambiguates_a_cross_package_interface_marker_reserved_by_a_foreign_template() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var foreign = Package( "foreign-id", "foreign-pkg", InterfaceModuleWithMarkerReservingTemplate("Splice.Holding", "Holding")); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, foreign), Substitute.For()); + new FakeDarSource(main, foreign)); var result = resolver.Resolve(new DamlTypeRef("foreign-id", "Splice.Holding", "Holding"), ContextFor(main)); @@ -144,10 +135,10 @@ public void resolve_disambiguates_a_cross_package_interface_marker_reserved_by_a } [Fact] - public void resolve_treats_an_empty_package_id_as_local() + public void Resolve_treats_an_empty_package_id_as_local() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var result = resolver.Resolve(new DamlTypeRef("", "M", "Widget"), ContextFor(main)); @@ -155,7 +146,7 @@ public void resolve_treats_an_empty_package_id_as_local() } [Fact] - public void resolve_qualifies_a_local_nested_choice_argument_with_its_parent_template() + public void Resolve_qualifies_a_local_nested_choice_argument_with_its_parent_template() { var argType = Record("TransferArg"); var choice = new DamlChoice @@ -165,7 +156,7 @@ public void resolve_qualifies_a_local_nested_choice_argument_with_its_parent_tem ArgumentType = new DamlTypeRef("", "M", "TransferArg"), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) }; - var template = new DamlTemplate { Name = "Account", Fields = [], Choices = [choice] }; + var template = new DamlTemplate { Name = "Account", Choices = [choice] }; var main = new DamlPackage { PackageId = "main-id", @@ -175,7 +166,7 @@ public void resolve_qualifies_a_local_nested_choice_argument_with_its_parent_tem Modules = [new DamlModule { Name = "M", DataTypes = [argType], Templates = [template], Interfaces = [] }], DependencyReferences = [] }; - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var result = resolver.Resolve(new DamlTypeRef("main-id", "M", "TransferArg"), ContextFor(main)); @@ -183,7 +174,7 @@ public void resolve_qualifies_a_local_nested_choice_argument_with_its_parent_tem } [Fact] - public void resolve_disambiguates_same_named_choice_args_declared_in_different_modules() + public void Resolve_disambiguates_same_named_choice_args_declared_in_different_modules() { DamlModule ModuleWithTransferChoice(string moduleName, string templateName) => new() { @@ -194,7 +185,6 @@ public void resolve_disambiguates_same_named_choice_args_declared_in_different_m new DamlTemplate { Name = templateName, - Fields = [], Choices = [ new DamlChoice @@ -214,7 +204,7 @@ public void resolve_disambiguates_same_named_choice_args_declared_in_different_m "my-pkg", ModuleWithTransferChoice("Banking", "Account"), ModuleWithTransferChoice("Custody", "Vault")); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var context = ContextFor(main); resolver.Resolve(new DamlTypeRef("main-id", "Banking", "Transfer"), context) @@ -224,7 +214,7 @@ public void resolve_disambiguates_same_named_choice_args_declared_in_different_m } [Fact] - public void resolve_disambiguates_same_named_choice_args_in_a_foreign_package() + public void Resolve_disambiguates_same_named_choice_args_in_a_foreign_package() { DamlModule ModuleWithTransferChoice(string moduleName, string templateName) => new() { @@ -235,7 +225,6 @@ public void resolve_disambiguates_same_named_choice_args_in_a_foreign_package() new DamlTemplate { Name = templateName, - Fields = [], Choices = [ new DamlChoice @@ -257,7 +246,7 @@ public void resolve_disambiguates_same_named_choice_args_in_a_foreign_package() ModuleWithTransferChoice("Banking", "Account"), ModuleWithTransferChoice("Custody", "Vault")); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, foreign), Substitute.For()); + new FakeDarSource(main, foreign)); var context = ContextFor(main); resolver.Resolve(new DamlTypeRef("foreign-id", "Banking", "Transfer"), context) @@ -267,12 +256,11 @@ public void resolve_disambiguates_same_named_choice_args_in_a_foreign_package() } [Fact] - public void resolve_warns_and_keeps_first_on_same_module_foreign_choice_arg_name_clash() + public void Resolve_warns_and_keeps_first_on_same_module_foreign_choice_arg_name_clash() { DamlTemplate TemplateWithTransferChoice(string templateName) => new() { Name = templateName, - Fields = [], Choices = [ new DamlChoice @@ -303,22 +291,23 @@ public void resolve_warns_and_keeps_first_on_same_module_foreign_choice_arg_name DependencyReferences = [] }; var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var logger = Substitute.For(); + var logger = new CapturingLogger(); var resolver = new DarCrossPackageResolver(new FakeDarSource(main, foreign), logger); var context = ContextFor(main); resolver.Resolve(new DamlTypeRef("foreign-id", "Banking", "Transfer"), context) .Should().Be("Foreign.Pkg.Account.Transfer"); - logger.Received(1).Warning(Arg.Is(m => m != null && m.Contains("Banking:Transfer") && m.Contains("Account") && m.Contains("Vault") && m.Contains("in the same package"))); + logger.Warnings.Should().ContainSingle() + .Which.Should().Contain("Banking:Transfer").And.Contain("Account").And.Contain("Vault").And.Contain("in the same package"); } [Fact] - public void resolve_maps_a_stdlib_ref_to_its_runtime_stdlib_name() + public void Resolve_maps_a_stdlib_ref_to_its_runtime_stdlib_name() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var stdlib = Package("stdlib-id", "daml-stdlib", Module("DA.Time.Types", Record("RelTime"))); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, stdlib), Substitute.For()); + new FakeDarSource(main, stdlib)); var result = resolver.Resolve(new DamlTypeRef("stdlib-id", "DA.Time.Types", "RelTime"), ContextFor(main)); @@ -327,12 +316,12 @@ public void resolve_maps_a_stdlib_ref_to_its_runtime_stdlib_name() } [Fact] - public void resolve_qualifies_a_cross_package_ref_and_records_the_package_id() + public void Resolve_qualifies_a_cross_package_ref_and_records_the_package_id() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var other = Package("other-id", "other-pkg", Module("N", Record("Gadget"))); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, other), Substitute.For()); + new FakeDarSource(main, other)); var result = resolver.Resolve(new DamlTypeRef("other-id", "N", "Gadget"), ContextFor(main)); @@ -341,10 +330,10 @@ public void resolve_qualifies_a_cross_package_ref_and_records_the_package_id() } [Fact] - public void resolve_throws_when_the_target_package_is_absent_from_the_dar() + public void Resolve_throws_when_the_target_package_is_absent_from_the_dar() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main)); var act = () => resolver.Resolve(new DamlTypeRef("missing-id", "N", "Gadget"), ContextFor(main)); @@ -352,13 +341,13 @@ public void resolve_throws_when_the_target_package_is_absent_from_the_dar() } [Fact] - public void discovered_external_package_ids_accumulates_across_a_sequence_of_resolves() + public void DarCrossPackageResolver_discovered_external_package_ids_accumulates_across_a_sequence_of_resolves() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var a = Package("a-id", "a-pkg", Module("A", Record("Alpha"))); var b = Package("b-id", "b-pkg", Module("B", Record("Beta"))); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, a, b), Substitute.For()); + new FakeDarSource(main, a, b)); var context = ContextFor(main); resolver.Resolve(new DamlTypeRef("a-id", "A", "Alpha"), context); @@ -369,7 +358,7 @@ public void discovered_external_package_ids_accumulates_across_a_sequence_of_res } [Fact] - public void resolve_qualifies_a_cross_package_nested_choice_argument_with_its_parent_template() + public void Resolve_qualifies_a_cross_package_nested_choice_argument_with_its_parent_template() { var argType = Record("ForeignArg"); var foreignChoice = new DamlChoice @@ -379,7 +368,7 @@ public void resolve_qualifies_a_cross_package_nested_choice_argument_with_its_pa ArgumentType = new DamlTypeRef("other-id", "N", "ForeignArg"), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) }; - var foreignTemplate = new DamlTemplate { Name = "Thing", Fields = [], Choices = [foreignChoice] }; + var foreignTemplate = new DamlTemplate { Name = "Thing", Choices = [foreignChoice] }; var other = new DamlPackage { PackageId = "other-id", @@ -390,7 +379,7 @@ public void resolve_qualifies_a_cross_package_nested_choice_argument_with_its_pa DependencyReferences = [] }; var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other)); var context = ContextFor(main); var first = resolver.Resolve(new DamlTypeRef("other-id", "N", "ForeignArg"), context); @@ -401,7 +390,7 @@ public void resolve_qualifies_a_cross_package_nested_choice_argument_with_its_pa } [Fact] - public void the_foreign_choice_arg_memo_builds_the_map_once_across_repeated_resolves() + public void DarCrossPackageResolver_the_foreign_choice_arg_memo_builds_the_map_once_across_repeated_resolves() { var argType = Record("ForeignArg"); var foreignChoice = new DamlChoice @@ -411,7 +400,7 @@ public void the_foreign_choice_arg_memo_builds_the_map_once_across_repeated_reso ArgumentType = new DamlTypeRef("other-id", "N", "ForeignArg"), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) }; - var foreignTemplate = new DamlTemplate { Name = "Thing", Fields = [], Choices = [foreignChoice] }; + var foreignTemplate = new DamlTemplate { Name = "Thing", Choices = [foreignChoice] }; var countingModules = new CountingModules( [new DamlModule { Name = "N", DataTypes = [argType], Templates = [foreignTemplate], Interfaces = [] }]); var other = new DamlPackage @@ -424,7 +413,7 @@ public void the_foreign_choice_arg_memo_builds_the_map_once_across_repeated_reso DependencyReferences = [] }; var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other)); var context = ContextFor(main); var first = resolver.Resolve(new DamlTypeRef("other-id", "N", "ForeignArg"), context); @@ -439,7 +428,7 @@ public void the_foreign_choice_arg_memo_builds_the_map_once_across_repeated_reso } [Fact] - public void the_foreign_interface_memo_builds_the_set_once_across_repeated_resolves() + public void DarCrossPackageResolver_the_foreign_interface_memo_builds_the_set_once_across_repeated_resolves() { var countingModules = new CountingModules([InterfaceModule("Splice.Holding", "Holding")]); var other = new DamlPackage @@ -452,7 +441,7 @@ public void the_foreign_interface_memo_builds_the_set_once_across_repeated_resol DependencyReferences = [] }; var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other)); var context = ContextFor(main); var first = resolver.Resolve(new DamlTypeRef("other-id", "Splice.Holding", "Holding"), context); @@ -467,7 +456,7 @@ public void the_foreign_interface_memo_builds_the_set_once_across_repeated_resol } [Fact] - public void the_foreign_choice_arg_memo_is_dar_scoped_across_packages_in_one_generate() + public void DarCrossPackageResolver_the_foreign_choice_arg_memo_is_dar_scoped_across_packages_in_one_generate() { var argType = Record("ForeignArg"); var foreignChoice = new DamlChoice @@ -477,7 +466,7 @@ public void the_foreign_choice_arg_memo_is_dar_scoped_across_packages_in_one_gen ArgumentType = new DamlTypeRef("other-id", "N", "ForeignArg"), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) }; - var foreignTemplate = new DamlTemplate { Name = "Thing", Fields = [], Choices = [foreignChoice] }; + var foreignTemplate = new DamlTemplate { Name = "Thing", Choices = [foreignChoice] }; var other = new DamlPackage { PackageId = "other-id", @@ -489,7 +478,7 @@ public void the_foreign_choice_arg_memo_is_dar_scoped_across_packages_in_one_gen }; var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var dep = Package("dep-id", "dep-pkg", Module("D", Record("DepThing"))); - var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other, dep), Substitute.For()); + var resolver = new DarCrossPackageResolver(new FakeDarSource(main, other, dep)); var fromMain = resolver.Resolve(new DamlTypeRef("other-id", "N", "ForeignArg"), ContextFor(main)); var fromDep = resolver.Resolve(new DamlTypeRef("other-id", "N", "ForeignArg"), ContextFor(dep)); @@ -500,12 +489,12 @@ public void the_foreign_choice_arg_memo_is_dar_scoped_across_packages_in_one_gen } [Fact] - public void resolve_returns_the_bare_name_and_records_nothing_for_an_unmapped_stdlib_type() + public void Resolve_returns_the_bare_name_and_records_nothing_for_an_unmapped_stdlib_type() { var main = Package("main-id", "my-pkg", Module("M", Record("Widget"))); var stdlib = Package("stdlib-id", "daml-stdlib", Module("DA.Mystery.Types", Record("Mystery"))); var resolver = new DarCrossPackageResolver( - new FakeDarSource(main, stdlib), Substitute.For()); + new FakeDarSource(main, stdlib)); var result = resolver.Resolve(new DamlTypeRef("stdlib-id", "DA.Mystery.Types", "Mystery"), ContextFor(main)); diff --git a/tests/Daml.Codegen.CSharp.Tests/DataTypeCodeGenTests.cs b/tests/Daml.Codegen.CSharp.Tests/DataTypeCodeGenTests.cs index f58e9c8..dd95d86 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DataTypeCodeGenTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DataTypeCodeGenTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -405,13 +405,11 @@ public void Generate_should_filter_templates_with_root_filter() new DamlTemplate { Name = "IncludeThis", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] }, new DamlTemplate { Name = "ExcludeThis", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], diff --git a/tests/Daml.Codegen.CSharp.Tests/DependencyTests.cs b/tests/Daml.Codegen.CSharp.Tests/DependencyTests.cs index 62f505f..c3a3f16 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DependencyTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DependencyTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -169,6 +169,40 @@ public void DarModel_GetPackageById_should_return_null_for_unknown_id() found.Should().BeNull(); } + [Fact] + public void DarModel_GetPackageById_should_resolve_on_the_concrete_type_without_recursing() + { + var mainPackage = new DamlPackage + { + PackageId = "main-pkg-id", + Name = "main", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + }; + + var depPackage = new DamlPackage + { + PackageId = "dep-pkg-id", + Name = "dependency", + Version = new Version(2, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + }; + + DarModel dar = new() + { + MainPackage = mainPackage, + Dependencies = [depPackage] + }; + + dar.GetPackageById("main-pkg-id").Should().Be(mainPackage); + dar.GetPackageById("dep-pkg-id").Should().Be(depPackage); + dar.GetPackageById("unknown-id").Should().BeNull(); + } + #endregion } diff --git a/tests/Daml.Codegen.CSharp.Tests/DriftDetectionTests.cs b/tests/Daml.Codegen.CSharp.Tests/DriftDetectionTests.cs index 545ddf1..2440d4c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/DriftDetectionTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/DriftDetectionTests.cs @@ -1,10 +1,12 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.Intermediate; using AwesomeAssertions; +using Microsoft.CodeAnalysis; using Xunit; namespace Daml.Codegen.CSharp.Tests; @@ -27,9 +29,30 @@ namespace Daml.Codegen.CSharp.Tests; /// emits-no-types marker file in its snapshot directory. Such a snapshot /// asserts that codegen produces zero .cs output; the day the package /// starts emitting a type, the drift test fails so the change is not missed. +/// +/// Each snapshot is also handed to Roslyn before its bytes are compared, so a +/// tree cannot be pinned — or re-blessed — containing C# that does not compile. +/// Byte equality alone only proves the emitter is deterministic; emitted files +/// are exempt from this repository's code-analysis analyzers, so nothing else +/// puts a compiler in front of them. /// public class DriftDetectionTests { + /// + /// Snapshots whose emitted C# is known not to compile, mapped to the emitter defect + /// that keeps them there. Each entry is asserted to still fail, so it cannot outlive + /// its fix: the day the emitter stops producing the defect, this test goes red until + /// the snapshot is removed from the list and joins the gate. + /// + private static readonly IReadOnlyDictionary SnapshotsWithKnownEmitterCompileDefects = + new Dictionary(StringComparer.Ordinal) + { + ["cross-module-collision"] = + "two modules of one package each declare a choice named Retag, and the emitter writes both " + + "of their `RetagResult` created-contract projections into the same C# namespace as " + + "duplicate top-level records (CS0101, CS8863, CS0111, CS1739, CS0121)", + }; + /// /// Enumerates every sub-directory under Snapshots/ that has an /// intermediate.binpb proto snapshot, yielding the directory name @@ -42,8 +65,17 @@ public class DriftDetectionTests /// public static TheoryData SnapshotNames() { - var snapshotsRoot = Path.Combine(AppContext.BaseDirectory, "Snapshots"); var data = new TheoryData(); + foreach (var name in DiscoveredSnapshotNames()) + { + data.Add(name); + } + return data; + } + + private static IReadOnlyList DiscoveredSnapshotNames() + { + var snapshotsRoot = Path.Combine(AppContext.BaseDirectory, "Snapshots"); if (!Directory.Exists(snapshotsRoot)) throw new DirectoryNotFoundException( @@ -51,20 +83,27 @@ public static TheoryData SnapshotNames() "Ensure the Snapshots/ directory is present in the test output; " + "check that snapshot fixture content is copied to the output directory in the .csproj."); - foreach (var dir in Directory.EnumerateDirectories(snapshotsRoot) - .Where(d => File.Exists(Path.Combine(d, "intermediate.binpb"))) - .OrderBy(d => Path.GetFileName(d), StringComparer.Ordinal)) - { - data.Add(Path.GetFileName(dir)!); - } + var names = Directory.EnumerateDirectories(snapshotsRoot) + .Where(d => File.Exists(Path.Combine(d, "intermediate.binpb"))) + .Select(d => Path.GetFileName(d)!) + .OrderBy(name => name, StringComparer.Ordinal) + .ToList(); - if (data.Count == 0) + if (names.Count == 0) throw new InvalidOperationException( $"No snapshot directories with an intermediate.binpb proto snapshot were found under '{snapshotsRoot}'. " + "The expected/ sub-directory is validated later for each discovered snapshot. " + "A zero-case theory would silently skip drift detection."); - return data; + return names; + } + + [Fact] + public void SnapshotsWithKnownEmitterCompileDefects_names_only_snapshots_that_exist() + { + SnapshotsWithKnownEmitterCompileDefects.Keys.Should().BeSubsetOf( + DiscoveredSnapshotNames(), + "an entry naming a snapshot that no longer exists exempts nothing and hides that the list is stale"); } [Theory] @@ -82,7 +121,7 @@ public async Task Codegen_output_matches_snapshot(string snapshotName) "the snapshot fixtures directory must ship alongside the test assembly at {0}", expectedDir); - var generator = new CSharpCodeGenerator(new CodeGenOptions(), new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(new CodeGenOptions()); IntermediateDar proto; await using (var stream = File.OpenRead(protoPath)) @@ -92,20 +131,14 @@ public async Task Codegen_output_matches_snapshot(string snapshotName) var dar = IntermediateDarReader.Read(proto); var allGenerated = generator.Generate(dar); - allGenerated.Should().ContainSingle( - f => f.RelativePath.EndsWith(".daml-langversion", StringComparison.Ordinal), - "the codegen always emits the LangVersion state file"); - var actualFiles = allGenerated - .Where(f => f.RelativePath.EndsWith(".cs", StringComparison.Ordinal) - || f.RelativePath.EndsWith(".daml-langversion", StringComparison.Ordinal)) + .Where(f => f.RelativePath.EndsWith(".cs", StringComparison.Ordinal)) .Select(f => new { f.RelativePath, f.Content }) .OrderBy(f => f.RelativePath, StringComparer.Ordinal) .ToList(); var expectedFiles = Directory.EnumerateFiles(expectedDir, "*", SearchOption.AllDirectories) - .Where(p => p.EndsWith(".cs", StringComparison.Ordinal) - || p.EndsWith(".daml-langversion", StringComparison.Ordinal)) + .Where(p => p.EndsWith(".cs", StringComparison.Ordinal)) .Select(absPath => new { RelativePath = Path.GetRelativePath(expectedDir, absPath).Replace('\\', '/'), @@ -141,6 +174,40 @@ public async Task Codegen_output_matches_snapshot(string snapshotName) "codegen must emit at least one .cs file from the proto snapshot; zero .cs output indicates a regression in IntermediateDarReader.Read or Generate."); } + var compileInput = MainAndNonStdlibDependencySources(generator, dar, allGenerated); + + if (!pinnedEmpty) + { + compileInput.Should().NotBeEmpty( + "compiling an empty file set asserts nothing; this snapshot is not pinned as emitting no types, so its emitted sources must reach Roslyn"); + } + + var compileErrors = EmittedCodeCompilesTestHelpers.CompileEmittedFiles(compileInput) + .Where(d => d.Severity == DiagnosticSeverity.Error) + .OrderBy(d => d.Location.GetLineSpan().Path, StringComparer.Ordinal) + .ThenBy(d => d.Location.GetLineSpan().StartLinePosition.Line) + .ThenBy(d => d.Location.GetLineSpan().StartLinePosition.Character) + .ThenBy(d => d.Id, StringComparer.Ordinal) + .ToList(); + + if (SnapshotsWithKnownEmitterCompileDefects.TryGetValue(snapshotName, out var knownDefect)) + { + compileErrors.Should().NotBeEmpty( + "snapshot `{0}` is listed in SnapshotsWithKnownEmitterCompileDefects because {1}; its emitted C# now " + + "compiles, so delete that entry and let the compile gate protect this snapshot from here on", + snapshotName, + knownDefect); + } + else if (compileErrors.Count > 0) + { + throw new Xunit.Sdk.XunitException( + $"Snapshot `{snapshotName}` emits C# that does not compile: " + + $"{compileErrors.Count} error-severity diagnostic(s) from {compileInput.Count} emitted file(s) " + + "(the snapshot's own output plus its non-stdlib dependencies, so the compilation is self-contained).\n\n" + + string.Join("\n", compileErrors.Select(RenderDiagnostic)) + + "\n\nFix the emitter — do not hand-edit or re-bless the snapshot around a compile error."); + } + actualFiles.Select(f => f.RelativePath).Should().Equal( expectedFiles.Select(f => f.RelativePath), because: "the set of generated files must match the snapshot. " + refreshHint); @@ -160,4 +227,31 @@ public async Task Codegen_output_matches_snapshot(string snapshotName) } } } + + private static IReadOnlyList MainAndNonStdlibDependencySources( + CSharpCodeGenerator generator, + DarModel dar, + IReadOnlyList mainGenerated) + { + var dependencySources = dar.Dependencies + .Where(dep => !StdlibPackages.IsStdlibPackage(dep.Name) + && !StdlibPackages.IsPlaceholderPackageName(dep.Name)) + .SelectMany(dep => generator.Generate( + new DarModel { MainPackage = dep, Dependencies = dar.Dependencies })); + + return mainGenerated + .Concat(dependencySources) + .Where(f => f.RelativePath.EndsWith(".cs", StringComparison.Ordinal)) + .DistinctBy(f => f.RelativePath, StringComparer.Ordinal) + .ToList(); + } + + private static string RenderDiagnostic(Diagnostic diagnostic) + { + var span = diagnostic.Location.GetLineSpan(); + var origin = string.IsNullOrEmpty(span.Path) + ? "(no source location)" + : $"{span.Path}({span.StartLinePosition.Line + 1},{span.StartLinePosition.Character + 1})"; + return $" {origin}: {diagnostic.Id}: {diagnostic.GetMessage(CultureInfo.InvariantCulture)}"; + } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedCodeCompilesTestHelpers.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedCodeCompilesTestHelpers.cs index d8d7d98..a325c83 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedCodeCompilesTestHelpers.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedCodeCompilesTestHelpers.cs @@ -1,8 +1,10 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; +using System.Reflection; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; @@ -43,8 +45,23 @@ internal static IReadOnlyList GenerateKeyBearingTemplate(bool use new DamlTemplate { Name = "AssetWithKey", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], - Choices = [], + Choices = + [ + new DamlChoice + { + Name = "Reissue", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = ContractIdOf("AssetWithKey"), + }, + new DamlChoice + { + Name = "Describe", + Consuming = false, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Text), + }, + ], Key = new DamlPrimitiveType(DamlPrimitive.Text), }, ], @@ -80,6 +97,32 @@ internal static IReadOnlyList GenerateKeyBearingTemplate(bool use return CreateGenerator(options).Generate(dar); } + /// + /// Compiles the emitted sources and loads them as an in-memory assembly, for tests + /// that must call a generated member rather than inspect its text — reflection over + /// a real invocation is the only way to observe whether an emitted method throws at + /// the call site or on the task it returns. + /// + internal static Assembly EmitToAssembly(IReadOnlyList files) + { + var compilation = CompileEmittedFilesToCompilation(files, DocumentationMode.Parse); + + using var stream = new MemoryStream(); + var emit = compilation.Emit(stream); + if (!emit.Success) + { + throw new InvalidOperationException( + "The emitted sources must compile before they can be invoked: " + + string.Join( + "\n", + emit.Diagnostics + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Select(d => d.GetMessage(CultureInfo.InvariantCulture) + " @ " + d.Location))); + } + + return Assembly.Load(stream.ToArray()); + } + internal static IReadOnlyList CompileEmittedFilesWithDocDiagnostics(IReadOnlyList files) => CompileEmittedFiles(files, DocumentationMode.Diagnose); @@ -87,6 +130,17 @@ internal static IReadOnlyList CompileEmittedFiles(IReadOnlyList CompileEmittedFiles( + IReadOnlyList files, + DocumentationMode documentationMode) => + CompileEmittedFilesToCompilation(files, documentationMode).GetDiagnostics(); + + /// + /// The compilation behind , + /// for tests that need the semantic model rather than the diagnostics — asking Roslyn + /// which member a call site bound to, rather than inferring it from the + /// overload-resolution rules. + /// + internal static CSharpCompilation CompileEmittedFilesToCompilation( IReadOnlyList files, DocumentationMode documentationMode) { @@ -145,6 +199,6 @@ internal static IReadOnlyList CompileEmittedFiles( OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable)); - return compilation.GetDiagnostics(); + return compilation; } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedContractKeySlotCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedContractKeySlotCompilesTests.cs new file mode 100644 index 0000000..6f4ff2f --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedContractKeySlotCompilesTests.cs @@ -0,0 +1,175 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Globalization; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Microsoft.CodeAnalysis; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// The active contract's key slot is named Key, and it sits in a record body that +/// nests Contract and ContractId types of its own. When the Daml key type +/// shares a spelling with one of those, or with the Id / Data / Key +/// members themselves, the slot and its decoder have to reach past the nearer name — a +/// compile question no text-compare drift test can answer. So is a template named after +/// its own key type, a key type named after a C# keyword, an Optional key whose +/// rendered Name? spelling no bare-name comparison matches, and a key-less template +/// whose own name collides with a member of the contract record its decoder stands in. +/// +public class EmittedContractKeySlotCompilesTests +{ + private static DarModel KeyedDar(string templateName, string keyTypeName) => + Dar(templateName, keyTypeName, new DamlTypeRef("", "Test.Module", keyTypeName)); + + private static DarModel OptionalKeyedDar(string templateName, string keyTypeName) => + Dar(templateName, keyTypeName, OptionalOf(new DamlTypeRef("", "Test.Module", keyTypeName))); + + private static DarModel KeylessDar(string templateName) => + Dar(templateName, keyTypeName: null, key: null); + + private static DarModel Dar(string templateName, string? keyTypeName, DamlType? key) + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = + [ + new DamlTemplate + { + Name = templateName, + Choices = + [ + new DamlChoice + { + Name = "Reissue", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + }, + ], + Key = key, + }, + ], + DataTypes = + [ + new DamlDataType + { + Name = templateName, + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, + .. keyTypeName is null || keyTypeName == templateName + ? Array.Empty() + : [new DamlDataType + { + Name = keyTypeName, + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }], + ], + Interfaces = [], + }; + + return new DarModel + { + MainPackage = new DamlPackage + { + PackageId = "test-package-id", + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.3", + Modules = [module], + DependencyReferences = [], + }, + Dependencies = [], + }; + } + + private static void CompilesCleanly(DarModel dar, string because) + { + var files = CreateGenerator().Generate(dar); + + var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + + errors.Should().BeEmpty( + because + ", but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + } + + [Theory] + [InlineData("Key")] + [InlineData("Data")] + [InlineData("Id")] + [InlineData("Contract")] + [InlineData("ContractId")] + public void EmittedContractKeySlot_compiles_when_the_key_type_is_named_after_a_contract_member(string keyTypeName) + { + CompilesCleanly( + KeyedDar("Vault", keyTypeName), + $"a Daml key type named '{keyTypeName}' collides with a positional parameter of the emitted active contract"); + } + + [Fact] + public void EmittedContractKeySlot_compiles_when_the_template_is_named_after_its_own_key_type() + { + CompilesCleanly( + KeyedDar("Key", "VaultKey"), + "the template supplies the active contract's Data parameter type while the key supplies its Key parameter type"); + } + + [Fact] + public void EmittedContractKeySlot_compiles_when_the_key_type_is_a_csharp_keyword() + { + CompilesCleanly( + KeyedDar("Vault", "event"), + "the key type escapes to @event, which is also the name the decoder gives its CreatedEvent parameter"); + } + + [Theory] + [InlineData("Contract")] + [InlineData("ContractId")] + public void EmittedContractKeySlot_compiles_when_an_optional_key_wraps_a_type_named_after_a_nested_type(string keyTypeName) + { + CompilesCleanly( + OptionalKeyedDar("Vault", keyTypeName), + $"an Optional key over a Daml type named '{keyTypeName}' renders as '{keyTypeName}?', which binds to the nested {keyTypeName} unless the slot and its decoder qualify it"); + } + + [Theory] + [InlineData("Id")] + [InlineData("Data")] + [InlineData("Key")] + public void EmittedContractKeySlot_compiles_when_a_keyless_template_is_named_after_a_contract_member(string templateName) + { + CompilesCleanly( + KeylessDar(templateName), + $"a key-less template named '{templateName}' supplies the active contract's Data parameter type, and its decoder names the template inside a static member of the contract record"); + } + + [Theory] + [InlineData("Contract")] + [InlineData("ContractId")] + public void EmittedContractKeySlot_refuses_a_keyless_template_named_after_a_type_it_nests(string templateName) + { + FluentActions.Invoking(() => CreateGenerator().Generate(KeylessDar(templateName))) + .Should().Throw( + "the emitter nests a record of that name inside the template record, which CS0542 forbids, so generation must fail with one diagnostic instead of emitting ten cascading C# errors") + .WithMessage($"*{templateName}*"); + } + + [Theory] + [InlineData("Contract")] + [InlineData("ContractId")] + public void EmittedContractKeySlot_refuses_a_keyed_template_named_after_a_type_it_nests(string templateName) + { + FluentActions.Invoking(() => CreateGenerator().Generate(KeyedDar(templateName, "VaultKey"))) + .Should().Throw( + "the collision is in the template record's own body, so carrying a key changes nothing about it") + .WithMessage($"*{templateName}*"); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedCrossPackageCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedCrossPackageCompilesTests.cs index 0c3581e..1a4917b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedCrossPackageCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedCrossPackageCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -49,7 +50,6 @@ public void Emitted_choice_with_cross_package_argument_compiles_against_both_pac new DamlTemplate { Name = "Trader", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -93,14 +93,14 @@ public void Emitted_choice_with_cross_package_argument_compiles_against_both_pac UsePrimaryConstructors = true, IncludeDependencies = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(dar); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "cross-package choice arg wrappers must compile alongside the foreign package's emission, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -138,11 +138,6 @@ public void Emitted_record_with_cross_package_enum_field_compiles_against_both_p new DamlTemplate { Name = "Alert", - Fields = - [ - new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("severity", new DamlTypeRef("other-pkg-id", "Other.Module", "Severity")), - ], Choices = [], }, ], @@ -180,14 +175,14 @@ public void Emitted_record_with_cross_package_enum_field_compiles_against_both_p UsePrimaryConstructors = true, IncludeDependencies = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(dar); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record field whose type is an enum from a dependency package must round-trip via the foreign enum's *Extensions.ToDamlEnum/FromDamlEnum, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -201,7 +196,6 @@ public void Emitted_cross_package_variant_constructor_referencing_nested_choice_ new DamlTemplate { Name = "AmuletRules", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -281,13 +275,13 @@ public void Emitted_cross_package_variant_constructor_referencing_nested_choice_ UsePrimaryConstructors = true, IncludeDependencies = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(dar); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a cross-package variant constructor referencing a nested choice-arg type must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedFacetDeclarationCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedFacetDeclarationCompilesTests.cs new file mode 100644 index 0000000..9609ba7 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedFacetDeclarationCompilesTests.cs @@ -0,0 +1,81 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Globalization; +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using Microsoft.CodeAnalysis; +using AwesomeAssertions; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Compiles emitted code carrying the IDamlRecord<TSelf> facet in the +/// declaration shapes the conformance corpus does not cover: the class-based, +/// no-primary-constructor emitter branch and the zero-field FromRecord +/// short-circuit. The facet turns FromRecord's shape from convention into +/// a compile-time contract, so each branch is compile-verified, not string-matched. +/// +public class EmittedFacetDeclarationCompilesTests +{ + [Fact] + public void Emitted_facet_compiles_without_record_types_and_on_zero_field_records() + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = + [ + new DamlTemplate + { + Name = "PlainAsset", + Choices = [], + }, + ], + DataTypes = + [ + new DamlDataType + { + Name = "PlainAsset", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, + new DamlDataType + { + Name = "EmptyMarker", + Definition = new DamlRecordDefinition([]), + }, + ], + Interfaces = [], + }; + var package = new DamlPackage + { + PackageId = "facet-shapes-id", + Name = "facet-shapes", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }; + var dar = new DarModel { MainPackage = package, Dependencies = [] }; + var options = new CodeGenOptions + { + EnableNullableReferenceTypes = true, + UseFileScopedNamespaces = true, + UseRecordTypes = false, + UsePrimaryConstructors = false, + }; + + var files = CreateGenerator(options).Generate(dar); + + files.Should().Contain(f => f.Content.Contains("IDamlRecord", StringComparison.Ordinal)); + files.Should().Contain(f => f.Content.Contains("IDamlRecord", StringComparison.Ordinal)); + var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + errors.Should().BeEmpty( + "the facet must hold in every emitted declaration shape, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedInterfaceChoiceExerciserInvocationTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedInterfaceChoiceExerciserInvocationTests.cs new file mode 100644 index 0000000..15aaaec --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedInterfaceChoiceExerciserInvocationTests.cs @@ -0,0 +1,172 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using Daml.Ledger.Abstractions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Outcomes; +using AwesomeAssertions; +using NSubstitute; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; +using Party = Daml.Runtime.Data.Party; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Invokes the emitted interface-choice exercisers for real, through reflection over a +/// compiled assembly, to pin where their argument-validation failures surface. An +/// interface choice has no typed result to project, so the exercisers hand back the +/// submission task instead of awaiting it — which moves a null-argument failure from +/// the returned task to the call site. The Func<Task> + ThrowAsync +/// idiom used elsewhere in the suite passes either way; MethodInfo.Invoke does +/// not, because it only wraps an exception the invoked method threw before returning. +/// +public class EmittedInterfaceChoiceExerciserInvocationTests +{ + private const string ChoiceMethodName = "TransferAsync"; + + private static readonly Assembly Emitted = EmitToAssembly(GenerateCustodyInterfacePackage()); + + [Fact] + public void EmittedInterfaceChoiceExerciser_rejects_a_null_client_before_returning_a_task() + { + var exerciser = ChoiceExerciser(); + + Action act = () => exerciser.Invoke(null, Arguments(null)); + + var thrown = act.Should().Throw( + "an exerciser that awaited its submission would be an async method, whose argument checks surface on the returned task rather than at the call site") + .Which; + thrown.InnerException.Should().BeOfType(); + } + + [Fact] + public void EmittedInterfaceChoiceExerciser_hands_back_the_submission_task_for_valid_arguments() + { + var exerciser = ChoiceExerciser(); + var client = Substitute.For(); + client.TrySubmitAndWaitForTransactionAsync( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(Task.FromResult>( + new ExerciseOutcome.None())); + + var returned = exerciser.Invoke(null, Arguments(client)); + + returned.Should().BeAssignableTo>>( + "only the argument checks run at the call site — a valid call still has to hand the caller the submission task, so an exerciser that threw unconditionally would satisfy the null-client pin while being useless"); + } + + private static MethodInfo ChoiceExerciser() => + EmittedType("ICustodyExtensions") + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == ChoiceMethodName + && m.GetParameters().Any(p => p.ParameterType == typeof(SubmitterInfo))); + + private static object?[] Arguments(ILedgerWriter? client) => + [ + TypedContractId(), + client, + Submitter(), + null, + null, + null, + CancellationToken.None, + ]; + + private static SubmitterInfo Submitter() => new Party("alice"); + + private static object TypedContractId() => + Activator.CreateInstance( + typeof(ContractId<>).MakeGenericType(EmittedType("ICustody")), + "cid-1")!; + + private static Type EmittedType(string name) => Emitted.GetTypes().Single(t => t.Name == name); + + private static IReadOnlyList GenerateCustodyInterfacePackage() + { + var fields = new[] + { + new DamlFieldDefinition("platform", new DamlPrimitiveType(DamlPrimitive.Party)), + new DamlFieldDefinition("counterparty", new DamlPrimitiveType(DamlPrimitive.Party)), + }; + + var module = new DamlModule + { + Name = "Acme.Offers", + Templates = + [ + new DamlTemplate + { + Name = "Offer", + Choices = + [ + new DamlChoice + { + Name = "Accept", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.ContractId), + [new DamlTypeRef("test-pkg", "Acme.Offers", "Offer")]), + Controllers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("counterparty")]), + Observers = DamlPartyAnalysis.Static([]), + }, + ], + Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("platform")]), + Observers = DamlPartyAnalysis.Static([]), + } + ], + DataTypes = + [ + new DamlDataType + { + Name = "Offer", + Definition = new DamlRecordDefinition(fields), + } + ], + Interfaces = + [ + new DamlInterface + { + Name = "Custody", + Choices = + [ + new DamlChoice + { + Name = "Transfer", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + Controllers = DamlPartyAnalysis.Dynamic, + Observers = DamlPartyAnalysis.Dynamic, + }, + ], + } + ], + }; + + var dar = new DarModel + { + MainPackage = new DamlPackage + { + PackageId = "test-pkg", + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }, + Dependencies = [], + }; + + return CreateGenerator().Generate(dar); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedKeywordNamedIdentifierCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedKeywordNamedIdentifierCompilesTests.cs index 06174f0..7325273 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedKeywordNamedIdentifierCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedKeywordNamedIdentifierCompilesTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -38,7 +38,6 @@ private static DarModel KeywordControllerDar(string partyFieldName) new DamlTemplate { Name = "Market", - Fields = fields, Choices = [ new DamlChoice @@ -171,7 +170,6 @@ public void Emitted_choice_with_keyword_named_observer_field_has_matching_param_ new DamlTemplate { Name = "Market", - Fields = fields, Choices = [ new DamlChoice diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedMangledTypeNamesCompileTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedMangledTypeNamesCompileTests.cs index 578081c..d1e7283 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedMangledTypeNamesCompileTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedMangledTypeNamesCompileTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionGlobalQualificationCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionGlobalQualificationCompilesTests.cs index a1e7da4..e4a0326 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionGlobalQualificationCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionGlobalQualificationCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -24,7 +25,6 @@ public void Emitted_contract_id_head_is_global_qualified_when_namespace_collides new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("custodian", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], }, ], @@ -75,7 +75,7 @@ [new DamlFieldDefinition("vaultCid", ContractIdOf("Vault"))]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .ContractId must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -127,7 +127,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .IReadOnlyList must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -141,7 +141,6 @@ public void Emitted_arity_protected_generic_heads_are_global_qualified_when_name new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), Choices = [ @@ -204,7 +203,7 @@ public void Emitted_arity_protected_generic_heads_are_global_qualified_when_name var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .Choice must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -218,7 +217,6 @@ public void Emitted_contract_interface_heads_are_global_qualified_when_namespace new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), Choices = [], }, @@ -261,7 +259,7 @@ public void Emitted_contract_interface_heads_are_global_qualified_when_namespace var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .IContract must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -275,7 +273,6 @@ public void Emitted_exercise_outcome_head_is_global_qualified_when_namespace_col new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), Choices = [ @@ -329,6 +326,6 @@ public void Emitted_exercise_outcome_head_is_global_qualified_when_namespace_col var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .ExerciseOutcome must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionRuntimeTypeNameCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionRuntimeTypeNameCompilesTests.cs index 6a2d826..e2cd7cb 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionRuntimeTypeNameCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionRuntimeTypeNameCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -21,8 +22,8 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_party() // whose tail segment is `Party` (real DAR: canton-party-replication-alpha), // that namespace shadows the type and Roslyn reports CS0118. Every emitted // Party TYPE site must be global::-qualified. This exercises the field type, - // the contract-key type + IHasKey<> argument, the choice actAs/controller - // params, the signatory-derived CreateAsync, and the Observers(payload) helper. + // the contract-key type, the choice actAs/controller params, the + // signatory-derived CreateAsync, and the Observers(payload) helper. var module = new DamlModule { Name = "Replication", @@ -31,12 +32,6 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_party() new DamlTemplate { Name = "Holding", - Fields = - [ - new DamlFieldDefinition("issuer", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("amount", new DamlPrimitiveType(DamlPrimitive.Int64)), - ], Key = new DamlPrimitiveType(DamlPrimitive.Party), Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("issuer")]), Observers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), @@ -94,7 +89,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_party() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .Party must global::-qualify the runtime Party type, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -108,7 +103,6 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_itemplate() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), Choices = [], }, @@ -151,7 +145,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_itemplate() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .ITemplate must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -199,7 +193,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_idamlrecord() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .IDamlRecord must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -250,7 +244,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_idamlvariant() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .IDamlValue must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -264,11 +258,6 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_submitterinfo() new DamlTemplate { Name = "Asset", - Fields = - [ - new DamlFieldDefinition("issuer", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - ], Signatories = DamlPartyAnalysis.Static( [ new DamlPartyPayloadField("issuer"), @@ -325,7 +314,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_submitterinfo() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .SubmitterInfo must global::-qualify the runtime SubmitterInfo type in parameter and constructor positions, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -339,7 +328,6 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_identifier() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("owner")]), Choices = [], }, @@ -382,7 +370,7 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_identifier() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .Identifier must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -433,6 +421,6 @@ public void Emitted_code_compiles_when_package_namespace_ends_in_damlparty() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code whose namespace ends in .DamlParty must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionShadowingCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionShadowingCompilesTests.cs index dadc39f..0453673 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionShadowingCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedNamespaceCollisionShadowingCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -30,14 +31,13 @@ public void Emitted_interface_code_compiles_when_package_namespace_shadows_a_run new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice { Name = "Reissue", Consuming = true, - ArgumentType = new DamlPrimitiveType(DamlPrimitive.Party), + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), ReturnType = ContractIdOf("Asset"), }, ], @@ -51,6 +51,12 @@ public void Emitted_interface_code_compiles_when_package_namespace_shadows_a_run Definition = new DamlRecordDefinition( [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), }, + new DamlDataType + { + Name = "Asset", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, ], Interfaces = [ @@ -112,17 +118,12 @@ public void Emitted_interface_code_compiles_when_package_namespace_shadows_a_run var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted interface code whose namespace shadows a runtime type must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] public void Emitted_key_bearing_template_in_party_colliding_namespace_has_no_cref_diagnostics() { - // Regression for CS1584/CS1658: WriteKeyProperty embedded the rendered key - // type inside the IHasKey cref braces. In a Party-shadowing namespace - // MapDamlTypeToCSharp returns `global::Daml.Runtime.Data.Party`, so the cref - // became IHasKey{global::Daml.Runtime.Data.Party} — a constructed type in - // cref braces, which Roslyn rejects under DocumentationMode.Diagnose. var module = new DamlModule { Name = "Replication", @@ -131,10 +132,18 @@ public void Emitted_key_bearing_template_in_party_colliding_namespace_has_no_cre new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("issuer", new DamlPrimitiveType(DamlPrimitive.Party))], Key = new DamlPrimitiveType(DamlPrimitive.Party), Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("issuer")]), - Choices = [], + Choices = + [ + new DamlChoice + { + Name = "Reissue", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + }, + ], }, ], DataTypes = @@ -172,17 +181,12 @@ public void Emitted_key_bearing_template_in_party_colliding_namespace_has_no_cre .ToList(); crefDiagnostics.Should().BeEmpty( "emitted XML-doc crefs must be single-global::, well-formed names so no malformed-cref diagnostic surfaces under DocumentationMode.Diagnose, but got: {0}", - string.Join("\n", crefDiagnostics.Select(e => e.Id + " " + e.GetMessage() + " @ " + e.Location))); + string.Join("\n", crefDiagnostics.Select(e => e.Id + " " + e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] public void Emitted_key_bearing_template_with_nested_generic_party_key_has_no_cref_diagnostics() { - // Regression for CS1584/CS1658 on a key whose mapped C# form is a NESTED - // generic wrapping an imported type: `List Party` renders as - // IReadOnlyList in a Party-shadowing - // namespace. ToCrefTypeArgument must strip the inner global:: AND escape - // the nested <>, yielding the prose form IReadOnlyList{Daml.Runtime.Data.Party}. var module = new DamlModule { Name = "Replication", @@ -191,12 +195,20 @@ public void Emitted_key_bearing_template_with_nested_generic_party_key_has_no_cr new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("issuer", new DamlPrimitiveType(DamlPrimitive.Party))], Key = new DamlTypeApp( new DamlPrimitiveType(DamlPrimitive.List), [new DamlPrimitiveType(DamlPrimitive.Party)]), Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("issuer")]), - Choices = [], + Choices = + [ + new DamlChoice + { + Name = "Reissue", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + }, + ], }, ], DataTypes = @@ -226,8 +238,11 @@ [new DamlPrimitiveType(DamlPrimitive.Party)]), var holding = files.First(f => f.RelativePath.EndsWith("Holding.cs", StringComparison.Ordinal)); holding.Content.Should().Contain( - "of type IReadOnlyList{Daml.Runtime.Data.Party}", - "the nested-generic key type appears in cref-escaped prose with every global:: stripped and the inner angle brackets rendered as braces"); + "public required ContractKey> Key { get; init; }", + "the nested-generic key slot on the active contract qualifies the shadowed imported type"); + holding.Content.Should().Contain( + "IReadOnlyList key)", + "the by-key command builder's key parameter qualifies the shadowed imported type too"); var diagnostics = CompileEmittedFilesWithDocDiagnostics(files); var crefDiagnostics = diagnostics @@ -235,7 +250,7 @@ [new DamlPrimitiveType(DamlPrimitive.Party)]), .ToList(); crefDiagnostics.Should().BeEmpty( "a nested-generic key over an imported type must produce a well-formed cref under DocumentationMode.Diagnose, but got: {0}", - string.Join("\n", crefDiagnostics.Select(e => e.Id + " " + e.GetMessage() + " @ " + e.Location))); + string.Join("\n", crefDiagnostics.Select(e => e.Id + " " + e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Theory] @@ -372,11 +387,6 @@ public void Emitted_stdlib_types_compile_when_package_namespace_shadows_a_stdlib new DamlTemplate { Name = "Lock", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("duration", relTime), - ], Choices = [ new DamlChoice @@ -437,6 +447,6 @@ public void Emitted_stdlib_types_compile_when_package_namespace_shadows_a_stdlib var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted stdlib types must global::-qualify (no CS0118) and import Daml.Runtime.Stdlib when the namespace shadows a stdlib simple name, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedPayloadPropertyCollidingWithTemplateNameCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedPayloadPropertyCollidingWithTemplateNameCompilesTests.cs new file mode 100644 index 0000000..19162ea --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedPayloadPropertyCollidingWithTemplateNameCompilesTests.cs @@ -0,0 +1,141 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Microsoft.CodeAnalysis; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Pins that a payload Party field whose PascalCased name equals its own +/// template's name is named identically everywhere it is emitted. The record +/// emitter has to rename such a property — a member may not share the name of +/// its enclosing type (CS0542) — so steward on template Steward is +/// declared as Steward_. Every site that reads the party back off the +/// payload has to agree, or the emitted code names a property that was never +/// declared and fails to compile with CS1061. +/// +/// +/// Four emission sites read a party off a payload, and each one is reached by a +/// different party shape: a single static signatory, several static signatories, +/// a static observer clause, and a static choice controller reached through the +/// fetched-contract exerciser overload. The fixture below drives all four in one +/// package, because each was written independently and only the compile gate +/// proves they still agree. +/// +/// +public class EmittedPayloadPropertyCollidingWithTemplateNameCompilesTests +{ + private static DamlFieldDefinition PartyField(string name) => + new(name, new DamlPrimitiveType(DamlPrimitive.Party)); + + private static DarModel CollidingPayloadPropertyDar() + { + var stewardFields = new[] { PartyField("steward"), PartyField("deputy") }; + var wardenFields = new[] { PartyField("warden"), PartyField("deputy") }; + + var module = new DamlModule + { + Name = "Test.Module", + Templates = + [ + new DamlTemplate + { + Name = "Steward", + Choices = + [ + new DamlChoice + { + Name = "Revise", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = ContractIdOf("Steward"), + Controllers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("steward")]), + }, + ], + Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("steward")]), + Observers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("steward")]), + }, + new DamlTemplate + { + Name = "Warden", + Choices = [], + Signatories = DamlPartyAnalysis.Static( + [new DamlPartyPayloadField("warden"), new DamlPartyPayloadField("deputy")]), + Observers = DamlPartyAnalysis.Dynamic, + }, + ], + DataTypes = + [ + new DamlDataType { Name = "Steward", Definition = new DamlRecordDefinition(stewardFields) }, + new DamlDataType { Name = "Warden", Definition = new DamlRecordDefinition(wardenFields) }, + ], + Interfaces = [], + }; + + return new DarModel + { + MainPackage = new DamlPackage + { + PackageId = "test-package-id", + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }, + Dependencies = [], + }; + } + + [Fact] + public void Emitted_payload_property_colliding_with_its_template_name_is_declared_renamed() + { + var steward = CreateGenerator().Generate(CollidingPayloadPropertyDar()) + .Single(f => f.RelativePath.EndsWith("Steward.cs", StringComparison.Ordinal)); + + steward.Content.Should().Contain( + "Party Steward_", + "the record emitter must rename a property that would otherwise share its enclosing type's name; " + + "if this fixture stops colliding the compile gate below passes without proving anything"); + } + + [Fact] + public void Emitted_multi_signatory_submitter_reads_every_payload_party_by_its_declared_name() + { + var warden = CreateGenerator().Generate(CollidingPayloadPropertyDar()) + .Single(f => f.RelativePath.EndsWith("Warden.cs", StringComparison.Ordinal)); + + warden.Content.Should().Contain( + "new SubmitterInfo(new HashSet", + "a template with several static signatories derives its submitter from a party set"); + warden.Content.Should().Contain( + "payload.Warden_", + "the signatory colliding with the template name has to be read through the renamed property " + + "the record emitter declared"); + warden.Content.Should().Contain( + "payload.Deputy", + "emitting the same payload property for both signatories still compiles, so the compile gate " + + "cannot see it; naming each signatory separately is what pins that the loop reads distinct fields"); + } + + [Fact] + public void Emitted_code_reading_a_colliding_payload_party_off_the_payload_compiles() + { + var files = CreateGenerator().Generate(CollidingPayloadPropertyDar()); + + var errors = CompileEmittedFiles(files) + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + errors.Should().BeEmpty( + "every site reading a party off the payload must name the property the record emitter declared, " + + "not the undisambiguated PascalCased field name; " + + $"got: {string.Join("; ", errors.Select(d => d.ToString()))}"); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedRecordFieldCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedRecordFieldCompilesTests.cs index 7049f57..9c01686 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedRecordFieldCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedRecordFieldCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -63,7 +64,7 @@ public void Emitted_record_with_field_that_pascalcases_to_the_type_name_compiles var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record whose field PascalCases to its own type name must still compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -131,14 +132,14 @@ public void Emitted_record_with_stdlib_DayOfWeek_enum_field_compiles() UsePrimaryConstructors = true, IncludeDependencies = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(dar); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record field whose type is the stdlib DayOfWeek enum must round-trip via the runtime-provided Daml.Runtime.Stdlib.DayOfWeekExtensions, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -152,7 +153,6 @@ public void Emitted_record_with_genmap_of_list_field_compiles() new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], }, ], @@ -212,7 +212,7 @@ [new DamlTypeRef("", "Test.Module", "Holding")])]) var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "GenMap-of-List FromRecord must compile without CS1503 against IReadOnlyDictionary>, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -275,7 +275,7 @@ [new DamlPrimitiveType(DamlPrimitive.Int64)])]) var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "nested Map-of-Map FromRecord must compile without CS1503 against the declared nested IReadOnlyDictionary>, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -345,7 +345,7 @@ [new DamlFieldDefinition("outcome", eitherTextInt)]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record field typed as DA.Types:Either must map to Daml.Runtime.Stdlib.Either and compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); var emitted = string.Join("\n", files.Select(f => f.Content)); emitted.Should().Contain( @@ -356,6 +356,90 @@ [new DamlFieldDefinition("outcome", eitherTextInt)]), "the emitted decoder must wire DA.Types:Either through Either.FromValue (EmitParametricStdlibFromValue)"); } + [Fact] + public void Emitted_record_with_either_field_carrying_an_optional_compiles() + { + var stdlibPackage = new DamlPackage + { + PackageId = "daml-prim-id", + Name = "daml-prim", + Version = new Version(0, 0, 0), + LfVersion = "2.1", + Modules = + [ + new DamlModule + { + Name = "DA.Types", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = "Either", + TypeParams = ["a", "b"], + Definition = new DamlVariantDefinition([]), + }, + ], + Interfaces = [], + }, + ], + DependencyReferences = [], + }; + + var eitherTextOptionalText = new DamlTypeApp( + new DamlTypeRef("daml-prim-id", "DA.Types", "Either"), + [ + new DamlPrimitiveType(DamlPrimitive.Text), + new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.Optional), + [new DamlPrimitiveType(DamlPrimitive.Text)]), + ]); + + var module = new DamlModule + { + Name = "Test.Module", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = "Decision", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("outcome", eitherTextOptionalText)]), + }, + ], + Interfaces = [], + }; + + var package = new DamlPackage + { + PackageId = "test-pkg", + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }; + + var dar = new DarModel { MainPackage = package, Dependencies = [stdlibPackage] }; + var files = CreateGenerator().Generate(dar); + + var diagnostics = CompileEmittedFiles(files); + var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + errors.Should().BeEmpty( + "an Optional in a parametric stdlib generic's type-argument position takes the wrapper, so " + + "Either's notnull type parameters are satisfied, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + + var emitted = string.Join("\n", files.Select(f => f.Content)); + emitted.Should().Contain( + "Either>", + "the Optional type argument must render as the wrapper rather than as string?"); + emitted.Should().NotContain( + "Either", + "string? cannot satisfy Either's notnull type parameter"); + } + [Fact] public void Emitted_record_with_unmappable_object_field_compiles() { @@ -411,6 +495,6 @@ [new DamlTypeVar("a")])), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record with an unmappable object-typed field must compile (no .ToRecord() on object), but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedRecordReferenceCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedRecordReferenceCompilesTests.cs index bca8aa9..655424e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedRecordReferenceCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedRecordReferenceCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -24,7 +25,6 @@ public void Emitted_sibling_record_referencing_nested_choice_arg_type_compiles() new DamlTemplate { Name = "MergeDelegation", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -83,7 +83,7 @@ [new DamlTypeRef("", "Test.Module", "MergeDelegation")])), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a sibling record referencing a same-package nested choice-arg type must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -97,7 +97,6 @@ public void Emitted_choice_argument_record_with_field_that_pascalcases_to_the_ch new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -153,7 +152,7 @@ public void Emitted_choice_argument_record_with_field_that_pascalcases_to_the_ch var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a nested choice-arg record whose field PascalCases to the choice (and thus the nested record) name would be CS0542 unless disambiguated, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -201,7 +200,7 @@ public void Emitted_record_with_a_variant_typed_field_compiles() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record field whose type is a variant must serialize via the variant's ToVariant/FromVariant, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -227,9 +226,9 @@ public void RequireForFieldType_recurses_into_arguments_when_base_is_a_nested_ty DependencyReferences = [], }; var resolver = new DarCrossPackageResolver( - new DarModel { MainPackage = package, Dependencies = [] }, new ConsoleLogger(0)); + new DarModel { MainPackage = package, Dependencies = [] }); - StdlibPackages.RequireForFieldType(resolver, indent, nestedAppCarryingContractId); + StdlibPackages.RequireForFieldType(resolver, package, indent, nestedAppCarryingContractId); indent.RequiredUsings.Should().Contain( "Daml.Runtime.Contracts", @@ -278,7 +277,6 @@ public void Emitted_record_with_tuple_field_wrapping_contract_id_compiles() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], }, ], @@ -317,6 +315,6 @@ [new DamlFieldDefinition("pair", contractIdTimesInt)]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a record field typed as a parametric stdlib type wrapping ContractId must compile (the file needs `using Daml.Runtime.Contracts;`), but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedSharedViewCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedSharedViewCompilesTests.cs new file mode 100644 index 0000000..a9eba49 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedSharedViewCompilesTests.cs @@ -0,0 +1,74 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Globalization; +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using Microsoft.CodeAnalysis; +using AwesomeAssertions; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Compiles emitted code for a view record two interfaces declare as their view type. +/// Stamping such a record with both markers would make it inherit two explicit +/// implementations of the same identity statics — no most specific implementation, a +/// compile error — so the emitter must leave a shared view record un-stamped and keep +/// both markers un-enriched, while each still carries its View witness. +/// +public class EmittedSharedViewCompilesTests +{ + [Fact] + public void Emitted_shared_view_record_compiles_without_a_marker_stamp() + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = "SharedView", + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, + ], + Interfaces = + [ + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "Test.Module", "SharedView"), + }, + new DamlInterface + { + Name = "Bond", + Choices = [], + ViewType = new DamlTypeRef("", "Test.Module", "SharedView"), + }, + ], + }; + var options = new CodeGenOptions + { + EnableNullableReferenceTypes = true, + UseFileScopedNamespaces = true, + UseRecordTypes = true, + UsePrimaryConstructors = true, + }; + + var files = CreateGenerator(options).Generate(CreateTestDar(module)); + + files.Should().Contain(f => f.Content.Contains("ViewDescriptor View", StringComparison.Ordinal)); + files.Should().Contain(f => f.Content.Contains("ViewDescriptor View", StringComparison.Ordinal)); + var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + errors.Should().BeEmpty( + "a view record shared by two interfaces must stay un-stamped rather than inherit two identity implementations, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedSubmitterOverloadResolutionTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedSubmitterOverloadResolutionTests.cs new file mode 100644 index 0000000..ca57ed3 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedSubmitterOverloadResolutionTests.cs @@ -0,0 +1,295 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Compiles the emitted surface together with hand-written call sites that pass a +/// multi-party SubmitterInfo to every generated <Choice>Async +/// flavour — the create-bearing ContractId<T> exerciser, its +/// T.Contract sibling, the value-returning exerciser, and the interface +/// exerciser. A missing overload fails the compilation with CS1503/CS1501, and an +/// overload that collides with the ergonomic named-Party shape fails it with +/// CS0121 — neither of which a string-shape assertion or a declaration-only compile +/// gate would catch. +/// +public class EmittedSubmitterOverloadResolutionTests +{ + [Fact] + public void Every_choice_async_flavour_accepts_a_readAs_bearing_SubmitterInfo() + { + var files = GenerateOfferModule(); + var diagnostics = CompileEmittedFiles([.. files, CallSites(files)]); + + diagnostics + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Select(d => d.ToString()) + .Should().BeEmpty(); + } + + [Fact] + public void Named_party_call_sites_still_resolve_alongside_the_SubmitterInfo_overloads() + { + var files = GenerateOfferModule(); + var diagnostics = CompileEmittedFiles([.. files, NamedPartyCallSites(files)]); + + diagnostics + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Select(d => d.ToString()) + .Should().BeEmpty(); + } + + [Theory] + [InlineData("PartyPositional_CreateBearingByContractId", "Party")] + [InlineData("PartyPositional_ValueReturning", "SubmitterInfo")] + [InlineData("PartyPositional_InterfaceChoice", "SubmitterInfo")] + [InlineData("SubmitterPositional_CreateBearingByContractId", "SubmitterInfo")] + [InlineData("SubmitterPositional_CreateBearingByContract", "SubmitterInfo")] + [InlineData("SubmitterPositional_ValueReturning", "SubmitterInfo")] + [InlineData("SubmitterPositional_InterfaceChoice", "SubmitterInfo")] + [InlineData("PartyPositional_ObserverBearing_AllPartiesNamed", "Party")] + public void Positional_call_binds_to_the_overload_matching_its_submitter_argument( + string callSite, + string expectedSubmitterParameterType) + { + BoundSubmitterParameterType(callSite).Should().Be( + expectedSubmitterParameterType, + "an overload reachable only through the implicit Party-to-SubmitterInfo conversion " + + "would be decorative, and a Party call site silently rebound to the SubmitterInfo " + + "overload would drop the observer-derived readAs the named-Party shape builds"); + } + + [Fact] + public void Observer_bearing_choice_called_with_the_controller_alone_falls_through_to_SubmitterInfo() + { + BoundSubmitterParameterType("PartyPositional_ObserverBearing_ControllerOnly") + .Should().Be( + "SubmitterInfo", + "the named-Party overload of an observer-bearing choice takes one Party per controller " + + "and one per observer, none optional, so a call naming only the controller is not " + + "applicable to it and lands on the SubmitterInfo overload with an empty readAs — " + + "pinned rather than fixed, because it predates the overloads added here and the " + + "readAs a caller did not name is not one codegen can invent"); + } + + private static string BoundSubmitterParameterType(string callSiteMethodName) + { + var files = GenerateOfferModule(); + var compilation = CompileEmittedFilesToCompilation( + [.. files, CallSites(files), NamedPartyCallSites(files)], + DocumentationMode.Parse); + + var declarations = compilation.SyntaxTrees + .Select(tree => (tree, declaration: tree.GetRoot().DescendantNodes() + .OfType() + .SingleOrDefault(m => m.Identifier.ValueText == callSiteMethodName))) + .Where(pair => pair.declaration is not null) + .ToList(); + + var (callerTree, callerDeclaration) = declarations.Should().ContainSingle().Subject; + + var invocation = callerDeclaration!.DescendantNodes() + .OfType() + .Single(i => i.Expression is MemberAccessExpressionSyntax access + && access.Name.Identifier.ValueText.EndsWith("Async", StringComparison.Ordinal)); + + var bound = compilation.GetSemanticModel(callerTree).GetSymbolInfo(invocation).Symbol as IMethodSymbol; + bound.Should().NotBeNull($"'{callSiteMethodName}' must bind to exactly one emitted overload"); + + return SubmitterParameterLocatedByTypeNotName(bound!).Type.Name; + } + + private static IParameterSymbol SubmitterParameterLocatedByTypeNotName(IMethodSymbol overload) => + overload.Parameters.First(p => p.Type.Name is "Party" or "SubmitterInfo"); + + [Fact] + public void Every_submitter_parameter_carries_a_matching_param_doc_tag() + { + var docDiagnostics = CompileEmittedFilesWithDocDiagnostics(GenerateOfferModule()) + .Where(d => d.Id is "CS1572" or "CS1573") + .Select(d => d.ToString()) + .ToList(); + + docDiagnostics.Should().BeEmpty( + "a submitter parameter whose tag still names the other overload's parameter " + + "fails a doc-generating consumer project with CS1572/CS1573"); + } + + private static GeneratedFile CallSites(IReadOnlyList files) => + GeneratedFile.Text("CallSites.cs", $$""" + using System.Collections.Generic; + using System.Threading.Tasks; + using Daml.Ledger.Abstractions; + using Daml.Runtime.Commands; + using Daml.Runtime.Contracts; + using Daml.Runtime.Data; + using {{GeneratedNamespace(files)}}; + + internal static class SubmitterInfoCallSites + { + private static SubmitterInfo ReadAsBearing() => new SubmitterInfo( + actAs: new HashSet { new Party("alice") }, + readAs: new HashSet { new Party("bob") }); + + internal static Task SubmitterPositional_CreateBearingByContractId(ILedgerWriter client, ContractId contractId) => + contractId.AcceptAsync(client, ReadAsBearing()); + + internal static Task SubmitterPositional_CreateBearingByContract(ILedgerWriter client, Offer.Contract contract) => + contract.AcceptAsync(client, ReadAsBearing()); + + internal static Task SubmitterPositional_ValueReturning(ILedgerWriter client, ContractId contractId) => + contractId.DescribeAsync(client, ReadAsBearing()); + + internal static Task SubmitterPositional_InterfaceChoice(ILedgerWriter client, ContractId contractId) => + contractId.TransferAsync(client, ReadAsBearing()); + } + """); + + private static GeneratedFile NamedPartyCallSites(IReadOnlyList files) => + GeneratedFile.Text("NamedPartyCallSites.cs", $$""" + using System.Threading.Tasks; + using Daml.Ledger.Abstractions; + using Daml.Runtime.Contracts; + using Daml.Runtime.Data; + using {{GeneratedNamespace(files)}}; + + internal static class NamedPartyCallSites + { + internal static Task PartyPositional_CreateBearingByContractId(ILedgerWriter client, ContractId contractId) => + contractId.AcceptAsync(client, new Party("alice")); + + internal static Task PartyPositional_CreateBearingByContract(ILedgerWriter client, Offer.Contract contract) => + contract.AcceptAsync(client); + + internal static Task PartyPositional_ValueReturning(ILedgerWriter client, ContractId contractId) => + contractId.DescribeAsync(client, new Party("alice")); + + internal static Task PartyPositional_InterfaceChoice(ILedgerWriter client, ContractId contractId) => + contractId.TransferAsync(client, new Party("alice")); + + internal static Task PartyPositional_ObserverBearing_AllPartiesNamed(ILedgerWriter client, ContractId contractId) => + contractId.RenewAsync(client, new Party("alice"), new Party("bob")); + + internal static Task PartyPositional_ObserverBearing_ControllerOnly(ILedgerWriter client, ContractId contractId) => + contractId.RenewAsync(client, new Party("alice")); + } + """); + + private static string GeneratedNamespace(IReadOnlyList files) + { + var offer = files.First(f => f.RelativePath.EndsWith("Offer.cs", StringComparison.Ordinal)).Content; + var declaration = offer + .Split('\n') + .First(line => line.StartsWith("namespace ", StringComparison.Ordinal)); + return declaration.Trim().TrimEnd(';')["namespace ".Length..]; + } + + private static IReadOnlyList GenerateOfferModule() + { + var fields = new[] + { + new DamlFieldDefinition("platform", new DamlPrimitiveType(DamlPrimitive.Party)), + new DamlFieldDefinition("counterparty", new DamlPrimitiveType(DamlPrimitive.Party)), + }; + + var module = new DamlModule + { + Name = "Acme.Offers", + Templates = + [ + new DamlTemplate + { + Name = "Offer", + Choices = + [ + new DamlChoice + { + Name = "Accept", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.ContractId), + [new DamlTypeRef("test-pkg", "Acme.Offers", "Offer")]), + Controllers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("counterparty")]), + Observers = DamlPartyAnalysis.Static([]), + }, + new DamlChoice + { + Name = "Renew", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.ContractId), + [new DamlTypeRef("test-pkg", "Acme.Offers", "Offer")]), + Controllers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("counterparty")]), + Observers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("platform")]), + }, + new DamlChoice + { + Name = "Describe", + Consuming = false, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Text), + Controllers = DamlPartyAnalysis.Static([new DamlPartyPayloadField("counterparty")]), + Observers = DamlPartyAnalysis.Static([]), + }, + ], + Signatories = DamlPartyAnalysis.Static([new DamlPartyPayloadField("platform")]), + Observers = DamlPartyAnalysis.Static([]), + } + ], + DataTypes = + [ + new DamlDataType + { + Name = "Offer", + Definition = new DamlRecordDefinition(fields), + } + ], + Interfaces = + [ + new DamlInterface + { + Name = "Custody", + Choices = + [ + new DamlChoice + { + Name = "Transfer", + Consuming = true, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + Controllers = DamlPartyAnalysis.Dynamic, + Observers = DamlPartyAnalysis.Dynamic, + }, + ], + } + ], + }; + + var dar = new DarModel + { + MainPackage = new DamlPackage + { + PackageId = "test-pkg", + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }, + Dependencies = [], + }; + + return CreateGenerator().Generate(dar); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateChoiceCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateChoiceCompilesTests.cs index 67e9ef5..c651079 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateChoiceCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateChoiceCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -14,17 +15,8 @@ namespace Daml.Codegen.CSharp.Tests; public class EmittedTemplateChoiceCompilesTests { [Fact] - public void Emitted_template_with_fallback_argument_choice_compiles() + public void Emitted_template_with_unmappable_choice_argument_fails_generation() { - // Regression: WriteChoiceMethod previously emitted - // `ArgumentEncoder = arg => arg.ToRecord()` - // for any non-Unit, non-external choice argument type. When the type - // hits the codegen fallback path (here: a non-Unit DamlPrimitiveType), - // WriteChoiceArgumentType emits a stub `Arg` record with no - // ToRecord() method — so the static `Choice` field site no - // longer compiles in consumer output. The B3 gate already extended to - // Async emission; this test pins the same gate on - // WriteChoiceMethod. var module = new DamlModule { Name = "Test.Module", @@ -33,16 +25,12 @@ public void Emitted_template_with_fallback_argument_choice_compiles() new DamlTemplate { Name = "Agreement", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice { Name = "Process", Consuming = false, - // Non-Unit primitive — routes through the GetChoiceArgumentInfo - // fallback branch (IsFallback=true) and emits a stub ProcessArg - // record with no ToRecord(). ArgumentType = new DamlPrimitiveType(DamlPrimitive.Text), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), }, @@ -71,13 +59,11 @@ public void Emitted_template_with_fallback_argument_choice_compiles() }; var dar = new DarModel { MainPackage = package, Dependencies = [] }; - var files = CreateGenerator().Generate(dar); - var diagnostics = CompileEmittedFiles(files); - var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); - errors.Should().BeEmpty( - "emitted code for a fallback-argument-type choice should compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + FluentActions.Invoking(() => CreateGenerator().Generate(dar)) + .Should().Throw( + "a choice argument the codegen cannot map to an argument record must fail generation instead of emitting an empty stub record") + .WithMessage("*Process*"); } [Fact] @@ -98,7 +84,6 @@ public void Emitted_template_with_create_bearing_choice_compiles() new DamlTemplate { Name = "Agreement", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -139,7 +124,7 @@ public void Emitted_template_with_create_bearing_choice_compiles() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted code should compile against Daml.Runtime + Daml.Ledger.Abstractions, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -158,7 +143,6 @@ public void Emitted_choice_returning_an_interface_contract_id_compiles() new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -206,11 +190,11 @@ public void Emitted_choice_returning_an_interface_contract_id_compiles() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a choice returning an interface-typed ContractId must match created contracts by InterfaceIds, not TemplateId, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] - public void Emitted_choice_returning_a_local_placeholder_interface_contract_id_compiles_and_uses_literals() + public void Emitted_choice_returning_a_local_interface_contract_id_compiles_and_reads_the_marker_identity() { var module = new DamlModule { @@ -220,7 +204,6 @@ public void Emitted_choice_returning_a_local_placeholder_interface_contract_id_c new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -265,16 +248,17 @@ public void Emitted_choice_returning_a_local_placeholder_interface_contract_id_c var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( - "a choice returning a local placeholder-interface-typed ContractId (backed by RecordEmitter's throwing ITemplate stub, with no InterfaceId member) must still compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + "a choice returning a local interface-typed ContractId resolves to the interface's marker, so the projector's InterfaceId reads must compile, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); var code = files.First(f => f.RelativePath.EndsWith("Vault.cs", StringComparison.Ordinal)).Content; code.Should().Contain("item.InterfaceIds.Any(interfaceId =>"); - code.Should().Contain("string.Equals(interfaceId.ModuleName, \"Test.Module\", StringComparison.Ordinal)"); - code.Should().Contain("string.Equals(interfaceId.EntityName, \"Holdable\", StringComparison.Ordinal)"); + code.Should().Contain("string.Equals(interfaceId.ModuleName, global::Test.Package.IHoldable.InterfaceId.ModuleName, StringComparison.Ordinal)"); + code.Should().Contain("string.Equals(interfaceId.EntityName, global::Test.Package.IHoldable.InterfaceId.EntityName, StringComparison.Ordinal)"); code.Should().NotContain( - "IHoldable.InterfaceId", - "the local placeholder record backing `Holdable` exposes no InterfaceId member — the projector must not reference one"); + "\"Holdable\", StringComparison.Ordinal", + "a local interface now resolves to a marker carrying InterfaceId, so the projector reads that " + + "static instead of baking the identity in as a string literal the marker could drift from"); } [Fact] @@ -288,7 +272,6 @@ public void Emitted_template_implementing_a_local_interface_compiles() new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], Implements = [new DamlTypeRef("", "Test.Module", "Holdable")], }, @@ -329,7 +312,7 @@ public void Emitted_template_implementing_a_local_interface_compiles() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a template implementing a local interface must emit IImplements and satisfy the where TInterface : IDamlInterface constraint, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -349,7 +332,6 @@ public void Emitted_create_bearing_choice_with_static_controllers_compiles_both_ new DamlTemplate { Name = "Agreement", - Fields = fields, Choices = [ new DamlChoice @@ -412,7 +394,7 @@ internal static System.Threading.Tasks.Task Use( var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "the payload-bearing overload must be reachable from a nested Agreement.Contract (the type FromCreatedEvent returns), but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -433,7 +415,6 @@ public void Emitted_contract_overload_with_record_argument_and_multiple_controll new DamlTemplate { Name = "Agreement", - Fields = fields, Choices = [ new DamlChoice @@ -511,7 +492,7 @@ internal static System.Threading.Tasks.Task Use( var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "the nested-Contract overload forwarding a record argument and multiple controllers should compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -535,12 +516,6 @@ public void Emitted_template_with_payload_derived_observers_compiles() new DamlTemplate { Name = "Agreement", - Fields = - [ - new DamlFieldDefinition("platform", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("holder", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("issuer", new DamlPrimitiveType(DamlPrimitive.Party)), - ], Choices = [ new DamlChoice @@ -593,6 +568,6 @@ [new DamlPartyPayloadField("platform")]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "observer-aware emitted code should compile cleanly, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateKeyAndChoiceWrapperCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateKeyAndChoiceWrapperCompilesTests.cs index 8d9d381..b3775fd 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateKeyAndChoiceWrapperCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedTemplateKeyAndChoiceWrapperCompilesTests.cs @@ -1,12 +1,14 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; namespace Daml.Codegen.CSharp.Tests; @@ -14,22 +16,30 @@ namespace Daml.Codegen.CSharp.Tests; public class EmittedTemplateKeyAndChoiceWrapperCompilesTests { [Fact] - public void Emitted_template_with_key_compiles_standalone_with_throwing_key_stub() + public void Emitted_template_with_key_compiles_standalone() { - // The body-less `partial Key` (CS9248 until a consumer supplied - // an implementing partial) blocked the automated DAR publish pipeline, which - // has no human to write that partial. The accessor is now a non-partial - // throwing stub, so a key-bearing package compiles standalone with no consumer - // contribution. A regression that re-introduced the partial declaration would - // surface here as CS9248. var files = GenerateKeyBearingTemplate(); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( - "a key-bearing template must compile standalone now that Key is a throwing stub, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + "a key-bearing package must compile with no consumer contribution, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + } + + [Fact] + public void Emitted_template_with_key_has_no_doc_diagnostics() + { + var files = GenerateKeyBearingTemplate(); + + var docDiagnostics = CompileEmittedFilesWithDocDiagnostics(files) + .Where(d => d.Id is "CS1570" or "CS1572" or "CS1573" or "CS1574" or "CS1580" or "CS1584" or "CS1658") + .ToList(); + + docDiagnostics.Should().BeEmpty( + "the by-key command builders carry crefs and param docs that a consumer building with GenerateDocumentationFile compiles, but got: {0}", + string.Join("\n", docDiagnostics.Select(e => e.Id + " " + e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -43,7 +53,6 @@ public void Emitted_non_contract_wrapper_with_nested_unit_return_compiles() new DamlTemplate { Name = "Sink", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -104,81 +113,20 @@ [new DamlPrimitiveType(DamlPrimitive.Unit)]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "non-CID wrappers with nested Unit returns must compile end-to-end, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] - public void Generate_should_emit_empty_langversion_marker_for_key_bearing_output() + public void Emitted_class_template_with_key_compiles_standalone() { - // With the Key accessor reverted from a `partial` to a plain - // throwing stub, key-bearing output no longer contains the C# 13 - // partial-property syntax, so the LangVersion marker stays empty just like - // keyless output — the build no longer raises the SDK floor for keys. - var files = GenerateKeyBearingTemplate(); - - var marker = files.Should().ContainSingle(f => f.RelativePath == ".daml-langversion", - "build-integration tooling reads this state file to bump LangVersion").Subject; - marker.Content.Should().BeEmpty( - "key-bearing output no longer requires C# 13 after the partial-property revert"); - } - - [Fact] - public void Generate_should_emit_empty_langversion_marker_for_keyless_output() - { - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "NoKey", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], - Choices = [], - Key = null, - }, - ], - DataTypes = - [ - new DamlDataType - { - Name = "NoKey", - Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), - }, - ], - Interfaces = [], - }; - var package = new DamlPackage - { - PackageId = "test-package-id", - Name = "test-package", - Version = new Version(1, 0, 0), - LfVersion = "2.1", - Modules = [module], - DependencyReferences = [], - }; - var dar = new DarModel { MainPackage = package, Dependencies = [] }; - var files = CreateGenerator().Generate(dar); - - var marker = files.Should().ContainSingle(f => f.RelativePath == ".daml-langversion").Subject; - marker.Content.Should().BeEmpty( - "keyless output records no required version, so the LangVersion bump is skipped"); - } - - [Fact] - public void Emitted_class_template_with_key_compiles_standalone_with_throwing_key_stub() - { - // Class-mode (`UseRecordTypes=false`) counterpart of the record-mode standalone - // test: a key-bearing `partial class` template must also compile with no - // consumer contribution now that Key is a throwing stub. var files = GenerateKeyBearingTemplate(useRecordTypes: false); var diagnostics = CompileEmittedFiles(files); var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( - "a class-mode key-bearing template must compile standalone now that Key is a throwing stub, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + "a class-mode key-bearing package must compile with no consumer contribution, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -198,7 +146,6 @@ public void Emitted_non_contract_choice_wrapper_compiles_for_optional_unit_retur new DamlTemplate { Name = "Probe", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -241,7 +188,7 @@ [new DamlPrimitiveType(DamlPrimitive.Unit)]), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted Optional () non-CID wrapper should compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); var probe = files.First(f => f.RelativePath.EndsWith("Probe.cs", StringComparison.Ordinal)); // The decoder reuses GetFromValueConversion; the new Unit arm must @@ -267,7 +214,6 @@ public void Emitted_non_contract_choice_wrappers_compile_for_decimal_record_and_ new DamlTemplate { Name = "Oracle", - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -331,6 +277,110 @@ public void Emitted_non_contract_choice_wrappers_compile_for_decimal_record_and_ var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "emitted non-CID wrapper code (Decimal / record / Unit returns) should compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + } + + [Fact] + public void Emitted_choice_exerciser_stops_compiling_without_the_ledger_extensions_using() + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = + [ + new DamlTemplate + { + Name = "Asset", + Choices = + [ + new DamlChoice + { + Name = "Touch", + Consuming = false, + ArgumentType = new DamlPrimitiveType(DamlPrimitive.Unit), + ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), + }, + ], + }, + ], + DataTypes = + [ + new DamlDataType + { + Name = "Asset", + Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, + ], + Interfaces = [], + }; + + var files = CreateGenerator().Generate(CreateTestDar(module)); + + var asEmitted = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + + asEmitted.Should().BeEmpty( + "the emitted exerciser surface carries the ledger-extensions using itself, but got: {0}", + string.Join("\n", asEmitted.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); + + var withoutLedgerExtensions = files + .Select(f => f.IsBinary + ? f + : GeneratedFile.Text( + f.RelativePath, + f.Content.Replace("using Daml.Ledger.Abstractions.Extensions;", string.Empty, StringComparison.Ordinal))) + .ToList(); + + var errors = CompileEmittedFiles(withoutLedgerExtensions) + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + errors.Should().Contain( + d => d.Id == "CS1061" + && d.GetMessage(CultureInfo.InvariantCulture).Contains("TrySubmitSingleAsync", StringComparison.Ordinal), + "the emitted exercisers reach TrySubmitSingleAsync as an extension method on ILedgerWriter, so dropping its namespace must break the build on that member specifically — any other missing-member error would satisfy a bare CS1061 gate while proving nothing, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture)))); + } + + [Fact] + public void Emitted_submission_extensions_compile_without_the_ledger_extensions_using() + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = + [ + new DamlTemplate + { + Name = "ChoicelessAssetWithKey", + Choices = [], + Key = new DamlPrimitiveType(DamlPrimitive.Text), + }, + ], + DataTypes = + [ + new DamlDataType + { + Name = "ChoicelessAssetWithKey", + Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), + }, + ], + Interfaces = [], + }; + + var files = CreateGenerator().Generate(CreateTestDar(module)); + + var template = files.Should().ContainSingle(f => f.RelativePath.EndsWith("ChoicelessAssetWithKey.cs", StringComparison.Ordinal)).Subject; + + template.Content.Should() + .Contain("public static class ChoicelessAssetWithKeySubmissionExtensions", + "a choice-free template emits the typed-submitter surface and nothing that exercises a choice") + .And.NotContain("using Daml.Ledger.Abstractions.Extensions;", + "the emitted CreateAsync calls ILedgerWriter.TryCreateAsync directly, so the typed-submitter surface needs no extension-method namespace"); + + var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + + errors.Should().BeEmpty( + "the typed-submitter surface must compile without the namespace the exercisers require, but got: {0}", + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedVariantCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedVariantCompilesTests.cs index 14faf9b..5df44f5 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmittedVariantCompilesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedVariantCompilesTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -52,7 +53,7 @@ public void Emitted_variant_with_constructor_that_matches_the_type_name_compiles var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a variant whose constructor name equals its own type name would be CS0542 unless disambiguated, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -66,7 +67,6 @@ public void Emitted_variant_constructor_referencing_nested_choice_arg_type_compi new DamlTemplate { Name = "DsoRules", - Fields = [new DamlFieldDefinition("dso", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -123,7 +123,7 @@ public void Emitted_variant_constructor_referencing_nested_choice_arg_type_compi var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a variant constructor referencing a same-package nested choice-arg type must compile, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -137,7 +137,6 @@ public void Emitted_variant_constructor_with_contract_id_argument_compiles() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], }, ], @@ -178,7 +177,7 @@ public void Emitted_variant_constructor_with_contract_id_argument_compiles() var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a variant constructor whose argument type is ContractId must compile (the file needs `using Daml.Runtime.Contracts;`), but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -228,7 +227,7 @@ public void Emitted_variant_constructor_whose_payload_is_another_variant_compile var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a variant constructor whose payload is another variant must round-trip through that variant's ToVariant/FromVariant, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -289,7 +288,7 @@ [new DamlTypeVar("a")])), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a recursive variant whose payload is the same variant must round-trip via ToVariant/FromVariant, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] @@ -346,6 +345,6 @@ [new DamlTypeVar("a")])])), var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a variant carrying a list of nested-variant payloads must round-trip via ToVariant/FromVariant, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/EmittedViewFieldCollisionCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmittedViewFieldCollisionCompilesTests.cs new file mode 100644 index 0000000..e0d0e53 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/EmittedViewFieldCollisionCompilesTests.cs @@ -0,0 +1,176 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Microsoft.CodeAnalysis; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; +using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; +using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Pins the field names that make a view record ineligible for the marker enrichment. +/// The record emitter and the interface emitter derive a field's C# member name +/// independently, each disambiguating only against its own enclosing type, and the marker +/// declares two members of its own — so four legal Daml view fields would otherwise emit a +/// marker the stamped record cannot satisfy: a field PascalCasing to the record's name is +/// renamed on the record but not on the marker (CS0535), a field PascalCasing to the +/// marker's name is renamed on the marker but not on the record (CS0535), and a field +/// PascalCasing to View or InterfaceId redeclares a member the marker already +/// owns (CS0102). Each must degrade to an un-stamped record beside an un-enriched marker +/// that still carries its View witness, so the interface stays subscribable. +/// +public class EmittedViewFieldCollisionCompilesTests +{ + private const string InterfaceName = "Asset"; + private const string MarkerFileName = "IAsset.cs"; + private const string ViewRecordName = "AssetView"; + private const string ViewRecordFileName = "AssetView.cs"; + + private static IReadOnlyList EmitViewedInterface(params string[] viewFieldNames) + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = ViewRecordName, + Definition = new DamlRecordDefinition( + [.. viewFieldNames.Select(name => + new DamlFieldDefinition(name, new DamlPrimitiveType(DamlPrimitive.Party)))]), + }, + ], + Interfaces = + [ + new DamlInterface + { + Name = InterfaceName, + Choices = [], + ViewType = new DamlTypeRef("", "Test.Module", ViewRecordName), + }, + ], + }; + + return CreateGenerator( + new CodeGenOptions + { + EnableNullableReferenceTypes = true, + UseFileScopedNamespaces = true, + UseRecordTypes = true, + UsePrimaryConstructors = true, + }) + .Generate(CreateTestDar(module)); + } + + private static string SourceOf(IReadOnlyList files, string fileName) => + files.Single(f => f.RelativePath.EndsWith(fileName, StringComparison.Ordinal)).Content; + + private static void ShouldCompileCleanly(IReadOnlyList files, string because) + { + var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); + + errors.Should().BeEmpty( + because + "; got: {0}", + string.Join("; ", errors.Select(d => d.ToString()))); + } + + [Fact] + public void Emitted_view_record_with_no_colliding_field_is_stamped_and_mirrored() + { + var files = EmitViewedInterface("owner"); + + SourceOf(files, ViewRecordFileName).Should().Contain( + $"Party Owner\n) : I{InterfaceName}, IDamlRecord<{ViewRecordName}>", + "the collision cases below only prove something if the same fixture enriches a clean view record"); + SourceOf(files, MarkerFileName).Should().Contain("Party Owner { get; }"); + ShouldCompileCleanly(files, "the enriched shape is the baseline every degraded case falls back from"); + } + + [Theory] + [InlineData("view", "the mirror would redeclare the marker's own View witness (CS0102)")] + [InlineData("interfaceId", "the mirror would redeclare the marker's own InterfaceId (CS0102)")] + [InlineData("assetView", "the record renames the property to AssetView_ but the marker declares AssetView (CS0535)")] + [InlineData("iAsset", "the marker renames the property to IAsset_ but the record declares IAsset (CS0535)")] + public void Emitted_view_record_with_a_colliding_field_is_left_un_stamped_and_compiles( + string viewFieldName, + string collision) + { + var files = EmitViewedInterface(viewFieldName); + + ShouldCompileCleanly(files, $"the emitter must degrade rather than emit uncompilable C#: {collision}"); + SourceOf(files, ViewRecordFileName).Should().NotContain( + $": I{InterfaceName},", + $"the view record must not be stamped with a marker it cannot satisfy: {collision}"); + SourceOf(files, MarkerFileName).Should().NotContain( + "Party ", + $"the marker must not mirror a field the stamped record would not implement: {collision}"); + SourceOf(files, MarkerFileName).Should().Contain( + $"ViewDescriptor View {{ get; }} = new();", + "degrading the enrichment must still leave a subscribable marker paired with its view record"); + } + + [Fact] + public void Emitted_marker_mirrors_a_collection_typed_and_a_keyword_named_view_field() + { + var module = new DamlModule + { + Name = "Test.Module", + Templates = [], + DataTypes = + [ + new DamlDataType + { + Name = ViewRecordName, + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition( + "holders", + new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.List), + [new DamlPrimitiveType(DamlPrimitive.Party)])), + new DamlFieldDefinition( + "quantities", + new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.TextMap), + [new DamlPrimitiveType(DamlPrimitive.Numeric)])), + new DamlFieldDefinition("interface", new DamlPrimitiveType(DamlPrimitive.Text)), + ]), + }, + ], + Interfaces = + [ + new DamlInterface + { + Name = InterfaceName, + Choices = [], + ViewType = new DamlTypeRef("", "Test.Module", ViewRecordName), + }, + ], + }; + + var files = CreateGenerator( + new CodeGenOptions + { + EnableNullableReferenceTypes = true, + UseFileScopedNamespaces = true, + UseRecordTypes = true, + UsePrimaryConstructors = true, + }) + .Generate(CreateTestDar(module)); + + var marker = SourceOf(files, MarkerFileName); + marker.Should().Contain( + "using System.Collections.Generic;", + "a mirrored collection field needs the same namespace the record emitter requires for it"); + marker.Should().Contain("Interface { get; }", "a keyword-named field PascalCases on the marker exactly as it does on the view record"); + marker.Should().NotContain("@interface", "the escape runs after PascalCasing, which already lifts the name clear of the keyword"); + ShouldCompileCleanly(files, "a mirrored field's type and name must resolve on the marker as on the record"); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/EmitterMachineryApiSurfaceTests.cs b/tests/Daml.Codegen.CSharp.Tests/EmitterMachineryApiSurfaceTests.cs index 931965b..168ecaa 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EmitterMachineryApiSurfaceTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EmitterMachineryApiSurfaceTests.cs @@ -29,7 +29,7 @@ public static TheoryData EmitterMachineryTypes() => [Theory] [MemberData(nameof(EmitterMachineryTypes))] - public void emitter_machinery_type_is_internal(Type emitterType) + public void EmitterMachineryApiSurface_emitter_machinery_type_is_internal(Type emitterType) { emitterType.IsPublic.Should().BeFalse( "{0} is codegen implementation detail used only within CodeGen/, and a public " + diff --git a/tests/Daml.Codegen.CSharp.Tests/EnumEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/EnumEmitterTests.cs index ef3c600..93582e0 100644 --- a/tests/Daml.Codegen.CSharp.Tests/EnumEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/EnumEmitterTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -49,7 +49,7 @@ private static string EmitEnum(string name, string[] constructors, bool generate } [Fact] - public void emits_the_enum_declaration_with_every_constructor() + public void EnumEmitter_emits_the_enum_declaration_with_every_constructor() { var output = EmitEnum("Status", ["Pending", "Active", "Completed", "Cancelled"]); @@ -61,7 +61,7 @@ public void emits_the_enum_declaration_with_every_constructor() } [Fact] - public void names_extension_methods_after_their_DamlEnum_type() + public void EnumEmitter_names_extension_methods_after_their_DamlEnum_type() { var output = EmitEnum("Status", ["Pending", "Active"]); @@ -72,7 +72,7 @@ public void names_extension_methods_after_their_DamlEnum_type() } [Fact] - public void round_trips_each_constructor_through_the_DamlEnum_serializers() + public void EnumEmitter_round_trips_each_constructor_through_the_DamlEnum_serializers() { var output = EmitEnum("Status", ["Pending", "Active"]); @@ -83,7 +83,7 @@ public void round_trips_each_constructor_through_the_DamlEnum_serializers() } [Fact] - public void emits_xml_docs_when_enabled() + public void EnumEmitter_emits_xml_docs_when_enabled() { var output = EmitEnum("Color", ["Red", "Green", "Blue"], generateXmlDocs: true); @@ -94,7 +94,7 @@ public void emits_xml_docs_when_enabled() } [Fact] - public void emits_xml_docs_for_every_enum_constructor_when_enabled() + public void EnumEmitter_emits_xml_docs_for_every_enum_constructor_when_enabled() { var output = EmitEnum("Color", ["Red", "Green", "Blue"], generateXmlDocs: true); @@ -104,7 +104,7 @@ public void emits_xml_docs_for_every_enum_constructor_when_enabled() } [Fact] - public void omits_the_type_xml_docs_when_disabled() + public void EnumEmitter_omits_the_type_xml_docs_when_disabled() { var output = EmitEnum("Color", ["Red", "Green", "Blue"], generateXmlDocs: false); diff --git a/tests/Daml.Codegen.CSharp.Tests/FourPartPackageVersionTests.cs b/tests/Daml.Codegen.CSharp.Tests/FourPartPackageVersionTests.cs index 96c15a9..65fbd0d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/FourPartPackageVersionTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/FourPartPackageVersionTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.CSharp.Versioning; using AwesomeAssertions; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/GenericTypeSerializationRoundTripTests.cs b/tests/Daml.Codegen.CSharp.Tests/GenericTypeSerializationRoundTripTests.cs index 3c18667..78bc939 100644 --- a/tests/Daml.Codegen.CSharp.Tests/GenericTypeSerializationRoundTripTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/GenericTypeSerializationRoundTripTests.cs @@ -1,9 +1,10 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Reflection; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Runtime.Data; using AwesomeAssertions; using Microsoft.CodeAnalysis; @@ -14,14 +15,18 @@ namespace Daml.Codegen.CSharp.Tests; /// /// Runtime proof that generic records and variants round-trip through the -/// emitted converter-delegate serialization surface. The conformance corpus has -/// no user-defined generic types, so this compiles a synthetic package containing -/// a generic record, a generic variant, and a non-generic record that embeds both -/// instantiated over , Roslyn-compiles it in memory, then -/// reflection-invokes the emitted ToRecord/FromRecord/ToVariant/ -/// FromVariant with real converters and asserts a serialize-then-deserialize -/// round trip preserves the object graph — including the embedding record whose -/// fields previously deserialized to a TODO stub. +/// emitted converter-delegate serialization surface, driven straight from the +/// emitter's model rather than from a compiled DAR: this compiles a synthetic +/// package containing a generic record, a generic variant, and a non-generic +/// record that embeds both instantiated over , +/// Roslyn-compiles it in memory, then reflection-invokes the emitted +/// ToRecord/FromRecord/ToVariant/FromVariant with +/// real converters and asserts a serialize-then-deserialize round trip +/// preserves the object graph — including the embedding record whose fields +/// previously deserialized to a TODO stub. The conformance corpus covers +/// the same shapes end to end through a real DAR (Box, Slot); the +/// synthetic package is what lets a type-parameter regression fail here without +/// waiting on a corpus rebuild. /// public class GenericTypeSerializationRoundTripTests { @@ -91,7 +96,7 @@ private static Assembly CompileAssembly() UseRecordTypes = true, UsePrimaryConstructors = true, }; - var files = new CSharpCodeGenerator(options, new ConsoleLogger(0)) + var files = new CSharpCodeGenerator(options) .Generate(new DarModel { MainPackage = package, Dependencies = [] }); return EmitAssembly(files); @@ -133,7 +138,7 @@ private static Assembly EmitAssembly(IReadOnlyList files) "\n", emit.Diagnostics .Where(d => d.Severity == DiagnosticSeverity.Error) - .Select(d => d.GetMessage() + " @ " + d.Location))); + .Select(d => d.GetMessage(CultureInfo.InvariantCulture) + " @ " + d.Location))); stream.Seek(0, SeekOrigin.Begin); return Assembly.Load(stream.ToArray()); @@ -148,7 +153,7 @@ private static Type EmittedType(string simpleOrGenericName) => private static readonly Func PartyFromValue = v => Party.FromDamlValue(v.As()); [Fact] - public void generic_record_round_trips_through_its_converter_delegates() + public void GenericTypeSerializationRoundTrip_generic_record_round_trips_through_its_converter_delegates() { var boxOfParty = EmittedType("Box`1").MakeGenericType(typeof(Party)); var box = Activator.CreateInstance(boxOfParty, new Party("alice"))!; @@ -161,7 +166,7 @@ public void generic_record_round_trips_through_its_converter_delegates() } [Fact] - public void generic_variant_round_trips_through_its_converter_delegates() + public void GenericTypeSerializationRoundTrip_generic_variant_round_trips_through_its_converter_delegates() { var wrapperOfParty = EmittedType("Wrapper`1").MakeGenericType(typeof(Party)); var fromVariant = wrapperOfParty.GetMethod("FromVariant", BindingFlags.Public | BindingFlags.Static)!; @@ -176,7 +181,7 @@ public void generic_variant_round_trips_through_its_converter_delegates() } [Fact] - public void record_embedding_instantiated_generics_round_trips_without_a_stub() + public void GenericTypeSerializationRoundTrip_record_embedding_instantiated_generics_round_trips_without_a_stub() { var boxOfParty = EmittedType("Box`1").MakeGenericType(typeof(Party)); var wrapperOfParty = EmittedType("Wrapper`1").MakeGenericType(typeof(Party)); @@ -195,7 +200,7 @@ public void record_embedding_instantiated_generics_round_trips_without_a_stub() } [Fact] - public void nullary_variant_constructor_round_trips_through_the_converter_delegate() + public void GenericTypeSerializationRoundTrip_nullary_variant_constructor_round_trips_through_the_converter_delegate() { var wrapperOfParty = EmittedType("Wrapper`1").MakeGenericType(typeof(Party)); var fromVariant = wrapperOfParty.GetMethod("FromVariant", BindingFlags.Public | BindingFlags.Static)!; diff --git a/tests/Daml.Codegen.CSharp.Tests/ICodegenLoggerTests.cs b/tests/Daml.Codegen.CSharp.Tests/ICodegenLoggerTests.cs deleted file mode 100644 index 61272db..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/ICodegenLoggerTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2026 Peaceful Studio OÜ -// SPDX-License-Identifier: Apache-2.0 - -using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; -using AwesomeAssertions; -using Xunit; - -namespace Daml.Codegen.CSharp.Tests; - -public class ICodegenLoggerTests -{ - [Fact] - public void code_generator_accepts_icodegenlogger_implementation() - { - var captured = new CapturingLogger(); - var options = new CodeGenOptions(); - - var generator = new CSharpCodeGenerator(options, captured); - generator.Should().NotBeNull("CSharpCodeGenerator's constructor must accept any ICodegenLogger, not only ConsoleLogger"); - } - - [Fact] - public void console_logger_implements_icodegen_logger() - { - var logger = new ConsoleLogger(0); - logger.Should().BeAssignableTo( - "ConsoleLogger keeps its existing role as the CLI logger but is now plug-compatible via ICodegenLogger"); - } - - private sealed class CapturingLogger : ICodegenLogger - { - public List Errors { get; } = []; - public List Warnings { get; } = []; - public List Infos { get; } = []; - public List Debugs { get; } = []; - - public void Error(string message) => Errors.Add(message); - public void Warning(string message) => Warnings.Add(message); - public void Info(string message) => Infos.Add(message); - public void Debug(string message) => Debugs.Add(message); - } -} diff --git a/tests/Daml.Codegen.CSharp.Tests/IdentifiersTests.cs b/tests/Daml.Codegen.CSharp.Tests/IdentifiersTests.cs index 70152ca..e223753 100644 --- a/tests/Daml.Codegen.CSharp.Tests/IdentifiersTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/IdentifiersTests.cs @@ -25,6 +25,9 @@ public void Disambiguate_appends_underscore_only_when_identifier_equals_enclosin [InlineData("period", "Period", "Period_")] [InlineData("other", "Period", "Other")] [InlineData("period", "", "Period")] + [InlineData("operator", "Agreement", "Operator")] + [InlineData("lock", "HoldingView", "Lock")] + [InlineData("lock", "Lock", "Lock_")] public void MemberName_pascal_cases_then_disambiguates_against_enclosing_type( string damlFieldName, string enclosingTypeName, string expected) { diff --git a/tests/Daml.Codegen.CSharp.Tests/InterfaceChoiceResultCs0117RegressionTests.cs b/tests/Daml.Codegen.CSharp.Tests/InterfaceChoiceResultCs0117RegressionTests.cs index 3431d62..c43a06c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/InterfaceChoiceResultCs0117RegressionTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/InterfaceChoiceResultCs0117RegressionTests.cs @@ -1,8 +1,9 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Microsoft.CodeAnalysis; using Xunit; @@ -55,7 +56,6 @@ private static DarModel ReferenceHoldingLocalInterface( new DamlTemplate { Name = "Reference", - Fields = [new DamlFieldDefinition("cid", ContractIdOf(moduleName, interfaceName))], Choices = [ new DamlChoice @@ -109,11 +109,11 @@ private static void CompilesCleanly(DarModel dar, string because) { var files = CreateGenerator().Generate(dar); var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); - errors.Should().BeEmpty(because + ", but got: {0}", string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + errors.Should().BeEmpty(because + ", but got: {0}", string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] - public void finance_holding_reference_choice_returning_local_factory_interface_cid_compiles() + public void InterfaceChoiceResultCs0117Regression_finance_holding_reference_choice_returning_local_factory_interface_cid_compiles() { var dar = ReferenceHoldingLocalInterface( "daml-finance-interface-holding-v4", @@ -125,7 +125,7 @@ public void finance_holding_reference_choice_returning_local_factory_interface_c } [Fact] - public void finance_instrument_base_reference_choice_returning_local_instrument_interface_cid_compiles() + public void InterfaceChoiceResultCs0117Regression_finance_instrument_base_reference_choice_returning_local_instrument_interface_cid_compiles() { var dar = ReferenceHoldingLocalInterface( "daml-finance-interface-instrument-base-v4", @@ -137,7 +137,7 @@ public void finance_instrument_base_reference_choice_returning_local_instrument_ } [Fact] - public void template_choice_returning_optional_local_interface_cid_compiles() + public void InterfaceChoiceResultCs0117Regression_template_choice_returning_optional_local_interface_cid_compiles() { var module = "Daml.Finance.Interface.Holding.V4.Factory"; var dar = ReferenceHoldingLocalInterface( @@ -150,7 +150,7 @@ public void template_choice_returning_optional_local_interface_cid_compiles() } [Fact] - public void template_choice_returning_list_of_local_interface_cid_compiles() + public void InterfaceChoiceResultCs0117Regression_template_choice_returning_list_of_local_interface_cid_compiles() { var module = "Daml.Finance.Interface.Holding.V4.Factory"; var dar = ReferenceHoldingLocalInterface( @@ -163,7 +163,7 @@ public void template_choice_returning_list_of_local_interface_cid_compiles() } [Fact] - public void template_choice_returning_foreign_package_interface_cid_compiles() + public void InterfaceChoiceResultCs0117Regression_template_choice_returning_foreign_package_interface_cid_compiles() { const string foreignPackageId = "foreign-holding-pkg-id"; var foreignModule = new DamlModule @@ -191,7 +191,6 @@ public void template_choice_returning_foreign_package_interface_cid_compiles() new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -239,11 +238,11 @@ [new DamlTypeRef(foreignPackageId, "Foreign.Holding", "Holding")]), var errors = CompileEmittedFiles(files).Where(d => d.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().BeEmpty( "a choice returning ContractId of a foreign-package interface must project via the foreign marker's generated InterfaceId symbol, but got: {0}", - string.Join("\n", errors.Select(e => e.GetMessage() + " @ " + e.Location))); + string.Join("\n", errors.Select(e => e.GetMessage(CultureInfo.InvariantCulture) + " @ " + e.Location))); } [Fact] - public void template_choice_returning_template_and_interface_cid_tuple_compiles() + public void InterfaceChoiceResultCs0117Regression_template_choice_returning_template_and_interface_cid_tuple_compiles() { var module = new DamlModule { @@ -253,13 +252,11 @@ public void template_choice_returning_template_and_interface_cid_tuple_compiles( new DamlTemplate { Name = "Widget", - Fields = [], Choices = [], }, new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -310,13 +307,11 @@ private static DarModel TemplateMarkerCollisionDar() new DamlTemplate { Name = "IFactory", - Fields = [], Choices = [], }, new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -356,13 +351,13 @@ private static DarModel TemplateMarkerCollisionDar() } [Fact] - public void disambiguates_template_colliding_with_interface_marker_name() + public void InterfaceChoiceResultCs0117Regression_disambiguates_template_colliding_with_interface_marker_name() { CompilesCleanly(TemplateMarkerCollisionDar(), "a template literally named IFactory and an interface Factory (whose generated marker is also IFactory) must not both declare a public IFactory type in the same namespace"); } [Fact] - public void writes_the_disambiguated_marker_file_for_a_template_colliding_with_it() + public void InterfaceChoiceResultCs0117Regression_writes_the_disambiguated_marker_file_for_a_template_colliding_with_it() { var files = CreateGenerator().Generate(TemplateMarkerCollisionDar()).ToList(); @@ -379,7 +374,6 @@ private static DarModel RecordMarkerCollisionDar() new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -419,13 +413,13 @@ private static DarModel RecordMarkerCollisionDar() } [Fact] - public void disambiguates_record_colliding_with_interface_marker_name() + public void InterfaceChoiceResultCs0117Regression_disambiguates_record_colliding_with_interface_marker_name() { CompilesCleanly(RecordMarkerCollisionDar(), "a record literally named IFactory and an interface Factory (whose generated marker is also IFactory) must not both declare a public IFactory type in the same namespace"); } [Fact] - public void writes_the_disambiguated_marker_file_for_a_record_colliding_with_it() + public void InterfaceChoiceResultCs0117Regression_writes_the_disambiguated_marker_file_for_a_record_colliding_with_it() { var files = CreateGenerator().Generate(RecordMarkerCollisionDar()).ToList(); @@ -442,13 +436,11 @@ private static DarModel RecordMarkerCollisionWithFirstRoundDisambiguatedTemplate new DamlTemplate { Name = "IFactory", - Fields = [], Choices = [], }, new DamlTemplate { Name = "Vault", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -491,7 +483,7 @@ private static DarModel RecordMarkerCollisionWithFirstRoundDisambiguatedTemplate } [Fact] - public void disambiguates_record_colliding_with_the_first_round_disambiguated_marker() + public void InterfaceChoiceResultCs0117Regression_disambiguates_record_colliding_with_the_first_round_disambiguated_marker() { CompilesCleanly( RecordMarkerCollisionWithFirstRoundDisambiguatedTemplateDar(), @@ -499,7 +491,7 @@ public void disambiguates_record_colliding_with_the_first_round_disambiguated_ma } [Fact] - public void writes_the_second_round_disambiguated_marker_file_when_both_rounds_collide() + public void InterfaceChoiceResultCs0117Regression_writes_the_second_round_disambiguated_marker_file_when_both_rounds_collide() { var files = CreateGenerator().Generate(RecordMarkerCollisionWithFirstRoundDisambiguatedTemplateDar()).ToList(); @@ -535,7 +527,7 @@ private static DarModel TwoModuleInterfaceMarkerCollisionDar(string firstModuleN } [Fact] - public void two_same_named_interfaces_in_different_modules_compile_cleanly_with_distinct_markers() + public void InterfaceChoiceResultCs0117Regression_two_same_named_interfaces_in_different_modules_compile_cleanly_with_distinct_markers() { CompilesCleanly( TwoModuleInterfaceMarkerCollisionDar("Alpha.Module", "Beta.Module"), @@ -543,7 +535,7 @@ public void two_same_named_interfaces_in_different_modules_compile_cleanly_with_ } [Fact] - public void two_same_named_interfaces_in_different_modules_deterministically_assign_the_same_winner_regardless_of_module_order() + public void InterfaceChoiceResultCs0117Regression_two_same_named_interfaces_in_different_modules_deterministically_assign_the_same_winner_regardless_of_module_order() { string WinnerModuleName(string firstModuleName, string secondModuleName) { diff --git a/tests/Daml.Codegen.CSharp.Tests/InterfaceEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/InterfaceEmitterTests.cs index 7af8915..d703d38 100644 --- a/tests/Daml.Codegen.CSharp.Tests/InterfaceEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/InterfaceEmitterTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -39,21 +39,25 @@ private static DamlPackage Package(DamlModule module) => private static CodeGenOptions Options(bool generateXmlDocs) => new() { RootNamespace = "Test.Package", GenerateXmlDocs = generateXmlDocs }; - private static string EmitInterface(DamlInterface iface, DamlDataType[]? dataTypes = null, bool generateXmlDocs = true) + private static string EmitInterface( + DamlInterface iface, + DamlDataType[]? dataTypes = null, + bool generateXmlDocs = true, + DamlInterface[]? siblingInterfaces = null) { var module = new DamlModule { Name = ModuleName, Templates = [], DataTypes = dataTypes ?? [], - Interfaces = [iface], + Interfaces = [iface, .. siblingInterfaces ?? []], }; var options = Options(generateXmlDocs); var context = PackageEmitContext.ForPackage(Package(module), options); var resolver = new StubResolver(); var mapper = new DamlTypeMapper(context, resolver); var choiceEmitter = new ChoiceEmitter(context, resolver, options, mapper, new PartyAnalysis()); - var emitter = new InterfaceEmitter(context, mapper, choiceEmitter, options); + var emitter = new InterfaceEmitter(context, mapper, resolver, choiceEmitter, options); var sb = new StringBuilder(); emitter.WriteInterfaceType(new IndentWriter(sb), Package(module), module, iface); return sb.ToString(); @@ -63,7 +67,7 @@ private static DamlInterface Interface(string name, DamlType? viewType = null, p new() { Name = name, Choices = choices, ViewType = viewType }; [Fact] - public void emits_the_interface_declaration_with_an_I_prefix() + public void InterfaceEmitter_emits_the_interface_declaration_with_an_I_prefix() { var output = EmitInterface(Interface("Transferable")); @@ -71,7 +75,7 @@ public void emits_the_interface_declaration_with_an_I_prefix() } [Fact] - public void declares_IDamlInterface_as_the_base_facet() + public void InterfaceEmitter_declares_IDamlInterface_as_the_base_facet() { var output = EmitInterface(Interface("Lockable")); @@ -79,7 +83,7 @@ public void declares_IDamlInterface_as_the_base_facet() } [Fact] - public void emits_static_interface_metadata() + public void InterfaceEmitter_emits_static_interface_metadata() { var output = EmitInterface(Interface("Holdable")); @@ -93,7 +97,7 @@ public void emits_static_interface_metadata() } [Fact] - public void emits_explicit_daml_type_descriptor() + public void InterfaceEmitter_emits_explicit_daml_type_descriptor() { var output = EmitInterface(Interface("Holdable")); @@ -101,8 +105,111 @@ public void emits_explicit_daml_type_descriptor() "static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier(\"test-package-id\", \"Test.Module\", \"Holdable\"), DamlTypeKind.Interface, \"test-package\");"); } + private static DamlDataType AssetViewRecord() => + new() + { + Name = "AssetView", + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), + new DamlFieldDefinition("amount", new DamlPrimitiveType(DamlPrimitive.Numeric)), + ]), + }; + + private static string EmitAssetWithLocalView(bool generateXmlDocs = true) => + EmitInterface( + Interface("Asset", viewType: new DamlTypeRef("", ModuleName, "AssetView")), + dataTypes: [AssetViewRecord()], + generateXmlDocs: generateXmlDocs); + + [Fact] + public void InterfaceEmitter_adds_the_IHasView_facet_when_the_interface_has_a_view_type() + { + var output = EmitAssetWithLocalView(); + + output.Should().Contain("IHasView"); + } + + [Fact] + public void InterfaceEmitter_omits_the_IHasView_facet_when_the_interface_has_no_view_type() + { + var output = EmitInterface(Interface("Lockable")); + + output.Should().NotContain("IHasView"); + } + [Fact] - public void adds_the_IHasView_facet_when_the_interface_has_a_view_type() + public void InterfaceEmitter_emits_the_View_witness_static_for_a_local_view_record() + { + var output = EmitAssetWithLocalView(); + + output.Should().Contain("public static ViewDescriptor View { get; } = new();"); + } + + [Fact] + public void InterfaceEmitter_mirrors_the_view_fields_as_marker_instance_properties() + { + var output = EmitAssetWithLocalView(); + + output.Should().Contain("Party Owner { get; }"); + output.Should().Contain("decimal Amount { get; }"); + } + + [Fact] + public void InterfaceEmitter_documents_marker_equality_as_reference_equality_when_the_view_is_local() + { + var output = EmitAssetWithLocalView(); + + output.Should().Contain("reference equality"); + } + + [Fact] + public void InterfaceEmitter_omits_the_view_enrichment_when_the_interface_has_no_view_type() + { + var output = EmitInterface(Interface("Lockable")); + + output.Should().NotContain("ViewDescriptor"); + output.Should().NotContain("View { get; }"); + output.Should().NotContain("reference equality"); + } + + [Fact] + public void InterfaceEmitter_emits_the_witness_without_view_properties_for_a_foreign_view_record() + { + var output = EmitInterface( + Interface("Asset", viewType: new DamlTypeRef("foreign-package-id", "Foreign.Module", "AssetView"))); + + output.Should().Contain("public static ViewDescriptor View { get; } = new();"); + output.Should().NotContain("Owner { get; }"); + output.Should().NotContain("reference equality"); + } + + [Fact] + public void InterfaceEmitter_does_not_enrich_a_foreign_view_ref_that_name_collides_with_a_local_view_record() + { + var output = EmitInterface( + Interface("Foreign", viewType: new DamlTypeRef("other-package-id", ModuleName, "AssetView")), + dataTypes: [AssetViewRecord()], + siblingInterfaces: [Interface("Asset", viewType: new DamlTypeRef("", ModuleName, "AssetView"))]); + + output.Should().Contain("public static ViewDescriptor View { get; } = new();"); + output.Should().NotContain("Owner { get; }"); + output.Should().NotContain("reference equality"); + } + + [Fact] + public void InterfaceEmitter_omits_the_witness_for_a_local_view_type_that_is_not_a_record() + { + var output = EmitInterface( + Interface("Coloured", viewType: new DamlTypeRef("", ModuleName, "Colour")), + dataTypes: [new DamlDataType { Name = "Colour", Definition = new DamlEnumDefinition(["Red"]) }]); + + output.Should().Contain("IHasView"); + output.Should().NotContain("ViewDescriptor"); + } + + [Fact] + public void InterfaceEmitter_omits_the_enrichment_for_a_view_record_whose_field_collides_with_the_witness() { var output = EmitInterface( Interface("Asset", viewType: new DamlTypeRef("", ModuleName, "AssetView")), @@ -112,26 +219,17 @@ public void adds_the_IHasView_facet_when_the_interface_has_a_view_type() { Name = "AssetView", Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("amount", new DamlPrimitiveType(DamlPrimitive.Numeric)), - ]), + [new DamlFieldDefinition("view", new DamlPrimitiveType(DamlPrimitive.Party))]), }, ]); - output.Should().Contain("IHasView"); - } - - [Fact] - public void omits_the_IHasView_facet_when_the_interface_has_no_view_type() - { - var output = EmitInterface(Interface("Lockable")); - - output.Should().NotContain("IHasView"); + output.Should().Contain("public static ViewDescriptor View { get; } = new();"); + output.Should().NotContain("Party View { get; }"); + output.Should().NotContain("reference equality"); } [Fact] - public void renders_each_choice_as_a_signature_comment() + public void InterfaceEmitter_writes_no_signature_comment_into_the_marker_body_for_a_choice() { var output = EmitInterface(Interface( "Transferable", @@ -144,11 +242,12 @@ public void renders_each_choice_as_a_signature_comment() ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit), })); - output.Should().Contain("// Choice Transfer"); + output.Should().NotContain("// Choice Transfer"); + output.Should().NotContain("// Interface method Transfer."); } [Fact] - public void emits_the_sibling_choice_exerciser_class_when_the_interface_has_choices() + public void InterfaceEmitter_emits_the_sibling_choice_exerciser_class_when_the_interface_has_choices() { var output = EmitInterface(Interface( "Transferable", @@ -164,11 +263,11 @@ public void emits_the_sibling_choice_exerciser_class_when_the_interface_has_choi })); output.Should().Contain("public static class ITransferableExtensions"); - output.Should().Contain("public static async Task> TransferAsync("); + output.Should().Contain("public static Task> TransferAsync("); } [Fact] - public void emits_no_choice_exerciser_class_when_the_interface_has_no_choices() + public void InterfaceEmitter_emits_no_choice_exerciser_class_when_the_interface_has_no_choices() { var output = EmitInterface(Interface("Lockable")); @@ -176,7 +275,7 @@ public void emits_no_choice_exerciser_class_when_the_interface_has_no_choices() } [Fact] - public void emits_every_xml_doc_when_enabled() + public void InterfaceEmitter_emits_every_xml_doc_when_enabled() { var output = EmitInterface(Interface("Documented"), generateXmlDocs: true); @@ -189,7 +288,7 @@ public void emits_every_xml_doc_when_enabled() } [Fact] - public void omits_every_xml_doc_when_disabled() + public void InterfaceEmitter_omits_every_xml_doc_when_disabled() { var output = EmitInterface(Interface("Documented"), generateXmlDocs: false); @@ -205,7 +304,7 @@ public void omits_every_xml_doc_when_disabled() } [Fact] - public void filters_interfaces_with_the_root_filter() + public void InterfaceEmitter_filters_interfaces_with_the_root_filter() { var options = new CodeGenOptions { diff --git a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderBasicTests.cs b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderBasicTests.cs index 037185f..ce76a13 100644 --- a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderBasicTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderBasicTests.cs @@ -18,14 +18,14 @@ namespace Daml.Codegen.CSharp.Tests; public partial class IntermediateDarReaderTests { [Fact] - public void intermediate_dar_proto_types_are_available() + public void IntermediateDar_proto_types_are_available() { var dar = new IntermediateDar(); dar.Should().NotBeNull(); } [Fact] - public void upgraded_package_id_round_trips_from_proto() + public void IntermediateDarReader_upgraded_package_id_round_trips_from_proto() { var proto = new IntermediateDar { @@ -44,7 +44,7 @@ public void upgraded_package_id_round_trips_from_proto() } [Fact] - public void empty_upgraded_package_id_round_trips_as_null() + public void IntermediateDarReader_empty_upgraded_package_id_round_trips_as_null() { var proto = new IntermediateDar { @@ -62,7 +62,7 @@ public void empty_upgraded_package_id_round_trips_as_null() } [Fact] - public void empty_intermediate_dar_round_trips_to_empty_model() + public void IntermediateDarReader_empty_intermediate_dar_round_trips_to_empty_model() { var proto = new IntermediateDar { @@ -86,14 +86,14 @@ public void empty_intermediate_dar_round_trips_to_empty_model() } [Fact] - public void null_proto_throws_argument_null_exception() + public void IntermediateDarReader_null_proto_throws_argument_null_exception() { var act = () => IntermediateDarReader.Read(null!); act.Should().Throw(); } [Fact] - public void proto_without_main_throws_invalid_data_exception() + public void IntermediateDarReader_proto_without_main_throws_invalid_data_exception() { var proto = new IntermediateDar(); var act = () => IntermediateDarReader.Read(proto); @@ -102,7 +102,7 @@ public void proto_without_main_throws_invalid_data_exception() } [Fact] - public void dependencies_round_trip_in_order() + public void IntermediateDarReader_dependencies_round_trip_in_order() { var proto = new IntermediateDar { @@ -135,7 +135,7 @@ public void dependencies_round_trip_in_order() } [Fact] - public void module_name_segments_join_with_dot() + public void IntermediateDarReader_module_name_segments_join_with_dot() { var proto = MakePackageWith(module => { @@ -150,7 +150,7 @@ public void module_name_segments_join_with_dot() } [Fact] - public void invalid_version_falls_back_to_three_part_zero() + public void IntermediateDarReader_invalid_version_falls_back_to_three_part_zero() { var proto = new IntermediateDar { @@ -168,7 +168,7 @@ public void invalid_version_falls_back_to_three_part_zero() } [Fact] - public void unit_typed_variant_constructor_emits_noarg_record() + public void IntermediateDarReader_unit_typed_variant_constructor_emits_noarg_record() { var proto = MakePackageWith(module => { @@ -188,7 +188,7 @@ public void unit_typed_variant_constructor_emits_noarg_record() var model = IntermediateDarReader.Read(proto); var options = new CodeGen.CodeGenOptions(); - var generator = new CodeGen.CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CodeGen.CSharpCodeGenerator(options); var files = generator.Generate(model); @@ -199,7 +199,7 @@ public void unit_typed_variant_constructor_emits_noarg_record() } [Fact] - public void generator_accepts_dar_model_from_intermediate_reader() + public void IntermediateDarReader_generator_accepts_dar_model_from_intermediate_reader() { var proto = MakePackageWith(module => { @@ -213,8 +213,7 @@ public void generator_accepts_dar_model_from_intermediate_reader() var model = IntermediateDarReader.Read(proto); var options = new CodeGen.CodeGenOptions(); - var logger = new ConsoleLogger(0); - var generator = new CodeGen.CSharpCodeGenerator(options, logger); + var generator = new CodeGen.CSharpCodeGenerator(options); var files = generator.Generate(model); diff --git a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderDataTypesTests.cs b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderDataTypesTests.cs index db1a12b..f19e48f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderDataTypesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderDataTypesTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.Intermediate; using AwesomeAssertions; using Xunit; @@ -22,7 +22,7 @@ namespace Daml.Codegen.CSharp.Tests; public partial class IntermediateDarReaderTests { [Fact] - public void record_data_type_round_trips_with_field_types() + public void IntermediateDarReader_record_data_type_round_trips_with_field_types() { var proto = MakePackageWith(module => { @@ -55,7 +55,7 @@ public void record_data_type_round_trips_with_field_types() } [Fact] - public void variant_data_type_round_trips_constructors() + public void IntermediateDarReader_variant_data_type_round_trips_constructors() { var proto = MakePackageWith(module => { @@ -87,7 +87,7 @@ public void variant_data_type_round_trips_constructors() } [Fact] - public void enum_data_type_round_trips_constructors() + public void IntermediateDarReader_enum_data_type_round_trips_constructors() { var proto = MakePackageWith(module => { @@ -109,7 +109,7 @@ public void enum_data_type_round_trips_constructors() } [Fact] - public void type_app_round_trips_with_function_and_arguments() + public void IntermediateDarReader_type_app_round_trips_with_function_and_arguments() { var proto = MakePackageWith(module => { @@ -148,7 +148,7 @@ public void type_app_round_trips_with_function_and_arguments() } [Fact] - public void type_con_round_trips_to_daml_type_ref() + public void IntermediateDarReader_type_con_round_trips_to_daml_type_ref() { var proto = MakePackageWith(module => { @@ -186,7 +186,7 @@ public void type_con_round_trips_to_daml_type_ref() } [Fact] - public void unspecified_builtin_throws_invalid_data() + public void IntermediateDarReader_unspecified_builtin_throws_invalid_data() { var proto = MakePackageWith(module => { @@ -209,7 +209,7 @@ public void unspecified_builtin_throws_invalid_data() } [Fact] - public void unmapped_builtin_throws_not_supported() + public void IntermediateDarReader_unmapped_builtin_throws_not_supported() { var proto = MakePackageWith(module => { @@ -232,7 +232,7 @@ public void unmapped_builtin_throws_not_supported() } [Fact] - public void type_with_no_sort_set_throws_invalid_data() + public void IntermediateDarReader_type_with_no_sort_set_throws_invalid_data() { var proto = MakePackageWith(module => { @@ -252,7 +252,7 @@ public void type_with_no_sort_set_throws_invalid_data() } [Fact] - public void nat_type_round_trips_as_type_var() + public void IntermediateDarReader_nat_type_round_trips_as_type_var() { var proto = MakePackageWith(module => { @@ -273,7 +273,7 @@ public void nat_type_round_trips_as_type_var() } [Fact] - public void field_with_null_type_throws() + public void IntermediateDarReader_field_with_null_type_throws() { var proto = new IntermediateDar { @@ -311,7 +311,7 @@ public void field_with_null_type_throws() } [Fact] - public void variant_constructor_with_null_type_throws() + public void IntermediateDarReader_variant_constructor_with_null_type_throws() { var proto = new IntermediateDar { @@ -349,7 +349,7 @@ public void variant_constructor_with_null_type_throws() } [Fact] - public void data_type_with_no_shape_throws() + public void IntermediateDarReader_data_type_with_no_shape_throws() { var proto = new IntermediateDar { diff --git a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderEntitiesTests.cs b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderEntitiesTests.cs index cc6af8b..463d396 100644 --- a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderEntitiesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderEntitiesTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.Intermediate; using AwesomeAssertions; using Xunit; @@ -21,7 +21,7 @@ namespace Daml.Codegen.CSharp.Tests; public partial class IntermediateDarReaderTests { [Fact] - public void template_round_trips_with_choices_and_fields_from_record() + public void IntermediateDarReader_template_round_trips_with_choices_and_payload_record() { var proto = MakePackageWith(module => { @@ -50,17 +50,19 @@ public void template_round_trips_with_choices_and_fields_from_record() }); var model = IntermediateDarReader.Read(proto); - var template = model.MainPackage.Modules[0].Templates.Single(); + var module = model.MainPackage.Modules[0]; + var template = module.Templates.Single(); template.Name.Should().Be("Iou"); - template.Fields.Should().HaveCount(2); - template.Fields[0].Name.Should().Be("issuer"); template.Choices.Should().HaveCount(1); template.Choices[0].Name.Should().Be("Transfer"); template.Choices[0].Consuming.Should().BeTrue(); + var payload = module.DataTypes.Single(dt => dt.Name == "Iou"); + payload.Definition.Should().BeOfType() + .Which.Fields.Select(f => f.Name).Should().Equal("issuer", "currency"); } [Fact] - public void interface_round_trips_with_view_type_and_choices() + public void IntermediateDarReader_interface_round_trips_with_view_type_and_choices() { var proto = MakePackageWith(module => { @@ -99,7 +101,7 @@ public void interface_round_trips_with_view_type_and_choices() } [Fact] - public void template_with_no_key_type_yields_null_key() + public void IntermediateDarReader_template_with_no_key_type_yields_null_key() { var proto = MakePackageWith(module => { @@ -116,7 +118,7 @@ public void template_with_no_key_type_yields_null_key() } [Fact] - public void template_with_key_type_round_trips() + public void IntermediateDarReader_template_with_key_type_round_trips() { var proto = MakePackageWith(module => { @@ -139,7 +141,7 @@ public void template_with_key_type_round_trips() } [Fact] - public void interface_with_no_view_type_yields_null_view() + public void IntermediateDarReader_interface_with_no_view_type_yields_null_view() { var proto = MakePackageWith(module => { @@ -151,7 +153,7 @@ public void interface_with_no_view_type_yields_null_view() } [Fact] - public void duplicate_template_and_data_type_name_collision_does_not_throw() + public void IntermediateDarReader_duplicate_template_and_data_type_name_collision_does_not_throw() { var proto = MakePackageWith(module => { @@ -164,13 +166,14 @@ public void duplicate_template_and_data_type_name_collision_does_not_throw() }); var model = IntermediateDarReader.Read(proto); - var template = model.MainPackage.Modules[0].Templates.Single(); - template.Fields.Should().HaveCount(1); - template.Fields[0].Name.Should().Be("issuer"); + var module = model.MainPackage.Modules[0]; + module.Templates.Single().Name.Should().Be("Iou"); + module.DataTypes.Single().Definition.Should().BeOfType() + .Which.Fields.Single().Name.Should().Be("issuer"); } [Fact] - public void choice_with_null_argument_type_throws() + public void IntermediateDarReader_choice_with_null_argument_type_throws() { var proto = new IntermediateDar { @@ -222,7 +225,7 @@ public void choice_with_null_argument_type_throws() } [Fact] - public void choice_with_null_return_type_throws() + public void IntermediateDarReader_choice_with_null_return_type_throws() { var proto = new IntermediateDar { diff --git a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderPartiesTests.cs b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderPartiesTests.cs index 4212169..2f107f6 100644 --- a/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderPartiesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/IntermediateDarReaderPartiesTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.Intermediate; using AwesomeAssertions; using Xunit; @@ -22,7 +22,7 @@ namespace Daml.Codegen.CSharp.Tests; public partial class IntermediateDarReaderTests { [Fact] - public void template_signatories_default_to_dynamic() + public void IntermediateDarReader_template_signatories_default_to_dynamic() { var proto = MakePackageWith(module => { @@ -41,7 +41,7 @@ public void template_signatories_default_to_dynamic() } [Fact] - public void template_with_unset_signatories_defaults_to_dynamic() + public void IntermediateDarReader_template_with_unset_signatories_defaults_to_dynamic() { var proto = MakeProtoWithSingleTemplate(new PbTemplate { @@ -56,7 +56,7 @@ public void template_with_unset_signatories_defaults_to_dynamic() } [Fact] - public void template_with_static_signatories_reads_payload_field_references() + public void IntermediateDarReader_template_with_static_signatories_reads_payload_field_references() { var proto = MakeProtoWithSingleTemplate(new PbTemplate { @@ -82,7 +82,7 @@ public void template_with_static_signatories_reads_payload_field_references() } [Fact] - public void choice_party_analysis_round_trips_static_and_dynamic() + public void IntermediateDarReader_choice_party_analysis_round_trips_static_and_dynamic() { var proto = MakeProtoWithSingleTemplate(new PbTemplate { diff --git a/tests/Daml.Codegen.CSharp.Tests/MirrorPromotionGateTests.cs b/tests/Daml.Codegen.CSharp.Tests/MirrorPromotionGateTests.cs index 7e9ff49..18789be 100644 --- a/tests/Daml.Codegen.CSharp.Tests/MirrorPromotionGateTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/MirrorPromotionGateTests.cs @@ -38,7 +38,7 @@ private static string BareReferenceExample(string prefix, string number, string [InlineData("mid sentence (", "397", ") regression")] [InlineData("remains tracked in ", "64", "; see the follow-up note")] [InlineData("(", "455", ")")] - public void bare_issue_reference_is_blocked_by_the_dead_ref_sweep(string prefix, string number, string suffix) + public void MirrorPromotionGate_bare_issue_reference_is_blocked_by_the_dead_ref_sweep(string prefix, string number, string suffix) { var line = BareReferenceExample(prefix, number, suffix); @@ -50,7 +50,7 @@ public void bare_issue_reference_is_blocked_by_the_dead_ref_sweep(string prefix, [InlineData("workaround for grpc/grpc#38538 — bundled Grpc.Tools linux_arm64 protoc")] [InlineData("upstream fix tracked in digital-asset/daml#123")] [InlineData("no reference at all in this line")] - public void allowed_cross_repo_reference_passes_the_dead_ref_sweep(string line) + public void MirrorPromotionGate_allowed_cross_repo_reference_passes_the_dead_ref_sweep(string line) { IsBlockedByDeadRefSweep(line).Should().BeFalse(); } @@ -59,7 +59,7 @@ public void allowed_cross_repo_reference_passes_the_dead_ref_sweep(string line) [InlineData("driven by the downstream consumer (canton#190) — also ", "529")] [InlineData("workaround for grpc/grpc#38538 and closes ", "529")] [InlineData("upstream fix tracked in digital-asset/daml#123, follow-up in ", "397")] - public void bare_ref_sharing_a_line_with_an_allowed_cross_repo_form_is_still_blocked(string prefix, string number) + public void MirrorPromotionGate_bare_ref_sharing_a_line_with_an_allowed_cross_repo_form_is_still_blocked(string prefix, string number) { var line = prefix + BareReferenceExample("", number, ""); @@ -69,7 +69,7 @@ public void bare_ref_sharing_a_line_with_an_allowed_cross_repo_form_is_still_blo [Theory] [InlineData("New `", "` package: add it and declare a DamlArchive item")] [InlineData("dotnet add package ", "")] - public void internal_only_msbuild_package_is_blocked_by_the_530_guard(string prefix, string suffix) + public void MirrorPromotionGate_internal_only_msbuild_package_is_blocked_by_the_530_guard(string prefix, string suffix) { var packageName = string.Concat("Daml.Codegen.CSharp", ".", "MSBuild"); var line = string.Concat(prefix, packageName, suffix); @@ -78,7 +78,7 @@ public void internal_only_msbuild_package_is_blocked_by_the_530_guard(string pre } [Fact] - public void the_already_public_emitter_package_passes_the_530_guard() + public void MirrorPromotionGate_the_already_public_emitter_package_passes_the_530_guard() { InternalOnlyMsBuildPackage.IsMatch("dotnet add package Daml.Codegen.CSharp").Should().BeFalse(); } diff --git a/tests/Daml.Codegen.CSharp.Tests/NamedSubmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/NamedSubmitterTests.cs index c10e4f0..a251214 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NamedSubmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NamedSubmitterTests.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; +using static Daml.Codegen.CSharp.Tests.TestHelpers.EmittedSubmissionShape; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; namespace Daml.Codegen.CSharp.Tests; @@ -73,7 +74,6 @@ private static DamlModule MakeAgreementModule(DamlPartyAnalysis signatories, Dam new DamlTemplate { Name = "Agreement", - Fields = fields, Choices = [], Signatories = signatories, } @@ -231,11 +231,6 @@ public void ChoiceAsync_with_single_payload_derived_controller_emits_one_party_p new DamlTemplate { Name = "Offer", - Fields = - [ - new DamlFieldDefinition("platform", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("counterparty", new DamlPrimitiveType(DamlPrimitive.Party)), - ], Choices = [ new DamlChoice @@ -310,12 +305,6 @@ public void ChoiceAsync_with_multiple_payload_derived_controllers_emits_one_part new DamlTemplate { Name = "Agreement", - Fields = - [ - new DamlFieldDefinition("platform", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("initiator", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("counterparty", new DamlPrimitiveType(DamlPrimitive.Party)), - ], Choices = [ new DamlChoice @@ -385,7 +374,6 @@ public void ChoiceAsync_with_dynamic_controllers_keeps_explicit_submitter_parame new DamlTemplate { Name = "Holding", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -430,7 +418,6 @@ public void ChoiceAsync_for_archive_choice_is_emitted() new DamlTemplate { Name = "Asset", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -550,10 +537,6 @@ public void ChoiceAsync_contract_sibling_passes_choice_argument_through() new DamlTemplate { Name = "Offer", - Fields = - [ - new DamlFieldDefinition("counterparty", new DamlPrimitiveType(DamlPrimitive.Party)), - ], Choices = [ new DamlChoice @@ -654,7 +637,6 @@ private static DamlModule MakeAgreementWithObservers( new DamlTemplate { Name = "Agreement", - Fields = fields, Choices = [ new DamlChoice @@ -774,9 +756,7 @@ public void Generate_choice_async_threads_template_observers_into_readAs() // holder/issuer into readAs. content.Should().Contain("actAs: new HashSet { platform }"); content.Should().Contain("readAs: new HashSet { holder, issuer }"); - // The submission is dispatched with the SubmitterInfo passed directly to - // TrySubmitAndWaitForTransactionAsync. - content.Should().Contain("TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken)"); + content.Should().Contain("client." + TrySubmitSingleArgumentOrder); } [Fact] @@ -821,9 +801,7 @@ public void Generate_choice_async_with_no_observers_emits_no_readAs_contribution // the named Party param (no HashSet allocation, no readAs argument). content.Should().Contain("SubmitterInfo submitter = platform;"); content.Should().NotContain("readAs:"); - // The submission is still dispatched with the SubmitterInfo overload, - // passed directly to TrySubmitAndWaitForTransactionAsync. - content.Should().Contain("TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken)"); + content.Should().Contain("client." + TrySubmitSingleArgumentOrder); } [Fact] @@ -846,11 +824,14 @@ public void Generate_choice_async_with_static_controllers_also_emits_readAs_capa content.Should().Contain("Party platform,"); content.Should().Contain("SubmitterInfo submitter,"); - content.Should().Contain("TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken)"); + content.Should().Contain("client." + TrySubmitSingleArgumentOrder); - var contractIdOverloads = - content.Split("public static async Task> RenewAsync(").Length - 1; + var contractIdOverloads = content + .Split("RenewAsync(\n this ContractId contractId,") + .Length - 1; contractIdOverloads.Should().Be(2); + content.Should().Contain("public static Task> RenewAsync("); + content.Should().Contain("public static async Task> RenewAsync("); } [Fact] diff --git a/tests/Daml.Codegen.CSharp.Tests/NewFeaturesCodeGenTests.cs b/tests/Daml.Codegen.CSharp.Tests/NewFeaturesCodeGenTests.cs index 43dffaf..4e0cdb4 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NewFeaturesCodeGenTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NewFeaturesCodeGenTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -17,149 +17,72 @@ public class NewFeaturesCodeGenTests { #region Contract Keys Tests - [Fact] - public void Generate_should_implement_IHasKey_when_template_has_primitive_key() - { - // Arrange - var module = new DamlModule + private static DamlModule KeyedModule(string templateName, DamlType key, IReadOnlyList? extraDataTypes = null) => + new() { Name = "Test.Module", Templates = [ new DamlTemplate { - Name = "AssetWithKey", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("assetId", new DamlPrimitiveType(DamlPrimitive.Text)) - ], + Name = templateName, Choices = [], - Key = new DamlPrimitiveType(DamlPrimitive.Text) + Key = key } ], DataTypes = [ new DamlDataType { - Name = "AssetWithKey", + Name = templateName, Definition = new DamlRecordDefinition( [ new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), new DamlFieldDefinition("assetId", new DamlPrimitiveType(DamlPrimitive.Text)) ]) - } + }, + .. extraDataTypes ?? [] ], Interfaces = [] }; - var dar = CreateTestDar(module); - var generator = CreateGenerator(); + private static string EmitKeyed(string templateName, DamlType key, IReadOnlyList? extraDataTypes = null) + { + var files = CreateGenerator().Generate(CreateTestDar(KeyedModule(templateName, key, extraDataTypes))); + var templateFile = files.FirstOrDefault(f => f.RelativePath.EndsWith($"{templateName}.cs", StringComparison.Ordinal)); + templateFile.Should().NotBeNull(); + return templateFile!.Content; + } - // Act - var files = generator.Generate(dar); - var assetFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("AssetWithKey.cs", StringComparison.Ordinal)); + [Fact] + public void Generate_should_put_a_primitive_key_on_the_active_contract() + { + var code = EmitKeyed("AssetWithKey", new DamlPrimitiveType(DamlPrimitive.Text)); - // Assert - assetFile.Should().NotBeNull(); - var code = assetFile!.Content; - - code.Should().Contain("IHasKey"); - // The Key accessor is a non-partial throwing stub until the DALF - // key-expression analysis lands. The template still - // declares `: IHasKey`, but the body throws rather than deferring to a - // consumer-supplied partial (which broke the automated publish pipeline). - code.Should().Contain("public string Key =>"); - code.Should().Contain("NotImplementedException"); - code.Should().NotContain("public partial string Key { get; }"); + code.Should().Contain("public sealed record Contract(ContractId Id, AssetWithKey Data)"); + + code.Should().Contain("public required ContractKey Key { get; init; }"); + code.Should().Contain("? new ContractKey(contractKey.Value.As().Value, contractKey.KeyHash)"); } [Fact] - public void Generate_should_implement_IHasKey_when_template_has_party_key() + public void Generate_should_put_a_party_key_on_the_active_contract() { - // Arrange - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "UserProfile", - Fields = - [ - new DamlFieldDefinition("user", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("name", new DamlPrimitiveType(DamlPrimitive.Text)) - ], - Choices = [], - Key = new DamlPrimitiveType(DamlPrimitive.Party) - } - ], - DataTypes = - [ - new DamlDataType - { - Name = "UserProfile", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("user", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("name", new DamlPrimitiveType(DamlPrimitive.Text)) - ]) - } - ], - Interfaces = [] - }; + var code = EmitKeyed("UserProfile", new DamlPrimitiveType(DamlPrimitive.Party)); - var dar = CreateTestDar(module); - var generator = CreateGenerator(); + code.Should().Contain("public sealed record Contract(ContractId Id, UserProfile Data)"); - // Act - var files = generator.Generate(dar); - var profileFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("UserProfile.cs", StringComparison.Ordinal)); - - // Assert - profileFile.Should().NotBeNull(); - var code = profileFile!.Content; - - // Party maps to Party, so key should be Party - code.Should().Contain("IHasKey"); - code.Should().Contain("public Party Key =>"); - code.Should().Contain("NotImplementedException"); - code.Should().NotContain("public partial Party Key { get; }"); + code.Should().Contain("public required ContractKey Key { get; init; }"); + code.Should().Contain("? new ContractKey(Party.FromDamlValue(contractKey.Value.As()), contractKey.KeyHash)"); } [Fact] - public void Generate_should_implement_IHasKey_when_template_has_record_type_key() + public void Generate_should_put_a_record_key_on_the_active_contract() { - // Arrange - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "CompositeKeyTemplate", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("assetId", new DamlPrimitiveType(DamlPrimitive.Text)) - ], - Choices = [], - Key = new DamlTypeRef("", "Test.Module", "AssetKey") - } - ], - DataTypes = + var code = EmitKeyed( + "CompositeKeyTemplate", + new DamlTypeRef("", "Test.Module", "AssetKey"), [ - new DamlDataType - { - Name = "CompositeKeyTemplate", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("assetId", new DamlPrimitiveType(DamlPrimitive.Text)) - ]) - }, new DamlDataType { Name = "AssetKey", @@ -169,87 +92,44 @@ public void Generate_should_implement_IHasKey_when_template_has_record_type_key( new DamlFieldDefinition("assetId", new DamlPrimitiveType(DamlPrimitive.Text)) ]) } - ], - Interfaces = [] - }; - - var dar = CreateTestDar(module); - var generator = CreateGenerator(); + ]); - // Act - var files = generator.Generate(dar); - var templateFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("CompositeKeyTemplate.cs", StringComparison.Ordinal)); + code.Should().Contain("public sealed record Contract(ContractId Id, CompositeKeyTemplate Data)"); - // Assert - templateFile.Should().NotBeNull(); - var code = templateFile!.Content; - - code.Should().Contain("IHasKey"); - code.Should().Contain("public AssetKey Key =>"); - code.Should().Contain("NotImplementedException"); - code.Should().NotContain("public partial AssetKey Key { get; }"); + code.Should().Contain("public required ContractKey Key { get; init; }"); + code.Should().Contain("? new ContractKey(global::Test.Package.AssetKey.FromRecord(contractKey.Value.As()), contractKey.KeyHash)"); } [Fact] - public void Generate_should_emit_valid_cref_when_key_type_contains_angle_brackets() + public void Generate_should_emit_no_unescaped_cref_when_the_key_type_contains_angle_brackets() { - // Pins the cref-escape transform on a key type whose mapped C# form - // contains generic angle brackets. `[Text]` (List Text) maps to - // `IReadOnlyList`, and cref-attribute syntax requires the angle - // brackets be rendered as `{ }` — without the escape, the emitted XML - // doc reads `cref="...IHasKey>"/>` which is - // malformed XML and breaks consumer builds with - // true + TreatWarningsAsErrors. - // The string-key test above doesn't exercise this path because `string` - // contains no angle brackets to escape. - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "ListKeyTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], - Choices = [], - Key = new DamlTypeApp( - new DamlPrimitiveType(DamlPrimitive.List), - [new DamlPrimitiveType(DamlPrimitive.Text)]), - }, - ], - DataTypes = - [ - new DamlDataType - { - Name = "ListKeyTemplate", - Definition = new DamlRecordDefinition([new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]), - }, - ], - Interfaces = [], - }; + var code = EmitKeyed( + "ListKeyTemplate", + new DamlTypeApp( + new DamlPrimitiveType(DamlPrimitive.List), + [new DamlPrimitiveType(DamlPrimitive.Text)])); - var dar = CreateTestDar(module); - var generator = CreateGenerator(); + code.Should().Contain("public sealed record Contract(ContractId Id, ListKeyTemplate Data)"); - var files = generator.Generate(dar); - var templateFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("ListKeyTemplate.cs", StringComparison.Ordinal)); + code.Should().Contain("public required ContractKey> Key { get; init; }"); + code.Should().NotMatchRegex(@"cref=""[^""]*<[^""]*""", + "a cref must render angle brackets as {{ }}, or a consumer building with GenerateDocumentationFile and TreatWarningsAsErrors fails on malformed XML"); + } - templateFile.Should().NotBeNull(); - var code = templateFile!.Content; + [Fact] + public void Generate_should_emit_no_instance_key_member_on_the_template_payload() + { + var code = EmitKeyed("AssetWithKey", new DamlPrimitiveType(DamlPrimitive.Text)); - code.Should().Contain("public IReadOnlyList Key =>", - "the property signature uses real C# generic syntax"); - code.Should().Contain( - "of type IReadOnlyList{string}, satisfying ", - "the concrete key type is cref-escaped prose while the cref targets the open generic by its declared type-parameter name TKey, because cref braces accept a type-parameter identifier not a constructed type (embedding a constructed type there is CS1584/CS1658)"); - code.Should().NotMatchRegex(@"cref=""[^""]*<[^""]*""", - "no unescaped angle bracket may appear inside any cref attribute"); + var payload = code[..code.IndexOf("public sealed record ContractId(", StringComparison.Ordinal)]; + + payload.Should().NotMatchRegex(@"\bpublic\s+(?!static\b)[^;{=]+?\s+Key\b", + "the payload is what a caller constructs locally, so it cannot know the key of a contract it has not created yet"); } [Fact] - public void Generate_should_not_implement_IHasKey_when_template_has_no_key() + public void Generate_should_leave_a_key_less_template_without_a_key_slot() { - // Arrange var module = new DamlModule { Name = "Test.Module", @@ -258,10 +138,6 @@ public void Generate_should_not_implement_IHasKey_when_template_has_no_key() new DamlTemplate { Name = "NoKeyTemplate", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)) - ], Choices = [], Key = null } @@ -280,86 +156,15 @@ public void Generate_should_not_implement_IHasKey_when_template_has_no_key() Interfaces = [] }; - var dar = CreateTestDar(module); - var generator = CreateGenerator(); - - // Act - var files = generator.Generate(dar); + var files = CreateGenerator().Generate(CreateTestDar(module)); var templateFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("NoKeyTemplate.cs", StringComparison.Ordinal)); - // Assert templateFile.Should().NotBeNull(); var code = templateFile!.Content; + code.Should().Contain("public sealed record Contract(ContractId Id, NoKeyTemplate Data) :"); code.Should().NotContain("IHasKey"); - // Belt-and-braces: no Key emission of any shape for a key-less template. - // The `IHasKey` check is the load-bearing one; this catches a Key - // emission of ANY type form — generic (`IReadOnlyList`), - // nullable (`string?`), qualified (`Foo.Bar`), or bare identifier — - // followed by the standalone `Key` token (so `IHasKey<...>` in the - // type declaration doesn't trip a false positive). The character class - // excludes `{`, `;`, `=` so the match can't span across the type - // declaration's own opening brace and into the property body. - code.Should().NotMatchRegex(@"\bpartial\s+[^;{=]+?\s+Key\b"); - } - - [Fact] - public void Generate_should_include_key_documentation() - { - // Arrange - var module = new DamlModule - { - Name = "Test.Module", - Templates = - [ - new DamlTemplate - { - Name = "DocumentedKey", - Fields = - [ - new DamlFieldDefinition("id", new DamlPrimitiveType(DamlPrimitive.Text)) - ], - Choices = [], - Key = new DamlPrimitiveType(DamlPrimitive.Text) - } - ], - DataTypes = - [ - new DamlDataType - { - Name = "DocumentedKey", - Definition = new DamlRecordDefinition( - [ - new DamlFieldDefinition("id", new DamlPrimitiveType(DamlPrimitive.Text)) - ]) - } - ], - Interfaces = [] - }; - - var dar = CreateTestDar(module); - var generator = CreateGenerator(); - - // Act - var files = generator.Generate(dar); - var templateFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("DocumentedKey.cs", StringComparison.Ordinal)); - - // Assert - templateFile.Should().NotBeNull(); - var code = templateFile!.Content; - - // Multi-line XML doc emitted by the throwing-Key shape. Assert on the - // doc-comment line itself (with the `///` prefix and the `` to - // the closed-generic IHasKey): a bare `Contain("IHasKey")` would - // pass from the type declaration `: IHasKey` even if the doc - // breadcrumb were dropped. The cref uses cref-attribute syntax (`{string}` - // instead of ``) so it survives on - // consumer projects without a CS1574 warning. - code.Should().Contain( - "/// Gets the contract key of type string, satisfying ", - "the cref targets the open generic by its declared type-parameter name TKey while the concrete key type appears in prose, because a keyword/constructed type inside cref braces is CS1584/CS1658"); - code.Should().Contain("Contract-key projection is not generated yet by daml-codegen-csharp", - "the throwing accessor's message is the consumer-facing signal explaining the un-projected key"); + code.Should().NotContain("ContractKey"); } #endregion @@ -614,10 +419,6 @@ public void Generate_should_implement_IUpgradeable_when_package_has_upgraded_pac new DamlTemplate { Name = "UpgradedTemplate", - Fields = - [ - new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text)) - ], Choices = [] } ], @@ -662,10 +463,6 @@ public void Generate_should_not_implement_IUpgradeable_when_no_upgraded_package( new DamlTemplate { Name = "NormalTemplate", - Fields = - [ - new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text)) - ], Choices = [] } ], @@ -710,10 +507,6 @@ public void Generate_should_include_upgraded_package_id_documentation() new DamlTemplate { Name = "DocumentedUpgrade", - Fields = - [ - new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Text)) - ], Choices = [] } ], @@ -746,7 +539,7 @@ public void Generate_should_include_upgraded_package_id_documentation() } [Fact] - public void Generate_should_implement_both_IHasKey_and_IUpgradeable_when_applicable() + public void Generate_should_carry_both_the_contract_key_and_IUpgradeable_when_applicable() { // Arrange var module = new DamlModule @@ -757,11 +550,6 @@ public void Generate_should_implement_both_IHasKey_and_IUpgradeable_when_applica new DamlTemplate { Name = "FullFeaturedTemplate", - Fields = - [ - new DamlFieldDefinition("id", new DamlPrimitiveType(DamlPrimitive.Text)), - new DamlFieldDefinition("value", new DamlPrimitiveType(DamlPrimitive.Numeric)) - ], Choices = [], Key = new DamlPrimitiveType(DamlPrimitive.Text) } @@ -792,10 +580,10 @@ public void Generate_should_implement_both_IHasKey_and_IUpgradeable_when_applica templateFile.Should().NotBeNull(); var code = templateFile!.Content; - // Should have all three interfaces code.Should().Contain("ITemplate"); - code.Should().Contain("IHasKey"); code.Should().Contain("IUpgradeable"); + code.Should().Contain("public sealed record Contract(ContractId Id, FullFeaturedTemplate Data)"); + code.Should().Contain("public required ContractKey Key { get; init; }"); } #endregion @@ -814,11 +602,6 @@ public void Generate_should_handle_module_with_templates_interfaces_and_data_typ new DamlTemplate { Name = "Asset", - Fields = - [ - new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)), - new DamlFieldDefinition("amount", new DamlPrimitiveType(DamlPrimitive.Numeric)) - ], Choices = [], Key = new DamlPrimitiveType(DamlPrimitive.Party) } @@ -867,7 +650,8 @@ public void Generate_should_handle_module_with_templates_interfaces_and_data_typ // Template should exist with key and upgrade support var assetFile = files.FirstOrDefault(f => f.RelativePath.EndsWith("Asset.cs", StringComparison.Ordinal)); assetFile.Should().NotBeNull(); - assetFile!.Content.Should().Contain("IHasKey"); + assetFile!.Content.Should().Contain("public sealed record Contract(ContractId Id, Asset Data)"); + assetFile!.Content.Should().Contain("public required ContractKey Key { get; init; }"); assetFile.Content.Should().Contain("IUpgradeable"); // Generic data type should exist diff --git a/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceProjectorTests.cs b/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceProjectorTests.cs index 722ed1a..7cde942 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceProjectorTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceProjectorTests.cs @@ -1,9 +1,10 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Reflection; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Runtime; using Daml.Runtime.Contracts; using Daml.Runtime.Outcomes; @@ -51,7 +52,6 @@ private static Assembly CompileWrapperAssembly() new DamlTemplate { Name = EntityName, - Fields = [new DamlFieldDefinition("operator", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -93,7 +93,7 @@ private static Assembly CompileWrapperAssembly() UseRecordTypes = true, UsePrimaryConstructors = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(new DarModel { MainPackage = package, Dependencies = [] }); return EmitAssembly(files); @@ -139,7 +139,7 @@ private static Assembly EmitAssembly(IReadOnlyList files) "\n", emit.Diagnostics .Where(d => d.Severity == DiagnosticSeverity.Error) - .Select(d => d.GetMessage() + " @ " + d.Location))); + .Select(d => d.GetMessage(CultureInfo.InvariantCulture) + " @ " + d.Location))); stream.Seek(0, SeekOrigin.Begin); return Assembly.Load(stream.ToArray()); @@ -192,7 +192,7 @@ private static TransactionResult TransactionWith(params ExercisedEvent[] events) private static readonly Assembly WrapperAssembly = CompileWrapperAssembly(); [Fact] - public void projector_returns_One_when_matching_exercise_event_present() + public void NonContractChoiceProjector_projector_returns_One_when_matching_exercise_event_present() { var tx = TransactionWith(ExercisedGetCount("contract-1", 42)); @@ -203,7 +203,7 @@ public void projector_returns_One_when_matching_exercise_event_present() } [Fact] - public void projector_ignores_same_choice_exercised_on_a_different_contract() + public void NonContractChoiceProjector_projector_ignores_same_choice_exercised_on_a_different_contract() { var tx = TransactionWith( ExercisedGetCount("other-contract", 99), @@ -216,7 +216,7 @@ public void projector_ignores_same_choice_exercised_on_a_different_contract() } [Fact] - public void projector_matches_through_template_package_id_drift() + public void NonContractChoiceProjector_projector_matches_through_template_package_id_drift() { var driftedTemplateId = new Identifier("upgraded-package-id", ModuleName, EntityName); var tx = TransactionWith(ExercisedGetCount("contract-1", 42, driftedTemplateId)); @@ -228,7 +228,7 @@ public void projector_matches_through_template_package_id_drift() } [Fact] - public void projector_throws_self_contained_ExercisedEvents_diagnostic_when_no_exercise_event_present() + public void NonContractChoiceProjector_projector_throws_self_contained_ExercisedEvents_diagnostic_when_no_exercise_event_present() { var tx = TransactionWith(); @@ -241,7 +241,7 @@ public void projector_throws_self_contained_ExercisedEvents_diagnostic_when_no_e } [Fact] - public void projector_throws_when_no_matching_contract_in_non_empty_events() + public void NonContractChoiceProjector_projector_throws_when_no_matching_contract_in_non_empty_events() { var tx = TransactionWith(ExercisedGetCount("other-contract", 99)); diff --git a/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceUnitExerciserRoundTripTests.cs b/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceUnitExerciserRoundTripTests.cs index 25af969..06a4cba 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceUnitExerciserRoundTripTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NonContractChoiceUnitExerciserRoundTripTests.cs @@ -1,9 +1,10 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Reflection; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Ledger.Abstractions; using Daml.Runtime; using Daml.Runtime.Commands; @@ -51,7 +52,6 @@ private static Assembly CompileWrapperAssembly() new DamlTemplate { Name = EntityName, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -93,7 +93,7 @@ private static Assembly CompileWrapperAssembly() UseRecordTypes = true, UsePrimaryConstructors = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(new DarModel { MainPackage = package, Dependencies = [] }); return EmitAssembly(files); @@ -139,7 +139,7 @@ private static Assembly EmitAssembly(IReadOnlyList files) "\n", emit.Diagnostics .Where(d => d.Severity == DiagnosticSeverity.Error) - .Select(d => d.GetMessage() + " @ " + d.Location))); + .Select(d => d.GetMessage(CultureInfo.InvariantCulture) + " @ " + d.Location))); stream.Seek(0, SeekOrigin.Begin); return Assembly.Load(stream.ToArray()); @@ -170,20 +170,30 @@ private static TransactionResult TransactionWith(params ExercisedEvent[] events) private static readonly Assembly WrapperAssembly = CompileWrapperAssembly(); - private static async Task> InvokeDoNothingAsync(ILedgerWriter client, string contractId) + private static MethodInfo DoNothingAsyncExerciser() { - var sinkType = WrapperAssembly.GetType($"{GeneratedNamespace}.{EntityName}", throwOnError: true)!; var extensionsType = WrapperAssembly.GetType( $"{GeneratedNamespace}.{EntityName}{NonContractExtensionsSuffix}", throwOnError: true)!; - var method = extensionsType.GetMethod($"{ChoiceName}Async", BindingFlags.Public | BindingFlags.Static) - ?? throw new InvalidOperationException($"{ChoiceName}Async not found on emitted extensions class"); + return extensionsType + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == $"{ChoiceName}Async" + && m.GetParameters().Any(p => p.ParameterType == typeof(SubmitterInfo))); + } - var contractIdType = typeof(ContractId<>).MakeGenericType(sinkType); - var typedContractId = Activator.CreateInstance(contractIdType, contractId); + private static object TypedContractId(string contractId) + { + var sinkType = WrapperAssembly.GetType($"{GeneratedNamespace}.{EntityName}", throwOnError: true)!; + return Activator.CreateInstance(typeof(ContractId<>).MakeGenericType(sinkType), contractId)!; + } - var task = (Task>)method.Invoke( + private static async Task> InvokeDoNothingAsync( + ILedgerWriter client, + string contractId, + SubmitterInfo submitter) + { + var task = (Task>)DoNothingAsyncExerciser().Invoke( null, - [typedContractId, client, new Party("alice"), null, null, null, CancellationToken.None])!; + [TypedContractId(contractId), client, submitter, null, null, null, CancellationToken.None])!; return await task; } @@ -196,12 +206,36 @@ public async Task DoNothingAsync_projects_a_unit_outcome_through_the_ledger_writ .Returns(Task.FromResult>( new ExerciseOutcome.One(TransactionWith(ExercisedDoNothing("contract-1"))))); - var outcome = await InvokeDoNothingAsync(client, "contract-1"); + SubmitterInfo singleParty = new Party("alice"); + + var outcome = await InvokeDoNothingAsync(client, "contract-1", singleParty); var one = outcome.Should().BeOfType.One>().Subject; one.Result.Should().Be(Unit.Value); } + [Fact] + public async Task DoNothingAsync_forwards_the_readAs_parties_of_an_explicit_submitter() + { + var client = Substitute.For(); + client.TrySubmitAndWaitForTransactionAsync( + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.FromResult>( + new ExerciseOutcome.One(TransactionWith(ExercisedDoNothing("contract-1"))))); + + var submitter = new SubmitterInfo( + actAs: new HashSet { new("alice"), new("carol") }, + readAs: new HashSet { new("bob") }); + + await InvokeDoNothingAsync(client, "contract-1", submitter); + + await client.Received(1).TrySubmitAndWaitForTransactionAsync( + Arg.Any(), + Arg.Is(s => s.ReadAs.Contains(new Party("bob")) && s.ActAs.Count == 2), + Arg.Any(), + Arg.Any()); + } + /// /// Regression: a Daml package declaring its own enum Unit (as /// splice-wallet-payments does) must still emit a global::-qualified @@ -210,7 +244,7 @@ public async Task DoNothingAsync_projects_a_unit_outcome_through_the_ledger_writ /// otherwise bind to the package-local enum instead (CS0117 on .Value). /// [Fact] - public void generated_wrapper_compiles_clean_when_the_package_declares_its_own_unit_enum() + public void NonContractChoiceUnitExerciserRoundTrip_generated_wrapper_compiles_clean_when_the_package_declares_its_own_unit_enum() { var module = new DamlModule { @@ -220,7 +254,6 @@ public void generated_wrapper_compiles_clean_when_the_package_declares_its_own_u new DamlTemplate { Name = EntityName, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [ new DamlChoice @@ -267,7 +300,7 @@ public void generated_wrapper_compiles_clean_when_the_package_declares_its_own_u UseRecordTypes = true, UsePrimaryConstructors = true, }; - var generator = new CSharpCodeGenerator(options, new ConsoleLogger(0)); + var generator = new CSharpCodeGenerator(options); var files = generator.Generate(new DarModel { MainPackage = package, Dependencies = [] }); EmitAssembly(files); diff --git a/tests/Daml.Codegen.CSharp.Tests/NuGetPackIntegrationTests.cs b/tests/Daml.Codegen.CSharp.Tests/NuGetPackIntegrationTests.cs index 9aa304d..fc51e6a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NuGetPackIntegrationTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NuGetPackIntegrationTests.cs @@ -38,7 +38,7 @@ public class NuGetPackIntegrationTests private const string ExpectedPackageIdPrefix = "Splice.Api.Token.Holding.V1."; [Fact] - public async Task generated_csproj_packs_into_nupkg_for_splice_holding_v1_fixture() + public async Task NuGetPackIntegration_generated_csproj_packs_into_nupkg_for_splice_holding_v1_fixture() { var snapshotDir = Path.Combine(AppContext.BaseDirectory, "Snapshots", FixtureSnapshotName); var darPath = Path.Combine(snapshotDir, $"{FixtureSnapshotName}.dar"); @@ -103,7 +103,7 @@ public async Task generated_csproj_packs_into_nupkg_for_splice_holding_v1_fixtur // dotnet pack walks up from the csproj for NuGet.config; place it at the // --out root (a csproj ancestor) so the generated Daml.* PackageReferences // resolve against the local feed. - WriteLocalNuGetConfig(outDir, localNuGetSource!); + WriteLocalNuGetConfig(outDir, localNuGetSource!, Path.Combine(workspace, "nuget-packages")); var packOutput = Path.Combine(workspace, "nupkg-out"); Directory.CreateDirectory(packOutput); @@ -223,12 +223,15 @@ private static void TryCleanup(string path) return null; } - private static void WriteLocalNuGetConfig(string directory, string localSource) + private static void WriteLocalNuGetConfig(string directory, string localSource, string packagesFolder) { var configPath = Path.Combine(directory, "NuGet.config"); var content = $@" + + + diff --git a/tests/Daml.Codegen.CSharp.Tests/NuGetVersionResolverTests.cs b/tests/Daml.Codegen.CSharp.Tests/NuGetVersionResolverTests.cs index 4adf190..f5aeffd 100644 --- a/tests/Daml.Codegen.CSharp.Tests/NuGetVersionResolverTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/NuGetVersionResolverTests.cs @@ -1,7 +1,6 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; using Daml.Codegen.CSharp.Versioning; using AwesomeAssertions; using Xunit; @@ -26,7 +25,7 @@ public void Dispose() } [Fact] - public void compute_stamps_generation_ordinal_from_store() + public void Compute_stamps_generation_ordinal_from_store() { var store = JsonReleaseCounterStore.OpenOrCreate(_storePath); @@ -40,7 +39,7 @@ public void compute_stamps_generation_ordinal_from_store() } [Fact] - public void compute_yields_same_ordinal_for_different_packages_under_one_codegen_version() + public void Compute_yields_same_ordinal_for_different_packages_under_one_codegen_version() { var store = JsonReleaseCounterStore.OpenOrCreate(_storePath); @@ -52,7 +51,7 @@ public void compute_yields_same_ordinal_for_different_packages_under_one_codegen } [Fact] - public void compute_increments_ordinal_when_codegen_version_changes() + public void Compute_increments_ordinal_when_codegen_version_changes() { var store = JsonReleaseCounterStore.OpenOrCreate(_storePath); diff --git a/tests/Daml.Codegen.CSharp.Tests/OptionalRepresentationTests.cs b/tests/Daml.Codegen.CSharp.Tests/OptionalRepresentationTests.cs new file mode 100644 index 0000000..16e0e8d --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/OptionalRepresentationTests.cs @@ -0,0 +1,493 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using Daml.Codegen.Intermediate.Model; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Codegen.CSharp.Tests; + +public class OptionalRepresentationTests +{ + private const string LocalPackageId = "local-pkg"; + private const string EmittedPackageId = "emitted-pkg"; + private const string StdlibPackageId = "stdlib-pkg"; + + private sealed class StubResolver : ICrossPackageResolver + { + public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) => "Resolved"; + + public IReadOnlySet DiscoveredExternalPackageIds => new HashSet(); + + public DamlPackage? LookupPackage(string packageId) => null; + } + + private static DamlPackage LocalPackage(params DamlModule[] modules) => + new() + { + PackageId = LocalPackageId, + Name = "local", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = modules, + DependencyReferences = [] + }; + + private static DamlType Rewrite(DamlType type) => + OptionalRepresentation.Rewrite(type, LocalPackage(), new StubResolver()); + + private sealed class DeclaringResolver(params DamlPackage[] packages) : ICrossPackageResolver + { + private readonly IReadOnlyDictionary _packages = + packages.ToDictionary(package => package.PackageId); + + public string Resolve(DamlTypeRef typeRef, PackageEmitContext context) => "Resolved"; + + public IReadOnlySet DiscoveredExternalPackageIds => new HashSet(); + + public DamlPackage? LookupPackage(string packageId) => + _packages.TryGetValue(packageId, out var package) ? package : null; + } + + private static DamlDataType Generic(string name, params DamlFieldDefinition[] fields) => + new() + { + Name = name, + TypeParams = ["a"], + Definition = new DamlRecordDefinition(fields), + }; + + private static DamlModule ShapesModule(params DamlDataType[] dataTypes) => + new() { Name = "Acme.Shapes", Templates = [], Interfaces = [], DataTypes = dataTypes }; + + private static DamlPackage EmittedPackage(params DamlDataType[] dataTypes) => + new() + { + PackageId = EmittedPackageId, + Name = "acme", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [ShapesModule(dataTypes)], + DependencyReferences = [] + }; + + private static DamlPackage StdlibPackage(params DamlDataType[] dataTypes) => + new() + { + PackageId = StdlibPackageId, + Name = "daml-stdlib", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [new DamlModule + { + Name = "DA.Types", + Templates = [], + Interfaces = [], + DataTypes = dataTypes, + }], + DependencyReferences = [] + }; + + private static DamlType RewriteAgainst(DamlType type, params DamlDataType[] declarations) => + OptionalRepresentation.Rewrite(type, LocalPackage(), new DeclaringResolver(EmittedPackage(declarations))); + + private static DamlType RewriteAgainstStdlib(DamlType type, params DamlDataType[] declarations) => + OptionalRepresentation.Rewrite(type, LocalPackage(), new DeclaringResolver(StdlibPackage(declarations))); + + private static DamlFieldDefinition Field(string name, DamlType type) => new(name, type); + + private static DamlPrimitiveType Prim(DamlPrimitive primitive) => new(primitive); + + private static DamlTypeApp App(DamlPrimitive constructor, params DamlType[] arguments) => + new(Prim(constructor), arguments); + + private static DamlTypeApp Emitted(string name, params DamlType[] arguments) => + new(new DamlTypeRef(EmittedPackageId, "Acme.Shapes", name), arguments); + + private static DamlTypeApp StdlibEither(params DamlType[] arguments) => + new(new DamlTypeRef(StdlibPackageId, "DA.Types", "Either"), arguments); + + [Fact] + public void Rewrite_leaves_a_flat_optional_as_a_nullable_type_app() + { + Rewrite(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))) + .Should().Be(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + } + + [Fact] + public void Rewrite_wraps_an_optional_over_a_type_variable() + { + Rewrite(App(DamlPrimitive.Optional, new DamlTypeVar("a"))) + .Should().Be(new DamlWrappedOptional(new DamlTypeVar("a"), OptionalEncoding.Flat)); + } + + [Fact] + public void Rewrite_wraps_an_optional_passed_to_an_emitted_generic_in_the_flat_encoding() + { + Rewrite(Emitted("Box", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))) + .Should().Be(Emitted("Box", new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat))); + } + + [Fact] + public void Rewrite_keeps_the_outer_optional_nullable_around_an_emitted_generic() + { + var damlType = App(DamlPrimitive.Optional, Emitted("Box", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + Rewrite(damlType).Should().Be(App( + DamlPrimitive.Optional, + Emitted("Box", new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat)))); + } + + [Fact] + public void Rewrite_leaves_an_optional_under_a_list_alone_even_inside_an_optional() + { + var damlType = App(DamlPrimitive.Optional, App(DamlPrimitive.List, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + Rewrite(damlType).Should().Be(damlType); + } + + [Fact] + public void Rewrite_leaves_an_optional_passed_to_a_container_primitive_alone() + { + var damlType = App(DamlPrimitive.List, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + Rewrite(damlType).Should().Be(damlType); + } + + [Fact] + public void Rewrite_wraps_both_levels_of_a_chain_in_the_nested_encoding() + { + Rewrite(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))) + .Should().Be(new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain)); + } + + [Fact] + public void Rewrite_wraps_every_level_of_a_three_deep_chain() + { + var damlType = App( + DamlPrimitive.Optional, + App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + Rewrite(damlType).Should().Be(new DamlWrappedOptional( + new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain)); + } + + [Fact] + public void Rewrite_wraps_a_chain_over_a_type_variable_at_every_level() + { + Rewrite(App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, new DamlTypeVar("a")))) + .Should().Be(new DamlWrappedOptional( + new DamlWrappedOptional(new DamlTypeVar("a"), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain)); + } + + [Fact] + public void Rewrite_wraps_a_chain_passed_to_an_emitted_generic_as_a_chain_not_as_flat() + { + var damlType = Emitted( + "Box", + App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + Rewrite(damlType).Should().Be(Emitted( + "Box", + new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain))); + } + + [Fact] + public void Rewrite_does_not_carry_the_chain_context_across_an_emitted_generic() + { + var damlType = App( + DamlPrimitive.Optional, + Emitted("Box", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + Rewrite(damlType).Should().Be(App( + DamlPrimitive.Optional, + Emitted("Box", new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat)))); + } + + [Fact] + public void Rewrite_wraps_an_optional_passed_to_a_stdlib_generic_in_the_flat_encoding() + { + var damlType = StdlibEither( + Prim(DamlPrimitive.Text), App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + RewriteAgainstStdlib(damlType).Should().Be(StdlibEither( + Prim(DamlPrimitive.Text), + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat))); + } + + [Fact] + public void Rewrite_wraps_a_chain_passed_to_a_stdlib_generic_as_a_chain_not_as_flat() + { + var damlType = StdlibEither( + Prim(DamlPrimitive.Text), + App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + RewriteAgainstStdlib(damlType).Should().Be(StdlibEither( + Prim(DamlPrimitive.Text), + new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain))); + } + + [Fact] + public void Rewrite_keeps_the_outer_optional_nullable_around_a_stdlib_generic() + { + var damlType = App( + DamlPrimitive.Optional, + StdlibEither(Prim(DamlPrimitive.Text), App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)))); + + RewriteAgainstStdlib(damlType).Should().Be(App( + DamlPrimitive.Optional, + StdlibEither( + Prim(DamlPrimitive.Text), + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat)))); + } + + [Fact] + public void Rewrite_rejects_an_optional_passed_to_a_stdlib_generic_that_wraps_that_parameter() + { + var wrappingEither = Generic("Either", Field("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))); + + var act = () => RewriteAgainstStdlib( + StdlibEither(App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), Prim(DamlPrimitive.Int64)), + wrappingEither); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of DA.Types:Either*") + .WithMessage("*one array level short*"); + } + + [Fact] + public void Rewrite_is_idempotent() + { + var damlType = Emitted("Box", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + Rewrite(Rewrite(damlType)).Should().Be(Rewrite(damlType)); + } + + [Fact] + public void Rewrite_is_idempotent_over_an_optional_type_variable() + { + var damlType = App(DamlPrimitive.Optional, new DamlTypeVar("a")); + + Rewrite(Rewrite(damlType)).Should().Be(Rewrite(damlType)); + } + + [Fact] + public void Rewrite_is_idempotent_over_a_nested_optional_chain() + { + var damlType = App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + Rewrite(Rewrite(damlType)).Should().Be(Rewrite(damlType)); + } + + [Fact] + public void Rewrite_marks_an_unrewritten_optional_under_a_chain_wrapper_as_a_chain() + { + var damlType = new DamlWrappedOptional( + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), OptionalEncoding.NestedChain); + + Rewrite(damlType).Should().Be(new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain)); + } + + [Fact] + public void Rewrite_rejects_an_optional_passed_to_an_emitted_generic_that_wraps_that_parameter() + { + var crate = Generic("Crate", Field("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))); + + var act = () => RewriteAgainst(Emitted("Crate", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), crate); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*") + .WithMessage("*one array level short*"); + } + + [Fact] + public void Rewrite_rejects_a_chain_passed_to_an_emitted_generic_that_wraps_that_parameter() + { + var crate = Generic("Crate", Field("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))); + var chain = App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + var act = () => RewriteAgainst(Emitted("Crate", chain), crate); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*") + .WithMessage("*one array level short*"); + } + + [Fact] + public void Rewrite_rejects_an_optional_passed_to_a_generic_declared_in_the_package_being_emitted() + { + var crate = Generic("Crate", Field("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))); + var selfReference = new DamlTypeApp( + new DamlTypeRef(string.Empty, "Acme.Shapes", "Crate"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]); + + var act = () => OptionalRepresentation.Rewrite( + selfReference, LocalPackage(ShapesModule(crate)), new DeclaringResolver()); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Crate*"); + } + + [Fact] + public void Rewrite_rejects_an_optional_reaching_a_wrapping_parameter_through_another_generic() + { + var crate = Generic("Crate", Field("item", App(DamlPrimitive.Optional, new DamlTypeVar("a")))); + var outer = Generic("Outer", Field("crate", Emitted("Crate", new DamlTypeVar("a")))); + + var act = () => RewriteAgainst( + Emitted("Outer", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), crate, outer); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Outer*"); + } + + [Fact] + public void Rewrite_rejects_an_optional_passed_to_a_generic_that_wraps_the_parameter_inside_a_list() + { + var shelf = Generic("Shelf", Field("items", App(DamlPrimitive.List, App(DamlPrimitive.Optional, new DamlTypeVar("a"))))); + + var act = () => RewriteAgainst(Emitted("Shelf", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), shelf); + + act.Should().Throw() + .WithMessage("*Optional as the 'a' type argument of Acme.Shapes:Shelf*"); + } + + [Fact] + public void Rewrite_wraps_a_chain_passed_to_an_emitted_generic_whose_declaration_adds_no_optional_level() + { + var box = Generic("Box", Field("item", new DamlTypeVar("a"))); + var chain = App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + RewriteAgainst(Emitted("Box", chain), box).Should().Be(Emitted( + "Box", + new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain))); + } + + [Fact] + public void Rewrite_leaves_an_optional_passed_to_a_generic_that_wraps_only_a_list_of_the_parameter() + { + var shelf = Generic("Shelf", Field("items", App(DamlPrimitive.Optional, App(DamlPrimitive.List, new DamlTypeVar("a"))))); + + RewriteAgainst(Emitted("Shelf", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), shelf) + .Should().Be(Emitted("Shelf", new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat))); + } + + [Fact] + public void Rewrite_terminates_on_a_generic_whose_declaration_references_itself() + { + var node = Generic("Node", Field("next", App(DamlPrimitive.Optional, Emitted("Node", new DamlTypeVar("a"))))); + + RewriteAgainst(Emitted("Node", App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), node) + .Should().Be(Emitted("Node", new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat))); + } + + [Fact] + public void Rewrite_wraps_an_optional_in_gen_map_key_position() + { + var damlType = App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64)); + + Rewrite(damlType).Should().Be(App( + DamlPrimitive.GenMap, + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat), + Prim(DamlPrimitive.Int64))); + } + + [Fact] + public void Rewrite_leaves_an_optional_in_gen_map_value_position_alone() + { + var damlType = App( + DamlPrimitive.GenMap, + Prim(DamlPrimitive.Text), + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))); + + Rewrite(damlType).Should().Be(damlType); + } + + [Fact] + public void Rewrite_wraps_every_level_of_a_nested_optional_gen_map_key_as_a_chain() + { + var damlType = App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), + Prim(DamlPrimitive.Int64)); + + Rewrite(damlType).Should().Be(App( + DamlPrimitive.GenMap, + new DamlWrappedOptional( + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.NestedChain), + OptionalEncoding.NestedChain), + Prim(DamlPrimitive.Int64))); + } + + [Fact] + public void Rewrite_leaves_an_optional_under_a_list_in_gen_map_key_position_alone() + { + var damlType = App( + DamlPrimitive.GenMap, + App(DamlPrimitive.List, App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))), + Prim(DamlPrimitive.Int64)); + + Rewrite(damlType).Should().Be(damlType); + } + + [Fact] + public void Rewrite_wraps_a_gen_map_key_under_an_outer_nullable_optional() + { + var damlType = App( + DamlPrimitive.Optional, + App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64))); + + Rewrite(damlType).Should().Be(App( + DamlPrimitive.Optional, + App( + DamlPrimitive.GenMap, + new DamlWrappedOptional(Prim(DamlPrimitive.Text), OptionalEncoding.Flat), + Prim(DamlPrimitive.Int64)))); + } + + [Fact] + public void Rewrite_is_idempotent_over_an_optional_gen_map_key() + { + var damlType = App( + DamlPrimitive.GenMap, + App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text)), + Prim(DamlPrimitive.Int64)); + + Rewrite(Rewrite(damlType)).Should().Be(Rewrite(damlType)); + } + + [Fact] + public void Rewrite_rejects_a_type_tree_deeper_than_the_supported_bound() + { + DamlType damlType = Prim(DamlPrimitive.Text); + for (var level = 0; level < 300; level++) + { + damlType = App(DamlPrimitive.List, damlType); + } + + var act = () => Rewrite(damlType); + + act.Should().Throw().WithMessage("*depth*"); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/PackageEmitContextTests.cs b/tests/Daml.Codegen.CSharp.Tests/PackageEmitContextTests.cs index 0575d32..99b293b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/PackageEmitContextTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/PackageEmitContextTests.cs @@ -3,9 +3,9 @@ using Daml.Codegen.CSharp; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; +using Daml.Codegen.CSharp.Tests.TestHelpers; using AwesomeAssertions; -using NSubstitute; using Xunit; namespace Daml.Codegen.CSharp.Tests; @@ -49,7 +49,7 @@ private static CodeGenOptions Options(string? rootNamespace = null) => new() { RootNamespace = rootNamespace }; [Fact] - public void for_package_derives_root_namespace_from_package_name() + public void ForPackage_derives_root_namespace_from_package_name() { var context = PackageEmitContext.ForPackage(Package("cats-markets"), Options()); @@ -57,7 +57,7 @@ public void for_package_derives_root_namespace_from_package_name() } [Fact] - public void for_package_honours_the_root_namespace_override() + public void ForPackage_honours_the_root_namespace_override() { var context = PackageEmitContext.ForPackage(Package("cats-markets"), Options("My.Override")); @@ -65,7 +65,7 @@ public void for_package_honours_the_root_namespace_override() } [Fact] - public void for_package_scopes_the_qualifier_to_the_root_namespace() + public void ForPackage_scopes_the_qualifier_to_the_root_namespace() { var context = PackageEmitContext.ForPackage(Package("canton-party-replication"), Options()); @@ -74,7 +74,7 @@ public void for_package_scopes_the_qualifier_to_the_root_namespace() } [Fact] - public void for_package_collects_data_types_across_all_modules() + public void ForPackage_collects_data_types_across_all_modules() { var context = PackageEmitContext.ForPackage( Package( @@ -87,7 +87,7 @@ public void for_package_collects_data_types_across_all_modules() } [Fact] - public void for_package_keeps_same_named_data_types_from_different_modules_distinct() + public void ForPackage_keeps_same_named_data_types_from_different_modules_distinct() { var first = Record("Amulet", new DamlFieldDefinition("a", new DamlPrimitiveType(DamlPrimitive.Text))); var second = Enum("Amulet", "X"); @@ -103,7 +103,7 @@ public void for_package_keeps_same_named_data_types_from_different_modules_disti } [Fact] - public void for_package_records_local_enums_module_qualified() + public void ForPackage_records_local_enums_module_qualified() { var context = PackageEmitContext.ForPackage( Package("p", Module("Splice.AmuletConfig", dataTypes: [Enum("Amulet", "Free", "Paid")])), @@ -113,7 +113,7 @@ public void for_package_records_local_enums_module_qualified() } [Fact] - public void for_package_records_local_variants_module_qualified() + public void ForPackage_records_local_variants_module_qualified() { var context = PackageEmitContext.ForPackage( Package("p", Module("M", dataTypes: @@ -126,7 +126,7 @@ public void for_package_records_local_variants_module_qualified() } [Fact] - public void for_package_flags_interface_placeholder_records_module_local() + public void ForPackage_flags_interface_shadowed_records_module_local() { var holdingRecord = Record("Holding"); var unrelatedHolding = Record("Holding"); @@ -138,11 +138,288 @@ public void for_package_flags_interface_placeholder_records_module_local() Module("Other", dataTypes: [unrelatedHolding])), Options()); - context.InterfacePlaceholderQualifiedNames.Should().BeEquivalentTo("Splice.Holding:Holding"); + context.LocalInterfaceQualifiedNames.Should().BeEquivalentTo("Splice.Holding:Holding"); } [Fact] - public void for_package_widens_reserved_names_to_a_record_colliding_with_an_interface_marker() + public void ForPackage_maps_a_local_view_record_to_its_interface_marker() + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Record("AssetView")], + interfaces: + [ + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "AssetView"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().Contain("M:AssetView", "IAsset"); + } + + [Fact] + public void ForPackage_excludes_a_view_record_shared_by_two_interfaces_from_the_view_record_map() + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Record("SharedView")], + interfaces: + [ + new DamlInterface + { + Name = "Bond", + Choices = [], + ViewType = new DamlTypeRef("", "M", "SharedView"), + }, + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "SharedView"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().BeEmpty(); + } + + [Fact] + public void ForPackage_excludes_foreign_missing_and_generic_view_types_from_the_view_record_map() + { + var genericView = new DamlDataType + { + Name = "GenericView", + TypeParams = ["a"], + Definition = new DamlRecordDefinition([]), + }; + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [genericView], + interfaces: + [ + new DamlInterface + { + Name = "Foreign", + Choices = [], + ViewType = new DamlTypeRef("other-pkg", "M", "ForeignView"), + }, + new DamlInterface + { + Name = "Dangling", + Choices = [], + ViewType = new DamlTypeRef("", "M", "NoSuchView"), + }, + new DamlInterface + { + Name = "Generic", + Choices = [], + ViewType = new DamlTypeRef("", "M", "GenericView"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().BeEmpty(); + } + + [Fact] + public void ForPackage_excludes_a_view_record_that_is_an_interface_placeholder_from_the_view_record_map() + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Record("Placeholder")], + interfaces: + [ + new DamlInterface { Name = "Placeholder", Choices = [] }, + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "Placeholder"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().BeEmpty(); + } + + [Fact] + public void ForPackage_excludes_a_non_record_view_type_from_the_view_record_map() + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Enum("Colour", "Red"), Variant("Shape", new DamlVariantConstructor("Circle", null))], + interfaces: + [ + new DamlInterface + { + Name = "Coloured", + Choices = [], + ViewType = new DamlTypeRef("", "M", "Colour"), + }, + new DamlInterface + { + Name = "Shaped", + Choices = [], + ViewType = new DamlTypeRef("", "M", "Shape"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().BeEmpty(); + } + + [Theory] + [InlineData("view")] + [InlineData("interfaceId")] + [InlineData("assetView")] + [InlineData("iAsset")] + public void ForPackage_excludes_a_view_record_whose_field_does_not_mirror_cleanly(string fieldName) + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Record("AssetView", new DamlFieldDefinition(fieldName, new DamlPrimitiveType(DamlPrimitive.Party)))], + interfaces: + [ + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "AssetView"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().BeEmpty(); + } + + [Fact] + public void ForPackage_keeps_a_view_record_whose_fields_mirror_cleanly() + { + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Record("AssetView", new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party)))], + interfaces: + [ + new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "AssetView"), + }, + ])), + Options()); + + context.LocalViewRecordMarkerNames.Should().Contain("M:AssetView", "IAsset"); + } + + [Fact] + public void HasWitnessableViewRecord_admits_a_local_record_and_any_foreign_reference() + { + var localRecordView = new DamlInterface + { + Name = "Asset", + Choices = [], + ViewType = new DamlTypeRef("", "M", "AssetView"), + }; + var foreignView = new DamlInterface + { + Name = "Foreign", + Choices = [], + ViewType = new DamlTypeRef("other-pkg", "Other", "ForeignView"), + }; + var context = PackageEmitContext.ForPackage( + Package("p", Module("M", dataTypes: [Record("AssetView")], interfaces: [localRecordView, foreignView])), + Options()); + + context.HasWitnessableViewRecord(localRecordView).Should().BeTrue(); + context.HasWitnessableViewRecord(foreignView).Should().BeTrue(); + } + + [Fact] + public void HasWitnessableViewRecord_rejects_a_view_type_that_is_not_a_local_non_generic_record() + { + var genericView = new DamlDataType + { + Name = "GenericView", + TypeParams = ["a"], + Definition = new DamlRecordDefinition([]), + }; + var viewless = new DamlInterface { Name = "Lockable", Choices = [] }; + var enumView = new DamlInterface + { + Name = "Coloured", + Choices = [], + ViewType = new DamlTypeRef("", "M", "Colour"), + }; + var genericViewInterface = new DamlInterface + { + Name = "Generic", + Choices = [], + ViewType = new DamlTypeRef("", "M", "GenericView"), + }; + var placeholderView = new DamlInterface + { + Name = "Placeheld", + Choices = [], + ViewType = new DamlTypeRef("", "M", "Placeholder"), + }; + var danglingView = new DamlInterface + { + Name = "Dangling", + Choices = [], + ViewType = new DamlTypeRef("", "M", "NoSuchView"), + }; + var context = PackageEmitContext.ForPackage( + Package( + "p", + Module( + "M", + dataTypes: [Enum("Colour", "Red"), genericView, Record("Placeholder")], + interfaces: + [ + new DamlInterface { Name = "Placeholder", Choices = [] }, + viewless, + enumView, + genericViewInterface, + placeholderView, + danglingView, + ])), + Options()); + + context.HasWitnessableViewRecord(viewless).Should().BeFalse(); + context.HasWitnessableViewRecord(enumView).Should().BeFalse(); + context.HasWitnessableViewRecord(genericViewInterface).Should().BeFalse(); + context.HasWitnessableViewRecord(placeholderView).Should().BeFalse(); + context.HasWitnessableViewRecord(danglingView).Should().BeFalse(); + } + + [Fact] + public void ForPackage_widens_reserved_names_to_a_record_colliding_with_an_interface_marker() { var context = PackageEmitContext.ForPackage( Package( @@ -158,7 +435,7 @@ public void for_package_widens_reserved_names_to_a_record_colliding_with_an_inte } [Fact] - public void for_package_widens_reserved_names_to_a_record_colliding_with_the_first_round_disambiguated_marker() + public void ForPackage_widens_reserved_names_to_a_record_colliding_with_the_first_round_disambiguated_marker() { var context = PackageEmitContext.ForPackage( Package( @@ -168,7 +445,7 @@ public void for_package_widens_reserved_names_to_a_record_colliding_with_the_fir dataTypes: [Record("IFactory_")], templates: [ - new DamlTemplate { Name = "IFactory", Fields = [], Choices = [] }, + new DamlTemplate { Name = "IFactory", Choices = [] }, ], interfaces: [new DamlInterface { Name = "Factory", Choices = [] }])), Options()); @@ -178,7 +455,7 @@ public void for_package_widens_reserved_names_to_a_record_colliding_with_the_fir } [Fact] - public void for_package_excludes_interface_placeholder_records_from_the_reserved_set() + public void ForPackage_excludes_interface_placeholder_records_from_the_reserved_set() { var context = PackageEmitContext.ForPackage( Package( @@ -194,7 +471,7 @@ public void for_package_excludes_interface_placeholder_records_from_the_reserved } [Fact] - public void for_package_deterministically_assigns_the_same_marker_winner_across_modules_regardless_of_declaration_order() + public void ForPackage_deterministically_assigns_the_same_marker_winner_across_modules_regardless_of_declaration_order() { DamlInterface Factory() => new() { Name = "Factory", Choices = [] }; @@ -218,7 +495,7 @@ public void for_package_deterministically_assigns_the_same_marker_winner_across_ } [Fact] - public void for_package_excludes_nested_choice_argument_types_from_the_reserved_set() + public void ForPackage_excludes_nested_choice_argument_types_from_the_reserved_set() { var argType = Record("IFactory", new DamlFieldDefinition("to", new DamlPrimitiveType(DamlPrimitive.Party))); var choice = new DamlChoice @@ -228,7 +505,7 @@ public void for_package_excludes_nested_choice_argument_types_from_the_reserved_ ArgumentType = new DamlTypeRef("", "M", "IFactory"), ReturnType = new DamlPrimitiveType(DamlPrimitive.Unit) }; - var template = new DamlTemplate { Name = "Account", Fields = [], Choices = [choice] }; + var template = new DamlTemplate { Name = "Account", Choices = [choice] }; var context = PackageEmitContext.ForPackage( Package( @@ -245,7 +522,7 @@ public void for_package_excludes_nested_choice_argument_types_from_the_reserved_ } [Fact] - public void for_package_maps_nested_choice_argument_types_to_their_parent_template() + public void ForPackage_maps_nested_choice_argument_types_to_their_parent_template() { var argType = Record("TransferArg", new DamlFieldDefinition("to", new DamlPrimitiveType(DamlPrimitive.Party))); var choice = new DamlChoice @@ -258,7 +535,6 @@ public void for_package_maps_nested_choice_argument_types_to_their_parent_templa var template = new DamlTemplate { Name = "Account", - Fields = [], Choices = [choice] }; var context = PackageEmitContext.ForPackage( @@ -270,7 +546,7 @@ public void for_package_maps_nested_choice_argument_types_to_their_parent_templa } [Fact] - public void for_package_does_not_map_choice_args_that_are_not_local_data_types() + public void ForPackage_does_not_map_choice_args_that_are_not_local_data_types() { var choice = new DamlChoice { @@ -282,7 +558,6 @@ public void for_package_does_not_map_choice_args_that_are_not_local_data_types() var template = new DamlTemplate { Name = "Account", - Fields = [], Choices = [choice] }; var context = PackageEmitContext.ForPackage( @@ -293,7 +568,7 @@ public void for_package_does_not_map_choice_args_that_are_not_local_data_types() } [Fact] - public void for_package_disambiguates_same_named_choice_arg_types_across_modules() + public void ForPackage_disambiguates_same_named_choice_arg_types_across_modules() { DamlModule ModuleWithTransferChoice(string moduleName, string templateName) => Module( moduleName, @@ -303,7 +578,6 @@ DamlModule ModuleWithTransferChoice(string moduleName, string templateName) => M new DamlTemplate { Name = templateName, - Fields = [], Choices = [ new DamlChoice @@ -329,12 +603,11 @@ DamlModule ModuleWithTransferChoice(string moduleName, string templateName) => M } [Fact] - public void for_package_warns_and_keeps_first_on_same_module_choice_arg_name_clash() + public void ForPackage_warns_and_keeps_first_on_same_module_choice_arg_name_clash() { DamlTemplate TemplateWithTransferChoice(string templateName) => new() { Name = templateName, - Fields = [], Choices = [ new DamlChoice @@ -346,7 +619,7 @@ public void for_package_warns_and_keeps_first_on_same_module_choice_arg_name_cla } ] }; - var logger = Substitute.For(); + var logger = new CapturingLogger(); var context = PackageEmitContext.ForPackage( Package( @@ -359,6 +632,7 @@ public void for_package_warns_and_keeps_first_on_same_module_choice_arg_name_cla logger); context.LocalChoiceArgToTemplate["M:Transfer"].Should().Be("Account"); - logger.Received(1).Warning(Arg.Is(m => m != null && m.Contains("M:Transfer") && m.Contains("Account") && m.Contains("Vault") && m.Contains("in the same package"))); + logger.Warnings.Should().ContainSingle() + .Which.Should().Contain("M:Transfer").And.Contain("Account").And.Contain("Vault").And.Contain("in the same package"); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/PackageVersionParserTests.cs b/tests/Daml.Codegen.CSharp.Tests/PackageVersionParserTests.cs index 57e4d98..7763980 100644 --- a/tests/Daml.Codegen.CSharp.Tests/PackageVersionParserTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/PackageVersionParserTests.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -10,7 +10,7 @@ namespace Daml.Codegen.CSharp.Tests; public class PackageVersionParserTests { [Fact] - public void overflowing_segment_falls_back_to_zero_zero_zero() + public void PackageVersionParser_overflowing_segment_falls_back_to_zero_zero_zero() { var result = PackageVersionParser.Parse("2147483648.0.0"); diff --git a/tests/Daml.Codegen.CSharp.Tests/PartyAnalysisTests.cs b/tests/Daml.Codegen.CSharp.Tests/PartyAnalysisTests.cs index 7254126..2408e58 100644 --- a/tests/Daml.Codegen.CSharp.Tests/PartyAnalysisTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/PartyAnalysisTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -27,7 +27,7 @@ private static IReadOnlyDictionary PartyFields(para StringComparer.Ordinal); [Fact] - public void union_of_two_static_sets_merges_payload_fields_in_declaration_order() + public void PartyAnalysis_union_of_two_static_sets_merges_payload_fields_in_declaration_order() { var result = _party.UnionStaticParties(Static("platform", "initiator"), Static("counterparty")); @@ -39,7 +39,7 @@ public void union_of_two_static_sets_merges_payload_fields_in_declaration_order( } [Fact] - public void union_dedups_a_field_named_on_both_sides_keeping_first_occurrence() + public void PartyAnalysis_union_dedups_a_field_named_on_both_sides_keeping_first_occurrence() { var result = _party.UnionStaticParties(Static("platform", "initiator"), Static("initiator", "counterparty")); @@ -50,7 +50,7 @@ public void union_dedups_a_field_named_on_both_sides_keeping_first_occurrence() } [Fact] - public void union_is_dynamic_when_the_left_side_is_dynamic() + public void PartyAnalysis_union_is_dynamic_when_the_left_side_is_dynamic() { var result = _party.UnionStaticParties(DamlPartyAnalysis.Dynamic, Static("counterparty")); @@ -58,7 +58,7 @@ public void union_is_dynamic_when_the_left_side_is_dynamic() } [Fact] - public void union_is_dynamic_when_the_right_side_is_dynamic() + public void PartyAnalysis_union_is_dynamic_when_the_right_side_is_dynamic() { var result = _party.UnionStaticParties(Static("platform"), DamlPartyAnalysis.Dynamic); @@ -66,7 +66,7 @@ public void union_is_dynamic_when_the_right_side_is_dynamic() } [Fact] - public void partition_camel_cases_controllers_and_omits_readas_when_no_observers() + public void PartyAnalysis_partition_camel_cases_controllers_and_omits_readas_when_no_observers() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("Platform", "initiator"), DamlPartyAnalysis.Dynamic); @@ -76,7 +76,7 @@ public void partition_camel_cases_controllers_and_omits_readas_when_no_observers } [Fact] - public void partition_routes_observer_only_parties_into_readas() + public void PartyAnalysis_partition_routes_observer_only_parties_into_readas() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("platform"), Static("regulator", "auditor")); @@ -86,7 +86,7 @@ public void partition_routes_observer_only_parties_into_readas() } [Fact] - public void partition_does_not_duplicate_an_observer_that_is_also_a_controller() + public void PartyAnalysis_partition_does_not_duplicate_an_observer_that_is_also_a_controller() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("platform", "counterparty"), Static("platform", "regulator")); @@ -96,7 +96,7 @@ public void partition_does_not_duplicate_an_observer_that_is_also_a_controller() } [Fact] - public void partition_dedups_a_controller_field_named_twice() + public void PartyAnalysis_partition_dedups_a_controller_field_named_twice() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("platform", "platform"), DamlPartyAnalysis.Dynamic); @@ -106,7 +106,7 @@ public void partition_dedups_a_controller_field_named_twice() } [Fact] - public void partition_field_names_preserves_the_raw_daml_field_names() + public void PartyAnalysis_partition_field_names_preserves_the_raw_daml_field_names() { var (controllers, readAs) = _party.PartitionControllerAndObserverFieldNames( Static("Platform", "initiator"), Static("regulator")); @@ -116,7 +116,7 @@ public void partition_field_names_preserves_the_raw_daml_field_names() } [Fact] - public void partition_field_names_dedups_an_observer_that_is_also_a_controller() + public void PartyAnalysis_partition_field_names_dedups_an_observer_that_is_also_a_controller() { var (controllers, readAs) = _party.PartitionControllerAndObserverFieldNames( Static("platform", "counterparty"), Static("platform", "regulator")); @@ -126,7 +126,7 @@ public void partition_field_names_dedups_an_observer_that_is_also_a_controller() } [Fact] - public void partition_emits_no_readas_when_observers_are_dynamic() + public void PartyAnalysis_partition_emits_no_readas_when_observers_are_dynamic() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("platform"), DamlPartyAnalysis.Dynamic); @@ -136,7 +136,7 @@ public void partition_emits_no_readas_when_observers_are_dynamic() } [Fact] - public void partition_emits_no_readas_when_observers_are_static_empty() + public void PartyAnalysis_partition_emits_no_readas_when_observers_are_static_empty() { var (controllers, readAs) = _party.PartitionControllersAndObservers( Static("platform"), DamlPartyAnalysis.Static([])); @@ -146,7 +146,7 @@ public void partition_emits_no_readas_when_observers_are_static_empty() } [Fact] - public void validate_keeps_a_static_analysis_when_every_field_is_a_party_field() + public void PartyAnalysis_validate_keeps_a_static_analysis_when_every_field_is_a_party_field() { var analysis = Static("platform", "counterparty"); @@ -156,7 +156,7 @@ public void validate_keeps_a_static_analysis_when_every_field_is_a_party_field() } [Fact] - public void validate_demotes_to_dynamic_when_a_named_field_is_absent() + public void PartyAnalysis_validate_demotes_to_dynamic_when_a_named_field_is_absent() { var result = _party.ValidatePayloadParties( Static("platform", "ghost"), PartyFields("platform", "counterparty")); @@ -165,7 +165,7 @@ public void validate_demotes_to_dynamic_when_a_named_field_is_absent() } [Fact] - public void validate_leaves_a_dynamic_analysis_untouched() + public void PartyAnalysis_validate_leaves_a_dynamic_analysis_untouched() { var result = _party.ValidatePayloadParties(DamlPartyAnalysis.Dynamic, PartyFields("platform")); @@ -173,7 +173,7 @@ public void validate_leaves_a_dynamic_analysis_untouched() } [Fact] - public void validate_keeps_a_static_empty_analysis() + public void PartyAnalysis_validate_keeps_a_static_empty_analysis() { var analysis = DamlPartyAnalysis.Static([]); @@ -183,7 +183,7 @@ public void validate_keeps_a_static_empty_analysis() } [Fact] - public void union_demotes_to_dynamic_when_the_left_side_holds_an_unknown_subtype() + public void PartyAnalysis_union_demotes_to_dynamic_when_the_left_side_holds_an_unknown_subtype() { var result = _party.UnionStaticParties( StaticWith(new DamlPartyPayloadField("platform"), new UnknownPartyReference()), @@ -193,7 +193,7 @@ public void union_demotes_to_dynamic_when_the_left_side_holds_an_unknown_subtype } [Fact] - public void union_demotes_to_dynamic_when_the_right_side_holds_an_unknown_subtype() + public void PartyAnalysis_union_demotes_to_dynamic_when_the_right_side_holds_an_unknown_subtype() { var result = _party.UnionStaticParties( Static("platform"), @@ -203,7 +203,7 @@ public void union_demotes_to_dynamic_when_the_right_side_holds_an_unknown_subtyp } [Fact] - public void validate_demotes_to_dynamic_when_an_unknown_subtype_is_present() + public void PartyAnalysis_validate_demotes_to_dynamic_when_an_unknown_subtype_is_present() { var result = _party.ValidatePayloadParties( StaticWith(new DamlPartyPayloadField("platform"), new UnknownPartyReference()), @@ -213,7 +213,7 @@ public void validate_demotes_to_dynamic_when_an_unknown_subtype_is_present() } [Fact] - public void partition_throws_when_static_controllers_carry_an_unknown_subtype() + public void PartyAnalysis_partition_throws_when_static_controllers_carry_an_unknown_subtype() { var act = () => _party.PartitionControllersAndObservers( StaticWith(new DamlPartyPayloadField("platform"), new UnknownPartyReference()), @@ -223,7 +223,7 @@ public void partition_throws_when_static_controllers_carry_an_unknown_subtype() } [Fact] - public void partition_throws_when_static_observers_carry_an_unknown_subtype() + public void PartyAnalysis_partition_throws_when_static_observers_carry_an_unknown_subtype() { var act = () => _party.PartitionControllersAndObservers( Static("platform"), diff --git a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Metadata.cs b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Metadata.cs index eca1695..15d485d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Metadata.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Metadata.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Output.cs b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Output.cs index a5dcb4a..5db459e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Output.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Output.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -103,83 +103,8 @@ public void GenerateProjectFile_should_include_implicit_usings() } [Fact] - public void GenerateProjectFile_should_pin_LangVersion_13_when_emitted_files_contain_partial_property() + public void GenerateProjectFile_should_not_pin_LangVersion_for_a_key_bearing_package() { - // Pinning LangVersion=13 is load-bearing for `--generate-project` builds - // whose emission contains the partial-property syntax: the codegen's - // partial-property `Key` accessor requires C# 13 to parse. Without this - // pin, `--target-framework net8.0` builds would fail on a syntax error - // before reaching the intentional CS9248 missing-implementation - // diagnostic. - // - // The decision is anchored to the EMITTED file set rather than the - // package's templates, so a key-bearing template added via - // `IncludeDependencies` still pins LangVersion correctly. - var options = CreateOptions(); - var generator = new ProjectFileGenerator(options); - var package = new DamlPackage - { - PackageId = "test-id", - Name = "any-package", - Version = new Version(1, 0, 0), - LfVersion = "2.1", - Modules = [], - DependencyReferences = [], - }; - var emittedFiles = new[] - { - GeneratedFile.Text( - "Foo.cs", - "namespace Test; public sealed partial record Foo { public partial string Key { get; } }"), - }; - - var file = generator.GenerateProjectFile(package, externalReferences: null, emittedFiles: emittedFiles); - - file.Content.Should().Contain("13"); - } - - [Fact] - public void GenerateProjectFile_should_not_pin_LangVersion_when_emitted_files_contain_no_partial_property() - { - // The pin is opt-in based on actually needing C# 13 syntax. Key-less - // emissions don't contain partial-property syntax and so shouldn't have - // their SDK floor raised — they continue to build with whatever - // LangVersion the consumer's project / SDK defaults supply. This also - // covers the `RootFilter` case: a keyed template that's filtered out of - // emission must not force the pin. - var options = CreateOptions(); - var generator = new ProjectFileGenerator(options); - var package = new DamlPackage - { - PackageId = "test-id", - Name = "any-package", - Version = new Version(1, 0, 0), - LfVersion = "2.1", - Modules = [], - DependencyReferences = [], - }; - var emittedFiles = new[] - { - GeneratedFile.Text( - "Foo.cs", - "namespace Test; public sealed partial record Foo(Party Owner) : ITemplate;"), - }; - - var file = generator.GenerateProjectFile(package, externalReferences: null, emittedFiles: emittedFiles); - - file.Content.Should().NotContain(""); - } - - [Fact] - public void GenerateProjectFile_should_pin_LangVersion_when_emittedFiles_omitted_but_package_has_key_bearing_template() - { - // Back-compat for older callers that pass only `package` (no emission - // set). Without this fallback, an old caller with a key-bearing package - // would silently produce a `.csproj` lacking 13 and the - // build would fail on a syntax error in the emitted partial property. - // The fallback is less precise than the emission-set scan (it can't see - // IncludeDependencies or RootFilter) but is functionally safe for the - // simple case. var options = CreateOptions(); var generator = new ProjectFileGenerator(options); var package = new DamlPackage @@ -187,7 +112,7 @@ public void GenerateProjectFile_should_pin_LangVersion_when_emittedFiles_omitted PackageId = "test-id", Name = "keyed-package", Version = new Version(1, 0, 0), - LfVersion = "2.1", + LfVersion = "2.3", Modules = [ new DamlModule @@ -198,7 +123,6 @@ public void GenerateProjectFile_should_pin_LangVersion_when_emittedFiles_omitted new DamlTemplate { Name = "KeyedTemplate", - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [], Key = new DamlPrimitiveType(DamlPrimitive.Text), }, @@ -212,30 +136,9 @@ public void GenerateProjectFile_should_pin_LangVersion_when_emittedFiles_omitted var file = generator.GenerateProjectFile(package); - file.Content.Should().Contain("13"); - } - - [Fact] - public void GenerateProjectFile_should_not_pin_LangVersion_when_emittedFiles_omitted_and_package_has_no_key_bearing_template() - { - // The back-compat fallback only triggers when the package itself has - // key-bearing templates. A key-less package with no emission set - // continues to build without a LangVersion pin (consumer SDK defaults). - var options = CreateOptions(); - var generator = new ProjectFileGenerator(options); - var package = new DamlPackage - { - PackageId = "test-id", - Name = "keyless-package", - Version = new Version(1, 0, 0), - LfVersion = "2.1", - Modules = [], - DependencyReferences = [], - }; - - var file = generator.GenerateProjectFile(package); - - file.Content.Should().NotContain(""); + file.Content.Should().NotContain( + "", + "a key-bearing package emits no C#-13-only syntax, so it must not raise the SDK floor of every consumer"); } [Fact] diff --git a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.References.cs b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.References.cs index 6f45f36..12b6b50 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.References.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.References.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using System.Reflection; using AwesomeAssertions; using Xunit; @@ -12,7 +12,120 @@ namespace Daml.Codegen.CSharp.Tests; public partial class ProjectFileGeneratorTests { [Fact] - public void GenerateProjectFile_should_apply_emitter_counter_and_suffix_to_sibling_references() + public void GenerateProjectFile_floats_dependency_generation_and_prerelease_when_no_release_coordinates_are_supplied() + { + var options = CreateOptions(); + var generator = new ProjectFileGenerator(options); + var package = new DamlPackage + { + PackageId = "test-id", + Name = "my-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + }; + var externalReferences = new List + { + new() + { + PackageId = "dep-id-1", + Name = "my-dependency", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + } + }; + + var file = generator.GenerateProjectFile(package, externalReferences); + + file.Content.Should().NotContain( + "", + "the 4th segment is a codegen-generation ordinal and the prerelease tag is a release-time decision, both facts about how the dependency was published and neither recoverable from the DAR, so pinning a dependency to generation 0 with no suffix names a version nobody published and fails restore with NU1103"); + file.Content.Should().Contain( + "", + "the DAR supplies only the intrinsic Major.Minor.Patch, so a run that is not itself producing the dependency must float over every generation and prerelease tag published for that intrinsic version"); + } + + [Fact] + public void GenerateProjectFile_still_floats_dependencies_when_a_version_suffix_stamps_this_package_only() + { + var options = CreateOptions(versionSuffix: "preview.1"); + var generator = new ProjectFileGenerator(options); + var package = new DamlPackage + { + PackageId = "test-id", + Name = "my-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + }; + var externalReferences = new List + { + new() + { + PackageId = "dep-id-1", + Name = "my-dependency", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + } + }; + + var file = generator.GenerateProjectFile(package, externalReferences); + + file.Content.Should().Contain( + "", + "a prerelease suffix stamps the version this run produces for its own package and says nothing about how the dependency was published, so it must not be projected onto the dependency reference"); + file.Content.Should().Contain( + "1.0.0.0-preview.1", + "the suffix still applies to the package this run does produce"); + } + + [Fact] + public void GenerateProjectFile_pins_sibling_references_at_generation_zero_when_the_run_publishes_them() + { + var options = new CodeGenOptions + { + TargetFramework = "net10.0", + GenerateProjectFile = true, + PublishesReferencedPackages = true, + }; + var generator = new ProjectFileGenerator(options); + var package = new DamlPackage + { + PackageId = "test-id", + Name = "my-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [], + DependencyReferences = [] + }; + var externalReferences = new List + { + new() + { + PackageId = "dep-id-1", + Name = "my-dependency", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + DependencyReferences = [], + Modules = [] + } + }; + + var file = generator.GenerateProjectFile(package, externalReferences); + + file.Content.Should().Contain( + "", + "an empty release-counter store mints generation ordinal 0, so a genuine first release of a family co-produces siblings at .0 and must pin them there — the ordinal's value cannot stand in for whether the run publishes its dependencies"); + } + + [Fact] + public void GenerateProjectFile_applies_emitter_counter_and_suffix_to_sibling_references() { var options = new CodeGenOptions { @@ -21,6 +134,7 @@ public void GenerateProjectFile_should_apply_emitter_counter_and_suffix_to_sibli RuntimePackageVersion = "1.2.3", EmitterCounter = 5, VersionSuffix = "preview.2", + PublishesReferencedPackages = true, }; var generator = new ProjectFileGenerator(options); var package = new DamlPackage @@ -146,7 +260,7 @@ public void GenerateProjectFile_should_include_dependency_references() var file = generator.GenerateProjectFile(package, externalReferences); // Assert - file.Content.Should().Contain(""); + file.Content.Should().Contain(""); } [Fact] @@ -178,7 +292,7 @@ public void GenerateProjectFile_should_not_emit_leading_dot_package_id_for_leadi var file = generator.GenerateProjectFile(package, externalReferences); - file.Content.Should().Contain(""); + file.Content.Should().Contain(""); file.Content.Should().NotContain("Include=\".No.Package.Metadata"); } @@ -243,8 +357,8 @@ public void GenerateProjectFile_should_emit_one_PackageReference_per_external_de var file = generator.GenerateProjectFile(package, externalReferences); // Assert - file.Content.Should().Contain(""); - file.Content.Should().Contain(""); + file.Content.Should().Contain(""); + file.Content.Should().Contain(""); } [Fact] diff --git a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Versioning.cs b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Versioning.cs index 918b0d7..60a36d7 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Versioning.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGeneratorTests.Versioning.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGrpcReferenceTests.cs b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGrpcReferenceTests.cs index 713912e..07e263a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/ProjectFileGrpcReferenceTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/ProjectFileGrpcReferenceTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -11,7 +11,7 @@ namespace Daml.Codegen.CSharp.Tests; public class ProjectFileGrpcReferenceTests { [Fact] - public void project_file_should_not_reference_grpc_transport_client() + public void ProjectFileGrpcReference_project_file_should_not_reference_grpc_transport_client() { var options = new CodeGenOptions { diff --git a/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/README.md b/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/README.md index 3b2f5fa..da5293a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/README.md @@ -30,7 +30,7 @@ changes. To rebuild it, build the DAR and re-run the helper from the repo root: ```bash (cd samples/QuickstartExample/daml && dpm build) -java -jar jvm-helper/target/scala-2.13/daml-codegen-jvm-helper.jar \ +java -jar jvm-helper/target/scala-2.13/daml-dar-to-proto.jar \ --dar samples/QuickstartExample/daml/.daml/dist/quickstart-0.0.1.dar \ --out tests/Daml.Codegen.CSharp.Tests/QuickstartSample/intermediate.binpb cp samples/QuickstartExample/daml/.daml/dist/quickstart-0.0.1.dar \ diff --git a/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/intermediate.binpb b/tests/Daml.Codegen.CSharp.Tests/QuickstartSample/intermediate.binpb index 9715d81375ddb403b9f35874439bec63ff53e470..aa18349e002bd97de02c95019657c4749b93c0e1 100644 GIT binary patch delta 78 zcmews+84&mb&zFp4x{r#Hwnfk6XT1yJ}_}Hd*+u4wJ}b-UCzzL#lgjvSzKJ2S~NM7 Z@q!o<--wfoor^WUJTJ9qvkucK4FKMY7w-T7 delta 54 zcmeAR`xeT~wUT9WDWmg5Hwnfw6XT0HZ!mE&d*+u4)lIxp&demlFgcF#0y~g40^-ds HOs6ye=XMYa diff --git a/tests/Daml.Codegen.CSharp.Tests/QuickstartSampleDriftTests.cs b/tests/Daml.Codegen.CSharp.Tests/QuickstartSampleDriftTests.cs index 77590cb..8a6764b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/QuickstartSampleDriftTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/QuickstartSampleDriftTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using Daml.Codegen.Intermediate; using AwesomeAssertions; using Xunit; @@ -41,18 +41,16 @@ public async Task Quickstart_sample_matches_current_codegen() proto = IntermediateDar.Parser.ParseFrom(stream); } var dar = IntermediateDarReader.Read(proto); - var generated = new CSharpCodeGenerator(new CodeGenOptions(), new ConsoleLogger(0)).Generate(dar); + var generated = new CSharpCodeGenerator(new CodeGenOptions()).Generate(dar); var actualFiles = generated - .Where(f => f.RelativePath.EndsWith(".cs", StringComparison.Ordinal) - || f.RelativePath.EndsWith(".daml-langversion", StringComparison.Ordinal)) + .Where(f => f.RelativePath.EndsWith(".cs", StringComparison.Ordinal)) .Select(f => new { f.RelativePath, f.Content }) .OrderBy(f => f.RelativePath, StringComparer.Ordinal) .ToList(); var committedFiles = Directory.EnumerateFiles(sampleDir, "*", SearchOption.AllDirectories) - .Where(p => p.EndsWith(".cs", StringComparison.Ordinal) - || p.EndsWith(".daml-langversion", StringComparison.Ordinal)) + .Where(p => p.EndsWith(".cs", StringComparison.Ordinal)) .Select(absPath => new { RelativePath = Path.GetRelativePath(sampleDir, absPath).Replace('\\', '/'), diff --git a/tests/Daml.Codegen.CSharp.Tests/RecordEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/RecordEmitterTests.cs index 7ae383c..074bb7e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/RecordEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/RecordEmitterTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -85,7 +85,7 @@ private static DamlDataType Variant(string name, params DamlVariantConstructor[] new() { Name = name, Definition = new DamlVariantDefinition(constructors) }; [Fact] - public void emits_the_sealed_record_declaration_with_primitive_fields() + public void RecordEmitter_emits_the_sealed_record_declaration_with_primitive_fields() { var output = EmitRecord(Record( "PersonInfo", @@ -97,11 +97,81 @@ public void emits_the_sealed_record_declaration_with_primitive_fields() output.Should().Contain("string Name"); output.Should().Contain("long Age"); output.Should().Contain("bool Active"); - output.Should().Contain(": IDamlRecord"); + output.Should().Contain(": IDamlRecord"); } [Fact] - public void maps_numeric_fields_to_decimal() + public void RecordEmitter_stamps_the_generic_IDamlRecord_facet_naming_the_record_itself() + { + var output = EmitRecord(Record("Profile", Field("nickname", DamlPrimitive.Text))); + + output.Should().Contain(": IDamlRecord"); + } + + private static DamlInterface ViewingInterface(string name, string viewRecordName) => + new() + { + Name = name, + Choices = [], + ViewType = new DamlTypeRef("", ModuleName, viewRecordName), + }; + + private static string EmitWithInterfaces(string targetName, DamlDataType[] packageTypes, DamlInterface[] interfaces) + { + var options = Options(generateXmlDocs: true); + var module = new DamlModule + { + Name = ModuleName, + Templates = [], + DataTypes = packageTypes, + Interfaces = interfaces, + }; + var package = new DamlPackage + { + PackageId = LocalPackageId, + Name = "test-package", + Version = new Version(1, 0, 0), + LfVersion = "2.1", + Modules = [module], + DependencyReferences = [], + }; + var context = PackageEmitContext.ForPackage(package, options); + var resolver = new StubResolver(); + var mapper = new DamlTypeMapper(context, resolver); + var serialization = new RecordSerializationEmitter(context, resolver, options, mapper); + var emitter = new RecordEmitter(context, options, serialization); + var target = packageTypes.First(d => d.Name == targetName); + var sb = new StringBuilder(); + emitter.WriteRecordType(new IndentWriter(sb), module, target, (DamlRecordDefinition)target.Definition!); + return sb.ToString(); + } + + [Fact] + public void RecordEmitter_stamps_a_view_record_with_its_interface_marker_before_the_record_facet() + { + var output = EmitWithInterfaces( + "AssetView", + [Record("AssetView", Field("owner", DamlPrimitive.Party))], + [ViewingInterface("Asset", "AssetView")]); + + output.Should().Contain(": IAsset, IDamlRecord"); + } + + [Fact] + public void RecordEmitter_leaves_a_view_record_shared_by_two_interfaces_unstamped() + { + var output = EmitWithInterfaces( + "SharedView", + [Record("SharedView", Field("owner", DamlPrimitive.Party))], + [ViewingInterface("Bond", "SharedView"), ViewingInterface("Asset", "SharedView")]); + + output.Should().Contain(": IDamlRecord"); + output.Should().NotContain("IAsset"); + output.Should().NotContain("IBond"); + } + + [Fact] + public void RecordEmitter_maps_numeric_fields_to_decimal() { var output = EmitRecord(Record( "Amount", @@ -113,7 +183,7 @@ public void maps_numeric_fields_to_decimal() } [Fact] - public void maps_date_and_timestamp_fields() + public void RecordEmitter_maps_date_and_timestamp_fields() { var output = EmitRecord(Record( "Event", @@ -125,7 +195,7 @@ public void maps_date_and_timestamp_fields() } [Fact] - public void maps_party_fields() + public void RecordEmitter_maps_party_fields() { var output = EmitRecord(Record( "Ownership", @@ -137,7 +207,7 @@ public void maps_party_fields() } [Fact] - public void maps_optional_fields_to_nullable() + public void RecordEmitter_maps_optional_fields_to_nullable() { var output = EmitRecord(Record( "OptionalData", @@ -153,7 +223,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), } [Fact] - public void decodes_optional_fields_through_AsOptional_in_FromRecord() + public void RecordEmitter_decodes_optional_fields_through_AsOptional_in_FromRecord() { var output = EmitRecord(Record( "OptionalData", @@ -169,7 +239,7 @@ public void decodes_optional_fields_through_AsOptional_in_FromRecord() } [Fact] - public void maps_list_fields_to_readonly_list() + public void RecordEmitter_maps_list_fields_to_readonly_list() { var output = EmitRecord(Record( "Collection", @@ -185,7 +255,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), } [Fact] - public void maps_textmap_field_to_readonly_dictionary() + public void RecordEmitter_maps_textmap_field_to_readonly_dictionary() { var output = EmitRecord(Record( "Metadata", @@ -197,7 +267,7 @@ public void maps_textmap_field_to_readonly_dictionary() } [Fact] - public void maps_contract_id_field_to_typed_contract_id() + public void RecordEmitter_maps_contract_id_field_to_typed_contract_id() { var output = EmitRecord(Record( "Reference", @@ -209,7 +279,7 @@ public void maps_contract_id_field_to_typed_contract_id() } [Fact] - public void emits_a_ToRecord_method() + public void RecordEmitter_emits_a_ToRecord_method() { var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text))); @@ -219,7 +289,7 @@ public void emits_a_ToRecord_method() } [Fact] - public void emits_a_FromRecord_method() + public void RecordEmitter_emits_a_FromRecord_method() { var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text))); @@ -228,7 +298,7 @@ public void emits_a_FromRecord_method() } [Fact] - public void references_other_record_types_through_FromRecord() + public void RecordEmitter_references_other_record_types_through_FromRecord() { var address = Record( "Address", @@ -246,7 +316,7 @@ public void references_other_record_types_through_FromRecord() } [Fact] - public void routes_record_field_through_ToVariant_and_FromVariant_when_field_is_a_variant() + public void RecordEmitter_routes_record_field_through_ToVariant_and_FromVariant_when_field_is_a_variant() { var module = new DamlModule { @@ -275,7 +345,7 @@ public void routes_record_field_through_ToVariant_and_FromVariant_when_field_is_ } [Fact] - public void declares_type_parameters_with_docs_when_enabled() + public void RecordEmitter_declares_type_parameters_with_docs_when_enabled() { var box = new DamlDataType { @@ -293,7 +363,7 @@ public void declares_type_parameters_with_docs_when_enabled() } [Fact] - public void declares_type_parameters_without_docs_when_disabled() + public void RecordEmitter_declares_type_parameters_without_docs_when_disabled() { var box = new DamlDataType { @@ -310,122 +380,71 @@ public void declares_type_parameters_without_docs_when_disabled() } [Fact] - public void emits_every_xml_doc_when_enabled() + public void RecordEmitter_constrains_every_type_parameter_to_notnull() { - var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text)), generateXmlDocs: true); + var pair = new DamlDataType + { + Name = "Pair", + TypeParams = ["a", "b"], + Definition = new DamlRecordDefinition( + [ + new DamlFieldDefinition("first", new DamlTypeVar("a")), + new DamlFieldDefinition("second", new DamlTypeVar("b")), + ]), + }; - output.Should().Contain("/// Generated from Daml record Simple"); - output.Should().Contain("/// Converts this value to a DamlRecord."); - output.Should().Contain("/// Creates an instance from a DamlRecord."); + var output = EmitRecord(pair, generateXmlDocs: false); + + output.Should().Contain(") where TA : notnull where TB : notnull", + "a Daml type variable ranges only over serialisable Daml types, none of which is nullable, " + + "and without the constraint a reflection-driven reader decodes the slot as an Optional"); } [Fact] - public void omits_every_xml_doc_when_disabled() + public void RecordEmitter_leaves_the_return_type_of_FromRecord_unconstrained() { - var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text)), generateXmlDocs: false); + var box = new DamlDataType + { + Name = "Box", + TypeParams = ["a"], + Definition = new DamlRecordDefinition([new DamlFieldDefinition("value", new DamlTypeVar("a"))]), + }; - output.Should().NotContain("/// Generated from Daml record Simple"); - output.Should().NotContain("Converts this value to a DamlRecord"); - output.Should().NotContain("Creates an instance from a DamlRecord"); + var output = EmitRecord(box, generateXmlDocs: false); - output.Should().Contain("public sealed record Simple("); - output.Should().Contain("public DamlRecord ToRecord()"); - output.Should().Contain("public static Simple FromRecord(DamlRecord record)"); + output.Should().Contain("public static Box FromRecord(", + "a constraint clause belongs on the declaration only; repeating it on a member's return type is not C#"); } [Fact] - public void emits_a_throwing_template_stub_for_interface_placeholder_records() - { - var output = EmitInterfacePlaceholder("Holding"); - - output.Should().Contain("public sealed record Holding : ITemplate"); - output.Should().Contain("Phantom placeholder for the Daml interface Test.Module:Holding."); - output.Should().Contain("throw new InvalidOperationException("); - } - - private static string EmitInterfacePlaceholder(string name) + public void RecordEmitter_leaves_a_non_generic_record_unconstrained() { - var package = new DamlPackage - { - PackageId = LocalPackageId, - Name = "test-package", - Version = new Version(1, 0, 0), - LfVersion = "2.1", - Modules = - [ - new DamlModule - { - Name = ModuleName, - Templates = [], - DataTypes = [Record(name)], - Interfaces = [new DamlInterface { Name = name, Choices = [] }], - }, - ], - DependencyReferences = [], - }; + var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text)), generateXmlDocs: false); - var options = Options(generateXmlDocs: true); - var context = PackageEmitContext.ForPackage(package, options); - var resolver = new StubResolver(); - var mapper = new DamlTypeMapper(context, resolver); - var serialization = new RecordSerializationEmitter(context, resolver, options, mapper); - var emitter = new RecordEmitter(context, options, serialization); - var target = package.Modules[0].DataTypes.First(d => d.Name == name); - var sb = new StringBuilder(); - emitter.WriteRecordType(new IndentWriter(sb), package.Modules[0], target, (DamlRecordDefinition)target.Definition!); - return sb.ToString(); + output.Should().NotContain("notnull"); } [Fact] - public void emits_a_remarks_block_clarifying_interface_choices_need_no_coercion() + public void RecordEmitter_emits_every_xml_doc_when_enabled() { - var output = EmitInterfacePlaceholder("Holding"); - - output.Should().Contain("/// "); - output.Should().Contain("/// "); - output.Should().Contain("exercise directly on ContractId<Holding>"); - output.Should().Contain("no supported way to recover a concrete ContractId<TConcrete>"); - - var summaryEnd = output.IndexOf("/// ", StringComparison.Ordinal); - var remarksStart = output.IndexOf("/// ", StringComparison.Ordinal); - var remarksEnd = output.IndexOf("/// ", StringComparison.Ordinal); - var recordDeclaration = output.IndexOf("public sealed record Holding : ITemplate", StringComparison.Ordinal); + var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text)), generateXmlDocs: true); - summaryEnd.Should().BeLessThan(remarksStart); - remarksStart.Should().BeLessThan(remarksEnd); - remarksEnd.Should().BeLessThan(recordDeclaration); + output.Should().Contain("/// Generated from Daml record Simple"); + output.Should().Contain("/// Converts this value to a DamlRecord."); + output.Should().Contain("/// Creates an instance from a DamlRecord."); } [Fact] - public void emits_exception_tags_on_the_five_throwing_placeholder_members_only() + public void RecordEmitter_omits_every_xml_doc_when_disabled() { - var output = EmitInterfacePlaceholder("Holding"); - var lines = output.Split('\n'); + var output = EmitRecord(Record("Simple", Field("value", DamlPrimitive.Text)), generateXmlDocs: false); - var identityPhraseByMember = new Dictionary - { - ["TemplateId"] = "template identity", - ["PackageId"] = "package identity", - ["PackageName"] = "package identity", - ["PackageVersion"] = "package identity", - ["DamlTypeId"] = "Daml type identity", - }; + output.Should().NotContain("/// Generated from Daml record Simple"); + output.Should().NotContain("Converts this value to a DamlRecord"); + output.Should().NotContain("Creates an instance from a DamlRecord"); - foreach (var member in new[] { "TemplateId", "PackageId", "PackageName", "PackageVersion", "DamlTypeId" }) - { - var declarationLineIndex = Array.FindIndex(lines, l => l.Contains($" {member} =>", StringComparison.Ordinal)); - declarationLineIndex.Should().BeGreaterThan(1); - lines[declarationLineIndex - 1].Should().Contain("/// "); - lines[declarationLineIndex - 2].Should().Contain("/// Always throws"); - lines[declarationLineIndex - 1].Should().Contain(identityPhraseByMember[member]); - } - - var toRecordIndex = Array.FindIndex(lines, l => l.Contains("ToRecord() =>", StringComparison.Ordinal)); - var fromRecordIndex = Array.FindIndex(lines, l => l.Contains("FromRecord(", StringComparison.Ordinal) && l.Contains("=>", StringComparison.Ordinal)); - - toRecordIndex.Should().BeGreaterThan(0); - fromRecordIndex.Should().BeGreaterThan(0); - lines[toRecordIndex - 1].Should().NotContain(" [InlineData(nameof(RuntimeTypeNames.Choice))] [InlineData(nameof(RuntimeTypeNames.IExercises))] [InlineData(nameof(RuntimeTypeNames.IImplements))] - public void the_cs8920_blocked_template_generic_literals_name_a_real_runtime_type(string constantFieldName) + public void RuntimeTypeNameDriftGuard_the_cs8920_blocked_template_generic_literals_name_a_real_runtime_type(string constantFieldName) { var literal = RuntimeTypeNamesConstant(constantFieldName); @@ -59,7 +59,7 @@ public void the_cs8920_blocked_template_generic_literals_name_a_real_runtime_typ [InlineData("DA.NonEmpty.Types", "NonEmpty")] [InlineData("DA.Map.Types", "Map")] [InlineData("DA.Internal.Map", "Map")] - public void every_mapped_stdlib_type_names_a_real_runtime_type(string module, string damlTypeName) + public void RuntimeTypeNameDriftGuard_every_mapped_stdlib_type_names_a_real_runtime_type(string module, string damlTypeName) { var mapped = StdlibPackages.MapStdlibType(module, damlTypeName); @@ -69,7 +69,7 @@ public void every_mapped_stdlib_type_names_a_real_runtime_type(string module, st } [Fact] - public void a_type_name_absent_from_daml_runtime_would_fail_the_drift_guard() + public void RuntimeTypeNameDriftGuard_a_type_name_absent_from_daml_runtime_would_fail_the_drift_guard() { RuntimeTypeSimpleNames.Should().NotContain("ThisTypeDoesNotExistInDamlRuntime"); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/README.md index 8e96e73..33439b9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/README.md @@ -17,25 +17,26 @@ data-type table by module-qualified name; this snapshot pins that the two makes one record inherit the other's fields — a silent field-drop that compiles but sends the wrong argument to the ledger — and this snapshot fails. -## The vendored input +## The vendored inputs `intermediate.binpb` is the canonical codegen input for the drift test. It is -derived from `conformance/crossmodulecollision/crossmodulecollision.dar`, whose -Daml source lives in `conformance/crossmodulecollision`. To regenerate the -proto after a corpus change, rebuild the DAR (`cd -conformance/crossmodulecollision && dpm build && cp -.daml/dist/crossmodulecollision-*.dar ./crossmodulecollision.dar`), then rerun -the JVM helper (`java -jar -jvm-helper/target/scala-2.13/daml-codegen-jvm-helper.jar --dar -conformance/crossmodulecollision/crossmodulecollision.dar --out -tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/intermediate.binpb`), -and refresh the `expected/` snapshot below. - -## Refreshing the `expected/` snapshot - -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, run -from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +generated from the vendored `cross-module-collision.dar`, which is maintained +as a copy of `conformance/crossmodulecollision/crossmodulecollision.dar` (Daml +source in `conformance/crossmodulecollision`). Nothing enforces that the two +stay equal, so keeping them in step is a maintenance obligation: after a corpus +change, rebuild the DAR (`cd conformance/crossmodulecollision && dpm build && cp +.daml/dist/crossmodulecollision-*.dar ./crossmodulecollision.dar`), copy it +over the vendored `cross-module-collision.dar`, and run the refresh below. Skip +the copy and the snapshot keeps regenerating cleanly from the stale vendored +DAR, which is exactly the staleness this snapshot's gate cannot see. + +## Refreshing the snapshot + +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the +vendored DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh cross-module-collision diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/cross-module-collision.dar b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/cross-module-collision.dar new file mode 100644 index 0000000000000000000000000000000000000000..0f1483fcbb0389a3f8dc98cdb6879eb7b28591d9 GIT binary patch literal 257010 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%0DV-KO<LeDIK<& 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 literal 0 HcmV?d00001 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.Retag.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.Retag.cs index 05ec59b..60e7f52 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.Retag.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.Retag.cs @@ -14,7 +14,9 @@ public sealed partial record Agreement /// /// Choice argument type for Retag. /// - public sealed record Retag([property: DamlFieldAttribute("newOperator")] Party NewOperator) : IDamlRecord + public sealed record Retag( + [property: DamlFieldAttribute("newOperator")] Party NewOperator + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.cs index 369a5a2..825c72d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Agreement.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -21,7 +22,9 @@ namespace Crossmodulecollision; /// /// Generated from Daml template CollisionA:Agreement /// -public sealed partial record Agreement([property: DamlFieldAttribute("operator")] Party @operator) : ITemplate +public sealed partial record Agreement( + [property: DamlFieldAttribute("operator")] Party Operator +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("5a7215d354b1b18068c2f264b60dcab8d8a0df37b02c7fcb19d1f051333d75d1", "CollisionA", "Agreement"); @@ -40,12 +43,12 @@ public sealed partial record Agreement([property: DamlFieldAttribute("operator") /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( - DamlField.Create("operator", @operator.ToDamlValue()) + DamlField.Create("operator", Operator.ToDamlValue()) ); /// Creates an instance from a DamlRecord. public static Agreement FromRecord(DamlRecord record) => new Agreement( - @operator: Party.FromDamlValue(record.GetRequiredField("operator").As()) + Operator: Party.FromDamlValue(record.GetRequiredField("operator").As()) ); /// @@ -73,6 +76,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Agreement. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -83,7 +87,7 @@ public sealed record Contract(ContractId Id, Agreement Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Agreement.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Crossmodulecollision.Agreement.FromRecord(@event.CreateArguments)); } } @@ -92,7 +96,9 @@ public static Contract FromCreatedEvent(CreatedEvent @event) => /// One field per template the choice creates; cardinality follows the choice's /// return type (single, optional, list). /// -public sealed record RetagResult(ContractId Agreement) +public sealed record RetagResult( + ContractId Agreement +) { /// /// Projects an upstream transaction's created contracts to a typed . @@ -149,7 +155,7 @@ public static ExerciseOutcome FromCreatedContracts(IEnumerable /// Static <Choice>Async extension methods for . /// One method per create-bearing choice; each delegates to -/// +/// /// and projects success via <Choice>Result.FromCreatedContracts. /// public static class AgreementExtensions @@ -183,10 +189,10 @@ public static ExerciseCommand RetagCommand( /// The choice argument. /// Controller party from the Daml controller clause, routed into the submission's actAs set. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> RetagAsync( + public static Task> RetagAsync( this ContractId contractId, ILedgerWriter client, Agreement.Retag argument, @@ -200,18 +206,14 @@ public static async Task> RetagAsync( SubmitterInfo submitter = @operator; - var command = contractId.RetagCommand(argument); - - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); - - return outcome.ProjectCommitted(tx => RetagResult.FromCreatedContracts(tx.CreatedContracts)); + return contractId.RetagAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); } /// @@ -225,8 +227,8 @@ public static async Task> RetagAsync( /// The choice argument. /// The submitter party set (actAs + optional readAs). /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> RetagAsync( this ContractId contractId, @@ -242,14 +244,7 @@ public static async Task> RetagAsync( var command = contractId.RetagCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => RetagResult.FromCreatedContracts(tx.CreatedContracts)); } @@ -264,8 +259,8 @@ public static async Task> RetagAsync( /// The ledger client. /// The choice argument. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static Task> RetagAsync( this Agreement.Contract contract, @@ -283,7 +278,46 @@ public static Task> RetagAsync( return contract.Id.RetagAsync( client, argument, - contract.Data.@operator, + contract.Data.Operator, + workflowId, + commandId, + timeout, + cancellationToken); + } + + /// + /// Exercises the Retag choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Agreement> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> RetagAsync( + this Agreement.Contract contract, + ILedgerWriter client, + Agreement.Retag argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.RetagAsync( + client, + argument, + submitter, workflowId, commandId, timeout, @@ -304,25 +338,23 @@ public static class AgreementSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, Agreement payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Operator; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -331,7 +363,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AgreementNonContractExtensions @@ -358,15 +390,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -376,14 +408,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.Retag.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.Retag.cs index 10b289d..1eac1e3 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.Retag.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.Retag.cs @@ -14,7 +14,10 @@ public sealed partial record Offer /// /// Choice argument type for Retag. /// - public sealed record Retag([property: DamlFieldAttribute("label")] string Label, [property: DamlFieldAttribute("count")] long Count) : IDamlRecord + public sealed record Retag( + [property: DamlFieldAttribute("label")] string Label, + [property: DamlFieldAttribute("count")] long Count + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.cs index 7c175be..f404029 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/expected/Crossmodulecollision/Offer.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -21,7 +22,10 @@ namespace Crossmodulecollision; /// /// Generated from Daml template CollisionB:Offer /// -public sealed partial record Offer([property: DamlFieldAttribute("owner")] Party Owner, [property: DamlFieldAttribute("memo")] string Memo) : ITemplate +public sealed partial record Offer( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("memo")] string Memo +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("5a7215d354b1b18068c2f264b60dcab8d8a0df37b02c7fcb19d1f051333d75d1", "CollisionB", "Offer"); @@ -75,6 +79,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Offer. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -85,7 +90,7 @@ public sealed record Contract(ContractId Id, Offer Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Offer.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Crossmodulecollision.Offer.FromRecord(@event.CreateArguments)); } } @@ -94,7 +99,9 @@ public static Contract FromCreatedEvent(CreatedEvent @event) => /// One field per template the choice creates; cardinality follows the choice's /// return type (single, optional, list). /// -public sealed record RetagResult(ContractId Offer) +public sealed record RetagResult( + ContractId Offer +) { /// /// Projects an upstream transaction's created contracts to a typed . @@ -151,7 +158,7 @@ public static ExerciseOutcome FromCreatedContracts(IEnumerable /// Static <Choice>Async extension methods for . /// One method per create-bearing choice; each delegates to -/// +/// /// and projects success via <Choice>Result.FromCreatedContracts. /// public static class OfferExtensions @@ -185,10 +192,10 @@ public static ExerciseCommand RetagCommand( /// The choice argument. /// Controller party from the Daml controller clause, routed into the submission's actAs set. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> RetagAsync( + public static Task> RetagAsync( this ContractId contractId, ILedgerWriter client, Offer.Retag argument, @@ -202,18 +209,14 @@ public static async Task> RetagAsync( SubmitterInfo submitter = owner; - var command = contractId.RetagCommand(argument); - - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); - - return outcome.ProjectCommitted(tx => RetagResult.FromCreatedContracts(tx.CreatedContracts)); + return contractId.RetagAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); } /// @@ -227,8 +230,8 @@ public static async Task> RetagAsync( /// The choice argument. /// The submitter party set (actAs + optional readAs). /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> RetagAsync( this ContractId contractId, @@ -244,14 +247,7 @@ public static async Task> RetagAsync( var command = contractId.RetagCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, submitter, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => RetagResult.FromCreatedContracts(tx.CreatedContracts)); } @@ -266,8 +262,8 @@ public static async Task> RetagAsync( /// The ledger client. /// The choice argument. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static Task> RetagAsync( this Offer.Contract contract, @@ -291,6 +287,45 @@ public static Task> RetagAsync( timeout, cancellationToken); } + + /// + /// Exercises the Retag choice on a fetched contract with an + /// explicit . Companion to the payload-derived overload for + /// multi-party submissions and for callers who must supply readAs parties the + /// payload cannot name. Delegates to the + /// ContractId<Offer> overload. + /// + /// The fetched contract on which to exercise the choice. + /// The ledger client. + /// The choice argument. + /// The submitter party set (actAs + optional readAs). + /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Cancellation token. + public static Task> RetagAsync( + this Offer.Contract contract, + ILedgerWriter client, + Offer.Retag argument, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(contract); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(argument); + + return contract.Id.RetagAsync( + client, + argument, + submitter, + workflowId, + commandId, + timeout, + cancellationToken); + } } /// @@ -306,25 +341,23 @@ public static class OfferSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, Offer payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Owner; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -333,7 +366,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class OfferNonContractExtensions @@ -360,15 +393,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -378,14 +411,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/intermediate.binpb b/tests/Daml.Codegen.CSharp.Tests/Snapshots/cross-module-collision/intermediate.binpb index e8216ed43779782f95ab191f73d4721f424aa262..108e3f6d79ba83768f067a94e6bf14336d05d050 100644 GIT binary patch delta 131 zcmdlMc`K5ctCwSP1GDo)Hzmd^6Epn@3MU%Oi NZy|9vzhFM60RT*KCp7>7 delta 93 zcmcZ=xh;~J>m~c-gN)7--IN&jPt5e^f6v6l>6l)Wnwp!MS0Yq5@kBW@lMusXAI68A nK-!3ji$UwaWCx~b?k7xKto~_fsYM7y8qBvqiZ}DHoYMdRm{1%8 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/README.md index b874229..1ae8150 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/README.md @@ -42,11 +42,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-amulet-name-service @@ -56,9 +59,11 @@ scripts/refresh-snapshot.sh splice-amulet-name-service `intermediate.binpb` is the canonical codegen input for the drift test; `splice-amulet-name-service.dar` is the upstream Splice archive it was derived -from (`splice-amulet-name-service-0.1.23.dar` from the Splice `0.6.13` -`splice-node` release tarball), kept alongside for provenance. Do not regenerate either -from a local build without a clear reason. If the upstream Splice package -genuinely needs to advance, replace the files in place, refresh the `expected/` -snapshot per the procedure above, and call out the version bump in the pull -request description. +from (`splice-amulet-name-service-0.1.23.dar` from the Splice `0.7.5` +`splice-node` release tarball), kept alongside as the frozen upstream artifact +and used as the regeneration input. Do not hand-edit `intermediate.binpb`: the +refresh script regenerates it from this DAR on every run, and CI regenerates +it the same way and fails on any byte delta. If the upstream Splice package +genuinely needs to advance, replace the files in place, refresh the snapshot +per the procedure above, and call out the version bump in the pull request +description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_ArchiveAsDso.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_ArchiveAsDso.cs index f5bffcd..97f9989 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_ArchiveAsDso.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_ArchiveAsDso.cs @@ -14,7 +14,9 @@ public sealed partial record AmuletConversionRateFeed /// /// Choice argument type for AmuletConversionRateFeed_ArchiveAsDso. /// - public sealed record AmuletConversionRateFeed_ArchiveAsDso([property: DamlFieldAttribute("reason")] string Reason) : IDamlRecord + public sealed record AmuletConversionRateFeed_ArchiveAsDso( + [property: DamlFieldAttribute("reason")] string Reason + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_Update.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_Update.cs index 440fd34..f67cbfc 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_Update.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.AmuletConversionRateFeed_Update.cs @@ -16,7 +16,12 @@ public sealed partial record AmuletConversionRateFeed /// /// Choice argument type for AmuletConversionRateFeed_Update. /// - public sealed record AmuletConversionRateFeed_Update([property: DamlFieldAttribute("amuletConversionRate")] decimal AmuletConversionRate, [property: DamlFieldAttribute("amuletRulesCid")] ContractId AmuletRulesCid, [property: DamlFieldAttribute("markerContextO")] MarkerContext? MarkerContextO, [property: DamlFieldAttribute("newNextUpdateAfter")] DateTimeOffset NewNextUpdateAfter) : IDamlRecord + public sealed record AmuletConversionRateFeed_Update( + [property: DamlFieldAttribute("amuletConversionRate")] decimal AmuletConversionRate, + [property: DamlFieldAttribute("amuletRulesCid")] ContractId AmuletRulesCid, + [property: DamlFieldAttribute("markerContextO")] MarkerContext? MarkerContextO, + [property: DamlFieldAttribute("newNextUpdateAfter")] DateTimeOffset NewNextUpdateAfter + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.cs index cd1c372..c97f1da 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed.cs @@ -6,12 +6,14 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using Daml.Runtime.Stdlib; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -20,7 +22,12 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml template Splice.Ans.AmuletConversionRateFeed:AmuletConversionRateFeed /// -public sealed partial record AmuletConversionRateFeed([property: DamlFieldAttribute("publisher")] Party Publisher, [property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("nextUpdateAfter")] DateTimeOffset? NextUpdateAfter, [property: DamlFieldAttribute("amuletConversionRate")] decimal AmuletConversionRate) : ITemplate +public sealed partial record AmuletConversionRateFeed( + [property: DamlFieldAttribute("publisher")] Party Publisher, + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("nextUpdateAfter")] DateTimeOffset? NextUpdateAfter, + [property: DamlFieldAttribute("amuletConversionRate")] decimal AmuletConversionRate +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("9cffe65feb664c9550937433067e9f969e3795c6fb38715e06a5e04fc1ae1f83", "Splice.Ans.AmuletConversionRateFeed", "AmuletConversionRateFeed"); @@ -90,6 +97,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for AmuletConversionRateFeed. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -100,7 +108,7 @@ public sealed record Contract(ContractId Id, AmuletConversionRateFeed Data) : IC { /// Creates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), AmuletConversionRateFeed.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Amulet.Name.Service.AmuletConversionRateFeed.FromRecord(@event.CreateArguments)); } } @@ -118,34 +126,49 @@ public static class AmuletConversionRateFeedSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, AmuletConversionRateFeed payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Publisher; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } + + /// + /// Returns the observers of a payload — + /// the parties named in the Daml observer clause, derived from + /// the payload's Party properties in declaration order. Useful + /// for inspecting / asserting the observer set without exercising a + /// choice. + /// + /// The contract payload. + public static IReadOnlyList Observers(AmuletConversionRateFeed payload) + { + ArgumentNullException.ThrowIfNull(payload); + return new Party[] + { + payload.Dso + }; + } } /// /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AmuletConversionRateFeedNonContractExtensions @@ -176,16 +199,16 @@ public static ExerciseCommand AmuletConversionRateFeed_ArchiveAsDsoCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AmuletConversionRateFeed_ArchiveAsDsoAsync( this ContractId contractId, ILedgerWriter client, AmuletConversionRateFeed.AmuletConversionRateFeed_ArchiveAsDso argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -195,14 +218,7 @@ public static async Task ProjectAmuletConversionRateFeed_ArchiveAsDsoResult(tx, contractId.Value)); } @@ -233,16 +249,16 @@ public static ExerciseCommand AmuletConversionRateFeed_UpdateCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AmuletConversionRateFeed_UpdateAsync( this ContractId contractId, ILedgerWriter client, AmuletConversionRateFeed.AmuletConversionRateFeed_Update argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -252,14 +268,7 @@ public static async Task> var command = contractId.AmuletConversionRateFeed_UpdateCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectAmuletConversionRateFeed_UpdateResult(tx, contractId.Value)); } @@ -286,15 +295,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -304,14 +313,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed_UpdateResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed_UpdateResult.cs index 09f3511..2f32006 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed_UpdateResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AmuletConversionRateFeed_UpdateResult.cs @@ -13,7 +13,9 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AmuletConversionRateFeed_UpdateResult /// -public sealed record AmuletConversionRateFeed_UpdateResult([property: DamlFieldAttribute("cid")] ContractId Cid) : IDamlRecord +public sealed record AmuletConversionRateFeed_UpdateResult( + [property: DamlFieldAttribute("cid")] ContractId Cid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Expire.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Expire.cs index c353038..0f4bc60 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Expire.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Expire.cs @@ -14,7 +14,9 @@ public sealed partial record AnsEntry /// /// Choice argument type for AnsEntry_Expire. /// - public sealed record AnsEntry_Expire([property: DamlFieldAttribute("actor")] Party Actor) : IDamlRecord + public sealed record AnsEntry_Expire( + [property: DamlFieldAttribute("actor")] Party Actor + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Renew.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Renew.cs index 963aafc..85e704e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Renew.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.AnsEntry_Renew.cs @@ -15,7 +15,9 @@ public sealed partial record AnsEntry /// /// Choice argument type for AnsEntry_Renew. /// - public sealed record AnsEntry_Renew([property: DamlFieldAttribute("extension")] RelTime Extension) : IDamlRecord + public sealed record AnsEntry_Renew( + [property: DamlFieldAttribute("extension")] RelTime Extension + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.cs index 6976e13..e183096 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,14 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml template Splice.Ans:AnsEntry /// -public sealed partial record AnsEntry([property: DamlFieldAttribute("user")] Party User, [property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("name")] string Name, [property: DamlFieldAttribute("url")] string Url, [property: DamlFieldAttribute("description")] string Description, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset ExpiresAt) : ITemplate +public sealed partial record AnsEntry( + [property: DamlFieldAttribute("user")] Party User, + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("name")] string Name, + [property: DamlFieldAttribute("url")] string Url, + [property: DamlFieldAttribute("description")] string Description, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset ExpiresAt +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("9cffe65feb664c9550937433067e9f969e3795c6fb38715e06a5e04fc1ae1f83", "Splice.Ans", "AnsEntry"); @@ -94,6 +102,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for AnsEntry. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -104,7 +113,7 @@ public sealed record Contract(ContractId Id, AnsEntry Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), AnsEntry.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Amulet.Name.Service.AnsEntry.FromRecord(@event.CreateArguments)); } } @@ -149,7 +158,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AnsEntryNonContractExtensions @@ -180,16 +189,16 @@ public static ExerciseCommand AnsEntry_ExpireCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntry_ExpireAsync( this ContractId contractId, ILedgerWriter client, AnsEntry.AnsEntry_Expire argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -199,14 +208,7 @@ public static async Task> AnsEntry_Expire var command = contractId.AnsEntry_ExpireCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectAnsEntry_ExpireResult(tx, contractId.Value)); } @@ -237,16 +239,16 @@ public static ExerciseCommand AnsEntry_RenewCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntry_RenewAsync( this ContractId contractId, ILedgerWriter client, AnsEntry.AnsEntry_Renew argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -256,14 +258,7 @@ public static async Task> AnsEntry_RenewAs var command = contractId.AnsEntry_RenewCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectAnsEntry_RenewResult(tx, contractId.Value)); } @@ -290,15 +285,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -308,14 +303,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectEntryRenewalPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectEntryRenewalPayment.cs index 046b974..1649b29 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectEntryRenewalPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectEntryRenewalPayment.cs @@ -15,7 +15,12 @@ public sealed partial record AnsEntryContext /// /// Choice argument type for AnsEntryContext_CollectEntryRenewalPayment. /// - public sealed record AnsEntryContext_CollectEntryRenewalPayment([property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid) : IDamlRecord + public sealed record AnsEntryContext_CollectEntryRenewalPayment( + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, + [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectInitialEntryPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectInitialEntryPayment.cs index e966a76..cae802d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectInitialEntryPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_CollectInitialEntryPayment.cs @@ -15,7 +15,11 @@ public sealed partial record AnsEntryContext /// /// Choice argument type for AnsEntryContext_CollectInitialEntryPayment. /// - public sealed record AnsEntryContext_CollectInitialEntryPayment([property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid) : IDamlRecord + public sealed record AnsEntryContext_CollectInitialEntryPayment( + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, + [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_RejectEntryInitialPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_RejectEntryInitialPayment.cs index 8b1c7ae..64601d6 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_RejectEntryInitialPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_RejectEntryInitialPayment.cs @@ -15,7 +15,11 @@ public sealed partial record AnsEntryContext /// /// Choice argument type for AnsEntryContext_RejectEntryInitialPayment. /// - public sealed record AnsEntryContext_RejectEntryInitialPayment([property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid) : IDamlRecord + public sealed record AnsEntryContext_RejectEntryInitialPayment( + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext, + [property: DamlFieldAttribute("ansRulesCid")] ContractId AnsRulesCid + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_Terminate.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_Terminate.cs index 9be2b5a..5745d66 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_Terminate.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.AnsEntryContext_Terminate.cs @@ -15,7 +15,10 @@ public sealed partial record AnsEntryContext /// /// Choice argument type for AnsEntryContext_Terminate. /// - public sealed record AnsEntryContext_Terminate([property: DamlFieldAttribute("actor")] Party Actor, [property: DamlFieldAttribute("terminatedSubscriptionCid")] ContractId TerminatedSubscriptionCid) : IDamlRecord + public sealed record AnsEntryContext_Terminate( + [property: DamlFieldAttribute("actor")] Party Actor, + [property: DamlFieldAttribute("terminatedSubscriptionCid")] ContractId TerminatedSubscriptionCid + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.cs index 18ab27e..95fca9d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,14 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml template Splice.Ans:AnsEntryContext /// -public sealed partial record AnsEntryContext([property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("user")] Party User, [property: DamlFieldAttribute("name")] string Name, [property: DamlFieldAttribute("url")] string Url, [property: DamlFieldAttribute("description")] string Description, [property: DamlFieldAttribute("reference")] ContractId Reference) : ITemplate +public sealed partial record AnsEntryContext( + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("user")] Party User, + [property: DamlFieldAttribute("name")] string Name, + [property: DamlFieldAttribute("url")] string Url, + [property: DamlFieldAttribute("description")] string Description, + [property: DamlFieldAttribute("reference")] ContractId Reference +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("9cffe65feb664c9550937433067e9f969e3795c6fb38715e06a5e04fc1ae1f83", "Splice.Ans", "AnsEntryContext"); @@ -115,6 +123,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for AnsEntryContext. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -125,7 +134,7 @@ public sealed record Contract(ContractId Id, AnsEntryContext Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), AnsEntryContext.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Amulet.Name.Service.AnsEntryContext.FromRecord(@event.CreateArguments)); } } @@ -170,7 +179,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AnsEntryContextNonContractExtensions @@ -201,16 +210,16 @@ public static ExerciseCommand AnsEntryContext_CollectEntryRenewalPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntryContext_CollectEntryRenewalPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsEntryContext.AnsEntryContext_CollectEntryRenewalPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -220,14 +229,7 @@ public static async Task ProjectAnsEntryContext_CollectEntryRenewalPaymentResult(tx, contractId.Value)); } @@ -258,16 +260,16 @@ public static ExerciseCommand AnsEntryContext_CollectInitialEntryPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntryContext_CollectInitialEntryPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsEntryContext.AnsEntryContext_CollectInitialEntryPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -277,14 +279,7 @@ public static async Task ProjectAnsEntryContext_CollectInitialEntryPaymentResult(tx, contractId.Value)); } @@ -315,16 +310,16 @@ public static ExerciseCommand AnsEntryContext_RejectEntryInitialPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntryContext_RejectEntryInitialPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsEntryContext.AnsEntryContext_RejectEntryInitialPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -334,14 +329,7 @@ public static async Task ProjectAnsEntryContext_RejectEntryInitialPaymentResult(tx, contractId.Value)); } @@ -372,16 +360,16 @@ public static ExerciseCommand AnsEntryContext_TerminateCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsEntryContext_TerminateAsync( this ContractId contractId, ILedgerWriter client, AnsEntryContext.AnsEntryContext_Terminate argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -391,14 +379,7 @@ public static async Task> AnsEn var command = contractId.AnsEntryContext_TerminateCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectAnsEntryContext_TerminateResult(tx, contractId.Value)); } @@ -425,15 +406,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -443,14 +424,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectEntryRenewalPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectEntryRenewalPaymentResult.cs index ead26f3..200885c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectEntryRenewalPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectEntryRenewalPaymentResult.cs @@ -13,7 +13,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntryContext_CollectEntryRenewalPaymentResult /// -public sealed record AnsEntryContext_CollectEntryRenewalPaymentResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid) : IDamlRecord +public sealed record AnsEntryContext_CollectEntryRenewalPaymentResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectInitialEntryPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectInitialEntryPaymentResult.cs index 7879869..30d7c20 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectInitialEntryPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_CollectInitialEntryPaymentResult.cs @@ -13,7 +13,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntryContext_CollectInitialEntryPaymentResult /// -public sealed record AnsEntryContext_CollectInitialEntryPaymentResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid) : IDamlRecord +public sealed record AnsEntryContext_CollectInitialEntryPaymentResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_RejectEntryInitialPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_RejectEntryInitialPaymentResult.cs index 4e5e86c..5038b68 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_RejectEntryInitialPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_RejectEntryInitialPaymentResult.cs @@ -13,7 +13,9 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntryContext_RejectEntryInitialPaymentResult /// -public sealed record AnsEntryContext_RejectEntryInitialPaymentResult([property: DamlFieldAttribute("amuletSum")] Splice.Amulet.AmuletCreateSummary> AmuletSum) : IDamlRecord +public sealed record AnsEntryContext_RejectEntryInitialPaymentResult( + [property: DamlFieldAttribute("amuletSum")] Splice.Amulet.AmuletCreateSummary> AmuletSum +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_TerminateResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_TerminateResult.cs index 24df398..8026176 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_TerminateResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntryContext_TerminateResult.cs @@ -12,7 +12,7 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntryContext_TerminateResult /// -public sealed record AnsEntryContext_TerminateResult : IDamlRecord +public sealed record AnsEntryContext_TerminateResult : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create(); diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_ExpireResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_ExpireResult.cs index 5ef2340..5c444e6 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_ExpireResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_ExpireResult.cs @@ -12,7 +12,7 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntry_ExpireResult /// -public sealed record AnsEntry_ExpireResult : IDamlRecord +public sealed record AnsEntry_ExpireResult : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create(); diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_RenewResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_RenewResult.cs index 9fb0a78..334d774 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_RenewResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsEntry_RenewResult.cs @@ -13,7 +13,9 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsEntry_RenewResult /// -public sealed record AnsEntry_RenewResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid) : IDamlRecord +public sealed record AnsEntry_RenewResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectEntryRenewalPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectEntryRenewalPayment.cs index 74579a1..16f822c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectEntryRenewalPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectEntryRenewalPayment.cs @@ -15,7 +15,13 @@ public sealed partial record AnsRules /// /// Choice argument type for AnsRules_CollectEntryRenewalPayment. /// - public sealed record AnsRules_CollectEntryRenewalPayment([property: DamlFieldAttribute("user")] Party User, [property: DamlFieldAttribute("entryContext")] ContractId EntryContext, [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext) : IDamlRecord + public sealed record AnsRules_CollectEntryRenewalPayment( + [property: DamlFieldAttribute("user")] Party User, + [property: DamlFieldAttribute("entryContext")] ContractId EntryContext, + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectInitialEntryPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectInitialEntryPayment.cs index 2677651..cc1bd71 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectInitialEntryPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_CollectInitialEntryPayment.cs @@ -15,7 +15,12 @@ public sealed partial record AnsRules /// /// Choice argument type for AnsRules_CollectInitialEntryPayment. /// - public sealed record AnsRules_CollectInitialEntryPayment([property: DamlFieldAttribute("user")] Party User, [property: DamlFieldAttribute("entryContext")] ContractId EntryContext, [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext) : IDamlRecord + public sealed record AnsRules_CollectInitialEntryPayment( + [property: DamlFieldAttribute("user")] Party User, + [property: DamlFieldAttribute("entryContext")] ContractId EntryContext, + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RejectEntryInitialPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RejectEntryInitialPayment.cs index b4584cb..aa0f8a0 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RejectEntryInitialPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RejectEntryInitialPayment.cs @@ -15,7 +15,10 @@ public sealed partial record AnsRules /// /// Choice argument type for AnsRules_RejectEntryInitialPayment. /// - public sealed record AnsRules_RejectEntryInitialPayment([property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext) : IDamlRecord + public sealed record AnsRules_RejectEntryInitialPayment( + [property: DamlFieldAttribute("paymentCid")] ContractId PaymentCid, + [property: DamlFieldAttribute("transferContext")] Splice.Amulet.AppTransferContext TransferContext + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RequestEntry.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RequestEntry.cs index 937e3b0..e961127 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RequestEntry.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.AnsRules_RequestEntry.cs @@ -14,7 +14,12 @@ public sealed partial record AnsRules /// /// Choice argument type for AnsRules_RequestEntry. /// - public sealed record AnsRules_RequestEntry([property: DamlFieldAttribute("name")] string Name, [property: DamlFieldAttribute("url")] string Url, [property: DamlFieldAttribute("description")] string Description, [property: DamlFieldAttribute("user")] Party User) : IDamlRecord + public sealed record AnsRules_RequestEntry( + [property: DamlFieldAttribute("name")] string Name, + [property: DamlFieldAttribute("url")] string Url, + [property: DamlFieldAttribute("description")] string Description, + [property: DamlFieldAttribute("user")] Party User + ) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.cs index 989dd76..89364bb 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml template Splice.Ans:AnsRules /// -public sealed partial record AnsRules([property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("config")] AnsRulesConfig Config) : ITemplate +public sealed partial record AnsRules( + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("config")] AnsRulesConfig Config +) : ITemplate, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("9cffe65feb664c9550937433067e9f969e3795c6fb38715e06a5e04fc1ae1f83", "Splice.Ans", "AnsRules"); @@ -106,6 +110,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for AnsRules. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -116,7 +121,7 @@ public sealed record Contract(ContractId Id, AnsRules Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), AnsRules.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Amulet.Name.Service.AnsRules.FromRecord(@event.CreateArguments)); } } @@ -134,25 +139,23 @@ public static class AnsRulesSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, AnsRules payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Dso; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -161,7 +164,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class AnsRulesNonContractExtensions @@ -192,16 +195,16 @@ public static ExerciseCommand AnsRules_CollectEntryRenewalPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsRules_CollectEntryRenewalPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsRules.AnsRules_CollectEntryRenewalPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -211,14 +214,7 @@ public static async Task ProjectAnsRules_CollectEntryRenewalPaymentResult(tx, contractId.Value)); } @@ -249,16 +245,16 @@ public static ExerciseCommand AnsRules_CollectInitialEntryPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsRules_CollectInitialEntryPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsRules.AnsRules_CollectInitialEntryPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -268,14 +264,7 @@ public static async Task ProjectAnsRules_CollectInitialEntryPaymentResult(tx, contractId.Value)); } @@ -306,16 +295,16 @@ public static ExerciseCommand AnsRules_RejectEntryInitialPaymentCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsRules_RejectEntryInitialPaymentAsync( this ContractId contractId, ILedgerWriter client, AnsRules.AnsRules_RejectEntryInitialPayment argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -325,14 +314,7 @@ public static async Task ProjectAnsRules_RejectEntryInitialPaymentResult(tx, contractId.Value)); } @@ -363,16 +345,16 @@ public static ExerciseCommand AnsRules_RequestEntryCommand( /// The contract on which to exercise the choice. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> AnsRules_RequestEntryAsync( this ContractId contractId, ILedgerWriter client, AnsRules.AnsRules_RequestEntry argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -382,14 +364,7 @@ public static async Task> AnsRules_ var command = contractId.AnsRules_RequestEntryCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectAnsRules_RequestEntryResult(tx, contractId.Value)); } @@ -416,15 +391,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -434,14 +409,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRulesConfig.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRulesConfig.cs index ec9527e..855da79 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRulesConfig.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRulesConfig.cs @@ -13,7 +13,12 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsRulesConfig /// -public sealed record AnsRulesConfig([property: DamlFieldAttribute("renewalDuration")] RelTime RenewalDuration, [property: DamlFieldAttribute("entryLifetime")] RelTime EntryLifetime, [property: DamlFieldAttribute("entryFee")] decimal EntryFee, [property: DamlFieldAttribute("descriptionPrefix")] string DescriptionPrefix) : IDamlRecord +public sealed record AnsRulesConfig( + [property: DamlFieldAttribute("renewalDuration")] RelTime RenewalDuration, + [property: DamlFieldAttribute("entryLifetime")] RelTime EntryLifetime, + [property: DamlFieldAttribute("entryFee")] decimal EntryFee, + [property: DamlFieldAttribute("descriptionPrefix")] string DescriptionPrefix +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectEntryRenewalPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectEntryRenewalPaymentResult.cs index a4d395a..5dd0013 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectEntryRenewalPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectEntryRenewalPaymentResult.cs @@ -13,7 +13,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsRules_CollectEntryRenewalPaymentResult /// -public sealed record AnsRules_CollectEntryRenewalPaymentResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid) : IDamlRecord +public sealed record AnsRules_CollectEntryRenewalPaymentResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectInitialEntryPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectInitialEntryPaymentResult.cs index 3680d6a..c344014 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectInitialEntryPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_CollectInitialEntryPaymentResult.cs @@ -13,7 +13,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsRules_CollectInitialEntryPaymentResult /// -public sealed record AnsRules_CollectInitialEntryPaymentResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid) : IDamlRecord +public sealed record AnsRules_CollectInitialEntryPaymentResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("subscriptionStateCid")] ContractId SubscriptionStateCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RejectEntryInitialPaymentResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RejectEntryInitialPaymentResult.cs index e4733ab..5071162 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RejectEntryInitialPaymentResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RejectEntryInitialPaymentResult.cs @@ -13,7 +13,9 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsRules_RejectEntryInitialPaymentResult /// -public sealed record AnsRules_RejectEntryInitialPaymentResult([property: DamlFieldAttribute("amuletSum")] Splice.Amulet.AmuletCreateSummary> AmuletSum) : IDamlRecord +public sealed record AnsRules_RejectEntryInitialPaymentResult( + [property: DamlFieldAttribute("amuletSum")] Splice.Amulet.AmuletCreateSummary> AmuletSum +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RequestEntryResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RequestEntryResult.cs index 5837ffa..c00e664 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RequestEntryResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/AnsRules_RequestEntryResult.cs @@ -13,7 +13,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record AnsRules_RequestEntryResult /// -public sealed record AnsRules_RequestEntryResult([property: DamlFieldAttribute("entryCid")] ContractId EntryCid, [property: DamlFieldAttribute("requestCid")] ContractId RequestCid) : IDamlRecord +public sealed record AnsRules_RequestEntryResult( + [property: DamlFieldAttribute("entryCid")] ContractId EntryCid, + [property: DamlFieldAttribute("requestCid")] ContractId RequestCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedEntryContext.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedEntryContext.cs index fcb523d..49661f3 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedEntryContext.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedEntryContext.cs @@ -13,7 +13,11 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record ExpectedEntryContext /// -public sealed record ExpectedEntryContext([property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("user")] Party User, [property: DamlFieldAttribute("reference")] ContractId Reference) : IDamlRecord +public sealed record ExpectedEntryContext( + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("user")] Party User, + [property: DamlFieldAttribute("reference")] ContractId Reference +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedPayment.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedPayment.cs index a9bce70..3b18755 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedPayment.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/ExpectedPayment.cs @@ -12,7 +12,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record ExpectedPayment /// -public sealed record ExpectedPayment([property: DamlFieldAttribute("dso")] Party Dso, [property: DamlFieldAttribute("sender")] Party Sender) : IDamlRecord +public sealed record ExpectedPayment( + [property: DamlFieldAttribute("dso")] Party Dso, + [property: DamlFieldAttribute("sender")] Party Sender +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/MarkerContext.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/MarkerContext.cs index 68eb1e1..b18450f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/MarkerContext.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/expected/Splice/Amulet/Name/Service/MarkerContext.cs @@ -15,7 +15,10 @@ namespace Splice.Amulet.Name.Service; /// /// Generated from Daml record MarkerContext /// -public sealed record MarkerContext([property: DamlFieldAttribute("featuredAppRightCid")] ContractId FeaturedAppRightCid, [property: DamlFieldAttribute("beneficiaries")] IReadOnlyList Beneficiaries) : IDamlRecord +public sealed record MarkerContext( + [property: DamlFieldAttribute("featuredAppRightCid")] ContractId FeaturedAppRightCid, + [property: DamlFieldAttribute("beneficiaries")] IReadOnlyList Beneficiaries +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/intermediate.binpb b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-amulet-name-service/intermediate.binpb index 190589d213bea30171d84246b21a30c9795466cf..dcc5e5fc065cab2b0f1d5c87c66568ee768c47f6 100644 GIT binary patch delta 1554 zcmah}ZAep581B61&dsB9bEeIkR%%w-2XjF|CKV&;lS)k@gPQJWqjm4P*J*-D(Vv2% zV&tg^!iu88NERIm`ceG}`l3RkuOAge21NzY2Nm7scIWO4f4-OJIp=ww=Q;OJxApr` zD<*>*!+m9*L)B%dyDw-dM*AZj#hn7|M&Zvz?c>eFM6gel-9W~aCk?M7T~nV5c#oNc zQF(quopA?XZR9^hr! z*uht*0QMc?7hj>DQ_xj@&H~d=Ox1)q-Jr@bMm%L9xvZiW@?ua+9Lhfu=)$)6=mrgZ z5wo5BXgqcdEt|kvZD2z7By4{=VSPQ2gQ6=Ypy%x{UC7g+nS>BaP?6jGKC18+W8slG zw;M=S4Q=--K93v<%2K`Ccp;l#2-}PGL;h*|WFlF$ZNq6s5??j}Y9LwVTTGa(7o4dp zRThC873x>mh?Ynrm1wiS$?sJ<#0VnnVJ%D=HF)tfD=LAU(Q)1EdI{vGZcTwwjPCKlGPq>QKZ}UN;|(;00^U|SWP_?3SE195wG_W#VqdMuO;lBl ztVC$HT~LD(W1$=F=Ap;z{Sv6mWm;N`wuS<_KP{(h~;AAE1h$m%R5I%oE0*M8vw1Zf&GWo#JiQ zVv5Pg9e#@bBw6P#IFos0E=j8m2K-HwI1k^zPG0>Rc4P@B>_cN}D;hF!yE$3cb^bgD zR|vLflYKNmTeJYg2+MY2F}BvM5hb{_=94s)N?P;FkC_un*sgWpJnU+?mUA?wkIH~)VIbUagz&QM(7W}R)Oudarf=SUhfU~ zeco1CS)m0dsnpmvzJ3utWiHU=M*pR4agumkAQRccDm*c6X_6|(Ghh#@@N_<~6uZ#4 L{~W@s%kj3qP2rpT delta 931 zcmY*XNoZ416wSNuXL==V@;fD=Dv7n#qS&NZC&VEr*gCdRL8TIt-x^5s( zU^2X?rw{wO*UdvG2Vy2S>dYNtO>Ji-r zJ+I?$@9t?^-Qp{ff zNS65fHDo5%bwVI1#f8x7Sgax3vM)(HYEhcwyMfR)8`zoM26>$EFM=&ChA|_55=ef7 zus;wF`9vuuMz?!a6umJ6lJm4ibKhE0@1 zi>4#b!=y@>gArqwtKhD_H`$@C29@vDi z!uG}sk4n%(I=CK|^QYu2R<}wKA-T3)h$c6PEm{`G>p;?WKy+2#pD}YieA|y+v){w; zZjQ3jH0`Q`q*;xN=$tRv=l8ISQw177!3u)LSoaTD#@Q$$mW@K|ULo#}2b!D1K}FyM z6@S6?yfcf5p)nQ-2L(b$t=Pd9O~R3Ue8H-f>L|=_!3pD(P>ww>!XC~b3p@J|P3y5; zY3q`6t~$#`9QXi0FVL?J?3{L@jZy5 z!qb=(Y>o-hjp1lG+R`9O(U8W}zYu?$nxl{2u9hWabcXHl;JZjRY^uT50=Q?Twi>Jw nr`X^ceAPI0FkPH{?&E8*gI3pLgUZVIdfcR)ifoByHsH2DhUXN7 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/README.md index a43ee46..7377223 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/README.md @@ -16,11 +16,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-allocation-instruction-v2 @@ -32,8 +35,10 @@ scripts/refresh-snapshot.sh splice-api-token-allocation-instruction-v2 `splice-api-token-allocation-instruction-v2.dar` is the upstream Splice archive it was derived from (`splice-api-token-allocation-instruction-v2-1.0.0.dar` from the Splice -`0.6.13` `splice-node` release tarball), kept alongside for provenance. Do not -regenerate either from a local build without a clear reason. If the upstream -Splice package genuinely needs to advance, replace the files in place, refresh -the `expected/` snapshot per the procedure above, and call out the version bump -in the pull request description. +`0.7.5` `splice-node` release tarball), kept alongside as the frozen upstream +artifact and used as the regeneration input. Do not hand-edit +`intermediate.binpb`: the refresh script regenerates it from this DAR on every +run, and CI regenerates it the same way and fails on any byte delta. If the +upstream Splice package genuinely needs to advance, replace the files in +place, refresh the snapshot per the procedure above, and call out the version +bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory.cs deleted file mode 100644 index 82ff8b6..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Allocation.Instruction.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.AllocationInstructionV2:AllocationFactory. -/// Implements so that ContractId<AllocationFactory> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.AllocationInstructionV2:AllocationFactory has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<AllocationFactory> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record AllocationFactory : ITemplate -{ - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationFactory interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'AllocationFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'AllocationFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'AllocationFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'AllocationFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationFactory interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'AllocationFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static AllocationFactory FromRecord(DamlRecord record) => new AllocationFactory(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactoryView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactoryView.cs index d879625..a89d49b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactoryView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactoryView.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationFactoryView /// -public sealed record AllocationFactoryView([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationFactoryView( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IAllocationFactory, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_Allocate.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_Allocate.cs index ee4f348..3e23167 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_Allocate.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_Allocate.cs @@ -16,7 +16,14 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationFactory_Allocate /// -public sealed record AllocationFactory_Allocate([property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, [property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V2.AllocationSpecification Allocation, [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs, [property: DamlFieldAttribute("actors")] IReadOnlyList Actors) : IDamlRecord +public sealed record AllocationFactory_Allocate( + [property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, + [property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V2.AllocationSpecification Allocation, + [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, + [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs, + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_PublicFetch.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_PublicFetch.cs index 854e9ab..4a008ba 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_PublicFetch.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationFactory_PublicFetch.cs @@ -14,7 +14,9 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationFactory_PublicFetch /// -public sealed record AllocationFactory_PublicFetch([property: DamlFieldAttribute("actors")] IReadOnlyList Actors) : IDamlRecord +public sealed record AllocationFactory_PublicFetch( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction.cs deleted file mode 100644 index 2b83eaf..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Allocation.Instruction.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.AllocationInstructionV2:AllocationInstruction. -/// Implements so that ContractId<AllocationInstruction> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.AllocationInstructionV2:AllocationInstruction has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<AllocationInstruction> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record AllocationInstruction : ITemplate -{ - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationInstruction interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'AllocationInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'AllocationInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'AllocationInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'AllocationInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationInstructionV2:AllocationInstruction interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'AllocationInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationInstructionV2:AllocationInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static AllocationInstruction FromRecord(DamlRecord record) => new AllocationInstruction(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionAction_AIA_Custom.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionAction_AIA_Custom.cs index 00758d0..27b6a6a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionAction_AIA_Custom.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionAction_AIA_Custom.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstructionAction.AIA_Custom /// -public sealed record AllocationInstructionAction_AIA_Custom([property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record AllocationInstructionAction_AIA_Custom( + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult.cs index 21c802e..f2a7be0 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult.cs @@ -15,7 +15,11 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstructionResult /// -public sealed record AllocationInstructionResult([property: DamlFieldAttribute("output")] AllocationInstructionResult_Output Output, [property: DamlFieldAttribute("authorizerChangeCids")] IReadOnlyDictionary>> AuthorizerChangeCids, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationInstructionResult( + [property: DamlFieldAttribute("output")] AllocationInstructionResult_Output Output, + [property: DamlFieldAttribute("authorizerChangeCids")] IReadOnlyDictionary>> AuthorizerChangeCids, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Completed.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Completed.cs index f3e2e64..daa3a62 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Completed.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Completed.cs @@ -13,7 +13,9 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstructionResult_Output.AllocationInstructionResult_Completed /// -public sealed record AllocationInstructionResult_Output_AllocationInstructionResult_Completed([property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid) : IDamlRecord +public sealed record AllocationInstructionResult_Output_AllocationInstructionResult_Completed( + [property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Pending.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Pending.cs index 74b878a..86fbd2e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Pending.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionResult_Output_AllocationInstructionResult_Pending.cs @@ -13,7 +13,9 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstructionResult_Output.AllocationInstructionResult_Pending /// -public sealed record AllocationInstructionResult_Output_AllocationInstructionResult_Pending([property: DamlFieldAttribute("allocationInstructionCid")] ContractId AllocationInstructionCid) : IDamlRecord +public sealed record AllocationInstructionResult_Output_AllocationInstructionResult_Pending( + [property: DamlFieldAttribute("allocationInstructionCid")] ContractId AllocationInstructionCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionView.cs index 87fadb3..899bcf9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstructionView.cs @@ -16,7 +16,16 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstructionView /// -public sealed record AllocationInstructionView([property: DamlFieldAttribute("originalInstructionCid")] ContractId? OriginalInstructionCid, [property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, [property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V2.AllocationSpecification Allocation, [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationInstructionView( + [property: DamlFieldAttribute("originalInstructionCid")] ContractId? OriginalInstructionCid, + [property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, + [property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V2.AllocationSpecification Allocation, + [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, + [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, + [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IAllocationInstruction, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Accept.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Accept.cs index 0065949..1ad5fbf 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Accept.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Accept.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstruction_Accept /// -public sealed record AllocationInstruction_Accept([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record AllocationInstruction_Accept( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Withdraw.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Withdraw.cs index 66d0099..59c377b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Withdraw.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/AllocationInstruction_Withdraw.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml record AllocationInstruction_Withdraw /// -public sealed record AllocationInstruction_Withdraw([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record AllocationInstruction_Withdraw( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationFactory.cs index 961da18..5ce688d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationFactory.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationFactory.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml interface Splice.Api.Token.AllocationInstructionV2:AllocationFactory /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IAllocationFactory : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,12 +47,15 @@ public interface IAllocationFactory : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("9818a0b5b827109de03a04c8f6151cde9d1e7fe5123dbb2dfeb0e52d7271287c", "Splice.Api.Token.AllocationInstructionV2", "AllocationFactory"), DamlTypeKind.Interface, "splice-api-token-allocation-instruction-v2"); - // Interface method AllocationFactory_Allocate. - // Choice AllocationFactory_Allocate(AllocationFactory_Allocate) -> AllocationInstructionResult - // Interface method AllocationFactory_PublicFetch. - // Choice AllocationFactory_PublicFetch(AllocationFactory_PublicFetch) -> AllocationFactoryView - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the admin field of the interface view. + Party Admin { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -52,7 +63,7 @@ public interface IAllocationFactory : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -79,37 +90,31 @@ public static ExerciseCommand AllocationFactory_AllocateCommand( /// /// Exercises the AllocationFactory_Allocate interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationFactory_AllocateAsync( + public static Task> AllocationFactory_AllocateAsync( this ContractId contractId, ILedgerWriter client, AllocationFactory_Allocate argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationFactory_AllocateCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationFactory_AllocateCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -131,37 +136,31 @@ public static ExerciseCommand AllocationFactory_PublicFetchCommand( /// /// Exercises the AllocationFactory_PublicFetch interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationFactory_PublicFetchAsync( + public static Task> AllocationFactory_PublicFetchAsync( this ContractId contractId, ILedgerWriter client, AllocationFactory_PublicFetch argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationFactory_PublicFetchCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationFactory_PublicFetchCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -180,34 +179,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationInstruction.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationInstruction.cs index a1807e3..6bc8a4f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationInstruction.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-instruction-v2/expected/Splice/Api/Token/Allocation/Instruction/V2/IAllocationInstruction.cs @@ -6,11 +6,14 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +22,13 @@ namespace Splice.Api.Token.Allocation.Instruction.V2; /// /// Generated from Daml interface Splice.Api.Token.AllocationInstructionV2:AllocationInstruction /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IAllocationInstruction : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,12 +49,33 @@ public interface IAllocationInstruction : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("9818a0b5b827109de03a04c8f6151cde9d1e7fe5123dbb2dfeb0e52d7271287c", "Splice.Api.Token.AllocationInstructionV2", "AllocationInstruction"), DamlTypeKind.Interface, "splice-api-token-allocation-instruction-v2"); - // Interface method AllocationInstruction_Accept. - // Choice AllocationInstruction_Accept(AllocationInstruction_Accept) -> AllocationInstructionResult - // Interface method AllocationInstruction_Withdraw. - // Choice AllocationInstruction_Withdraw(AllocationInstruction_Withdraw) -> AllocationInstructionResult - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the originalInstructionCid field of the interface view. + ContractId? OriginalInstructionCid { get; } + + /// Gets the settlement field of the interface view. + Splice.Api.Token.Allocation.V2.SettlementInfo Settlement { get; } + + /// Gets the allocation field of the interface view. + Splice.Api.Token.Allocation.V2.AllocationSpecification Allocation { get; } + + /// Gets the requestedAt field of the interface view. + DateTimeOffset RequestedAt { get; } + + /// Gets the inputHoldingCids field of the interface view. + IReadOnlyList> InputHoldingCids { get; } + + /// Gets the expiresAt field of the interface view. + DateTimeOffset? ExpiresAt { get; } + + /// Gets the availableActions field of the interface view. + IReadOnlyDictionary>> AvailableActions { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -52,7 +83,7 @@ public interface IAllocationInstruction : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -79,37 +110,31 @@ public static ExerciseCommand AllocationInstruction_AcceptCommand( /// /// Exercises the AllocationInstruction_Accept interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationInstruction_AcceptAsync( + public static Task> AllocationInstruction_AcceptAsync( this ContractId contractId, ILedgerWriter client, AllocationInstruction_Accept argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationInstruction_AcceptCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationInstruction_AcceptCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -131,37 +156,31 @@ public static ExerciseCommand AllocationInstruction_WithdrawCommand( /// /// Exercises the AllocationInstruction_Withdraw interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationInstruction_WithdrawAsync( + public static Task> AllocationInstruction_WithdrawAsync( this ContractId contractId, ILedgerWriter client, AllocationInstruction_Withdraw argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationInstruction_WithdrawCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationInstruction_WithdrawCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -180,34 +199,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/README.md index 1f45fe2..ba7e4e9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/README.md @@ -15,11 +15,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-allocation-request-v2 @@ -30,8 +33,10 @@ scripts/refresh-snapshot.sh splice-api-token-allocation-request-v2 `intermediate.binpb` is the canonical codegen input for the drift test; `splice-api-token-allocation-request-v2.dar` is the upstream Splice archive it was derived from (`splice-api-token-allocation-request-v2-1.0.0.dar` from the -Splice `0.6.13` `splice-node` release tarball), kept alongside for -provenance. Do not regenerate either from a local build without a clear -reason. If the upstream Splice package genuinely needs to advance, replace the -files in place, refresh the `expected/` snapshot per the procedure above, and -call out the version bump in the pull request description. +Splice `0.7.5` `splice-node` release tarball), kept alongside as the frozen +upstream artifact and used as the regeneration input. Do not hand-edit +`intermediate.binpb`: the refresh script regenerates it from this DAR on every +run, and CI regenerates it the same way and fails on any byte delta. If the +upstream Splice package genuinely needs to advance, replace the files in +place, refresh the snapshot per the procedure above, and call out the version +bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest.cs deleted file mode 100644 index 79b6fba..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Allocation.Request.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.AllocationRequestV2:AllocationRequest. -/// Implements so that ContractId<AllocationRequest> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.AllocationRequestV2:AllocationRequest has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<AllocationRequest> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record AllocationRequest : ITemplate -{ - /// Always throws — the Splice.Api.Token.AllocationRequestV2:AllocationRequest interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'AllocationRequest' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationRequestV2:AllocationRequest' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationRequestV2:AllocationRequest interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'AllocationRequest' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationRequestV2:AllocationRequest' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationRequestV2:AllocationRequest interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'AllocationRequest' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationRequestV2:AllocationRequest' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationRequestV2:AllocationRequest interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'AllocationRequest' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationRequestV2:AllocationRequest' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationRequestV2:AllocationRequest interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'AllocationRequest' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationRequestV2:AllocationRequest' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static AllocationRequest FromRecord(DamlRecord record) => new AllocationRequest(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestAction_ARA_Custom.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestAction_ARA_Custom.cs index 342eb17..1822ace 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestAction_ARA_Custom.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestAction_ARA_Custom.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequestAction.ARA_Custom /// -public sealed record AllocationRequestAction_ARA_Custom([property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record AllocationRequestAction_ARA_Custom( + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestView.cs index 42d28e6..b48ba65 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequestView.cs @@ -16,7 +16,15 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequestView /// -public sealed record AllocationRequestView([property: DamlFieldAttribute("originalRequestCid")] ContractId? OriginalRequestCid, [property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, [property: DamlFieldAttribute("allocations")] IReadOnlyList Allocations, [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, [property: DamlFieldAttribute("settleAt")] DateTimeOffset? SettleAt, [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationRequestView( + [property: DamlFieldAttribute("originalRequestCid")] ContractId? OriginalRequestCid, + [property: DamlFieldAttribute("settlement")] Splice.Api.Token.Allocation.V2.SettlementInfo Settlement, + [property: DamlFieldAttribute("allocations")] IReadOnlyList Allocations, + [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, + [property: DamlFieldAttribute("settleAt")] DateTimeOffset? SettleAt, + [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IAllocationRequest, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Accept.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Accept.cs index 23ecbde..7b4254c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Accept.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Accept.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_Accept /// -public sealed record AllocationRequest_Accept([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record AllocationRequest_Accept( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_AcceptResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_AcceptResult.cs index ab6a5e5..44e2be3 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_AcceptResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_AcceptResult.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_AcceptResult /// -public sealed record AllocationRequest_AcceptResult([property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationRequest_AcceptResult( + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Reject.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Reject.cs index 229edc0..4db4367 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Reject.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Reject.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_Reject /// -public sealed record AllocationRequest_Reject([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record AllocationRequest_Reject( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_RejectResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_RejectResult.cs index 18ac322..cc69055 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_RejectResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_RejectResult.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_RejectResult /// -public sealed record AllocationRequest_RejectResult([property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationRequest_RejectResult( + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Withdraw.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Withdraw.cs index b652147..0a5d12d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Withdraw.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_Withdraw.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_Withdraw /// -public sealed record AllocationRequest_Withdraw([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record AllocationRequest_Withdraw( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_WithdrawResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_WithdrawResult.cs index 30c2520..8c3ba2e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_WithdrawResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/AllocationRequest_WithdrawResult.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml record AllocationRequest_WithdrawResult /// -public sealed record AllocationRequest_WithdrawResult([property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationRequest_WithdrawResult( + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/IAllocationRequest.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/IAllocationRequest.cs index 4265f8c..464cab1 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/IAllocationRequest.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-request-v2/expected/Splice/Api/Token/Allocation/Request/V2/IAllocationRequest.cs @@ -6,11 +6,14 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +22,13 @@ namespace Splice.Api.Token.Allocation.Request.V2; /// /// Generated from Daml interface Splice.Api.Token.AllocationRequestV2:AllocationRequest /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IAllocationRequest : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,14 +49,30 @@ public interface IAllocationRequest : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("adc16315a8943a8433886694720a2a000ae84c2315c4414bd6d0db4d1660de9c", "Splice.Api.Token.AllocationRequestV2", "AllocationRequest"), DamlTypeKind.Interface, "splice-api-token-allocation-request-v2"); - // Interface method AllocationRequest_Accept. - // Choice AllocationRequest_Accept(AllocationRequest_Accept) -> AllocationRequest_AcceptResult - // Interface method AllocationRequest_Reject. - // Choice AllocationRequest_Reject(AllocationRequest_Reject) -> AllocationRequest_RejectResult - // Interface method AllocationRequest_Withdraw. - // Choice AllocationRequest_Withdraw(AllocationRequest_Withdraw) -> AllocationRequest_WithdrawResult - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the originalRequestCid field of the interface view. + ContractId? OriginalRequestCid { get; } + + /// Gets the settlement field of the interface view. + Splice.Api.Token.Allocation.V2.SettlementInfo Settlement { get; } + + /// Gets the allocations field of the interface view. + IReadOnlyList Allocations { get; } + + /// Gets the requestedAt field of the interface view. + DateTimeOffset RequestedAt { get; } + + /// Gets the settleAt field of the interface view. + DateTimeOffset? SettleAt { get; } + + /// Gets the availableActions field of the interface view. + IReadOnlyDictionary>> AvailableActions { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -54,7 +80,7 @@ public interface IAllocationRequest : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -81,37 +107,31 @@ public static ExerciseCommand AllocationRequest_AcceptCommand( /// /// Exercises the AllocationRequest_Accept interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationRequest_AcceptAsync( + public static Task> AllocationRequest_AcceptAsync( this ContractId contractId, ILedgerWriter client, AllocationRequest_Accept argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationRequest_AcceptCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationRequest_AcceptCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -133,37 +153,31 @@ public static ExerciseCommand AllocationRequest_RejectCommand( /// /// Exercises the AllocationRequest_Reject interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationRequest_RejectAsync( + public static Task> AllocationRequest_RejectAsync( this ContractId contractId, ILedgerWriter client, AllocationRequest_Reject argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationRequest_RejectCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationRequest_RejectCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -185,37 +199,31 @@ public static ExerciseCommand AllocationRequest_WithdrawCommand( /// /// Exercises the AllocationRequest_Withdraw interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> AllocationRequest_WithdrawAsync( + public static Task> AllocationRequest_WithdrawAsync( this ContractId contractId, ILedgerWriter client, AllocationRequest_Withdraw argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.AllocationRequest_WithdrawCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.AllocationRequest_WithdrawCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -234,34 +242,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/README.md index b05360e..dcba7aa 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/README.md @@ -15,11 +15,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-allocation-v2 @@ -30,8 +33,10 @@ scripts/refresh-snapshot.sh splice-api-token-allocation-v2 `intermediate.binpb` is the canonical codegen input for the drift test; `splice-api-token-allocation-v2.dar` is the upstream Splice archive it was derived from (`splice-api-token-allocation-v2-1.0.0.dar` from the Splice -`0.6.13` `splice-node` release tarball), kept alongside for provenance. Do not -regenerate either from a local build without a clear reason. If the upstream -Splice package genuinely needs to advance, replace the files in place, refresh -the `expected/` snapshot per the procedure above, and call out the version bump -in the pull request description. +`0.7.5` `splice-node` release tarball), kept alongside as the frozen upstream +artifact and used as the regeneration input. Do not hand-edit +`intermediate.binpb`: the refresh script regenerates it from this DAR on every +run, and CI regenerates it the same way and fails on any byte delta. If the +upstream Splice package genuinely needs to advance, replace the files in +place, refresh the snapshot per the procedure above, and call out the version +bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation.cs deleted file mode 100644 index e7ab068..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Allocation.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.AllocationV2:Allocation. -/// Implements so that ContractId<Allocation> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.AllocationV2:Allocation has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<Allocation> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record Allocation : ITemplate -{ - /// Always throws — the Splice.Api.Token.AllocationV2:Allocation interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'Allocation' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:Allocation' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:Allocation interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'Allocation' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:Allocation' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:Allocation interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'Allocation' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:Allocation' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:Allocation interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'Allocation' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:Allocation' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:Allocation interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'Allocation' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:Allocation' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static Allocation FromRecord(DamlRecord record) => new Allocation(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationAction_AA_Custom.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationAction_AA_Custom.cs index faedd8f..60d3fc2 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationAction_AA_Custom.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationAction_AA_Custom.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationAction.AA_Custom /// -public sealed record AllocationAction_AA_Custom([property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record AllocationAction_AA_Custom( + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult.cs index e46cd6f..32ae503 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult.cs @@ -15,7 +15,11 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationResult /// -public sealed record AllocationResult([property: DamlFieldAttribute("output")] AllocationResult_Output Output, [property: DamlFieldAttribute("authorizerHoldingCids")] IReadOnlyDictionary>> AuthorizerHoldingCids, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationResult( + [property: DamlFieldAttribute("output")] AllocationResult_Output Output, + [property: DamlFieldAttribute("authorizerHoldingCids")] IReadOnlyDictionary>> AuthorizerHoldingCids, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Pending.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Pending.cs index b8a188e..efaee7c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Pending.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Pending.cs @@ -13,7 +13,9 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationResult_Output.AllocationResult_Pending /// -public sealed record AllocationResult_Output_AllocationResult_Pending([property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid) : IDamlRecord +public sealed record AllocationResult_Output_AllocationResult_Pending( + [property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Settled.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Settled.cs index a93ab13..ad96f34 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Settled.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationResult_Output_AllocationResult_Settled.cs @@ -13,7 +13,9 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationResult_Output.AllocationResult_Settled /// -public sealed record AllocationResult_Output_AllocationResult_Settled([property: DamlFieldAttribute("nextIterationAllocationCid")] ContractId? NextIterationAllocationCid) : IDamlRecord +public sealed record AllocationResult_Output_AllocationResult_Settled( + [property: DamlFieldAttribute("nextIterationAllocationCid")] ContractId? NextIterationAllocationCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationSpecification.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationSpecification.cs index 3b68ed8..d9a9fba 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationSpecification.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationSpecification.cs @@ -15,7 +15,15 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationSpecification /// -public sealed record AllocationSpecification([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("authorizer")] Splice.Api.Token.Holding.V2.Account Authorizer, [property: DamlFieldAttribute("transferLegSides")] IReadOnlyList TransferLegSides, [property: DamlFieldAttribute("settlementDeadline")] DateTimeOffset? SettlementDeadline, [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding, [property: DamlFieldAttribute("committed")] bool Committed, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationSpecification( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("authorizer")] Splice.Api.Token.Holding.V2.Account Authorizer, + [property: DamlFieldAttribute("transferLegSides")] IReadOnlyList TransferLegSides, + [property: DamlFieldAttribute("settlementDeadline")] DateTimeOffset? SettlementDeadline, + [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding, + [property: DamlFieldAttribute("committed")] bool Committed, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationView.cs index bd5502b..78a0a59 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/AllocationView.cs @@ -16,7 +16,17 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record AllocationView /// -public sealed record AllocationView([property: DamlFieldAttribute("originalAllocationCid")] ContractId? OriginalAllocationCid, [property: DamlFieldAttribute("settlement")] SettlementInfo Settlement, [property: DamlFieldAttribute("allocation")] AllocationSpecification Allocation, [property: DamlFieldAttribute("holdingCids")] IReadOnlyList> HoldingCids, [property: DamlFieldAttribute("createdAt")] DateTimeOffset CreatedAt, [property: DamlFieldAttribute("numIterations")] long NumIterations, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record AllocationView( + [property: DamlFieldAttribute("originalAllocationCid")] ContractId? OriginalAllocationCid, + [property: DamlFieldAttribute("settlement")] SettlementInfo Settlement, + [property: DamlFieldAttribute("allocation")] AllocationSpecification Allocation, + [property: DamlFieldAttribute("holdingCids")] IReadOnlyList> HoldingCids, + [property: DamlFieldAttribute("createdAt")] DateTimeOffset CreatedAt, + [property: DamlFieldAttribute("numIterations")] long NumIterations, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, + [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IAllocation, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Cancel.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Cancel.cs index a463355..7de2361 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Cancel.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Cancel.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record Allocation_Cancel /// -public sealed record Allocation_Cancel([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record Allocation_Cancel( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Settle.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Settle.cs index b770e20..bb72ca4 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Settle.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Settle.cs @@ -14,7 +14,12 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record Allocation_Settle /// -public sealed record Allocation_Settle([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraTransferLegSides")] IReadOnlyList ExtraTransferLegSides, [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record Allocation_Settle( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraTransferLegSides")] IReadOnlyList ExtraTransferLegSides, + [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Withdraw.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Withdraw.cs index 5a58b53..f5f2f4f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Withdraw.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/Allocation_Withdraw.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record Allocation_Withdraw /// -public sealed record Allocation_Withdraw([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record Allocation_Withdraw( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/FinalizedAllocation.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/FinalizedAllocation.cs index 8be1e43..9cd941b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/FinalizedAllocation.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/FinalizedAllocation.cs @@ -15,7 +15,11 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record FinalizedAllocation /// -public sealed record FinalizedAllocation([property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid, [property: DamlFieldAttribute("extraTransferLegSides")] IReadOnlyList ExtraTransferLegSides, [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding) : IDamlRecord +public sealed record FinalizedAllocation( + [property: DamlFieldAttribute("allocationCid")] ContractId AllocationCid, + [property: DamlFieldAttribute("extraTransferLegSides")] IReadOnlyList ExtraTransferLegSides, + [property: DamlFieldAttribute("nextIterationFunding")] IReadOnlyDictionary? NextIterationFunding +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/IAllocation.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/IAllocation.cs index 768b93c..8df6cac 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/IAllocation.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/IAllocation.cs @@ -6,11 +6,14 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +22,13 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml interface Splice.Api.Token.AllocationV2:Allocation /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IAllocation : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,14 +49,36 @@ public interface IAllocation : IDamlInterface, IHasView /// Gets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("051a3b0563a6fa4df4cb34448081e48b061e555aa1a265abf6ae8f3f4cafe439", "Splice.Api.Token.AllocationV2", "Allocation"), DamlTypeKind.Interface, "splice-api-token-allocation-v2"); - // Interface method Allocation_Cancel. - // Choice Allocation_Cancel(Allocation_Cancel) -> AllocationResult - // Interface method Allocation_Settle. - // Choice Allocation_Settle(Allocation_Settle) -> AllocationResult - // Interface method Allocation_Withdraw. - // Choice Allocation_Withdraw(Allocation_Withdraw) -> AllocationResult - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the originalAllocationCid field of the interface view. + ContractId? OriginalAllocationCid { get; } + + /// Gets the settlement field of the interface view. + SettlementInfo Settlement { get; } + + /// Gets the allocation field of the interface view. + AllocationSpecification Allocation { get; } + + /// Gets the holdingCids field of the interface view. + IReadOnlyList> HoldingCids { get; } + + /// Gets the createdAt field of the interface view. + DateTimeOffset CreatedAt { get; } + + /// Gets the numIterations field of the interface view. + long NumIterations { get; } + + /// Gets the expiresAt field of the interface view. + DateTimeOffset? ExpiresAt { get; } + + /// Gets the availableActions field of the interface view. + IReadOnlyDictionary>> AvailableActions { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -54,7 +86,7 @@ public interface IAllocation : IDamlInterface, IHasView /// One method per choice; each submits an interface-typed /// built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -81,37 +113,31 @@ public static ExerciseCommand Allocation_CancelCommand( /// /// Exercises the Allocation_Cancel interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> Allocation_CancelAsync( + public static Task> Allocation_CancelAsync( this ContractId contractId, ILedgerWriter client, Allocation_Cancel argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.Allocation_CancelCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.Allocation_CancelCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -133,37 +159,31 @@ public static ExerciseCommand Allocation_SettleCommand( /// /// Exercises the Allocation_Settle interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> Allocation_SettleAsync( + public static Task> Allocation_SettleAsync( this ContractId contractId, ILedgerWriter client, Allocation_Settle argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.Allocation_SettleCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.Allocation_SettleCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -185,37 +205,31 @@ public static ExerciseCommand Allocation_WithdrawCommand( /// /// Exercises the Allocation_Withdraw interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> Allocation_WithdrawAsync( + public static Task> Allocation_WithdrawAsync( this ContractId contractId, ILedgerWriter client, Allocation_Withdraw argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.Allocation_WithdrawCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.Allocation_WithdrawCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -234,34 +248,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/ISettlementFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/ISettlementFactory.cs index 4ceca20..13c202d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/ISettlementFactory.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/ISettlementFactory.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml interface Splice.Api.Token.AllocationV2:SettlementFactory /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface ISettlementFactory : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,12 +47,15 @@ public interface ISettlementFactory : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("051a3b0563a6fa4df4cb34448081e48b061e555aa1a265abf6ae8f3f4cafe439", "Splice.Api.Token.AllocationV2", "SettlementFactory"), DamlTypeKind.Interface, "splice-api-token-allocation-v2"); - // Interface method Archive. - // Choice Archive() -> DamlUnit - // Interface method SettlementFactory_PublicFetch. - // Choice SettlementFactory_PublicFetch(SettlementFactory_PublicFetch) -> SettlementFactoryView - // Interface method SettlementFactory_SettleBatch. - // Choice SettlementFactory_SettleBatch(SettlementFactory_SettleBatch) -> SettlementFactory_SettleBatchResult + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the admin field of the interface view. + Party Admin { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -52,7 +63,7 @@ public interface ISettlementFactory : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -76,35 +87,29 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -126,37 +131,31 @@ public static ExerciseCommand SettlementFactory_PublicFetchCommand( /// /// Exercises the SettlementFactory_PublicFetch interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> SettlementFactory_PublicFetchAsync( + public static Task> SettlementFactory_PublicFetchAsync( this ContractId contractId, ILedgerWriter client, SettlementFactory_PublicFetch argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.SettlementFactory_PublicFetchCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.SettlementFactory_PublicFetchCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -178,36 +177,30 @@ public static ExerciseCommand SettlementFactory_SettleBatchCommand( /// /// Exercises the SettlementFactory_SettleBatch interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> SettlementFactory_SettleBatchAsync( + public static Task> SettlementFactory_SettleBatchAsync( this ContractId contractId, ILedgerWriter client, SettlementFactory_SettleBatch argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.SettlementFactory_SettleBatchCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.SettlementFactory_SettleBatchCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory.cs deleted file mode 100644 index d18e8d4..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Allocation.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.AllocationV2:SettlementFactory. -/// Implements so that ContractId<SettlementFactory> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.AllocationV2:SettlementFactory has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<SettlementFactory> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record SettlementFactory : ITemplate -{ - /// Always throws — the Splice.Api.Token.AllocationV2:SettlementFactory interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'SettlementFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:SettlementFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:SettlementFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'SettlementFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:SettlementFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:SettlementFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'SettlementFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:SettlementFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:SettlementFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'SettlementFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:SettlementFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.AllocationV2:SettlementFactory interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'SettlementFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.AllocationV2:SettlementFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static SettlementFactory FromRecord(DamlRecord record) => new SettlementFactory(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactoryView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactoryView.cs index 6196097..2332930 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactoryView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactoryView.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record SettlementFactoryView /// -public sealed record SettlementFactoryView([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record SettlementFactoryView( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : ISettlementFactory, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_PublicFetch.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_PublicFetch.cs index 5a195f4..caae379 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_PublicFetch.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_PublicFetch.cs @@ -14,7 +14,9 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record SettlementFactory_PublicFetch /// -public sealed record SettlementFactory_PublicFetch([property: DamlFieldAttribute("actors")] IReadOnlyList Actors) : IDamlRecord +public sealed record SettlementFactory_PublicFetch( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatch.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatch.cs index cfbe9ae..9ae5bc9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatch.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatch.cs @@ -14,7 +14,13 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record SettlementFactory_SettleBatch /// -public sealed record SettlementFactory_SettleBatch([property: DamlFieldAttribute("settlement")] SettlementInfo Settlement, [property: DamlFieldAttribute("transferLegs")] IReadOnlyList TransferLegs, [property: DamlFieldAttribute("allocations")] IReadOnlyList Allocations, [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record SettlementFactory_SettleBatch( + [property: DamlFieldAttribute("settlement")] SettlementInfo Settlement, + [property: DamlFieldAttribute("transferLegs")] IReadOnlyList TransferLegs, + [property: DamlFieldAttribute("allocations")] IReadOnlyList Allocations, + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatchResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatchResult.cs index 46bcd6e..3867171 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatchResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementFactory_SettleBatchResult.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record SettlementFactory_SettleBatchResult /// -public sealed record SettlementFactory_SettleBatchResult([property: DamlFieldAttribute("allocationSettleResults")] IReadOnlyList AllocationSettleResults, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record SettlementFactory_SettleBatchResult( + [property: DamlFieldAttribute("allocationSettleResults")] IReadOnlyList AllocationSettleResults, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementInfo.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementInfo.cs index 8cc3ae4..c0cdc7f 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementInfo.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/SettlementInfo.cs @@ -15,7 +15,12 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record SettlementInfo /// -public sealed record SettlementInfo([property: DamlFieldAttribute("executors")] IReadOnlyList Executors, [property: DamlFieldAttribute("id")] string Id, [property: DamlFieldAttribute("cid")] ContractId? Cid, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record SettlementInfo( + [property: DamlFieldAttribute("executors")] IReadOnlyList Executors, + [property: DamlFieldAttribute("id")] string Id, + [property: DamlFieldAttribute("cid")] ContractId? Cid, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLeg.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLeg.cs index 82ab2b7..926aa7c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLeg.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLeg.cs @@ -12,7 +12,14 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record TransferLeg /// -public sealed record TransferLeg([property: DamlFieldAttribute("transferLegId")] string TransferLegId, [property: DamlFieldAttribute("sender")] Splice.Api.Token.Holding.V2.Account Sender, [property: DamlFieldAttribute("receiver")] Splice.Api.Token.Holding.V2.Account Receiver, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("instrumentId")] string InstrumentId, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferLeg( + [property: DamlFieldAttribute("transferLegId")] string TransferLegId, + [property: DamlFieldAttribute("sender")] Splice.Api.Token.Holding.V2.Account Sender, + [property: DamlFieldAttribute("receiver")] Splice.Api.Token.Holding.V2.Account Receiver, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("instrumentId")] string InstrumentId, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLegSide.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLegSide.cs index 25690e0..fbc529d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLegSide.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-allocation-v2/expected/Splice/Api/Token/Allocation/V2/TransferLegSide.cs @@ -12,7 +12,14 @@ namespace Splice.Api.Token.Allocation.V2; /// /// Generated from Daml record TransferLegSide /// -public sealed record TransferLegSide([property: DamlFieldAttribute("transferLegId")] string TransferLegId, [property: DamlFieldAttribute("side")] TransferSide Side, [property: DamlFieldAttribute("otherside")] Splice.Api.Token.Holding.V2.Account Otherside, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("instrumentId")] string InstrumentId, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferLegSide( + [property: DamlFieldAttribute("transferLegId")] string TransferLegId, + [property: DamlFieldAttribute("side")] TransferSide Side, + [property: DamlFieldAttribute("otherside")] Splice.Api.Token.Holding.V2.Account Otherside, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("instrumentId")] string InstrumentId, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/README.md index 090ffd1..7a91d94 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/README.md @@ -22,11 +22,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-holding-v1 @@ -36,8 +39,11 @@ scripts/refresh-snapshot.sh splice-api-token-holding-v1 `intermediate.binpb` is the canonical codegen input for the drift test; `splice-api-token-holding-v1.dar` is the upstream Splice archive it was -derived from, kept alongside for provenance. Do not regenerate either from a -local build without a clear reason. If the upstream Splice package genuinely -needs to advance, replace the files in place, refresh the `expected/` snapshot -per the procedure above, and call out the version bump in the pull request +derived from (`splice-api-token-holding-v1-1.0.0.dar` from the Splice `0.7.5` +`splice-node` release tarball), kept alongside as the frozen upstream artifact +and used as the regeneration input. Do not hand-edit `intermediate.binpb`: the +refresh script regenerates it from this DAR on every run, and CI regenerates it +the same way and fails on any byte delta. If the upstream Splice package +genuinely needs to advance, replace the files in place, refresh the snapshot per +the procedure above, and call out the version bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Holding.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Holding.cs deleted file mode 100644 index dadd72c..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Holding.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Holding.V1; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.HoldingV1:Holding. -/// Implements so that ContractId<Holding> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.HoldingV1:Holding has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<Holding> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record Holding : ITemplate -{ - /// Always throws — the Splice.Api.Token.HoldingV1:Holding interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV1:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV1:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV1:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV1:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV1:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV1:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV1:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV1:Holding interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV1:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static Holding FromRecord(DamlRecord record) => new Holding(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/HoldingView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/HoldingView.cs index f62c9af..7200cac 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/HoldingView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/HoldingView.cs @@ -12,14 +12,20 @@ namespace Splice.Api.Token.Holding.V1; /// /// Generated from Daml record HoldingView /// -public sealed record HoldingView([property: DamlFieldAttribute("owner")] Party Owner, [property: DamlFieldAttribute("instrumentId")] InstrumentId InstrumentId, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("lock")] Lock? @lock, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record HoldingView( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("instrumentId")] InstrumentId InstrumentId, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("lock")] Lock? Lock, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IHolding, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( DamlField.Create("owner", Owner.ToDamlValue()), DamlField.Create("instrumentId", InstrumentId.ToRecord()), DamlField.Create("amount", new DamlNumeric(Amount)), - DamlField.Create("lock", @lock is { } __lock ? new DamlOptional(__lock.ToRecord()) : DamlOptional.None), + DamlField.Create("lock", Lock is { } __Lock ? new DamlOptional(__Lock.ToRecord()) : DamlOptional.None), DamlField.Create("meta", Meta.ToRecord()) ); @@ -28,7 +34,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( Owner: Party.FromDamlValue(record.GetRequiredField("owner").As()), InstrumentId: InstrumentId.FromRecord(record.GetRequiredField("instrumentId").As()), Amount: record.GetRequiredField("amount").As().Value, - @lock: record.GetRequiredField("lock").AsOptional().HasValue ? Lock.FromRecord(record.GetRequiredField("lock").AsOptional().Value!.As()) : null, + Lock: record.GetRequiredField("lock").AsOptional().HasValue ? Lock.FromRecord(record.GetRequiredField("lock").AsOptional().Value!.As()) : null, Meta: Splice.Api.Token.Metadata.V1.Metadata.FromRecord(record.GetRequiredField("meta").As()) ); diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/IHolding.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/IHolding.cs index 2cdad73..74722d4 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/IHolding.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/IHolding.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Holding.V1; /// /// Generated from Daml interface Splice.Api.Token.HoldingV1:Holding /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IHolding : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,8 +47,24 @@ public interface IHolding : IDamlInterface, IHasView /// Gets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", "Splice.Api.Token.HoldingV1", "Holding"), DamlTypeKind.Interface, "splice-api-token-holding-v1"); - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the owner field of the interface view. + Party Owner { get; } + + /// Gets the instrumentId field of the interface view. + InstrumentId InstrumentId { get; } + + /// Gets the amount field of the interface view. + decimal Amount { get; } + + /// Gets the lock field of the interface view. + Lock? Lock { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -48,7 +72,7 @@ public interface IHolding : IDamlInterface, IHasView /// One method per choice; each submits an interface-typed /// built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -72,34 +96,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/InstrumentId.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/InstrumentId.cs index bb215be..8b8a994 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/InstrumentId.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/InstrumentId.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Holding.V1; /// /// Generated from Daml record InstrumentId /// -public sealed record InstrumentId([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record InstrumentId( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Lock.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Lock.cs index 7a04894..25cf1c7 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Lock.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v1/expected/Splice/Api/Token/Holding/V1/Lock.cs @@ -16,7 +16,12 @@ namespace Splice.Api.Token.Holding.V1; /// /// Generated from Daml record Lock /// -public sealed record Lock([property: DamlFieldAttribute("holders")] IReadOnlyList Holders, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, [property: DamlFieldAttribute("expiresAfter")] RelTime? ExpiresAfter, [property: DamlFieldAttribute("context")] string? Context) : IDamlRecord +public sealed record Lock( + [property: DamlFieldAttribute("holders")] IReadOnlyList Holders, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, + [property: DamlFieldAttribute("expiresAfter")] RelTime? ExpiresAfter, + [property: DamlFieldAttribute("context")] string? Context +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/README.md index dc80e71..de38d92 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/README.md @@ -19,11 +19,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-holding-v2 @@ -32,10 +35,12 @@ scripts/refresh-snapshot.sh splice-api-token-holding-v2 ## The vendored inputs `intermediate.binpb` is the canonical codegen input for the drift test; -`splice-api-token-holding-v2.dar` is the upstream Splice archive it was derived -from (`splice-api-token-holding-v2-1.0.0.dar` from the Splice `0.6.13` -`splice-node` release tarball), kept alongside for provenance. Do not -regenerate either from a local build without a clear reason. If the upstream -Splice package genuinely needs to advance, replace the files in place, refresh -the `expected/` snapshot per the procedure above, and call out the version bump -in the pull request description. +`splice-api-token-holding-v2.dar` is the upstream Splice archive it was +derived from (`splice-api-token-holding-v2-1.0.0.dar` from the Splice `0.7.5` +`splice-node` release tarball), kept alongside as the frozen upstream artifact +and used as the regeneration input. Do not hand-edit `intermediate.binpb`: the +refresh script regenerates it from this DAR on every run, and CI regenerates +it the same way and fails on any byte delta. If the upstream Splice package +genuinely needs to advance, replace the files in place, refresh the snapshot +per the procedure above, and call out the version bump in the pull request +description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Account.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Account.cs index facbdd5..c8dae30 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Account.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Account.cs @@ -12,7 +12,11 @@ namespace Splice.Api.Token.Holding.V2; /// /// Generated from Daml record Account /// -public sealed record Account([property: DamlFieldAttribute("owner")] Party? Owner, [property: DamlFieldAttribute("provider")] Party? Provider, [property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record Account( + [property: DamlFieldAttribute("owner")] Party? Owner, + [property: DamlFieldAttribute("provider")] Party? Provider, + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Holding.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Holding.cs deleted file mode 100644 index c20b56f..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Holding.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Holding.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.HoldingV2:Holding. -/// Implements so that ContractId<Holding> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.HoldingV2:Holding has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<Holding> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record Holding : ITemplate -{ - /// Always throws — the Splice.Api.Token.HoldingV2:Holding interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV2:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV2:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV2:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV2:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV2:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV2:Holding interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV2:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.HoldingV2:Holding interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'Holding' is the C# placeholder for the Daml interface 'Splice.Api.Token.HoldingV2:Holding' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static Holding FromRecord(DamlRecord record) => new Holding(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/HoldingView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/HoldingView.cs index 98bf371..523236e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/HoldingView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/HoldingView.cs @@ -12,14 +12,20 @@ namespace Splice.Api.Token.Holding.V2; /// /// Generated from Daml record HoldingView /// -public sealed record HoldingView([property: DamlFieldAttribute("account")] Account Account, [property: DamlFieldAttribute("instrumentId")] InstrumentId InstrumentId, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("lock")] Lock? @lock, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record HoldingView( + [property: DamlFieldAttribute("account")] Account Account, + [property: DamlFieldAttribute("instrumentId")] InstrumentId InstrumentId, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("lock")] Lock? Lock, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IHolding, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( DamlField.Create("account", Account.ToRecord()), DamlField.Create("instrumentId", InstrumentId.ToRecord()), DamlField.Create("amount", new DamlNumeric(Amount)), - DamlField.Create("lock", @lock is { } __lock ? new DamlOptional(__lock.ToRecord()) : DamlOptional.None), + DamlField.Create("lock", Lock is { } __Lock ? new DamlOptional(__Lock.ToRecord()) : DamlOptional.None), DamlField.Create("meta", Meta.ToRecord()) ); @@ -28,7 +34,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( Account: Account.FromRecord(record.GetRequiredField("account").As()), InstrumentId: InstrumentId.FromRecord(record.GetRequiredField("instrumentId").As()), Amount: record.GetRequiredField("amount").As().Value, - @lock: record.GetRequiredField("lock").AsOptional().HasValue ? Lock.FromRecord(record.GetRequiredField("lock").AsOptional().Value!.As()) : null, + Lock: record.GetRequiredField("lock").AsOptional().HasValue ? Lock.FromRecord(record.GetRequiredField("lock").AsOptional().Value!.As()) : null, Meta: Splice.Api.Token.Metadata.V1.Metadata.FromRecord(record.GetRequiredField("meta").As()) ); diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/IHolding.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/IHolding.cs index 45627fb..cd3c4eb 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/IHolding.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/IHolding.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Holding.V2; /// /// Generated from Daml interface Splice.Api.Token.HoldingV2:Holding /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IHolding : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,8 +47,24 @@ public interface IHolding : IDamlInterface, IHasView /// Gets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("4b7ecfc366d79ccc5ed07c80f26fe489cf2dfd43ce2856c06a78e6a048db7032", "Splice.Api.Token.HoldingV2", "Holding"), DamlTypeKind.Interface, "splice-api-token-holding-v2"); - // Interface method Archive. - // Choice Archive() -> DamlUnit + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the account field of the interface view. + Account Account { get; } + + /// Gets the instrumentId field of the interface view. + InstrumentId InstrumentId { get; } + + /// Gets the amount field of the interface view. + decimal Amount { get; } + + /// Gets the lock field of the interface view. + Lock? Lock { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -48,7 +72,7 @@ public interface IHolding : IDamlInterface, IHasView /// One method per choice; each submits an interface-typed /// built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -72,34 +96,28 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/InstrumentId.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/InstrumentId.cs index 586715d..c10b42d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/InstrumentId.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/InstrumentId.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Holding.V2; /// /// Generated from Daml record InstrumentId /// -public sealed record InstrumentId([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record InstrumentId( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Lock.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Lock.cs index 164cfec..efcb926 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Lock.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-holding-v2/expected/Splice/Api/Token/Holding/V2/Lock.cs @@ -16,7 +16,12 @@ namespace Splice.Api.Token.Holding.V2; /// /// Generated from Daml record Lock /// -public sealed record Lock([property: DamlFieldAttribute("holders")] IReadOnlyList Holders, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, [property: DamlFieldAttribute("expiresAfter")] RelTime? ExpiresAfter, [property: DamlFieldAttribute("context")] string? Context) : IDamlRecord +public sealed record Lock( + [property: DamlFieldAttribute("holders")] IReadOnlyList Holders, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, + [property: DamlFieldAttribute("expiresAfter")] RelTime? ExpiresAfter, + [property: DamlFieldAttribute("context")] string? Context +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/README.md index 989ef41..6bf3810 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/README.md @@ -17,11 +17,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-transfer-events-v2 @@ -32,8 +35,10 @@ scripts/refresh-snapshot.sh splice-api-token-transfer-events-v2 `intermediate.binpb` is the canonical codegen input for the drift test; `splice-api-token-transfer-events-v2.dar` is the upstream Splice archive it was derived from (`splice-api-token-transfer-events-v2-1.0.0.dar` from the -Splice `0.6.13` `splice-node` release tarball), kept alongside for -provenance. Do not regenerate either from a local build without a clear -reason. If the upstream Splice package genuinely needs to advance, replace the -files in place, refresh the `expected/` snapshot per the procedure above, and -call out the version bump in the pull request description. +Splice `0.7.5` `splice-node` release tarball), kept alongside as the frozen +upstream artifact and used as the regeneration input. Do not hand-edit +`intermediate.binpb`: the refresh script regenerates it from this DAR on every +run, and CI regenerates it the same way and fails on any byte delta. If the +upstream Splice package genuinely needs to advance, replace the files in +place, refresh the snapshot per the procedure above, and call out the version +bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog.cs deleted file mode 100644 index 54063f9..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Transfer.Events.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.TransferEventsV2:EventLog. -/// Implements so that ContractId<EventLog> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.TransferEventsV2:EventLog has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<EventLog> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record EventLog : ITemplate -{ - /// Always throws — the Splice.Api.Token.TransferEventsV2:EventLog interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'EventLog' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferEventsV2:EventLog' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferEventsV2:EventLog interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'EventLog' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferEventsV2:EventLog' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferEventsV2:EventLog interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'EventLog' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferEventsV2:EventLog' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferEventsV2:EventLog interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'EventLog' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferEventsV2:EventLog' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferEventsV2:EventLog interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'EventLog' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferEventsV2:EventLog' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static EventLog FromRecord(DamlRecord record) => new EventLog(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLogView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLogView.cs index d2201af..4a5f374 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLogView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLogView.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Transfer.Events.V2; /// /// Generated from Daml record EventLogView /// -public sealed record EventLogView([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record EventLogView( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IEventLog, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChange.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChange.cs index 1b86b4d..2aa21c4 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChange.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChange.cs @@ -15,7 +15,15 @@ namespace Splice.Api.Token.Transfer.Events.V2; /// /// Generated from Daml record EventLog_HoldingsChange /// -public sealed record EventLog_HoldingsChange([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("account")] Splice.Api.Token.Holding.V2.Account Account, [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, [property: DamlFieldAttribute("transferLegSides")] IReadOnlyList TransferLegSides, [property: DamlFieldAttribute("outputHoldingCids")] IReadOnlyList> OutputHoldingCids, [property: DamlFieldAttribute("observers")] IReadOnlyList Observers, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record EventLog_HoldingsChange( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("account")] Splice.Api.Token.Holding.V2.Account Account, + [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, + [property: DamlFieldAttribute("transferLegSides")] IReadOnlyList TransferLegSides, + [property: DamlFieldAttribute("outputHoldingCids")] IReadOnlyList> OutputHoldingCids, + [property: DamlFieldAttribute("observers")] IReadOnlyList Observers, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChangeResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChangeResult.cs index 342088e..b252bf5 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChangeResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/EventLog_HoldingsChangeResult.cs @@ -12,7 +12,7 @@ namespace Splice.Api.Token.Transfer.Events.V2; /// /// Generated from Daml record EventLog_HoldingsChangeResult /// -public sealed record EventLog_HoldingsChangeResult : IDamlRecord +public sealed record EventLog_HoldingsChangeResult : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create(); diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/IEventLog.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/IEventLog.cs index a4644da..b784fe7 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/IEventLog.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/IEventLog.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Transfer.Events.V2; /// /// Generated from Daml interface Splice.Api.Token.TransferEventsV2:EventLog /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface IEventLog : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,10 +47,15 @@ public interface IEventLog : IDamlInterface, IHasView /// Gets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("5c1097a9bad0af4bcfe6d3fb0fe55112d3d11f18eae57ddfb14c20836fee226c", "Splice.Api.Token.TransferEventsV2", "EventLog"), DamlTypeKind.Interface, "splice-api-token-transfer-events-v2"); - // Interface method Archive. - // Choice Archive() -> DamlUnit - // Interface method EventLog_HoldingsChange. - // Choice EventLog_HoldingsChange(EventLog_HoldingsChange) -> EventLog_HoldingsChangeResult + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the admin field of the interface view. + Party Admin { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -50,7 +63,7 @@ public interface IEventLog : IDamlInterface, IHasView /// One method per choice; each submits an interface-typed /// built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -74,35 +87,29 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -124,36 +131,30 @@ public static ExerciseCommand EventLog_HoldingsChangeCommand( /// /// Exercises the EventLog_HoldingsChange interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> EventLog_HoldingsChangeAsync( + public static Task> EventLog_HoldingsChangeAsync( this ContractId contractId, ILedgerWriter client, EventLog_HoldingsChange argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.EventLog_HoldingsChangeCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.EventLog_HoldingsChangeCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/TransferLegSide.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/TransferLegSide.cs index 970bac5..90ec6e8 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/TransferLegSide.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-events-v2/expected/Splice/Api/Token/Transfer/Events/V2/TransferLegSide.cs @@ -12,7 +12,14 @@ namespace Splice.Api.Token.Transfer.Events.V2; /// /// Generated from Daml record TransferLegSide /// -public sealed record TransferLegSide([property: DamlFieldAttribute("transferLegId")] string TransferLegId, [property: DamlFieldAttribute("side")] TransferSide Side, [property: DamlFieldAttribute("otherside")] Splice.Api.Token.Holding.V2.Account Otherside, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("instrumentId")] string InstrumentId, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferLegSide( + [property: DamlFieldAttribute("transferLegId")] string TransferLegId, + [property: DamlFieldAttribute("side")] TransferSide Side, + [property: DamlFieldAttribute("otherside")] Splice.Api.Token.Holding.V2.Account Otherside, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("instrumentId")] string InstrumentId, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/README.md index 0431408..a4ceeef 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/README.md @@ -16,11 +16,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-api-token-transfer-instruction-v2 @@ -31,8 +34,10 @@ scripts/refresh-snapshot.sh splice-api-token-transfer-instruction-v2 `intermediate.binpb` is the canonical codegen input for the drift test; `splice-api-token-transfer-instruction-v2.dar` is the upstream Splice archive it was derived from (`splice-api-token-transfer-instruction-v2-1.0.0.dar` from -the Splice `0.6.13` `splice-node` release tarball), kept alongside for -provenance. Do not regenerate either from a local build without a clear -reason. If the upstream Splice package genuinely needs to advance, replace the -files in place, refresh the `expected/` snapshot per the procedure above, and -call out the version bump in the pull request description. +the Splice `0.7.5` `splice-node` release tarball), kept alongside as the +frozen upstream artifact and used as the regeneration input. Do not hand-edit +`intermediate.binpb`: the refresh script regenerates it from this DAR on every +run, and CI regenerates it the same way and fails on any byte delta. If the +upstream Splice package genuinely needs to advance, replace the files in +place, refresh the snapshot per the procedure above, and call out the version +bump in the pull request description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferFactory.cs index b016cb5..0867b85 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferFactory.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferFactory.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -19,6 +20,13 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml interface Splice.Api.Token.TransferInstructionV2:TransferFactory /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface ITransferFactory : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,12 +47,15 @@ public interface ITransferFactory : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("29317e3b7b165d2bbf16721bcca0ec4869e53eddb2738bddf790d61af28e0099", "Splice.Api.Token.TransferInstructionV2", "TransferFactory"), DamlTypeKind.Interface, "splice-api-token-transfer-instruction-v2"); - // Interface method Archive. - // Choice Archive() -> DamlUnit - // Interface method TransferFactory_PublicFetch. - // Choice TransferFactory_PublicFetch(TransferFactory_PublicFetch) -> TransferFactoryView - // Interface method TransferFactory_Transfer. - // Choice TransferFactory_Transfer(TransferFactory_Transfer) -> TransferInstructionResult + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the admin field of the interface view. + Party Admin { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -52,7 +63,7 @@ public interface ITransferFactory : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -76,35 +87,29 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -126,37 +131,31 @@ public static ExerciseCommand TransferFactory_PublicFetchCommand( /// /// Exercises the TransferFactory_PublicFetch interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferFactory_PublicFetchAsync( + public static Task> TransferFactory_PublicFetchAsync( this ContractId contractId, ILedgerWriter client, TransferFactory_PublicFetch argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.TransferFactory_PublicFetchCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.TransferFactory_PublicFetchCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -178,36 +177,30 @@ public static ExerciseCommand TransferFactory_TransferCommand( /// /// Exercises the TransferFactory_Transfer interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferFactory_TransferAsync( + public static Task> TransferFactory_TransferAsync( this ContractId contractId, ILedgerWriter client, TransferFactory_Transfer argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.TransferFactory_TransferCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.TransferFactory_TransferCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferInstruction.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferInstruction.cs index e374db5..55b7c42 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferInstruction.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/ITransferInstruction.cs @@ -6,11 +6,14 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +22,13 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml interface Splice.Api.Token.TransferInstructionV2:TransferInstruction /// +/// +/// Instance properties mirror the fields of the interface view , +/// which implements this marker, so a view can be read through a marker-typed variable. +/// == between marker-typed variables compares by reference equality; view payloads +/// materialize as concrete values, whose record value equality +/// applies once concretely typed. +/// public interface ITransferInstruction : IDamlInterface, IHasView { /// Gets the interface identifier. @@ -39,14 +49,24 @@ public interface ITransferInstruction : IDamlInterface, IHasViewGets the compile-time Daml type descriptor. static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => new(new Identifier("29317e3b7b165d2bbf16721bcca0ec4869e53eddb2738bddf790d61af28e0099", "Splice.Api.Token.TransferInstructionV2", "TransferInstruction"), DamlTypeKind.Interface, "splice-api-token-transfer-instruction-v2"); - // Interface method Archive. - // Choice Archive() -> DamlUnit - // Interface method TransferInstruction_Accept. - // Choice TransferInstruction_Accept(TransferInstruction_Accept) -> TransferInstructionResult - // Interface method TransferInstruction_Reject. - // Choice TransferInstruction_Reject(TransferInstruction_Reject) -> TransferInstructionResult - // Interface method TransferInstruction_Withdraw. - // Choice TransferInstruction_Withdraw(TransferInstruction_Withdraw) -> TransferInstructionResult + /// Gets the pure type witness pairing this marker with its view record ; passing it to a generic method infers both type parameters from one argument. + public static ViewDescriptor View { get; } = new(); + + /// Gets the originalInstructionCid field of the interface view. + ContractId? OriginalInstructionCid { get; } + + /// Gets the transfer field of the interface view. + Transfer Transfer { get; } + + /// Gets the expiresAt field of the interface view. + DateTimeOffset? ExpiresAt { get; } + + /// Gets the availableActions field of the interface view. + IReadOnlyDictionary>> AvailableActions { get; } + + /// Gets the meta field of the interface view. + Splice.Api.Token.Metadata.V1.Metadata Meta { get; } + } /// @@ -54,7 +74,7 @@ public interface ITransferInstruction : IDamlInterface, IHasView built via /// -/// through +/// through /// and surfaces the raw — /// interface choices have no typed <Choice>Result projection because the /// implementing template (and therefore the produced contracts' shapes) is unknown @@ -78,35 +98,29 @@ public static ExerciseCommand ArchiveCommand( /// /// Exercises the Archive interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> ArchiveAsync( + public static Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.ArchiveCommand(); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -128,37 +142,31 @@ public static ExerciseCommand TransferInstruction_AcceptCommand( /// /// Exercises the TransferInstruction_Accept interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferInstruction_AcceptAsync( + public static Task> TransferInstruction_AcceptAsync( this ContractId contractId, ILedgerWriter client, TransferInstruction_Accept argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.TransferInstruction_AcceptCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.TransferInstruction_AcceptCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -180,37 +188,31 @@ public static ExerciseCommand TransferInstruction_RejectCommand( /// /// Exercises the TransferInstruction_Reject interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferInstruction_RejectAsync( + public static Task> TransferInstruction_RejectAsync( this ContractId contractId, ILedgerWriter client, TransferInstruction_Reject argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.TransferInstruction_RejectCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.TransferInstruction_RejectCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } /// @@ -232,36 +234,30 @@ public static ExerciseCommand TransferInstruction_WithdrawCommand( /// /// Exercises the TransferInstruction_Withdraw interface choice on this contract id, submitting the /// resulting through - /// and awaiting the outcome. + /// . /// /// The interface-typed contract id to exercise on. /// The ledger client. /// The choice argument. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. - public static async Task> TransferInstruction_WithdrawAsync( + public static Task> TransferInstruction_WithdrawAsync( this ContractId contractId, ILedgerWriter client, TransferInstruction_Withdraw argument, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); - var command = contractId.TransferInstruction_WithdrawCommand(argument); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } + var command = contractId.TransferInstruction_WithdrawCommand(argument); - return await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + return client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken); } } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/Transfer.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/Transfer.cs index fc497bb..9fb5625 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/Transfer.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/Transfer.cs @@ -16,7 +16,16 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record Transfer /// -public sealed record Transfer([property: DamlFieldAttribute("sender")] Splice.Api.Token.Holding.V2.Account Sender, [property: DamlFieldAttribute("receiver")] Splice.Api.Token.Holding.V2.Account Receiver, [property: DamlFieldAttribute("amount")] decimal Amount, [property: DamlFieldAttribute("instrumentId")] Splice.Api.Token.Holding.V2.InstrumentId InstrumentId, [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, [property: DamlFieldAttribute("executeBefore")] DateTimeOffset ExecuteBefore, [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record Transfer( + [property: DamlFieldAttribute("sender")] Splice.Api.Token.Holding.V2.Account Sender, + [property: DamlFieldAttribute("receiver")] Splice.Api.Token.Holding.V2.Account Receiver, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("instrumentId")] Splice.Api.Token.Holding.V2.InstrumentId InstrumentId, + [property: DamlFieldAttribute("requestedAt")] DateTimeOffset RequestedAt, + [property: DamlFieldAttribute("executeBefore")] DateTimeOffset ExecuteBefore, + [property: DamlFieldAttribute("inputHoldingCids")] IReadOnlyList> InputHoldingCids, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory.cs deleted file mode 100644 index 6e8be89..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Transfer.Instruction.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.TransferInstructionV2:TransferFactory. -/// Implements so that ContractId<TransferFactory> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.TransferInstructionV2:TransferFactory has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<TransferFactory> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record TransferFactory : ITemplate -{ - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferFactory interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'TransferFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'TransferFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'TransferFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferFactory interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'TransferFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferFactory interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'TransferFactory' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferFactory' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static TransferFactory FromRecord(DamlRecord record) => new TransferFactory(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactoryView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactoryView.cs index 7460c5d..cb554a1 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactoryView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactoryView.cs @@ -12,7 +12,10 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferFactoryView /// -public sealed record TransferFactoryView([property: DamlFieldAttribute("admin")] Party Admin, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferFactoryView( + [property: DamlFieldAttribute("admin")] Party Admin, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : ITransferFactory, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_PublicFetch.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_PublicFetch.cs index 546605a..402e8b3 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_PublicFetch.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_PublicFetch.cs @@ -14,7 +14,9 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferFactory_PublicFetch /// -public sealed record TransferFactory_PublicFetch([property: DamlFieldAttribute("actors")] IReadOnlyList Actors) : IDamlRecord +public sealed record TransferFactory_PublicFetch( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_Transfer.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_Transfer.cs index 7663e3d..09a1c06 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_Transfer.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferFactory_Transfer.cs @@ -14,7 +14,11 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferFactory_Transfer /// -public sealed record TransferFactory_Transfer([property: DamlFieldAttribute("transfer")] Transfer Transfer, [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record TransferFactory_Transfer( + [property: DamlFieldAttribute("transfer")] Transfer Transfer, + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction.cs deleted file mode 100644 index 5778f86..0000000 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// This code was generated by daml-codegen-csharp. -// Do not edit this file manually. -// - -#nullable enable - -using Daml.Runtime.Contracts; -using Daml.Runtime.Data; -using System; - -namespace Splice.Api.Token.Transfer.Instruction.V2; - -/// -/// Phantom placeholder for the Daml interface Splice.Api.Token.TransferInstructionV2:TransferInstruction. -/// Implements so that ContractId<TransferInstruction> -/// satisfies its where T : ITemplate constraint, but every static -/// metadata accessor throws — interface placeholders carry no template identity. -/// -/// -/// Interface-instance choices need a concrete template to compile against, but the Daml -/// interface Splice.Api.Token.TransferInstructionV2:TransferInstruction has none, so every static metadata accessor on this -/// placeholder throws by design. This does not affect interface choices, which the generated -/// extension methods exercise directly on ContractId<TransferInstruction> — no coercion -/// needed. There is no supported way to recover a concrete ContractId<TConcrete> -/// from this placeholder; obtain one independently if concrete metadata is required. -/// -public sealed record TransferInstruction : ITemplate -{ - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferInstruction interface placeholder carries no template identity. - /// Thrown unconditionally — this placeholder carries no template identity to report. - public static Identifier TemplateId => - throw new InvalidOperationException("'TransferInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageId => - throw new InvalidOperationException("'TransferInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static string PackageName => - throw new InvalidOperationException("'TransferInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferInstruction interface placeholder carries no package identity. - /// Thrown unconditionally — this placeholder carries no package identity to report. - public static Version PackageVersion => - throw new InvalidOperationException("'TransferInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Always throws — the Splice.Api.Token.TransferInstructionV2:TransferInstruction interface placeholder carries no Daml type identity. - /// Thrown unconditionally — this placeholder carries no Daml type identity to report. - public static DamlTypeDescriptor DamlTypeId => - throw new InvalidOperationException("'TransferInstruction' is the C# placeholder for the Daml interface 'Splice.Api.Token.TransferInstructionV2:TransferInstruction' and carries no template metadata. Exercise interface choices directly on ContractId; there is no supported coercion to a concrete ContractId."); - - /// Converts this placeholder to an empty DamlRecord. - public DamlRecord ToRecord() => DamlRecord.Create(); - - /// Creates an empty placeholder instance from a DamlRecord. - public static TransferInstruction FromRecord(DamlRecord record) => new TransferInstruction(); -} diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionAction_TIA_Custom.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionAction_TIA_Custom.cs index 1977a1c..62d8777 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionAction_TIA_Custom.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionAction_TIA_Custom.cs @@ -12,7 +12,9 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstructionAction.TIA_Custom /// -public sealed record TransferInstructionAction_TIA_Custom([property: DamlFieldAttribute("id")] string Id) : IDamlRecord +public sealed record TransferInstructionAction_TIA_Custom( + [property: DamlFieldAttribute("id")] string Id +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult.cs index e5c03c7..876f66d 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult.cs @@ -15,7 +15,11 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstructionResult /// -public sealed record TransferInstructionResult([property: DamlFieldAttribute("output")] TransferInstructionResult_Output Output, [property: DamlFieldAttribute("senderChangeCids")] IReadOnlyList> SenderChangeCids, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferInstructionResult( + [property: DamlFieldAttribute("output")] TransferInstructionResult_Output Output, + [property: DamlFieldAttribute("senderChangeCids")] IReadOnlyList> SenderChangeCids, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Completed.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Completed.cs index 6d86d8a..4dda24c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Completed.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Completed.cs @@ -15,7 +15,9 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstructionResult_Output.TransferInstructionResult_Completed /// -public sealed record TransferInstructionResult_Output_TransferInstructionResult_Completed([property: DamlFieldAttribute("receiverHoldingCids")] IReadOnlyList> ReceiverHoldingCids) : IDamlRecord +public sealed record TransferInstructionResult_Output_TransferInstructionResult_Completed( + [property: DamlFieldAttribute("receiverHoldingCids")] IReadOnlyList> ReceiverHoldingCids +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Pending.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Pending.cs index d99a96b..1bde91b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Pending.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionResult_Output_TransferInstructionResult_Pending.cs @@ -13,7 +13,9 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstructionResult_Output.TransferInstructionResult_Pending /// -public sealed record TransferInstructionResult_Output_TransferInstructionResult_Pending([property: DamlFieldAttribute("transferInstructionCid")] ContractId TransferInstructionCid) : IDamlRecord +public sealed record TransferInstructionResult_Output_TransferInstructionResult_Pending( + [property: DamlFieldAttribute("transferInstructionCid")] ContractId TransferInstructionCid +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionView.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionView.cs index e4a468b..c5bc978 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionView.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstructionView.cs @@ -16,7 +16,13 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstructionView /// -public sealed record TransferInstructionView([property: DamlFieldAttribute("originalInstructionCid")] ContractId? OriginalInstructionCid, [property: DamlFieldAttribute("transfer")] Transfer Transfer, [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta) : IDamlRecord +public sealed record TransferInstructionView( + [property: DamlFieldAttribute("originalInstructionCid")] ContractId? OriginalInstructionCid, + [property: DamlFieldAttribute("transfer")] Transfer Transfer, + [property: DamlFieldAttribute("expiresAt")] DateTimeOffset? ExpiresAt, + [property: DamlFieldAttribute("availableActions")] IReadOnlyDictionary>> AvailableActions, + [property: DamlFieldAttribute("meta")] Splice.Api.Token.Metadata.V1.Metadata Meta +) : ITransferInstruction, IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Accept.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Accept.cs index 7c15225..b863deb 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Accept.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Accept.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstruction_Accept /// -public sealed record TransferInstruction_Accept([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record TransferInstruction_Accept( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Reject.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Reject.cs index 8bcbe21..fe5b1d6 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Reject.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Reject.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstruction_Reject /// -public sealed record TransferInstruction_Reject([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record TransferInstruction_Reject( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Withdraw.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Withdraw.cs index eb6a743..3f761d9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Withdraw.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-api-token-transfer-instruction-v2/expected/Splice/Api/Token/Transfer/Instruction/V2/TransferInstruction_Withdraw.cs @@ -14,7 +14,10 @@ namespace Splice.Api.Token.Transfer.Instruction.V2; /// /// Generated from Daml record TransferInstruction_Withdraw /// -public sealed record TransferInstruction_Withdraw([property: DamlFieldAttribute("actors")] IReadOnlyList Actors, [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs) : IDamlRecord +public sealed record TransferInstruction_Withdraw( + [property: DamlFieldAttribute("actors")] IReadOnlyList Actors, + [property: DamlFieldAttribute("extraArgs")] Splice.Api.Token.Metadata.V1.ExtraArgs ExtraArgs +) : IDamlRecord { /// Converts this value to a DamlRecord. public DamlRecord ToRecord() => DamlRecord.Create( diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/README.md index 1885e33..47bdbf9 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/README.md @@ -110,11 +110,14 @@ Each generated file emits only the namespaces its body actually references, tracked at codegen time. No generated file emits an unused `using`, so the generated headers carry no `#pragma warning disable CS8019`. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-test-token-v1 @@ -123,10 +126,12 @@ scripts/refresh-snapshot.sh splice-test-token-v1 ## The vendored inputs `intermediate.binpb` is the canonical codegen input for the drift test; -`splice-test-token-v1.dar` is the upstream Splice archive it was derived -from (`splice-test-token-v1-1.0.0.dar` from the Splice `0.6.13` -`splice-node` release tarball), kept alongside for provenance. Do not -regenerate either from a local build without a clear reason. If the upstream -Splice package genuinely needs to advance, replace the files in place, -refresh the `expected/` snapshot per the procedure above, and call out the -version bump in the pull request description. +`splice-test-token-v1.dar` is the upstream Splice archive it was derived from +(`splice-test-token-v1-1.0.0.dar` from the Splice `0.7.5` `splice-node` +release tarball), kept alongside as the frozen upstream artifact and used as +the regeneration input. Do not hand-edit `intermediate.binpb`: the refresh +script regenerates it from this DAR on every run, and CI regenerates it the +same way and fails on any byte delta. If the upstream Splice package genuinely +needs to advance, replace the files in place, refresh the snapshot per the +procedure above, and call out the version bump in the pull request +description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/Token.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/Token.cs index e2e3c4d..ac7e1e6 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/Token.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/Token.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,9 @@ namespace Splice.Test.Token.V1; /// /// Generated from Daml template Splice.Testing.Tokens.TestTokenV1:Token /// -public sealed partial record Token([property: DamlFieldAttribute("holding")] Splice.Api.Token.Holding.V1.HoldingView Holding) : ITemplate, IImplements +public sealed partial record Token( + [property: DamlFieldAttribute("holding")] Splice.Api.Token.Holding.V1.HoldingView Holding +) : ITemplate, IImplements, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("aaa0b576b5a3db49b3a4f7a4710fe6f8ae462aabbde4da89f639eb87fd62e90c", "Splice.Testing.Tokens.TestTokenV1", "Token"); @@ -60,6 +63,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for Token. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -70,7 +74,7 @@ public sealed record Contract(ContractId Id, Token Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), Token.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Test.Token.V1.Token.FromRecord(@event.CreateArguments)); } } @@ -115,7 +119,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class TokenNonContractExtensions @@ -142,15 +146,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -160,14 +164,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenAllocation.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenAllocation.cs index 3d8cecc..32bfe5a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenAllocation.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenAllocation.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,9 @@ namespace Splice.Test.Token.V1; /// /// Generated from Daml template Splice.Testing.Tokens.TestTokenV1:TokenAllocation /// -public sealed partial record TokenAllocation([property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V1.AllocationSpecification Allocation) : ITemplate, IImplements, IImplements +public sealed partial record TokenAllocation( + [property: DamlFieldAttribute("allocation")] Splice.Api.Token.Allocation.V1.AllocationSpecification Allocation +) : ITemplate, IImplements, IImplements, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("aaa0b576b5a3db49b3a4f7a4710fe6f8ae462aabbde4da89f639eb87fd62e90c", "Splice.Testing.Tokens.TestTokenV1", "TokenAllocation"); @@ -60,6 +63,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for TokenAllocation. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -70,7 +74,7 @@ public sealed record Contract(ContractId Id, TokenAllocation Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), TokenAllocation.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Test.Token.V1.TokenAllocation.FromRecord(@event.CreateArguments)); } } @@ -115,7 +119,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class TokenAllocationNonContractExtensions @@ -142,15 +146,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -160,14 +164,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenRules.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenRules.cs index af1a870..ef92c14 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenRules.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenRules.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,9 @@ namespace Splice.Test.Token.V1; /// /// Generated from Daml template Splice.Testing.Tokens.TestTokenV1:TokenRules /// -public sealed partial record TokenRules([property: DamlFieldAttribute("admin")] Party Admin) : ITemplate, IImplements, IImplements +public sealed partial record TokenRules( + [property: DamlFieldAttribute("admin")] Party Admin +) : ITemplate, IImplements, IImplements, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("aaa0b576b5a3db49b3a4f7a4710fe6f8ae462aabbde4da89f639eb87fd62e90c", "Splice.Testing.Tokens.TestTokenV1", "TokenRules"); @@ -60,6 +63,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for TokenRules. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -70,7 +74,7 @@ public sealed record Contract(ContractId Id, TokenRules Data) : IContractCreates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), TokenRules.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Test.Token.V1.TokenRules.FromRecord(@event.CreateArguments)); } } @@ -88,25 +92,23 @@ public static class TokenRulesSubmissionExtensions { /// /// Creates a new contract on the ledger. - /// The submitter is passed explicitly via . The static - /// analyzer could not resolve the Daml signatory clause to payload-field - /// references — typically because the expression involves the template key, a - /// constant, or a function call. implicitly converts - /// from a single Party, so single-party callers still pass one literal. + /// The submitting parties are derived from the payload — each Daml signatory is + /// a reference to a payload field, so the caller never restates a party that's + /// already in the record. /// /// The ledger client. /// The contract payload. - /// The submitter party set (actAs + optional readAs). /// Cancellation token. public static Task>> CreateAsync( this ILedgerWriter client, TokenRules payload, - SubmitterInfo submitter, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(payload); + SubmitterInfo submitter = payload.Admin; + return client.TryCreateAsync(payload, submitter, cancellationToken: cancellationToken); } } @@ -115,7 +117,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class TokenRulesNonContractExtensions @@ -142,15 +144,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -160,14 +162,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenTransferOffer.cs b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenTransferOffer.cs index 8dd7eff..33da84a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenTransferOffer.cs +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/expected/Splice/Test/Token/V1/TokenTransferOffer.cs @@ -6,6 +6,7 @@ #nullable enable using Daml.Ledger.Abstractions; +using Daml.Ledger.Abstractions.Extensions; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -20,7 +21,9 @@ namespace Splice.Test.Token.V1; /// /// Generated from Daml template Splice.Testing.Tokens.TestTokenV1:TokenTransferOffer /// -public sealed partial record TokenTransferOffer([property: DamlFieldAttribute("transfer")] Splice.Api.Token.Transfer.Instruction.V1.Transfer Transfer) : ITemplate, IImplements, IImplements +public sealed partial record TokenTransferOffer( + [property: DamlFieldAttribute("transfer")] Splice.Api.Token.Transfer.Instruction.V1.Transfer Transfer +) : ITemplate, IImplements, IImplements, IDamlRecord { /// Gets the template identifier. public static Identifier TemplateId { get; } = new("aaa0b576b5a3db49b3a4f7a4710fe6f8ae462aabbde4da89f639eb87fd62e90c", "Splice.Testing.Tokens.TestTokenV1", "TokenTransferOffer"); @@ -60,6 +63,7 @@ public DamlRecord ToRecord() => DamlRecord.Create( }; /// Contract ID for TokenTransferOffer. + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] public sealed record ContractId(string Value) : ContractId(Value), IExercises { ContractId IExercises.ContractId => this; @@ -70,7 +74,7 @@ public sealed record Contract(ContractId Id, TokenTransferOffer Data) : IContrac { /// Creates a Contract from a CreatedEvent. public static Contract FromCreatedEvent(CreatedEvent @event) => - new(new ContractId(@event.ContractId), TokenTransferOffer.FromRecord(@event.CreateArguments)); + new(new ContractId(@event.ContractId), global::Splice.Test.Token.V1.TokenTransferOffer.FromRecord(@event.CreateArguments)); } } @@ -115,7 +119,7 @@ public static Task>> CreateAsync( /// Async exerciser extensions for contract IDs whose choices /// return a non-contract-id payload (Decimal, records, lists, Unit, etc.). /// Each method submits the choice via -/// ILedgerWriter.TrySubmitAndWaitForTransactionAsync and lifts the typed result +/// SingleCommandExtensions.TrySubmitSingleAsync and lifts the typed result /// into ExerciseOutcome<TReturn>. /// public static class TokenTransferOfferNonContractExtensions @@ -142,15 +146,15 @@ public static ExerciseCommand ArchiveCommand( /// /// The contract on which to exercise the choice. /// The ledger client. - /// The party submitting the command. + /// The submitter party set (actAs + optional readAs), so a submitter that must read contracts it does not act as stays expressible. /// Optional workflow id; passed through to the ledger when supplied. No default — workflow IDs are correlation keys, and a per-choice default would bucket every submission of the same choice under one ID. - /// Optional command id for deduplication; a fresh id is minted only when omitted. Pass the same id across a retry of a lost-but-accepted submission so the ledger deduplicates the resubmission instead of re-executing the choice. - /// Optional per-call deadline, enforced server-side; the default null applies no deadline. An overrun surfaces as an InfraError outcome. + /// Optional command id for deduplication; a fresh id is minted only when omitted, and a minted id is not reported back on a failed submission. Supply and retain your own id to make a retry of a lost-but-accepted submission deduplicable, so the ledger deduplicates the resubmission instead of re-executing the choice. + /// Optional per-call deadline, applied best-effort by the transport; transports without a server-side deadline apply a client-side bound only. The default null applies no deadline. An overrun surfaces as an InfraError outcome. /// Cancellation token. public static async Task> ArchiveAsync( this ContractId contractId, ILedgerWriter client, - Party actAs, + SubmitterInfo submitter, string? workflowId = null, CommandId? commandId = null, TimeSpan? timeout = null, @@ -160,14 +164,7 @@ public static async Task> ArchiveAsync( var command = contractId.ArchiveCommand(); - var submission = CommandsSubmission.Single(command) - .WithCommandId(commandId ?? new CommandId(Guid.NewGuid().ToString())); - if (!string.IsNullOrEmpty(workflowId)) - { - submission = submission.WithWorkflowId(new WorkflowId(workflowId)); - } - - var outcome = await client.TrySubmitAndWaitForTransactionAsync(submission, actAs, timeout: timeout, cancellationToken: cancellationToken).ConfigureAwait(false); + var outcome = await client.TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken).ConfigureAwait(false); return outcome.ProjectCommitted(tx => ProjectArchiveResult(tx, contractId.Value)); } diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/intermediate.binpb b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-test-token-v1/intermediate.binpb index 31f7c3f3f8fd57fd8d02f300d10cb646f254d191..1884336b4d9ecbbfcd2023ed78da8e3238014d20 100644 GIT binary patch delta 10935 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 PS3#hxtfI0)7yI=;C4`6! diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/README.md b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/README.md index 589f219..a1a384a 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/README.md +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/README.md @@ -6,9 +6,10 @@ against the `expected/` tree. This package emits no C# types, so its snapshot is pinned by the `emits-no-types` marker file in this directory: the drift test asserts that -codegen produces zero `.cs` output (only the `.daml-langversion` state file the -codegen always writes). The day the package starts emitting a type, the drift -test fails, so the change is caught rather than shipped unnoticed. +codegen produces zero `.cs` output, and the publish-time guard reads the same +marker before accepting an empty generated tree. The day the package starts +emitting a type, both fail, so the change is caught rather than shipped +unnoticed. ## Why this DAR @@ -27,11 +28,14 @@ zero generated `.cs`. When a package legitimately gains a codegen-visible type, remove the marker and refresh the `expected/` snapshot so it pins the emitted types instead. -## Refreshing the `expected/` snapshot +## Refreshing the snapshot -The `expected/` tree is the emitter's output for the vendored -`intermediate.binpb`. To refresh it after an intentional codegen change, -run from the repo root (POSIX shell; on Windows, use WSL or Git Bash): +`scripts/refresh-snapshot.sh` regenerates `intermediate.binpb` from the vendored +DAR with a JVM helper assembled from the working tree's sources, then +regenerates the `expected/` tree from that proto with the current codegen +source. Pass `--skip-binpb` to keep the vendored proto and refresh only +`expected/`. Run it from the repo root (POSIX shell; on Windows, use WSL or Git +Bash): ```bash scripts/refresh-snapshot.sh splice-token-standard-utils @@ -41,9 +45,11 @@ scripts/refresh-snapshot.sh splice-token-standard-utils `intermediate.binpb` is the canonical codegen input for the drift test; `splice-token-standard-utils.dar` is the upstream Splice archive it was -derived from (`splice-token-standard-utils-2.0.0.dar` from the Splice -`0.6.13` `splice-node` release tarball), kept alongside for provenance. Do not -regenerate either from a local build without a clear reason. If the upstream -Splice package genuinely needs to advance, replace the files in place, refresh -the `expected/` snapshot per the procedure above, and call out the version bump -in the pull request description. +derived from (`splice-token-standard-utils-2.0.0.dar` from the Splice `0.7.5` +`splice-node` release tarball), kept alongside as the frozen upstream artifact +and used as the regeneration input. Do not hand-edit `intermediate.binpb`: the +refresh script regenerates it from this DAR on every run, and CI regenerates +it the same way and fails on any byte delta. If the upstream Splice package +genuinely needs to advance, replace the files in place, refresh the snapshot +per the procedure above, and call out the version bump in the pull request +description. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/emits-no-types b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/emits-no-types index 4595fc0..6f78e98 100644 --- a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/emits-no-types +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/emits-no-types @@ -3,7 +3,8 @@ re-exports of types owned by other packages, so the C# codegen produces no .cs output for it. The presence of this marker tells DriftDetectionTests to pin the snapshot as -emitting zero .cs (only the .daml-langversion state file), and to fail if -codegen ever produces a .cs — so a future type-bearing change is caught rather -than shipped unnoticed. See README.md. If the package legitimately gains a -codegen-visible type, remove this marker and refresh the expected/ snapshot. +emitting zero .cs, and to fail if codegen ever produces a .cs — so a future +type-bearing change is caught rather than shipped unnoticed. The publish-time +guard reads the same marker before accepting an empty generated tree. See +README.md. If the package legitimately gains a codegen-visible type, remove this +marker and refresh the expected/ snapshot. diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.daml-langversion b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.daml-langversion deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.gitkeep b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.gitkeep new file mode 100644 index 0000000..193c521 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/Snapshots/splice-token-standard-utils/expected/.gitkeep @@ -0,0 +1,4 @@ +This package emits no C# types, so its pinned snapshot tree is empty. The +directory itself is the fixture: both the in-process drift test and the +publish-time drift guard require it to exist, and git does not track an empty +directory without a file in it. diff --git a/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesRequireForFieldTypeTests.cs b/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesRequireForFieldTypeTests.cs index 0c58216..b0e474c 100644 --- a/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesRequireForFieldTypeTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesRequireForFieldTypeTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -51,7 +51,7 @@ private static DamlTypeRef Ref(string packageId, string module, string name) => private static IReadOnlyCollection RequiredFor(ICrossPackageResolver resolver, DamlType type) { var indent = new IndentWriter(new StringBuilder()); - StdlibPackages.RequireForFieldType(resolver, indent, type); + StdlibPackages.RequireForFieldType(resolver, NamedPackage("local-pkg", "local"), indent, type); return indent.RequiredUsings; } @@ -103,4 +103,24 @@ public void RequireForFieldType_recurses_into_user_type_app_arguments_skipping_t RequiredFor(resolver, type).Should().BeEquivalentTo("System"); } + + [Fact] + public void RequireForFieldType_requires_stdlib_for_an_optional_argument_to_an_emitted_generic() + { + var resolver = Resolver(NamedPackage(UserPackageId, "my-app")); + var type = new DamlTypeApp( + Ref(UserPackageId, "MyModule", "Box"), + [App(DamlPrimitive.Optional, Prim(DamlPrimitive.Text))]); + + RequiredFor(resolver, type).Should().BeEquivalentTo("Daml.Runtime.Stdlib"); + } + + [Fact] + public void RequireForFieldType_requires_stdlib_for_an_optional_over_a_type_variable() + { + var resolver = Resolver(NamedPackage(UserPackageId, "my-app")); + + RequiredFor(resolver, App(DamlPrimitive.Optional, new DamlTypeVar("a"))) + .Should().BeEquivalentTo("Daml.Runtime.Stdlib"); + } } diff --git a/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesTests.cs b/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesTests.cs index 1e6d310..d159b1e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/StdlibPackagesTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/SubmissionExtensionsEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/SubmissionExtensionsEmitterTests.cs index 894c4df..5cc6936 100644 --- a/tests/Daml.Codegen.CSharp.Tests/SubmissionExtensionsEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/SubmissionExtensionsEmitterTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -21,19 +21,22 @@ private static DamlFieldDefinition PartyField(string name) => private static DamlPartyAnalysis StaticParties(params string[] fieldNames) => DamlPartyAnalysis.Static(fieldNames.Select(n => (DamlPartyReference)new DamlPartyPayloadField(n)).ToList()); - private static DamlTemplate Template( + private sealed record TemplateFixture(DamlTemplate Template, IReadOnlyList Fields); + + private static TemplateFixture Template( string name, IReadOnlyList fields, DamlPartyAnalysis? signatories = null, DamlPartyAnalysis? observers = null) => - new() - { - Name = name, - Fields = fields, - Choices = [], - Signatories = signatories ?? DamlPartyAnalysis.Dynamic, - Observers = observers ?? DamlPartyAnalysis.Dynamic, - }; + new( + new DamlTemplate + { + Name = name, + Choices = [], + Signatories = signatories ?? DamlPartyAnalysis.Dynamic, + Observers = observers ?? DamlPartyAnalysis.Dynamic, + }, + fields); private static PackageEmitContext Context(DamlTemplate template) { @@ -58,18 +61,18 @@ private static PackageEmitContext Context(DamlTemplate template) return PackageEmitContext.ForPackage(package, new CodeGenOptions()); } - private string Emit(DamlTemplate template, CodeGenOptions? options = null) + private string Emit(TemplateFixture fixture, CodeGenOptions? options = null) { - var context = Context(template); + var context = Context(fixture.Template); var emitter = new SubmissionExtensionsEmitter(context, options ?? new CodeGenOptions(), _party); var sb = new StringBuilder(); var indent = new IndentWriter(sb); - emitter.TryWriteSubmissionExtensions(indent, template, template.Fields); + emitter.TryWriteSubmissionExtensions(indent, fixture.Template, fixture.Fields); return sb.ToString(); } [Fact] - public void emits_the_submission_extensions_class_for_a_template() + public void SubmissionExtensionsEmitter_emits_the_submission_extensions_class_for_a_template() { var template = Template( "Asset", @@ -82,7 +85,7 @@ public void emits_the_submission_extensions_class_for_a_template() } [Fact] - public void emits_the_submission_extensions_class_even_when_signatories_are_dynamic() + public void SubmissionExtensionsEmitter_emits_the_submission_extensions_class_even_when_signatories_are_dynamic() { var template = Template( "Vault", @@ -95,7 +98,7 @@ public void emits_the_submission_extensions_class_even_when_signatories_are_dyna } [Fact] - public void static_single_signatory_create_derives_submitter_from_the_payload_and_omits_the_parameter() + public void SubmissionExtensionsEmitter_static_single_signatory_create_derives_submitter_from_the_payload_and_omits_the_parameter() { var template = Template( "Asset", @@ -109,7 +112,7 @@ public void static_single_signatory_create_derives_submitter_from_the_payload_an } [Fact] - public void static_multiple_signatory_create_builds_a_submitter_set_from_the_payload_fields() + public void SubmissionExtensionsEmitter_static_multiple_signatory_create_builds_a_submitter_set_from_the_payload_fields() { var template = Template( "Trade", @@ -125,7 +128,7 @@ public void static_multiple_signatory_create_builds_a_submitter_set_from_the_pay } [Fact] - public void dynamic_signatory_create_takes_an_explicit_submitter_parameter() + public void SubmissionExtensionsEmitter_dynamic_signatory_create_takes_an_explicit_submitter_parameter() { var template = Template( "Keyed", @@ -140,7 +143,7 @@ public void dynamic_signatory_create_takes_an_explicit_submitter_parameter() } [Fact] - public void static_non_empty_observers_emit_the_observers_helper() + public void SubmissionExtensionsEmitter_static_non_empty_observers_emit_the_observers_helper() { var template = Template( "Note", @@ -155,7 +158,7 @@ public void static_non_empty_observers_emit_the_observers_helper() } [Fact] - public void static_empty_observers_skip_the_observers_helper() + public void SubmissionExtensionsEmitter_static_empty_observers_skip_the_observers_helper() { var template = Template( "Note", @@ -169,7 +172,7 @@ public void static_empty_observers_skip_the_observers_helper() } [Fact] - public void dynamic_observers_skip_the_observers_helper() + public void SubmissionExtensionsEmitter_dynamic_observers_skip_the_observers_helper() { var template = Template( "Note", @@ -183,7 +186,7 @@ public void dynamic_observers_skip_the_observers_helper() } [Fact] - public void unknown_signatory_subtype_falls_back_to_an_explicit_submitter_parameter() + public void SubmissionExtensionsEmitter_unknown_signatory_subtype_falls_back_to_an_explicit_submitter_parameter() { var template = Template( "Asset", diff --git a/tests/Daml.Codegen.CSharp.Tests/TemplateCodeGenTests.cs b/tests/Daml.Codegen.CSharp.Tests/TemplateCodeGenTests.cs index b8edf53..1323476 100644 --- a/tests/Daml.Codegen.CSharp.Tests/TemplateCodeGenTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/TemplateCodeGenTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; @@ -28,11 +28,18 @@ private static DamlModule SingleTemplateModule(string moduleName, string templat new DamlTemplate { Name = templateName, - Fields = [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))], Choices = [] } ], - DataTypes = [], + DataTypes = + [ + new DamlDataType + { + Name = templateName, + Definition = new DamlRecordDefinition( + [new DamlFieldDefinition("owner", new DamlPrimitiveType(DamlPrimitive.Party))]) + } + ], Interfaces = [] }; @@ -81,6 +88,89 @@ public void Generate_should_place_the_template_file_under_the_root_namespace_dir templateFile!.RelativePath.Should().Contain("Test/Package/"); } + private static DamlModule ModuleWithTemplateButNoDataTypes(string moduleName, string templateName) => + new() + { + Name = moduleName, + Templates = + [ + new DamlTemplate + { + Name = templateName, + Choices = [] + } + ], + DataTypes = [], + Interfaces = [] + }; + + [Fact] + public void Generate_should_fail_loudly_when_a_template_has_no_same_named_record_definition() + { + var generator = CreateGenerator(); + + var act = () => generator.Generate(CreateTestDar(ModuleWithTemplateButNoDataTypes("Test.Module", "Orphan"))).ToList(); + + act.Should().Throw() + .WithMessage( + "*Test.Module:Orphan*", + "an LF template payload is always a same-named record, so a template without one is a malformed " + + "model that must not be papered over with an empty payload — and the diagnostic has to name the " + + "module too, since template names are only unique within one"); + } + + [Fact] + public void Generate_should_not_validate_a_recordless_template_the_root_filter_excludes() + { + var generator = CreateGenerator(new CodeGenOptions { RootFilter = "Test\\.Module:Keep.*" }); + + var act = () => generator.Generate( + CreateTestDar(ModuleWithTemplateButNoDataTypes("Test.Module", "Orphan"))).ToList(); + + act.Should().NotThrow( + "the root filter skips a template before the payload lookup runs, so excluding a template also " + + "excludes it from validation — the boundary between what this generator fails loudly on and what " + + "it never looks at"); + } + + private static DamlModule ModuleWithTemplateAndSameNamedVariant(string moduleName, string templateName) => + new() + { + Name = moduleName, + Templates = + [ + new DamlTemplate + { + Name = templateName, + Choices = [] + } + ], + DataTypes = + [ + new DamlDataType + { + Name = templateName, + Definition = new DamlVariantDefinition([new DamlVariantConstructor("Only", null)]) + } + ], + Interfaces = [] + }; + + [Fact] + public void Generate_should_name_the_kind_when_the_same_named_definition_is_not_a_record() + { + var generator = CreateGenerator(); + + var act = () => generator.Generate( + CreateTestDar(ModuleWithTemplateAndSameNamedVariant("Test.Module", "Orphan"))).ToList(); + + act.Should().Throw() + .WithMessage( + "*DamlVariantDefinition*", + "a same-named definition of the wrong kind is a different malformation from an absent one, and " + + "reporting only that no record was found leaves the reader staring at a definition that is right there"); + } + [Fact] public void Generate_should_delegate_template_emission_to_the_template_emitter() { diff --git a/tests/Daml.Codegen.CSharp.Tests/TemplateEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/TemplateEmitterTests.cs index 3220222..fd0a3e2 100644 --- a/tests/Daml.Codegen.CSharp.Tests/TemplateEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/TemplateEmitterTests.cs @@ -3,8 +3,10 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; +using Daml.Codegen.CSharp.Tests.TestHelpers; +using Microsoft.Extensions.Logging; using Xunit; using static Daml.Codegen.CSharp.Tests.TestHelpers.DamlModelBuilder; using static Daml.Codegen.CSharp.Tests.TestHelpers.GeneratorFactory; @@ -50,17 +52,18 @@ private static CodeGenOptions Options( }; private static string EmitTemplate( - DamlTemplate template, + TemplateFixture fixture, DamlDataType[]? dataTypes = null, DamlInterface[]? interfaces = null, CodeGenOptions? options = null, Version? version = null, - string? upgradedPackageId = null) + string? upgradedPackageId = null, + ILogger? logger = null) { var module = new DamlModule { Name = ModuleName, - Templates = [template], + Templates = [fixture.Template], DataTypes = dataTypes ?? [], Interfaces = interfaces ?? [], }; @@ -73,30 +76,46 @@ private static string EmitTemplate( var recordSerialization = new RecordSerializationEmitter(context, resolver, options, mapper); var choiceEmitter = new ChoiceEmitter(context, resolver, options, mapper, party); var submissionExtensions = new SubmissionExtensionsEmitter(context, options, party); - var emitter = new TemplateEmitter(context, resolver, mapper, recordSerialization, choiceEmitter, submissionExtensions, options); + var emitter = new TemplateEmitter(context, resolver, recordSerialization, choiceEmitter, submissionExtensions, options, logger); var sb = new StringBuilder(); - emitter.WriteTemplateType(new IndentWriter(sb), package, module, template, template.Fields); + emitter.WriteTemplateType(new IndentWriter(sb), package, module, fixture.Template, fixture.Fields); return sb.ToString(); } - private static DamlTemplate Template( + private static string EmitTemplate( + DamlTemplate template, + DamlDataType[]? dataTypes = null, + DamlInterface[]? interfaces = null, + CodeGenOptions? options = null, + Version? version = null, + string? upgradedPackageId = null, + ILogger? logger = null) => + EmitTemplate(new TemplateFixture(template, []), dataTypes, interfaces, options, version, upgradedPackageId, logger); + + private sealed record TemplateFixture(DamlTemplate Template, IReadOnlyList Fields); + + private static TemplateFixture Template( string name, IReadOnlyList? fields = null, IReadOnlyList? choices = null, DamlType? key = null) => - new() - { - Name = name, - Fields = fields ?? [], - Choices = choices ?? [], - Key = key, - }; + new( + new DamlTemplate + { + Name = name, + Choices = choices ?? [], + Key = key, + }, + fields ?? []); private static DamlFieldDefinition Field(string name, DamlPrimitive primitive) => new(name, new DamlPrimitiveType(primitive)); + private static DamlDataType RecordDataType(string name, params DamlFieldDefinition[] fields) => + new() { Name = name, Definition = new DamlRecordDefinition(fields) }; + [Fact] - public void emits_the_template_record_with_the_ITemplate_facet() + public void TemplateEmitter_emits_the_template_record_with_the_ITemplate_facet() { var output = EmitTemplate(Template("SimpleTemplate", [Field("owner", DamlPrimitive.Party)])); @@ -105,13 +124,12 @@ public void emits_the_template_record_with_the_ITemplate_facet() } [Fact] - public void adds_the_IImplements_facet_when_the_template_implements_an_interface() + public void TemplateEmitter_adds_the_IImplements_facet_when_the_template_implements_an_interface() { var output = EmitTemplate( new DamlTemplate { Name = "Vault", - Fields = [Field("owner", DamlPrimitive.Party)], Choices = [], Implements = [new DamlTypeRef(LocalPackageId, ModuleName, "Asset")], }, @@ -121,30 +139,143 @@ public void adds_the_IImplements_facet_when_the_template_implements_an_interface } [Fact] - public void orders_IImplements_after_the_key_facet_in_the_base_list() + public void TemplateEmitter_adds_the_IHasKey_facet_when_the_template_declares_a_key() { var output = EmitTemplate( new DamlTemplate { Name = "KeyedVault", - Fields = [Field("owner", DamlPrimitive.Party)], Choices = [], Key = new DamlPrimitiveType(DamlPrimitive.Party), Implements = [new DamlTypeRef(LocalPackageId, ModuleName, "Asset")], }, interfaces: [new DamlInterface { Name = "Asset", Choices = [] }]); - output.Should().Contain(": ITemplate, IHasKey, IImplements"); + output.Should().Contain(": ITemplate, IImplements, IHasKey"); + } + + [Fact] + public void TemplateEmitter_keeps_a_key_less_template_off_the_IHasKey_facet() + { + var output = EmitTemplate(Template("Keyless", [Field("owner", DamlPrimitive.Party)])); + + output.Should().NotContain("IHasKey"); + output.Should().NotContain("KeyDescriptor"); + } + + [Fact] + public void TemplateEmitter_emits_the_key_witness_carrying_the_codec_for_a_record_key() + { + var output = EmitTemplate( + Template( + "Account", + [Field("custodian", DamlPrimitive.Party)], + key: new DamlTypeRef(LocalPackageId, ModuleName, "AccountKey")), + dataTypes: [RecordDataType("AccountKey", Field("custodian", DamlPrimitive.Party))]); + + output.Should().Contain( + ": ITemplate, IHasKey"); + output.Should().Contain( + "public static KeyDescriptor Key { get; } ="); + output.Should().Contain( + "KeyEncoder = key => key.ToRecord(),"); + output.Should().Contain( + "KeyDecoder = value => global::Test.Package.AccountKey.FromRecord(value.As()),"); + } + + [Fact] + public void TemplateEmitter_emits_the_key_witness_for_a_key_that_is_not_a_record() + { + var output = EmitTemplate( + Template( + "Steward", + [Field("steward", DamlPrimitive.Party)], + key: new DamlPrimitiveType(DamlPrimitive.Party))); + + output.Should().Contain(": ITemplate, IHasKey"); + output.Should().Contain("public static KeyDescriptor Key { get; } ="); + output.Should().Contain( + "KeyEncoder = key => key.ToDamlValue(),"); + output.Should().Contain( + "KeyDecoder = value => Party.FromDamlValue(value.As()),"); + } + + [Fact] + public void TemplateEmitter_hides_the_key_witness_behind_the_facet_when_a_field_takes_the_name() + { + var logger = new CapturingLogger(); + + var output = EmitTemplate( + Template( + "Locker", + [Field("key", DamlPrimitive.Text)], + key: new DamlPrimitiveType(DamlPrimitive.Party)), + logger: logger); + + output.Should().Contain( + "static KeyDescriptor IHasKey.Key { get; } ="); + output.Should().Contain("KeyEncoder = key => key.ToDamlValue(),"); + output.Should().Contain("KeyDecoder = value => Party.FromDamlValue(value.As()),"); + output.Should().NotContain("public static KeyDescriptor Key"); + output.Should().Contain("string Key"); + } + + [Fact] + public void TemplateEmitter_hides_the_key_witness_behind_the_facet_when_the_template_is_named_Key() + { + var output = EmitTemplate( + Template( + "Key", + [Field("owner", DamlPrimitive.Party)], + key: new DamlPrimitiveType(DamlPrimitive.Party))); + + output.Should().Contain( + "static KeyDescriptor IHasKey.Key { get; } ="); + output.Should().Contain("KeyEncoder = key => key.ToDamlValue(),"); + output.Should().Contain("KeyDecoder = value => Party.FromDamlValue(value.As()),"); + output.Should().NotContain("public static KeyDescriptor Key"); + } + + [Fact] + public void TemplateEmitter_warns_when_the_template_name_takes_the_key_witness_name() + { + var logger = new CapturingLogger(); + + EmitTemplate( + Template( + "Key", + [Field("owner", DamlPrimitive.Party)], + key: new DamlPrimitiveType(DamlPrimitive.Party)), + logger: logger); + + logger.Warnings.Should().ContainSingle() + .Which.Should().Contain("Test.Module:Key"); + } + + [Fact] + public void TemplateEmitter_warns_when_a_field_takes_the_key_witness_name() + { + var logger = new CapturingLogger(); + + EmitTemplate( + Template( + "Locker", + [Field("key", DamlPrimitive.Text)], + key: new DamlPrimitiveType(DamlPrimitive.Party)), + logger: logger); + + logger.Warnings.Should().ContainSingle() + .Which.Should().Contain("Test.Module:Locker") + .And.Contain("key"); } [Fact] - public void orders_IImplements_after_the_IUpgradeable_facet_in_the_base_list() + public void TemplateEmitter_orders_IImplements_after_the_IUpgradeable_facet_in_the_base_list() { var output = EmitTemplate( new DamlTemplate { Name = "KeyedUpgradedVault", - Fields = [Field("owner", DamlPrimitive.Party)], Choices = [], Key = new DamlPrimitiveType(DamlPrimitive.Party), Implements = [new DamlTypeRef(LocalPackageId, ModuleName, "Asset")], @@ -152,11 +283,20 @@ public void orders_IImplements_after_the_IUpgradeable_facet_in_the_base_list() interfaces: [new DamlInterface { Name = "Asset", Choices = [] }], upgradedPackageId: "old-package-id"); - output.Should().Contain(": ITemplate, IHasKey, IUpgradeable, IImplements"); + output.Should().Contain( + ": ITemplate, IUpgradeable, IImplements, IHasKey, IDamlRecord"); } [Fact] - public void emits_static_template_metadata() + public void TemplateEmitter_adds_the_generic_IDamlRecord_facet_naming_the_template_itself() + { + var output = EmitTemplate(Template("SimpleTemplate", [Field("owner", DamlPrimitive.Party)])); + + output.Should().Contain(": ITemplate, IDamlRecord"); + } + + [Fact] + public void TemplateEmitter_emits_static_template_metadata() { var output = EmitTemplate(Template("Asset", [Field("owner", DamlPrimitive.Party)])); @@ -170,7 +310,7 @@ public void emits_static_template_metadata() } [Fact] - public void emits_static_daml_type_descriptor() + public void TemplateEmitter_emits_static_daml_type_descriptor() { var output = EmitTemplate(Template("Asset", [Field("owner", DamlPrimitive.Party)])); @@ -179,7 +319,7 @@ public void emits_static_daml_type_descriptor() } [Fact] - public void emits_the_nested_ContractId_record() + public void TemplateEmitter_emits_the_nested_ContractId_record() { var output = EmitTemplate(Template("Token", [Field("issuer", DamlPrimitive.Party)])); @@ -189,7 +329,7 @@ public void emits_the_nested_ContractId_record() } [Fact] - public void emits_the_nested_Contract_record() + public void TemplateEmitter_emits_the_nested_Contract_record() { var output = EmitTemplate(Template("Holding", [Field("amount", DamlPrimitive.Numeric)])); @@ -199,7 +339,7 @@ public void emits_the_nested_Contract_record() } [Fact] - public void maps_all_primitive_fields_to_their_csharp_types() + public void TemplateEmitter_maps_all_primitive_fields_to_their_csharp_types() { var output = EmitTemplate(Template("AllPrimitives", [ @@ -222,7 +362,7 @@ public void maps_all_primitive_fields_to_their_csharp_types() } [Fact] - public void maps_complex_container_fields() + public void TemplateEmitter_maps_complex_container_fields() { var output = EmitTemplate(Template("ComplexFields", [ @@ -243,7 +383,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), } [Fact] - public void emits_the_ToRecord_method() + public void TemplateEmitter_emits_the_ToRecord_method() { var output = EmitTemplate(Template("Item", [ @@ -257,7 +397,7 @@ public void emits_the_ToRecord_method() } [Fact] - public void emits_the_FromRecord_method() + public void TemplateEmitter_emits_the_FromRecord_method() { var output = EmitTemplate(Template("Status", [ @@ -271,7 +411,7 @@ public void emits_the_FromRecord_method() } [Fact] - public void serializes_list_fields_through_the_shared_serializer() + public void TemplateEmitter_serializes_list_fields_through_the_shared_serializer() { var output = EmitTemplate(Template("Tagged", [ @@ -284,7 +424,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), } [Fact] - public void serializes_optional_fields_through_the_shared_serializer() + public void TemplateEmitter_serializes_optional_fields_through_the_shared_serializer() { var output = EmitTemplate(Template("OptionalTemplate", [ @@ -297,7 +437,7 @@ [new DamlPrimitiveType(DamlPrimitive.Text)])), } [Fact] - public void emits_required_properties_when_primary_constructors_are_disabled() + public void TemplateEmitter_emits_required_properties_when_primary_constructors_are_disabled() { var output = EmitTemplate( Template("NoConstructor", [Field("value", DamlPrimitive.Text)]), @@ -308,7 +448,7 @@ public void emits_required_properties_when_primary_constructors_are_disabled() } [Fact] - public void emits_a_class_when_record_types_are_disabled() + public void TemplateEmitter_emits_a_class_when_record_types_are_disabled() { var output = EmitTemplate( Template("ClassTemplate", [Field("value", DamlPrimitive.Text)]), @@ -318,7 +458,7 @@ public void emits_a_class_when_record_types_are_disabled() } [Fact] - public void handles_a_template_with_no_fields() + public void TemplateEmitter_handles_a_template_with_no_fields() { var output = EmitTemplate(Template("EmptyTemplate")); @@ -328,7 +468,7 @@ public void handles_a_template_with_no_fields() } [Fact] - public void uses_the_package_version_in_metadata() + public void TemplateEmitter_uses_the_package_version_in_metadata() { var output = EmitTemplate( Template("Versioned", [Field("value", DamlPrimitive.Text)]), @@ -338,17 +478,28 @@ public void uses_the_package_version_in_metadata() } [Fact] - public void adds_the_IHasKey_facet_and_a_throwing_key_accessor_when_the_template_has_a_key() + public void TemplateEmitter_puts_the_key_on_the_active_contract_not_the_payload() { var output = EmitTemplate( Template("Keyed", [Field("owner", DamlPrimitive.Party)], key: new DamlPrimitiveType(DamlPrimitive.Party))); - output.Should().Contain(": ITemplate, IHasKey"); - output.Should().Contain("public Party Key => throw new global::System.NotImplementedException("); + output.Should().Contain("public sealed record Contract(ContractId Id, Keyed Data)"); + + output.Should().Contain("public required ContractKey Key { get; init; }"); + output.Should().Contain("? new ContractKey(Party.FromDamlValue(contractKey.Value.As()), contractKey.KeyHash)"); } [Fact] - public void adds_the_IUpgradeable_facet_when_the_package_is_an_upgrade() + public void TemplateEmitter_leaves_the_active_contract_of_a_key_less_template_with_two_parameters() + { + var output = EmitTemplate(Template("Keyless", [Field("owner", DamlPrimitive.Party)])); + + output.Should().Contain("public sealed record Contract(ContractId Id, Keyless Data) :"); + output.Should().NotContain("ContractKey"); + } + + [Fact] + public void TemplateEmitter_adds_the_IUpgradeable_facet_when_the_package_is_an_upgrade() { var output = EmitTemplate( Template("Upgraded", [Field("owner", DamlPrimitive.Party)]), @@ -359,7 +510,7 @@ public void adds_the_IUpgradeable_facet_when_the_package_is_an_upgrade() } [Fact] - public void delegates_choice_descriptor_emission_to_the_choice_emitter() + public void TemplateEmitter_delegates_choice_descriptor_emission_to_the_choice_emitter() { var output = EmitTemplate(Template( "WithChoice", @@ -379,7 +530,7 @@ public void delegates_choice_descriptor_emission_to_the_choice_emitter() } [Fact] - public void delegates_submission_extension_emission_to_the_submission_emitter() + public void TemplateEmitter_delegates_submission_extension_emission_to_the_submission_emitter() { var output = EmitTemplate(Template("Submittable", [Field("owner", DamlPrimitive.Party)])); @@ -388,7 +539,7 @@ public void delegates_submission_extension_emission_to_the_submission_emitter() } [Fact] - public void emits_every_xml_doc_when_enabled() + public void TemplateEmitter_emits_every_xml_doc_when_enabled() { var output = EmitTemplate( Template("Documented", [Field("owner", DamlPrimitive.Party)], key: new DamlPrimitiveType(DamlPrimitive.Party))); @@ -398,14 +549,13 @@ public void emits_every_xml_doc_when_enabled() output.Should().Contain("/// Gets the package ID."); output.Should().Contain("/// Gets the package name."); output.Should().Contain("/// Gets the package version."); - output.Should().Contain("/// Gets the contract key of type"); output.Should().Contain("/// Contract ID for Documented."); output.Should().Contain("/// Active contract for Documented."); output.Should().Contain("/// Creates a Contract from a CreatedEvent."); } [Fact] - public void omits_every_xml_doc_when_disabled() + public void TemplateEmitter_omits_every_xml_doc_when_disabled() { var output = EmitTemplate( Template("Documented", [Field("owner", DamlPrimitive.Party)], key: new DamlPrimitiveType(DamlPrimitive.Party)), @@ -416,7 +566,6 @@ public void omits_every_xml_doc_when_disabled() output.Should().NotContain("Gets the package ID"); output.Should().NotContain("Gets the package name"); output.Should().NotContain("Gets the package version"); - output.Should().NotContain("Gets the contract key of type"); output.Should().NotContain("Contract ID for Documented"); output.Should().NotContain("Active contract for Documented"); output.Should().NotContain("Creates a Contract from a CreatedEvent"); @@ -424,11 +573,12 @@ public void omits_every_xml_doc_when_disabled() output.Should().Contain("public sealed partial record Documented"); output.Should().Contain("public static Identifier TemplateId { get; }"); output.Should().Contain("public sealed record ContractId(string Value)"); - output.Should().Contain("public Party Key => throw new global::System.NotImplementedException("); + output.Should().Contain("public sealed record Contract(ContractId Id, Documented Data)"); + output.Should().Contain("public required ContractKey Key { get; init; }"); } [Fact] - public void emits_the_nested_choice_argument_partial_record() + public void TemplateEmitter_emits_the_nested_choice_argument_partial_record() { var module = new DamlModule { @@ -446,9 +596,9 @@ public void emits_the_nested_choice_argument_partial_record() var recordSerialization = new RecordSerializationEmitter(context, resolver, options, mapper); var choiceEmitter = new ChoiceEmitter(context, resolver, options, mapper, party); var submissionExtensions = new SubmissionExtensionsEmitter(context, options, party); - var emitter = new TemplateEmitter(context, resolver, mapper, recordSerialization, choiceEmitter, submissionExtensions, options); + var emitter = new TemplateEmitter(context, resolver, recordSerialization, choiceEmitter, submissionExtensions, options); - var template = Template("Account", [Field("owner", DamlPrimitive.Party)]); + var template = Template("Account", [Field("owner", DamlPrimitive.Party)]).Template; var choice = new DamlChoice { Name = "Transfer", @@ -473,7 +623,7 @@ public void emits_the_nested_choice_argument_partial_record() } [Fact] - public void filters_templates_with_the_root_filter() + public void TemplateEmitter_filters_templates_with_the_root_filter() { var options = new CodeGenOptions { @@ -489,10 +639,22 @@ public void filters_templates_with_the_root_filter() Name = ModuleName, Templates = [ - Template("IncludeMe", [Field("owner", DamlPrimitive.Party)]), - Template("ExcludeMe", [Field("owner", DamlPrimitive.Party)]), + Template("IncludeMe").Template, + Template("ExcludeMe").Template, + ], + DataTypes = + [ + new DamlDataType + { + Name = "IncludeMe", + Definition = new DamlRecordDefinition([Field("owner", DamlPrimitive.Party)]) + }, + new DamlDataType + { + Name = "ExcludeMe", + Definition = new DamlRecordDefinition([Field("owner", DamlPrimitive.Party)]) + }, ], - DataTypes = [], Interfaces = [], }; diff --git a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/CapturingLogger.cs b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/CapturingLogger.cs new file mode 100644 index 0000000..7246339 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/CapturingLogger.cs @@ -0,0 +1,39 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Logging; + +namespace Daml.Codegen.CSharp.Tests.TestHelpers; + +/// +/// An that keeps every record in memory, so a test can assert on the +/// severity and the formatted text the emitter produced. +/// +public sealed class CapturingLogger : ILogger +{ + /// Every record logged so far, in order. + public List<(LogLevel Level, string Message)> Records { get; } = []; + + /// The formatted text of every warning logged so far. + public IEnumerable Warnings => + Records.Where(r => r.Level == LogLevel.Warning).Select(r => r.Message); + + /// + public IDisposable? BeginScope(TState state) + where TState : notnull => null; + + /// + public bool IsEnabled(LogLevel logLevel) => true; + + /// + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + ArgumentNullException.ThrowIfNull(formatter); + Records.Add((logLevel, formatter(state, exception))); + } +} diff --git a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/DamlModelBuilder.cs b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/DamlModelBuilder.cs index 4f03e18..239cd79 100644 --- a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/DamlModelBuilder.cs +++ b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/DamlModelBuilder.cs @@ -1,7 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; namespace Daml.Codegen.CSharp.Tests.TestHelpers; diff --git a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/EmittedSubmissionShape.cs b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/EmittedSubmissionShape.cs new file mode 100644 index 0000000..0b92ec6 --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/EmittedSubmissionShape.cs @@ -0,0 +1,22 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +namespace Daml.Codegen.CSharp.Tests.TestHelpers; + +/// +/// The emitted call into the shared single-command submission path, pinned once for the +/// whole suite. Every generated exerciser forwards the same six arguments in the same +/// order, so an emitter-side transposition — commandId and workflowId +/// swapped, say — must fail loudly. Holding the expected text in one place keeps a +/// legitimate signature change from being applied at some call sites and missed at +/// others, which would leave the stragglers pinning a signature that no longer exists. +/// +internal static class EmittedSubmissionShape +{ + /// + /// The TrySubmitSingleAsync call as emitted, with its arguments in + /// declaration order. + /// + internal const string TrySubmitSingleArgumentOrder = + "TrySubmitSingleAsync(command, submitter, workflowId, commandId, timeout, cancellationToken)"; +} diff --git a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/GeneratorFactory.cs b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/GeneratorFactory.cs index b9b1b0e..4d66a7e 100644 --- a/tests/Daml.Codegen.CSharp.Tests/TestHelpers/GeneratorFactory.cs +++ b/tests/Daml.Codegen.CSharp.Tests/TestHelpers/GeneratorFactory.cs @@ -9,18 +9,19 @@ namespace Daml.Codegen.CSharp.Tests.TestHelpers; /// Shared factory for the used across the /// codegen test suite. Centralizes the default /// (nullable reference types, file-scoped namespaces, record types, primary -/// constructors) and a silent logger so individual test classes import this -/// via using static and call directly. +/// constructors) so individual test classes import this via using static +/// and call directly. The generator's logger is +/// left at its NullLogger default, so the suite stays silent. /// public static class GeneratorFactory { /// - /// Creates a with a silent logger. When + /// Creates a silent . When /// is null, the suite-wide default options /// are used. /// public static CSharpCodeGenerator CreateGenerator(CodeGenOptions? options = null) => - new(options ?? DefaultOptions(), new ConsoleLogger(0)); + new(options ?? DefaultOptions()); private static CodeGenOptions DefaultOptions() => new() diff --git a/tests/Daml.Codegen.CSharp.Tests/TypeReferenceQualifierTests.cs b/tests/Daml.Codegen.CSharp.Tests/TypeReferenceQualifierTests.cs index a45a93b..1cc4ae0 100644 --- a/tests/Daml.Codegen.CSharp.Tests/TypeReferenceQualifierTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/TypeReferenceQualifierTests.cs @@ -10,7 +10,7 @@ namespace Daml.Codegen.CSharp.Tests; public class TypeReferenceQualifierTests { [Fact] - public void all_namespaces_expands_a_leaf_into_every_ancestor_prefix() + public void TypeReferenceQualifier_all_namespaces_expands_a_leaf_into_every_ancestor_prefix() { var qualifier = new TypeReferenceQualifier(["Canton.Party.Replication"]); @@ -19,7 +19,7 @@ public void all_namespaces_expands_a_leaf_into_every_ancestor_prefix() } [Fact] - public void qualify_returns_the_bare_name_when_no_namespace_collides() + public void Qualify_returns_the_bare_name_when_no_namespace_collides() { var qualifier = new TypeReferenceQualifier(["Splice.Api.Token.Holding.V1"]); @@ -28,7 +28,7 @@ public void qualify_returns_the_bare_name_when_no_namespace_collides() } [Fact] - public void qualify_global_qualifies_party_when_a_namespace_segment_shadows_it() + public void Qualify_global_qualifies_party_when_a_namespace_segment_shadows_it() { var qualifier = new TypeReferenceQualifier(["Canton.Party.Replication"]); @@ -37,7 +37,7 @@ public void qualify_global_qualifies_party_when_a_namespace_segment_shadows_it() } [Fact] - public void qualify_leaves_party_bare_when_no_namespace_segment_shadows_it() + public void Qualify_leaves_party_bare_when_no_namespace_segment_shadows_it() { var qualifier = new TypeReferenceQualifier(["Splice.Api.Token.Holding.V1"]); @@ -46,7 +46,7 @@ public void qualify_leaves_party_bare_when_no_namespace_segment_shadows_it() } [Fact] - public void qualify_does_not_double_prefix_an_already_global_qualified_name() + public void Qualify_does_not_double_prefix_an_already_global_qualified_name() { var qualifier = new TypeReferenceQualifier(["Canton.Party.Replication"]); @@ -55,7 +55,7 @@ public void qualify_does_not_double_prefix_an_already_global_qualified_name() } [Fact] - public void qualify_leaves_unregistered_local_names_unchanged() + public void Qualify_leaves_unregistered_local_names_unchanged() { var qualifier = new TypeReferenceQualifier(["Canton.Party.Replication"]); @@ -68,8 +68,9 @@ public void qualify_leaves_unregistered_local_names_unchanged() [InlineData("CommandsSubmission", "Daml.Runtime.Commands")] [InlineData("IDamlInterface", "Daml.Runtime.Contracts")] [InlineData("IHasView", "Daml.Runtime.Contracts")] + [InlineData("ViewDescriptor", "Daml.Runtime.Contracts")] [InlineData("CreatedEvent", "Daml.Runtime.Contracts")] - public void qualify_global_qualifies_command_and_contract_names_when_a_namespace_segment_shadows_it( + public void Qualify_global_qualifies_command_and_contract_names_when_a_namespace_segment_shadows_it( string simpleName, string owningNamespace) { var qualifier = new TypeReferenceQualifier([$"Acme.{simpleName}.V1"]); @@ -83,8 +84,9 @@ public void qualify_global_qualifies_command_and_contract_names_when_a_namespace [InlineData("CommandsSubmission")] [InlineData("IDamlInterface")] [InlineData("IHasView")] + [InlineData("ViewDescriptor")] [InlineData("CreatedEvent")] - public void qualify_leaves_command_and_contract_names_bare_when_no_namespace_segment_shadows_it( + public void Qualify_leaves_command_and_contract_names_bare_when_no_namespace_segment_shadows_it( string simpleName) { var qualifier = new TypeReferenceQualifier(["Splice.Api.Token.Holding.V1"]); @@ -102,8 +104,9 @@ public void qualify_leaves_command_and_contract_names_bare_when_no_namespace_seg [InlineData("NonEmpty")] [InlineData("Map")] [InlineData("Unit")] + [InlineData("Optional")] [InlineData("GenericStub")] - public void qualify_global_qualifies_stdlib_names_when_a_namespace_segment_shadows_it( + public void Qualify_global_qualifies_stdlib_names_when_a_namespace_segment_shadows_it( string simpleName) { var qualifier = new TypeReferenceQualifier([$"Acme.{simpleName}.V1"]); @@ -121,8 +124,9 @@ public void qualify_global_qualifies_stdlib_names_when_a_namespace_segment_shado [InlineData("NonEmpty")] [InlineData("Map")] [InlineData("Unit")] + [InlineData("Optional")] [InlineData("GenericStub")] - public void qualify_leaves_stdlib_names_bare_when_no_namespace_segment_shadows_it( + public void Qualify_leaves_stdlib_names_bare_when_no_namespace_segment_shadows_it( string simpleName) { var qualifier = new TypeReferenceQualifier(["My.Package.Module"]); @@ -132,7 +136,7 @@ public void qualify_leaves_stdlib_names_bare_when_no_namespace_segment_shadows_i } [Fact] - public void qualify_global_qualifies_unit_when_the_package_declares_its_own_unit_type() + public void Qualify_global_qualifies_unit_when_the_package_declares_its_own_unit_type() { var qualifier = new TypeReferenceQualifier( ["Splice.Wallet.Payments"], declaredTypeNames: ["Unit"]); @@ -142,7 +146,7 @@ public void qualify_global_qualifies_unit_when_the_package_declares_its_own_unit } [Fact] - public void qualify_leaves_unit_bare_when_no_declared_type_name_matches_it() + public void Qualify_leaves_unit_bare_when_no_declared_type_name_matches_it() { var qualifier = new TypeReferenceQualifier( ["Splice.Wallet.Payments"], declaredTypeNames: ["Sink"]); @@ -150,4 +154,14 @@ public void qualify_leaves_unit_bare_when_no_declared_type_name_matches_it() qualifier.Qualify("Unit", "Splice.Wallet.Payments") .Should().Be("Unit"); } + + [Fact] + public void Qualify_global_qualifies_optional_when_the_package_declares_its_own_optional_type() + { + var qualifier = new TypeReferenceQualifier( + ["Splice.Wallet.Payments"], declaredTypeNames: ["Optional"]); + + qualifier.Qualify("Optional", "Splice.Wallet.Payments") + .Should().Be("global::Daml.Runtime.Stdlib.Optional"); + } } diff --git a/tests/Daml.Codegen.CSharp.Tests/UnifiedDiff.cs b/tests/Daml.Codegen.CSharp.Tests/UnifiedDiff.cs index 05c53b4..14d0546 100644 --- a/tests/Daml.Codegen.CSharp.Tests/UnifiedDiff.cs +++ b/tests/Daml.Codegen.CSharp.Tests/UnifiedDiff.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using System.Text; namespace Daml.Codegen.CSharp.Tests; @@ -115,7 +116,7 @@ private static string FormatHunks(string[] expected, string[] actual, List { if (emitted >= MaxHunks) { - sb.AppendLine($"... (truncated after {MaxHunks} hunks)"); + sb.AppendLine(CultureInfo.InvariantCulture, $"... (truncated after {MaxHunks} hunks)"); break; } @@ -124,7 +125,7 @@ private static string FormatHunks(string[] expected, string[] actual, List var actStart = Math.Max(0, hunk[0].ActualIndex - ContextLines); var actEnd = Math.Min(actual.Length - 1, hunk[^1].ActualIndex + ContextLines); - sb.AppendLine($"@@ -{expStart + 1},{expEnd - expStart + 1} +{actStart + 1},{actEnd - actStart + 1} @@"); + sb.AppendLine(CultureInfo.InvariantCulture, $"@@ -{expStart + 1},{expEnd - expStart + 1} +{actStart + 1},{actEnd - actStart + 1} @@"); var editIdx = 0; var expLine = expStart; @@ -136,19 +137,19 @@ private static string FormatHunks(string[] expected, string[] actual, List if (edit != null && edit.Kind == EditKind.Delete && edit.ExpectedIndex == expLine) { - sb.AppendLine($"-{expected[expLine]}"); + sb.AppendLine(CultureInfo.InvariantCulture, $"-{expected[expLine]}"); expLine++; editIdx++; } else if (edit != null && edit.Kind == EditKind.Insert && edit.ActualIndex == actLine) { - sb.AppendLine($"+{actual[actLine]}"); + sb.AppendLine(CultureInfo.InvariantCulture, $"+{actual[actLine]}"); actLine++; editIdx++; } else if (expLine <= expEnd && actLine <= actEnd) { - sb.AppendLine($" {expected[expLine]}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {expected[expLine]}"); expLine++; actLine++; } diff --git a/tests/Daml.Codegen.CSharp.Tests/VariantEmitterTests.cs b/tests/Daml.Codegen.CSharp.Tests/VariantEmitterTests.cs index 4b86999..c27f30b 100644 --- a/tests/Daml.Codegen.CSharp.Tests/VariantEmitterTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/VariantEmitterTests.cs @@ -3,7 +3,7 @@ using System.Text; using Daml.Codegen.CSharp.CodeGen; -using Daml.Codegen.CSharp.Model; +using Daml.Codegen.Intermediate.Model; using AwesomeAssertions; using Xunit; @@ -71,7 +71,38 @@ private static string EmitVariant(DamlDataType target, bool generateXmlDocs = tr Emit(target.Name, [target], generateXmlDocs); [Fact] - public void emits_the_abstract_base_record_and_every_constructor() + public void VariantEmitter_constrains_every_type_parameter_to_notnull() + { + var output = EmitVariant(new DamlDataType + { + Name = "Slot", + TypeParams = ["a"], + Definition = new DamlVariantDefinition( + [ + Ctor("Filled", new DamlTypeVar("a")), + Ctor("Vacant"), + ]), + }); + + output.Should().Contain("public abstract record Slot where TA : notnull", + "a Daml type variable ranges only over serialisable Daml types, none of which is nullable, " + + "and without the constraint a reflection-driven reader decodes the arm payload as an Optional"); + output.Should().Contain("public static Slot FromVariant(", + "a constraint clause belongs on the declaration only; repeating it on a member's return type is not C#"); + output.Should().Contain("public sealed record Filled(TA Value) : Slot", + "a nested arm reuses the enclosing type parameter and may not redeclare its constraint"); + } + + [Fact] + public void VariantEmitter_leaves_a_non_generic_variant_unconstrained() + { + var output = EmitVariant(Variant("PaymentMethod", Ctor("Cash"), Ctor("Card", Text))); + + output.Should().NotContain("notnull"); + } + + [Fact] + public void VariantEmitter_emits_the_abstract_base_record_and_every_constructor() { var output = EmitVariant(Variant( "PaymentMethod", @@ -92,7 +123,7 @@ public void emits_the_abstract_base_record_and_every_constructor() } [Fact] - public void documents_case_Tag_and_ToVariant_overrides_with_inheritdoc() + public void VariantEmitter_documents_case_Tag_and_ToVariant_overrides_with_inheritdoc() { var output = EmitVariant(Variant("PaymentMethod", Ctor("Cash"), Ctor("Card", Text))); @@ -102,7 +133,7 @@ public void documents_case_Tag_and_ToVariant_overrides_with_inheritdoc() } [Fact] - public void maps_numeric_payload_constructors_to_decimal() + public void VariantEmitter_maps_numeric_payload_constructors_to_decimal() { var numeric = new DamlPrimitiveType(DamlPrimitive.Numeric); var output = EmitVariant(Variant("Amount", Ctor("Fixed", numeric), Ctor("Percentage", numeric))); @@ -112,7 +143,7 @@ public void maps_numeric_payload_constructors_to_decimal() } [Fact] - public void round_trips_primitive_payload_through_ToVariant_and_FromVariant() + public void VariantEmitter_round_trips_primitive_payload_through_ToVariant_and_FromVariant() { var output = EmitVariant(Variant("Maybe", Ctor("Nothing"), Ctor("Just", Text))); @@ -130,7 +161,7 @@ public void round_trips_primitive_payload_through_ToVariant_and_FromVariant() } [Fact] - public void routes_variant_payload_through_ToVariant_and_FromVariant() + public void VariantEmitter_routes_variant_payload_through_ToVariant_and_FromVariant() { var inner = Variant("Inner", Ctor("Lit", new DamlPrimitiveType(DamlPrimitive.Int64))); var outer = Variant("Outer", Ctor("Wrap", new DamlTypeRef(string.Empty, ModuleName, "Inner"))); @@ -144,7 +175,7 @@ public void routes_variant_payload_through_ToVariant_and_FromVariant() } [Fact] - public void uses_indefinite_article_an_for_vowel_initial_variant_in_FromVariant_summary() + public void VariantEmitter_uses_indefinite_article_an_for_vowel_initial_variant_in_FromVariant_summary() { var output = EmitVariant(Variant("Outcome", Ctor("Win", Text), Ctor("Pending"))); @@ -153,7 +184,7 @@ public void uses_indefinite_article_an_for_vowel_initial_variant_in_FromVariant_ } [Fact] - public void uses_indefinite_article_a_for_consonant_initial_variant_in_FromVariant_summary() + public void VariantEmitter_uses_indefinite_article_a_for_consonant_initial_variant_in_FromVariant_summary() { var output = EmitVariant(Variant("PaymentMethod", Ctor("Cash"), Ctor("Card", Text))); @@ -162,7 +193,7 @@ public void uses_indefinite_article_a_for_consonant_initial_variant_in_FromVaria } [Fact] - public void emits_every_xml_doc_when_enabled() + public void VariantEmitter_emits_every_xml_doc_when_enabled() { var output = EmitVariant(Variant("Maybe", Ctor("Nothing"), Ctor("Just", Text)), generateXmlDocs: true); @@ -176,7 +207,7 @@ public void emits_every_xml_doc_when_enabled() } [Fact] - public void omits_every_xml_doc_when_disabled() + public void VariantEmitter_omits_every_xml_doc_when_disabled() { var output = EmitVariant(Variant("Maybe", Ctor("Nothing"), Ctor("Just", Text)), generateXmlDocs: false); diff --git a/tests/Daml.Codegen.CSharp.Tests/VersioningApiSurfaceTests.cs b/tests/Daml.Codegen.CSharp.Tests/VersioningApiSurfaceTests.cs index 19c34d6..6b991a5 100644 --- a/tests/Daml.Codegen.CSharp.Tests/VersioningApiSurfaceTests.cs +++ b/tests/Daml.Codegen.CSharp.Tests/VersioningApiSurfaceTests.cs @@ -1,7 +1,6 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 -using Daml.Codegen.CSharp.Model; using Daml.Codegen.CSharp.Versioning; using AwesomeAssertions; using Xunit; diff --git a/tests/Daml.Codegen.CSharp.Tests/ViewDescriptorPairingCompilesTests.cs b/tests/Daml.Codegen.CSharp.Tests/ViewDescriptorPairingCompilesTests.cs new file mode 100644 index 0000000..83cc81e --- /dev/null +++ b/tests/Daml.Codegen.CSharp.Tests/ViewDescriptorPairingCompilesTests.cs @@ -0,0 +1,100 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Codegen.CSharp.CodeGen; +using AwesomeAssertions; +using Microsoft.CodeAnalysis; +using Xunit; +using static Daml.Codegen.CSharp.Tests.EmittedCodeCompilesTestHelpers; + +namespace Daml.Codegen.CSharp.Tests; + +/// +/// Pins the guarantee the emitter relies on when it hands a marker a static +/// View witness: +/// is unconstructible for a mismatched pair. Its TInterface : IHasView<TView> +/// constraint is what stops a call site from smuggling one interface's view record past +/// another interface's marker, and only a rejected compilation proves the constraint is +/// still load-bearing — every other test in the suite names a matching pair, which would +/// keep passing if the constraint were dropped. +/// +public class ViewDescriptorPairingCompilesTests +{ + private const string PairingSource = """ + using System; + using Daml.Runtime; + using Daml.Runtime.Contracts; + using Daml.Runtime.Data; + + namespace Pairing; + + public interface ITestHolding : IDamlInterface, IHasView + { + static Identifier IDamlInterface.InterfaceId => new("p", "M", "TestHolding"); + static string IDamlInterface.PackageId => "p"; + static string IDamlInterface.PackageName => "pkg"; + static Version IDamlInterface.PackageVersion => new(1, 0, 0); + static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => + new(new Identifier("p", "M", "TestHolding"), DamlTypeKind.Interface, "pkg"); + } + + public sealed record TestHoldingView(decimal Amount) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("amount", new DamlNumeric(Amount))); + + public static TestHoldingView FromRecord(DamlRecord record) => + new(record.GetRequiredField("amount").As().Value); + } + + public sealed record WrongView(decimal Amount) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("amount", new DamlNumeric(Amount))); + + public static WrongView FromRecord(DamlRecord record) => + new(record.GetRequiredField("amount").As().Value); + } + """; + + private static IReadOnlyList CompilePairing(string descriptorDeclaration) => + CompileEmittedFiles( + [ + GeneratedFile.Text("Pairing.cs", PairingSource), + GeneratedFile.Text( + "Witness.cs", + $$""" + using Daml.Runtime.Contracts; + + namespace Pairing; + + public static class Witness + { + public static readonly {{descriptorDeclaration}} Value = new(); + } + """), + ]); + + [Fact] + public void ViewDescriptor_accepts_the_marker_paired_with_its_own_view_record() + { + var errors = CompilePairing("ViewDescriptor") + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + errors.Should().BeEmpty( + "the mismatch below only proves something if the matching pair is accepted; got: {0}", + string.Join("; ", errors.Select(d => d.ToString()))); + } + + [Fact] + public void ViewDescriptor_rejects_a_marker_paired_with_a_foreign_view_record() + { + var errors = CompilePairing("ViewDescriptor") + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + errors.Should().Contain( + d => d.Id == "CS0311", + "a marker that does not declare IHasView must not satisfy the descriptor's " + + "TInterface constraint, so the mismatched pair is unconstructible rather than silently accepted"); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/ArchiveViaContractIdTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/ArchiveViaContractIdTests.cs new file mode 100644 index 0000000..d79a960 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/ArchiveViaContractIdTests.cs @@ -0,0 +1,110 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// Archive is synthesised by the emitter rather than declared in Daml, and it takes +/// no arguments. Canton's gRPC preprocessor rejects a Unit choice argument, so the +/// synthesised choice has to encode an empty record instead. These tests pin that +/// encoding on the corpus's own generated types, for both the template-typed and +/// the interface-typed contract-id path. +/// +public class ArchiveViaContractIdTests +{ + private static readonly ContractId MarkerCid = new("marker-cid"); + private static readonly ContractId HoldingCid = new("holding-cid"); + + private static TransactionResult ArchivingTransaction(string contractId, Identifier templateId) => + new( + UpdateId: "upd-1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: Array.Empty(), + ArchivedContractIds: new[] { contractId }, + CommandId: default) + { + ExercisedEvents = new[] + { + new ExercisedEvent( + ContractId: contractId, + TemplateId: templateId, + InterfaceId: null, + ChoiceName: "Archive", + ChoiceArgument: DamlRecord.Create(), + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: Array.Empty(), + WitnessParties: Array.Empty()), + }, + }; + + [Fact] + public void ArchiveCommand_on_a_template_contract_id_encodes_an_empty_record_not_unit() + { + var command = MarkerCid.ArchiveCommand(); + + command.ChoiceArgument.Should().BeOfType().Subject.Fields.Should().BeEmpty(); + command.ChoiceArgument.Should().NotBeOfType(); + command.Choice.Should().Be(new ChoiceName("Archive")); + command.TemplateId.Should().Be(Marker.TemplateId); + } + + [Fact] + public void ArchiveCommand_on_an_interface_contract_id_encodes_an_empty_record_not_unit() + { + var command = HoldingCid.ArchiveCommand(); + + command.ChoiceArgument.Should().BeOfType().Subject.Fields.Should().BeEmpty(); + command.ChoiceArgument.Should().NotBeOfType(); + command.TemplateId.Should().Be(IHolding.InterfaceId); + } + + [Fact] + public void ChoiceArchive_descriptor_encodes_an_empty_record_not_unit() + { + Marker.ChoiceArchive.ArgumentEncoder(DamlUnit.Instance) + .Should().BeOfType().Subject.Fields.Should().BeEmpty(); + TypeCorners.ChoiceArchive.ArgumentEncoder(DamlUnit.Instance) + .Should().BeOfType().Subject.Fields.Should().BeEmpty(); + } + + [Fact] + public async Task ArchiveAsync_submits_the_empty_record_argument_for_the_target_contract_id() + { + using var client = new FakeLedgerClient( + _ => new ExerciseOutcome.One(ArchivingTransaction("marker-cid", Marker.TemplateId))); + + await MarkerCid.ArchiveAsync(client, new Party("alice"), + cancellationToken: TestContext.Current.CancellationToken); + + var command = client.LastSubmission!.Commands.Should().ContainSingle().Which + .Should().BeOfType().Subject; + command.ContractId.Value.Should().Be("marker-cid"); + command.Choice.Should().Be(new ChoiceName("Archive")); + command.ChoiceArgument.Should().BeOfType().Subject.Fields.Should().BeEmpty(); + } + + [Fact] + public async Task ArchiveAsync_on_an_interface_contract_id_targets_the_interface_id() + { + using var client = new FakeLedgerClient( + _ => new ExerciseOutcome.One(ArchivingTransaction("holding-cid", IHolding.InterfaceId))); + + await HoldingCid.ArchiveAsync(client, new Party("alice"), + cancellationToken: TestContext.Current.CancellationToken); + + var command = client.LastSubmission!.Commands.Should().ContainSingle().Which + .Should().BeOfType().Subject; + command.TemplateId.Should().Be(IHolding.InterfaceId); + command.ChoiceArgument.Should().BeOfType().Subject.Fields.Should().BeEmpty(); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/ConformanceCorpusTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/ConformanceCorpusTests.cs index 0663cc7..ea968b9 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/ConformanceCorpusTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/ConformanceCorpusTests.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.IO.Compression; using AwesomeAssertions; using Xunit; @@ -8,20 +9,25 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class ConformanceCorpusTests { - [Fact] - public void open_dar_returns_a_readable_non_empty_stream() + public static TheoryData EveryPackage => + new(Enum.GetValues()); + + [Theory] + [MemberData(nameof(EveryPackage))] + public void OpenDar_returns_a_readable_stream_holding_a_whole_archive(ConformancePackage package) { - using var stream = ConformanceCorpus.OpenDar(); + using var stream = ConformanceCorpus.OpenDar(package); stream.Should().NotBeNull(); stream.CanRead.Should().BeTrue(); - stream.Length.Should().BeGreaterThan(0); + stream.Length.Should().BeGreaterThan(1000); } - [Fact] - public void open_dar_returns_a_zip_archive_with_pk_magic_bytes() + [Theory] + [MemberData(nameof(EveryPackage))] + public void OpenDar_returns_a_zip_archive_with_pk_magic_bytes(ConformancePackage package) { - using var stream = ConformanceCorpus.OpenDar(); + using var stream = ConformanceCorpus.OpenDar(package); var header = new byte[4]; stream.ReadExactly(header); @@ -31,14 +37,42 @@ public void open_dar_returns_a_zip_archive_with_pk_magic_bytes() header[3].Should().Be(0x04); } - [Fact] - public void open_dar_returns_independent_streams_on_each_call() + [Theory] + [MemberData(nameof(EveryPackage))] + public void OpenDar_returns_independent_streams_on_each_call(ConformancePackage package) { - using var first = ConformanceCorpus.OpenDar(); - using var second = ConformanceCorpus.OpenDar(); + using var first = ConformanceCorpus.OpenDar(package); + using var second = ConformanceCorpus.OpenDar(package); first.Should().NotBeSameAs(second); first.ReadByte().Should().Be((byte)'P'); second.Position.Should().Be(0); } + + [Theory] + [MemberData(nameof(EveryPackage))] + public void OpenDar_returns_the_archive_whose_entries_name_that_package(ConformancePackage package) + { + using var zip = new ZipArchive(ConformanceCorpus.OpenDar(package)); + var expectedPrefix = package + "-"; + + zip.Entries.Should().Contain(entry => + entry.FullName.StartsWith(expectedPrefix, StringComparison.OrdinalIgnoreCase)); + } + + [Fact] + public void OpenDar_with_no_argument_opens_the_RichTypes_dar() + { + using var noArg = ConformanceCorpus.OpenDar(); + using var richTypes = ConformanceCorpus.OpenDar(ConformancePackage.RichTypes); + + ReadAll(noArg).Should().Equal(ReadAll(richTypes)); + } + + private static byte[] ReadAll(Stream stream) + { + using var buffer = new MemoryStream(); + stream.CopyTo(buffer); + return buffer.ToArray(); + } } diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/ContractKeyOnActiveContractTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/ContractKeyOnActiveContractTests.cs new file mode 100644 index 0000000..a84717a --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/ContractKeyOnActiveContractTests.cs @@ -0,0 +1,133 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Contractkeys; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// The key travels on the created event, so the generated active contract — not the +/// template payload a caller constructs locally — is where it lands. These tests run +/// against the corpus's own generated types for a record key, a record key built by a +/// helper in another module, and a bare Party key. +/// +public class ContractKeyOnActiveContractTests +{ + private static readonly Party Custodian = new("custodian::1220"); + + private const string LedgerKeyHash = "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA="; + + private static CreatedEvent CreatedEvent(Identifier templateId, DamlRecord createArguments, DamlValue? key) => + new( + EventId: "event-1", + ContractId: "contract-1", + TemplateId: templateId, + CreateArguments: createArguments, + WitnessParties: [], + Signatories: [Custodian], + Observers: [], + ContractKey: key is null ? null : new ContractKey(key)); + + [Fact] + public void FromCreatedEvent_reads_a_record_key_off_the_created_event() + { + var payload = new Account(Custodian, "savings", 42); + var key = new AccountKey(Custodian, "savings"); + + var contract = Account.Contract.FromCreatedEvent( + CreatedEvent(Account.TemplateId, payload.ToRecord(), key.ToRecord())); + + contract.Key.Value.Should().Be(key); + contract.Data.Should().Be(payload); + contract.Id.Value.Should().Be("contract-1"); + } + + [Fact] + public void FromCreatedEvent_reads_a_bare_party_key_off_the_created_event() + { + var payload = new Steward(Custodian, "charter"); + + var contract = Steward.Contract.FromCreatedEvent( + CreatedEvent(Steward.TemplateId, payload.ToRecord(), Custodian.ToDamlValue())); + + contract.Key.Value.Should().Be(Custodian); + } + + [Fact] + public void FromCreatedEvent_reads_a_key_whose_fields_share_no_name_with_the_payload() + { + var payload = new Schedule(new ScheduleView(Custodian, "2026-Q1")); + var key = new ScheduleKey(Custodian, "2026-Q1"); + + var contract = Schedule.Contract.FromCreatedEvent( + CreatedEvent(Schedule.TemplateId, payload.ToRecord(), key.ToRecord())); + + contract.Key.Value.Should().Be(key); + } + + [Fact] + public void FromCreatedEvent_carries_the_ledgers_hash_of_the_key() + { + var payload = new Account(Custodian, "savings", 42); + var key = new AccountKey(Custodian, "savings"); + var createdEvent = CreatedEvent(Account.TemplateId, payload.ToRecord(), key.ToRecord()) with + { + ContractKey = new ContractKey(key.ToRecord()) { KeyHash = LedgerKeyHash }, + }; + + var contract = Account.Contract.FromCreatedEvent(createdEvent); + + contract.Key.Hash.Should().Be( + LedgerKeyHash, + "the hash is Canton-computed over the key and the template id, so a generated contract " + + "that drops it leaves the caller unable to address the contract by key"); + } + + [Fact] + public void FromCreatedEvent_rejects_a_keyed_event_carrying_no_key() + { + var payload = new Account(Custodian, "savings", 42); + + var decoding = () => Account.Contract.FromCreatedEvent( + CreatedEvent(Account.TemplateId, payload.ToRecord(), key: null)); + + decoding.Should().Throw( + "a keyed template's generated contract declares a non-nullable key, so an event that " + + "carries none has to fail loudly rather than reach a caller through that shape") + .WithMessage("*carried no contract key*"); + } + + [Fact] + public void Constructing_a_contract_has_to_name_the_key_slot() + { + var key = new AccountKey(Custodian, "savings"); + + var contract = new Account.Contract(new Account.ContractId("contract-1"), new Account(Custodian, "savings", 42)) + { + Key = new ContractKey(key, null), + }; + + contract.Key.Value.Should().Be( + key, + "the slot is required, so the key is a choice the call site states rather than a default it inherits"); + } + + [Fact] + public void Deconstructing_a_contract_stays_source_compatible() + { + var contract = new Account.Contract(new Account.ContractId("contract-1"), new Account(Custodian, "savings", 42)) + { + Key = new ContractKey(new AccountKey(Custodian, "savings"), null), + }; + + var (id, data) = contract; + + id.Value.Should().Be("contract-1"); + data.Should().Be(new Account(Custodian, "savings", 42), + "moving the key off the positional list leaves Deconstruct at two parameters, so var (id, data) = contract still compiles"); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/EmittedContractIdJsonTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/EmittedContractIdJsonTests.cs new file mode 100644 index 0000000..1b60605 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/EmittedContractIdJsonTests.cs @@ -0,0 +1,59 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Contractkeys; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// System.Text.Json reads [JsonConverter] off the type being converted and does not +/// walk the base chain, so the attribute on ContractId<T> never reached the +/// emitted T.ContractId — the type a consumer DTO actually declares. These run against +/// the corpus's own generated types rather than a hand-written stand-in, so they cannot drift +/// away from what the emitter writes. +/// +public class EmittedContractIdJsonTests +{ + private sealed record AccountReference(Account.ContractId Id); + + [Fact] + public void Emitted_ContractId_serializes_as_a_bare_string_without_AddDamlConverters() + { + JsonSerializer.Serialize(new Account.ContractId("00abc")).Should().Be( + "\"00abc\"", + "the emitter writes the converter attribute onto the generated record, so the wire shape no longer depends on the consumer remembering to register converters"); + } + + [Fact] + public void Emitted_ContractId_deserializes_from_a_bare_string_without_AddDamlConverters() + { + var contractId = JsonSerializer.Deserialize("\"00abc\""); + + contractId.Should().BeOfType("reading must construct the derived record, not its base"); + contractId!.Value.Should().Be("00abc"); + } + + [Fact] + public void Emitted_ContractId_round_trips_inside_a_dto_without_AddDamlConverters() + { + const string json = "{\"Id\":\"00abc\"}"; + + var reference = JsonSerializer.Deserialize(json); + + reference!.Id.Value.Should().Be("00abc"); + JsonSerializer.Serialize(reference).Should().Be(json, + "a DTO field declared as the emitted nested type is the shape the gap was about"); + } + + [Fact] + public void Emitted_ContractId_carries_the_converter_on_a_keyless_template_too() + { + JsonSerializer.Serialize(new Asset.ContractId("00def")).Should().Be( + "\"00def\"", + "the attribute is emitted from the contract-id writer, which does not consult the key"); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/ExerciseByKeyCommandTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/ExerciseByKeyCommandTests.cs new file mode 100644 index 0000000..521795d --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/ExerciseByKeyCommandTests.cs @@ -0,0 +1,71 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Commands; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Contractkeys; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// The by-key command builders are the key type's reason to exist on the emitted +/// surface: they address a contract the caller has never fetched. These tests pin the +/// command they build on the corpus's own generated types, across a create-bearing +/// choice, a value-returning choice, and the synthesised Archive. +/// +public class ExerciseByKeyCommandTests +{ + private static readonly Party Custodian = new("custodian::1220"); + private static readonly AccountKey Key = new(Custodian, "savings"); + + [Fact] + public void ByKeyCommand_for_a_create_bearing_choice_carries_the_key_as_its_record() + { + var command = Account.CreditByKeyCommand(Key, new Account.Credit(7)); + + command.TemplateId.Should().Be(Account.TemplateId); + command.Choice.Should().Be(new ChoiceName("Credit")); + command.ContractKey.Should().Be(Key.ToRecord()); + command.ChoiceArgument.Should().BeOfType() + .Subject.GetRequiredField("delta").Should().Be(new DamlInt64(7)); + } + + [Fact] + public void ByKeyCommand_for_a_value_returning_choice_carries_the_key_as_its_record() + { + var command = Account.CurrentBalanceByKeyCommand(Key, new Account.CurrentBalance()); + + command.TemplateId.Should().Be(Account.TemplateId); + command.Choice.Should().Be(new ChoiceName("CurrentBalance")); + command.ContractKey.Should().Be(Key.ToRecord()); + } + + [Fact] + public void ByKeyCommand_for_archive_encodes_an_empty_record_not_unit() + { + var command = Account.ArchiveByKeyCommand(Key); + + command.Choice.Should().Be(new ChoiceName("Archive")); + command.ChoiceArgument.Should().BeOfType().Subject.Fields.Should().BeEmpty(); + command.ChoiceArgument.Should().NotBeOfType(); + } + + [Fact] + public void ByKeyCommand_for_a_bare_party_key_carries_the_key_as_a_party_value() + { + var command = Steward.ReviseByKeyCommand(Custodian, new Steward.Revise("revised")); + + command.TemplateId.Should().Be(Steward.TemplateId); + command.ContractKey.Should().Be(Custodian.ToDamlValue()); + } + + [Fact] + public void ByKeyCommand_rejects_a_null_reference_typed_key() + { + var exercise = () => Account.CreditByKeyCommand(null!, new Account.Credit(7)); + + exercise.Should().Throw().WithParameterName("key"); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/FakeLedgerClient.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/FakeLedgerClient.cs index 4d89ac4..9dd1619 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/FakeLedgerClient.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/FakeLedgerClient.cs @@ -5,6 +5,7 @@ using Daml.Runtime; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; +using Daml.Runtime.Data; using Daml.Runtime.Outcomes; using Daml.Runtime.Streams; @@ -17,6 +18,8 @@ internal sealed class FakeLedgerClient : ILedgerClient public CommandsSubmission? LastSubmission { get; private set; } + public SubmitterInfo? LastCreateSubmitter { get; private set; } + public FakeLedgerClient( Func>? transaction = null, Func>? create = null) @@ -40,10 +43,12 @@ public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate { + LastCreateSubmitter = submitter; var projected = _create is null ? new ExerciseOutcome>.InfraError(0, "unset") : Project(_create(payload!)); @@ -59,7 +64,7 @@ private static ExerciseOutcome> Project(Exercis ExerciseOutcome.DamlError e => new ExerciseOutcome>.DamlError(e.Category, e.ErrorId, e.Message, e.Metadata), ExerciseOutcome.InfraError e => - new ExerciseOutcome>.InfraError(e.StatusCode, e.Message, e.SourceException), + new ExerciseOutcome>.InfraError(e.StatusCode, e.Message, e.Category, e.SourceException), _ => new ExerciseOutcome>.None(), }; @@ -67,6 +72,7 @@ public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); @@ -88,7 +94,7 @@ public IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeLedgerEffectsAsync( @@ -96,14 +102,43 @@ public IAsyncEnumerable> SubscribeLedgerEffectsAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => throw new NotSupportedException(); public void Dispose() diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/GenericRecordFacetTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/GenericRecordFacetTests.cs new file mode 100644 index 0000000..ec48c12 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/GenericRecordFacetTests.cs @@ -0,0 +1,50 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Data; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// Generated types satisfy , so callers can materialize +/// them generically through the static abstract factory instead of reflection. The two +/// materialization cases are not duplicates: Asset exercises the template +/// emitter's stamp and HoldingView the record emitter's. +/// +public class GenericRecordFacetTests +{ + private static T Materialize(DamlRecord record) where T : IDamlRecord => + T.FromRecord(record); + + [Fact] + public void Asset_should_materialize_generically_through_IDamlRecord_TSelf_facet() + { + var original = new Asset(new Party("alice"), 42m); + + Materialize(original.ToRecord()).Should().Be(original); + } + + [Fact] + public void HoldingView_should_materialize_generically_through_IDamlRecord_TSelf_facet() + { + var original = new HoldingView(42m); + + Materialize(original.ToRecord()).Should().Be(original); + } + + [Fact] + public void GenericRecordFacet_every_generated_IDamlRecord_TSelf_implementor_should_name_itself() + { + var facets = ( + from type in typeof(Asset).Assembly.GetTypes() + from facet in type.GetInterfaces() + where facet.IsGenericType && facet.GetGenericTypeDefinition() == typeof(IDamlRecord<>) + select (Implementor: type, Self: facet.GetGenericArguments()[0])).ToList(); + + facets.Should().NotBeEmpty(); + facets.Should().OnlyContain(f => f.Implementor == f.Self); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/HoldingInterfaceChoiceTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/HoldingInterfaceChoiceTests.cs new file mode 100644 index 0000000..dd2ac0b --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/HoldingInterfaceChoiceTests.cs @@ -0,0 +1,84 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +public class HoldingInterfaceChoiceTests +{ + private static readonly ContractId Target = new("holding-cid"); + + private static TransactionResult EmptyTransaction() => + new( + UpdateId: "upd-1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: Array.Empty(), + ArchivedContractIds: Array.Empty(), + CommandId: default); + + [Fact] + public void Describe_argument_round_trips_through_its_record() + { + var argument = new Describe("balance: "); + + var restored = Describe.FromRecord(argument.ToRecord()); + + restored.Should().Be(argument); + } + + [Fact] + public void DescribeCommand_carries_the_interface_id_and_the_choice_argument() + { + var command = Target.DescribeCommand(new Describe("balance: ")); + + command.TemplateId.Should().Be(IHolding.InterfaceId); + command.Choice.Should().Be(new ChoiceName("Describe")); + command.ContractId.Value.Should().Be("holding-cid"); + command.ChoiceArgument.As() + .GetRequiredField("prefix").As().Value.Should().Be("balance: "); + } + + [Fact] + public void ReissueCommand_carries_the_interface_id_and_the_choice_argument() + { + var command = Target.ReissueCommand(new Reissue(12.5m)); + + command.TemplateId.Should().Be(IHolding.InterfaceId); + command.Choice.Should().Be(new ChoiceName("Reissue")); + command.ChoiceArgument.As() + .GetRequiredField("newAmount").As().Value.Should().Be(12.5m); + } + + [Fact] + public async Task DescribeAsync_submits_the_interface_typed_exercise_command() + { + using var client = new FakeLedgerClient(_ => new ExerciseOutcome.One(EmptyTransaction())); + + var outcome = await Target.DescribeAsync(client, new Describe("balance: "), new Party("alice"), + cancellationToken: TestContext.Current.CancellationToken); + + outcome.Should().BeOfType.One>(); + client.LastSubmission!.CommandId.Should().NotBeNull( + "a generated interface exerciser must route through the shared submission helper, which assigns a command id"); + var command = client.LastSubmission!.Commands.Should().ContainSingle().Which + .Should().BeOfType().Subject; + command.TemplateId.Should().Be(IHolding.InterfaceId); + command.Choice.Should().Be(new ChoiceName("Describe")); + } + + [Fact] + public void HoldingView_round_trips_through_its_record() + { + var view = new HoldingView(42m); + + HoldingView.FromRecord(view.ToRecord()).Should().Be(view); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/InterfaceViewEnrichmentTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/InterfaceViewEnrichmentTests.cs new file mode 100644 index 0000000..f0ffcc7 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/InterfaceViewEnrichmentTests.cs @@ -0,0 +1,56 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// The generated interface marker is enriched for the typed interface/view family: +/// it exposes the static witness, mirrors +/// the view's fields as instance properties, and the view record implements the marker so +/// it answers with the interface's identity in identity-keyed generic code. +/// +public class InterfaceViewEnrichmentTests +{ + private static (Type Interface, Type View) InferredPair(ViewDescriptor descriptor) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord + { + descriptor.Should().NotBeNull(); + return (typeof(TInterface), typeof(TView)); + } + + private static DamlTypeDescriptor IdentityOf() where T : IDamlType => T.DamlTypeId; + + [Fact] + public void IHolding_View_witness_infers_the_marker_and_view_pair() + { + var pair = InferredPair(IHolding.View); + + pair.Should().Be((typeof(IHolding), typeof(HoldingView))); + } + + [Fact] + public void HoldingView_answers_with_the_interface_identity() + { + var identity = IdentityOf(); + + identity.Should().Be(IdentityOf()); + identity.Kind.Should().Be(DamlTypeKind.Interface); + identity.Identifier.EntityName.Should().Be("Holding"); + } + + [Fact] + public void HoldingView_amount_is_readable_through_a_marker_typed_variable() + { + IHolding marker = new HoldingView(42.5m); + + marker.Amount.Should().Be(42.5m); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/KeyDescriptorWitnessTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/KeyDescriptorWitnessTests.cs new file mode 100644 index 0000000..294f9e0 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/KeyDescriptorWitnessTests.cs @@ -0,0 +1,63 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Contractkeys; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// The emitted Key witness is what lets generic code reach a template's key codec from +/// the template type alone, with no reflection and no constraint on the key type. These tests +/// drive it through a generic constraint over both corpus key shapes: Account's record +/// key and Steward's bare Party key. +/// +public class KeyDescriptorWitnessTests +{ + private static readonly Party Custodian = new("custodian::1220"); + + private static TKey RoundTrip(TKey key) + where TTemplate : ITemplate, IHasKey + { + return TTemplate.Key.KeyDecoder(TTemplate.Key.KeyEncoder(key)); + } + + private static ExerciseByKeyCommand ArchiveByKey(TKey key) + where TTemplate : ITemplate, IHasKey + { + return new( + TTemplate.TemplateId, + TTemplate.Key.KeyEncoder(key), + new ChoiceName("Archive"), + DamlRecord.Create()); + } + + [Fact] + public void KeyDescriptorWitness_round_trips_a_record_key() + { + var key = new AccountKey(Custodian, "savings"); + + Account.Key.KeyEncoder(key).Should().Be(key.ToRecord()); + RoundTrip(key).Should().Be(key); + } + + [Fact] + public void KeyDescriptorWitness_round_trips_a_bare_party_key() + { + Steward.Key.KeyEncoder(Custodian).Should().Be(Custodian.ToDamlValue()); + RoundTrip(Custodian).Should().Be(Custodian); + } + + [Fact] + public void KeyDescriptorWitness_builds_the_by_key_command_the_emitted_builder_builds() + { + var key = new AccountKey(Custodian, "savings"); + + ArchiveByKey(key).Should().Be(Account.ArchiveByKeyCommand(key)); + ArchiveByKey(Custodian).Should().Be(Steward.ArchiveByKeyCommand(Custodian)); + } +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/MarkerTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/MarkerTests.cs index 829c0e3..b9ef04a 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/MarkerTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/MarkerTests.cs @@ -11,10 +11,10 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class MarkerTests { - private const string PackageHash = "8fe55b3b757427d13c28d7d3e39d95b3e7079dfe6ded9dd6daccec57ec7803ef"; + private const string PackageHash = "1e0f96e54a2b32b2e081b86edb35567a3ec6f087804f416583d54527e2b52e38"; [Fact] - public void to_record_then_from_record_round_trips_the_owner() + public void ToRecord_then_from_record_round_trips_the_owner() { var original = new Marker(new Party("alice")); @@ -24,7 +24,7 @@ public void to_record_then_from_record_round_trips_the_owner() } [Fact] - public void template_id_carries_package_module_and_entity() + public void Marker_template_id_carries_package_module_and_entity() { Marker.TemplateId.PackageId.Should().Be(PackageHash); Marker.TemplateId.ModuleName.Should().Be("RichTypes"); @@ -32,7 +32,7 @@ public void template_id_carries_package_module_and_entity() } [Fact] - public void package_metadata_matches_the_corpus_dar() + public void Marker_package_metadata_matches_the_corpus_dar() { Marker.PackageId.Should().Be(PackageHash); Marker.PackageName.Should().Be("richtypes"); @@ -40,14 +40,14 @@ public void package_metadata_matches_the_corpus_dar() } [Fact] - public void archive_choice_is_consuming_and_named_archive() + public void Marker_archive_choice_is_consuming_and_named_archive() { Marker.ChoiceArchive.Name.Value.Should().Be("Archive"); Marker.ChoiceArchive.Consuming.Should().BeTrue(); } [Fact] - public void contract_from_created_event_pairs_typed_id_with_decoded_payload() + public void Marker_contract_from_created_event_pairs_typed_id_with_decoded_payload() { var payload = new Marker(new Party("alice")); var @event = new CreatedEvent( @@ -57,7 +57,8 @@ public void contract_from_created_event_pairs_typed_id_with_decoded_payload() CreateArguments: payload.ToRecord(), WitnessParties: Array.Empty(), Signatories: Array.Empty(), - Observers: Array.Empty()); + Observers: Array.Empty(), + ContractKey: null); var contract = Marker.Contract.FromCreatedEvent(@event); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/OutcomeRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/OutcomeRoundTripTests.cs index 34f8e41..73f3ff7 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/OutcomeRoundTripTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/OutcomeRoundTripTests.cs @@ -12,7 +12,7 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class OutcomeRoundTripTests { [Fact] - public void win_constructor_round_trips_through_DamlVariant() + public void Win_constructor_round_trips_through_DamlVariant() { Outcome original = new Outcome.Win(new Outcome_Win(Prize: 12.34m, Tier: "gold")); @@ -22,7 +22,7 @@ public void win_constructor_round_trips_through_DamlVariant() } [Fact] - public void pending_constructor_round_trips_through_DamlVariant() + public void Pending_constructor_round_trips_through_DamlVariant() { Outcome original = new Outcome.Pending(); @@ -32,7 +32,7 @@ public void pending_constructor_round_trips_through_DamlVariant() } [Fact] - public void win_variant_serializes_to_tag_value_wire_shape() + public void Win_variant_serializes_to_tag_value_wire_shape() { Outcome outcome = new Outcome.Win(new Outcome_Win(Prize: 1.5m, Tier: "gold")); @@ -42,7 +42,7 @@ public void win_variant_serializes_to_tag_value_wire_shape() } [Fact] - public void pending_variant_serializes_to_tag_value_wire_shape() + public void Pending_variant_serializes_to_tag_value_wire_shape() { Outcome outcome = new Outcome.Pending(); @@ -52,7 +52,7 @@ public void pending_variant_serializes_to_tag_value_wire_shape() } [Fact] - public void pending_round_trips_through_json_deserialization() + public void Pending_round_trips_through_json_deserialization() { Outcome original = new Outcome.Pending(); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/ProfileRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/ProfileRoundTripTests.cs index 1c45c9a..1f8fcc2 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/ProfileRoundTripTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/ProfileRoundTripTests.cs @@ -11,7 +11,7 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class ProfileRoundTripTests { [Fact] - public void to_record_emits_fields_in_declaration_order_with_typed_values() + public void ToRecord_emits_fields_in_declaration_order_with_typed_values() { var profile = new Profile("ace", 7); @@ -23,7 +23,7 @@ public void to_record_emits_fields_in_declaration_order_with_typed_values() } [Fact] - public void from_record_reconstructs_the_original_value() + public void FromRecord_reconstructs_the_original_value() { var original = new Profile("ace", 7); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelAsyncTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelAsyncTests.cs index c9409cd..f69ba53 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelAsyncTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelAsyncTests.cs @@ -17,16 +17,26 @@ public class RelabelAsyncTests private static readonly ContractId Target = new("rich-cid"); private static readonly RichRecord.Relabel Argument = new("renamed"); + private static CreatedContract CreatedOf(string contractId, Identifier templateId) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: templateId, + Payload: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); + private static TransactionResult TransactionCreating(string newRichRecordId) => new( UpdateId: "upd-1", CompletionOffset: LedgerOffset.At(1), - CreatedContracts: new[] { new CreatedContract(newRichRecordId, RichRecord.TemplateId, "{}") }, + CreatedContracts: new[] { CreatedOf(newRichRecordId, RichRecord.TemplateId) }, ArchivedContractIds: Array.Empty(), CommandId: default); [Fact] - public async Task projects_the_created_rich_record_on_success() + public async Task RelabelAsync_projects_the_created_rich_record_on_success() { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.One(TransactionCreating("new-rich-cid"))); @@ -39,7 +49,7 @@ public async Task projects_the_created_rich_record_on_success() } [Fact] - public async Task builds_an_exercise_command_for_the_relabel_choice_on_the_target() + public async Task RelabelAsync_builds_an_exercise_command_for_the_relabel_choice_on_the_target() { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.One(TransactionCreating("new-rich-cid"))); @@ -49,51 +59,45 @@ public async Task builds_an_exercise_command_for_the_relabel_choice_on_the_targe client.LastSubmission.Should().NotBeNull(); client.LastSubmission!.WorkflowId.Should().Be(new WorkflowId("wf-7")); + client.LastSubmission.CommandId.Should().NotBeNull( + "a generated exerciser must route through the shared submission helper, which assigns a command id"); var command = client.LastSubmission.Commands.Should().ContainSingle().Which .Should().BeOfType().Subject; command.ContractId.Value.Should().Be("rich-cid"); command.Choice.Should().Be(new ChoiceName("Relabel")); } - [Fact] - public async Task omits_workflow_id_when_null() - { - using var client = new FakeLedgerClient( - _ => new ExerciseOutcome.One(TransactionCreating("new-rich-cid"))); - - await Target.RelabelAsync(client, Argument, new Party("alice"), workflowId: null, - cancellationToken: TestContext.Current.CancellationToken); - - client.LastSubmission!.WorkflowId.Should().BeNull(); - } - - [Fact] - public async Task omits_workflow_id_when_empty() + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t\n")] + public async Task RelabelAsync_omits_workflow_id_when_blank(string? workflowId) { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.One(TransactionCreating("new-rich-cid"))); - await Target.RelabelAsync(client, Argument, new Party("alice"), workflowId: "", + await Target.RelabelAsync(client, Argument, new Party("alice"), workflowId: workflowId, cancellationToken: TestContext.Current.CancellationToken); client.LastSubmission!.WorkflowId.Should().BeNull(); } [Fact] - public async Task forwards_whitespace_workflow_id_verbatim() + public async Task RelabelAsync_forwards_a_non_blank_workflow_id_verbatim() { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.One(TransactionCreating("new-rich-cid"))); - await Target.RelabelAsync(client, Argument, new Party("alice"), workflowId: " ", + await Target.RelabelAsync(client, Argument, new Party("alice"), workflowId: " padded ", cancellationToken: TestContext.Current.CancellationToken); client.LastSubmission!.WorkflowId.Should().NotBeNull(); - client.LastSubmission!.WorkflowId!.Value.Value.Should().Be(" "); + client.LastSubmission!.WorkflowId!.Value.Value.Should().Be(" padded "); } [Fact] - public async Task maps_a_daml_error_outcome_through_to_the_typed_result() + public async Task RelabelAsync_maps_a_daml_error_outcome_through_to_the_typed_result() { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.DamlError( @@ -111,7 +115,7 @@ public async Task maps_a_daml_error_outcome_through_to_the_typed_result() } [Fact] - public async Task maps_an_infra_error_outcome_through_to_the_typed_result() + public async Task RelabelAsync_maps_an_infra_error_outcome_through_to_the_typed_result() { using var client = new FakeLedgerClient( _ => new ExerciseOutcome.InfraError(14, "unavailable")); @@ -125,7 +129,7 @@ public async Task maps_an_infra_error_outcome_through_to_the_typed_result() } [Fact] - public async Task throws_on_null_contract_id() + public async Task RelabelAsync_throws_on_null_contract_id() { using var client = new FakeLedgerClient(); @@ -136,7 +140,7 @@ public async Task throws_on_null_contract_id() } [Fact] - public async Task throws_on_null_argument() + public async Task RelabelAsync_throws_on_null_argument() { using var client = new FakeLedgerClient(); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelResultTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelResultTests.cs index 3931594..39fe9f4 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelResultTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/RelabelResultTests.cs @@ -13,10 +13,20 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class RelabelResultTests { private static CreatedContract RichRecordCreated(string contractId) => - new(contractId, RichRecord.TemplateId, "{}"); + CreatedOf(contractId, RichRecord.TemplateId); + + private static CreatedContract CreatedOf(string contractId, Identifier templateId) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: templateId, + Payload: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); [Fact] - public void from_created_contracts_returns_one_with_the_typed_id_on_a_single_match() + public void FromCreatedContracts_returns_one_with_the_typed_id_on_a_single_match() { var outcome = RelabelResult.FromCreatedContracts(new[] { RichRecordCreated("rich-cid-1") }); @@ -25,9 +35,9 @@ public void from_created_contracts_returns_one_with_the_typed_id_on_a_single_mat } [Fact] - public void from_created_contracts_returns_none_when_the_required_template_is_absent() + public void FromCreatedContracts_returns_none_when_the_required_template_is_absent() { - var foreign = new CreatedContract("foreign-cid", new Identifier("other", "Other.Mod", "Foo"), "{}"); + var foreign = CreatedOf("foreign-cid", new Identifier("other", "Other.Mod", "Foo")); var outcome = RelabelResult.FromCreatedContracts(new[] { foreign }); @@ -35,7 +45,7 @@ public void from_created_contracts_returns_none_when_the_required_template_is_ab } [Fact] - public void from_created_contracts_returns_many_when_the_single_slot_overshoots() + public void FromCreatedContracts_returns_many_when_the_single_slot_overshoots() { var outcome = RelabelResult.FromCreatedContracts(new[] { @@ -50,12 +60,11 @@ public void from_created_contracts_returns_many_when_the_single_slot_overshoots( } [Fact] - public void from_created_contracts_matches_on_module_and_entity_ignoring_package_id() + public void FromCreatedContracts_matches_on_module_and_entity_ignoring_package_id() { - var upgraded = new CreatedContract( + var upgraded = CreatedOf( "rich-cid-1", - RichRecord.TemplateId with { PackageId = "a-different-package-hash" }, - "{}"); + RichRecord.TemplateId with { PackageId = "a-different-package-hash" }); var outcome = RelabelResult.FromCreatedContracts(new[] { upgraded }); @@ -63,7 +72,7 @@ public void from_created_contracts_matches_on_module_and_entity_ignoring_package } [Fact] - public void from_created_contracts_throws_on_null_input() + public void FromCreatedContracts_throws_on_null_input() { var act = () => RelabelResult.FromCreatedContracts(null!); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordChoiceTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordChoiceTests.cs index 074f9be..24a177d 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordChoiceTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordChoiceTests.cs @@ -12,7 +12,7 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class RichRecordChoiceTests { [Fact] - public void relabel_argument_round_trips_through_its_record() + public void Relabel_argument_round_trips_through_its_record() { var argument = new RichRecord.Relabel("renamed"); @@ -23,21 +23,21 @@ public void relabel_argument_round_trips_through_its_record() } [Fact] - public void relabel_choice_is_non_consuming_and_named_relabel() + public void Relabel_choice_is_non_consuming_and_named_relabel() { RichRecord.ChoiceRelabel.Name.Value.Should().Be("Relabel"); RichRecord.ChoiceRelabel.Consuming.Should().BeFalse(); } [Fact] - public void archive_choice_is_consuming_and_named_archive() + public void RichRecordChoice_archive_choice_is_consuming_and_named_archive() { RichRecord.ChoiceArchive.Name.Value.Should().Be("Archive"); RichRecord.ChoiceArchive.Consuming.Should().BeTrue(); } [Fact] - public void contract_from_created_event_pairs_typed_id_with_decoded_payload() + public void RichRecordChoice_contract_from_created_event_pairs_typed_id_with_decoded_payload() { var payload = new RichRecord( Owner: new Party("alice"), @@ -64,7 +64,8 @@ public void contract_from_created_event_pairs_typed_id_with_decoded_payload() CreateArguments: payload.ToRecord(), WitnessParties: Array.Empty(), Signatories: Array.Empty(), - Observers: Array.Empty()); + Observers: Array.Empty(), + ContractKey: null); var contract = RichRecord.Contract.FromCreatedEvent(@event); @@ -73,7 +74,7 @@ public void contract_from_created_event_pairs_typed_id_with_decoded_payload() } [Fact] - public void contract_identifiers_expose_template_ids_for_pqs_queries() + public void RichRecordChoice_contract_identifiers_expose_template_ids_for_pqs_queries() { ContractIdentifiers.Marker.Should().Contain("RichTypes:Marker"); ContractIdentifiers.RichRecord.Should().Contain("RichTypes:RichRecord"); diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordRoundTripTests.cs index 1858688..63998ed 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordRoundTripTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/RichRecordRoundTripTests.cs @@ -36,7 +36,7 @@ public class RichRecordRoundTripTests Fee: 1.5m); [Fact] - public void round_trips_every_field_when_optional_is_present() + public void RichRecordRoundTrip_round_trips_every_field_when_optional_is_present() { var original = Sample(note: "hello"); @@ -46,7 +46,7 @@ public void round_trips_every_field_when_optional_is_present() } [Fact] - public void round_trips_every_field_when_optional_is_absent() + public void RichRecordRoundTrip_round_trips_every_field_when_optional_is_absent() { var original = Sample(note: null); @@ -57,7 +57,7 @@ public void round_trips_every_field_when_optional_is_absent() } [Fact] - public void to_record_maps_primitives_to_their_daml_backing_types() + public void ToRecord_maps_primitives_to_their_daml_backing_types() { var record = Sample(note: "hello").ToRecord(); @@ -72,7 +72,7 @@ public void to_record_maps_primitives_to_their_daml_backing_types() } [Fact] - public void to_record_emits_none_for_an_absent_optional() + public void ToRecord_emits_none_for_an_absent_optional() { var record = Sample(note: null).ToRecord(); @@ -80,7 +80,7 @@ public void to_record_emits_none_for_an_absent_optional() } [Fact] - public void to_record_serializes_list_and_textmap_fields() + public void ToRecord_serializes_list_and_textmap_fields() { var record = Sample(note: "hello").ToRecord(); @@ -92,7 +92,7 @@ public void to_record_serializes_list_and_textmap_fields() } [Fact] - public void to_record_nests_the_profile_record() + public void ToRecord_nests_the_profile_record() { var record = Sample(note: "hello").ToRecord(); @@ -102,7 +102,7 @@ public void to_record_nests_the_profile_record() } [Fact] - public void to_record_wires_the_outcome_variant() + public void ToRecord_wires_the_outcome_variant() { var record = Sample(note: "hello").ToRecord(); @@ -114,7 +114,7 @@ public void to_record_wires_the_outcome_variant() } [Fact] - public void to_record_wires_the_suit_enum() + public void ToRecord_wires_the_suit_enum() { var record = Sample(note: "hello").ToRecord(); @@ -122,7 +122,7 @@ public void to_record_wires_the_suit_enum() } [Fact] - public void non_default_scale_fee_serializes_unpadded() + public void RichRecordRoundTrip_non_default_scale_fee_serializes_unpadded() { var record = new RichRecord( Owner: new Party("alice"), @@ -149,7 +149,7 @@ public void non_default_scale_fee_serializes_unpadded() } [Fact] - public void interface_typed_contract_id_fields_round_trip_without_throwing() + public void RichRecordRoundTrip_interface_typed_contract_id_fields_round_trip_without_throwing() { var scalarCid = "00aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"; var listCids = new[] diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/SerializeThenReadRecordRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/SerializeThenReadRecordRoundTripTests.cs new file mode 100644 index 0000000..85f7eed --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/SerializeThenReadRecordRoundTripTests.cs @@ -0,0 +1,168 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Contractkeys; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Daml.Runtime.Stdlib; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +/// +/// Pipes the writer's output straight into the typed reader for the whole conformance corpus. +/// The writer and the untyped reader form a self-consistent pair, so only crossing the seam to +/// the typed reader — the one the ledger's own grammar is pinned against — can catch a wire-shape +/// divergence. +/// +public class SerializeThenReadRecordRoundTripTests +{ + private static readonly IReadOnlyDictionary Corpus = + new Dictionary(StringComparer.Ordinal) + { + [nameof(RichRecord)] = (typeof(RichRecord), RichRecordSample(new Outcome.Win( + new Outcome_Win(Prize: 12.34m, Tier: "gold"))).ToRecord()), + ["RichRecord_nullary_variant_arm"] = (typeof(RichRecord), + RichRecordSample(new Outcome.Pending()).ToRecord()), + [nameof(TypeCorners)] = (typeof(TypeCorners), TypeCornersSample().ToRecord()), + [nameof(Profile)] = (typeof(Profile), new Profile("ace", 7).ToRecord()), + [nameof(Outcome_Win)] = (typeof(Outcome_Win), new Outcome_Win(Prize: 12.34m, Tier: "gold").ToRecord()), + [nameof(Account)] = (typeof(Account), + new Account(new Party("alice"), "savings", 1_000).ToRecord()), + [nameof(AccountKey)] = (typeof(AccountKey), new AccountKey(new Party("alice"), "savings").ToRecord()), + }; + + /// + /// The generated generic records Box, Crate and Slot take their field + /// converters as arguments, so the emitter cannot put IDamlRecord on them and the typed + /// reader has no slot mapping for them. TypeCorners carries three of them, which puts the + /// whole template outside the reader today; the gap is pinned here so that closing it fails this + /// test and forces TypeCorners into the round-trip theory. + /// + private const string TypeCornersReaderGap = + "CLR type 'Daml.Codegen.Testing.Conformance.Richtypes.Box`1[System.String]' at " + + "'TypeCorners.boxedText' lies outside the Daml type mapping"; + + public static TheoryData RoundTrippableCorpusEntries => + [.. Corpus.Keys.Where(entry => entry != nameof(TypeCorners))]; + + [Theory] + [MemberData(nameof(RoundTrippableCorpusEntries))] + public void ReadRecord_round_trips_the_writer_output_for_every_corpus_record(string entry) + { + var (recordType, record) = Corpus[entry]; + + var restored = DamlLfJsonReader.ReadRecord(DamlJsonSerializer.Serialize(record), recordType); + + restored.Should().Be(WithoutTypeIdentifiers(record)); + } + + [Fact] + public void ReadRecord_pins_the_reader_gap_that_keeps_TypeCorners_out_of_the_round_trip() + { + var act = () => DamlLfJsonReader.ReadRecord( + DamlJsonSerializer.Serialize(Corpus[nameof(TypeCorners)].Record)); + + act.Should().Throw().WithMessage($"*{TypeCornersReaderGap}*"); + } + + [Fact] + public void SerializeThenReadRecordRoundTrip_covers_every_arm_of_the_writer_switch() + { + var reached = Corpus.Values + .SelectMany(entry => ValuesReachableFrom(entry.Record)) + .Select(value => value.GetType().Name) + .Distinct(); + + reached.Should().BeEquivalentTo( + WriterSwitchArms(), + "the corpus is only a wire-grammar gate for the arms it actually exercises, and a new " + + "DamlValue arm that no corpus record reaches would ship unchecked"); + } + + private static IEnumerable WriterSwitchArms() => + typeof(DamlValue).Assembly.GetTypes() + .Where(type => type.IsSubclassOf(typeof(DamlValue)) && !type.IsAbstract) + .Select(type => type.Name); + + private static IEnumerable ValuesReachableFrom(DamlValue value) => + [value, .. DirectChildrenOf(value).SelectMany(ValuesReachableFrom)]; + + private static IEnumerable DirectChildrenOf(DamlValue value) => value switch + { + DamlRecord record => record.Fields.Select(field => field.Value), + DamlVariant variant => [variant.Value], + DamlList list => list.Values, + DamlTextMap map => map.Values.Values, + DamlGenMap map => map.Entries.SelectMany(entry => new[] { entry.Key, entry.Value }), + DamlOptional { Value: { } inner } => [inner], + DamlOptionalChain { Value: { } inner } => [inner], + _ => [], + }; + + /// + /// Drops the Daml type identifiers the LF-JSON wire never carries, so the two sides of the + /// round trip are compared on wire content alone. ContractId<T>.ToDamlValue() + /// stamps a template id and the generated enum and variant converters may stamp a type id; + /// the reader can only ever produce them unstamped. + /// + private static DamlValue WithoutTypeIdentifiers(DamlValue value) => value switch + { + DamlRecord record => new DamlRecord(null, + [.. record.Fields.Select(field => new DamlField(field.Label, WithoutTypeIdentifiers(field.Value)))]), + DamlVariant variant => new DamlVariant(null, variant.Constructor, WithoutTypeIdentifiers(variant.Value)), + DamlEnum enumValue => new DamlEnum(null, enumValue.Constructor), + DamlContractId contractId => new DamlContractId(contractId.Value), + DamlList list => new DamlList([.. list.Values.Select(WithoutTypeIdentifiers)]), + DamlTextMap map => new DamlTextMap(map.Values.ToDictionary( + entry => entry.Key, entry => WithoutTypeIdentifiers(entry.Value))), + DamlGenMap map => new DamlGenMap([.. map.Entries.Select(entry => + (WithoutTypeIdentifiers(entry.Key), WithoutTypeIdentifiers(entry.Value)))]), + DamlOptional { Value: { } inner } => new DamlOptional(WithoutTypeIdentifiers(inner)), + DamlOptionalChain { Value: { } inner } => new DamlOptionalChain(WithoutTypeIdentifiers(inner)), + _ => value, + }; + + private static RichRecord RichRecordSample(Outcome outcome) => new( + Owner: new Party("alice"), + Count: 42, + Amount: 19.95m, + Label: "first", + Active: true, + AsOf: new DateOnly(2026, 6, 4), + ObservedAt: new DateTimeOffset(2026, 6, 4, 12, 30, 0, TimeSpan.Zero), + Note: "hello", + Tags: ["a", "b"], + Attributes: new Dictionary { ["k1"] = "v1", ["k2"] = "v2" }, + Marker: new ContractId("marker-cid"), + HoldingCid: new ContractId("00aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"), + HoldingCids: + [ + new ContractId("0011112222333344445555666677778888999900001111222233334444555566aa"), + ], + Profile: new Profile("ace", 7), + Outcome: outcome, + Suit: Suit.Hearts, + Fee: 1.5m); + + private static TypeCorners TypeCornersSample() => new( + Owner: new Party("alice"), + BoxedText: new Box("boxed"), + BoxedProfile: new Box(new Profile("ace", 7)), + Slot: new Slot.Filled(11), + NestedNote: new Box>(new Optional.Some("inner")), + MaybeMaybeNote: new Optional>.Some(new Optional.Some("nested")), + Crate: new Crate(new Optional.Some("crated")), + QuotaByParty: new Dictionary { [new Party("alice")] = 1 }, + LabelByRank: new Dictionary { [1] = "gold" }, + RankOrLabel: new Either.Right("runner-up"), + NoteOrRank: new Either, long>.Left(new Optional.Some("noted")), + Pair: new Tuple2("pair", 3), + Triple: new Tuple3("triple", 4, true), + Branch: new Branch("root", [new Branch("leaf", [])]), + Whole: 42m, + Finest: 0.5m); +} diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/SubmissionExtensionsTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/SubmissionExtensionsTests.cs index 3e30afd..52db4b0 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/SubmissionExtensionsTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/SubmissionExtensionsTests.cs @@ -12,36 +12,54 @@ namespace Daml.Codegen.Testing.Conformance.Tests; public class SubmissionExtensionsTests { + private const string AlicePartyId = "alice"; + [Fact] - public async Task marker_create_async_projects_a_created_contract_id() + public async Task Marker_create_async_projects_a_created_contract_id() { using var client = new FakeLedgerClient( create: _ => new ExerciseOutcome.One("marker-cid")); - var payload = new Marker(new Party("alice")); + var payload = new Marker(new Party(AlicePartyId)); - var outcome = await client.CreateAsync(payload, new Party("alice"), TestContext.Current.CancellationToken); + var outcome = await client.CreateAsync(payload, TestContext.Current.CancellationToken); outcome.Should().BeOfType>.One>(); ((ExerciseOutcome>.One)outcome).Result.Value.Should().Be("marker-cid"); } [Fact] - public async Task marker_create_async_throws_on_null_payload() + public async Task Marker_create_async_submits_as_the_owner_carried_by_the_payload() + { + using var client = new FakeLedgerClient( + create: _ => new ExerciseOutcome.One("marker-cid")); + var payload = new Marker(new Party(AlicePartyId)); + + await client.CreateAsync(payload, TestContext.Current.CancellationToken); + + client.LastCreateSubmitter.Should().NotBeNull( + "the payload-derived overload takes no submitter argument, so the wrapper is the only thing that can supply one"); + client.LastCreateSubmitter!.Value.ActAs.Should().ContainSingle().Which.Should().Be( + new Party(AlicePartyId), + "the sole Daml signatory is the payload's owner field, so the wrapper must act as that party and no other"); + } + + [Fact] + public async Task Marker_create_async_throws_on_null_payload() { using var client = new FakeLedgerClient(); - var act = async () => await client.CreateAsync((Marker)null!, new Party("alice")); + var act = async () => await client.CreateAsync((Marker)null!); await act.Should().ThrowAsync(); } [Fact] - public async Task rich_record_create_async_projects_a_created_contract_id() + public async Task RichRecord_create_async_projects_a_created_contract_id() { using var client = new FakeLedgerClient( create: _ => new ExerciseOutcome.One("rich-cid")); var payload = new RichRecord( - Owner: new Party("alice"), + Owner: new Party(AlicePartyId), Count: 1, Amount: 1m, Label: "l", @@ -59,9 +77,12 @@ public async Task rich_record_create_async_projects_a_created_contract_id() Suit: Suit.Diamonds, Fee: 0m); - var outcome = await client.CreateAsync(payload, new Party("alice"), TestContext.Current.CancellationToken); + var outcome = await client.CreateAsync(payload, TestContext.Current.CancellationToken); outcome.Should().BeOfType>.One>(); ((ExerciseOutcome>.One)outcome).Result.Value.Should().Be("rich-cid"); + client.LastCreateSubmitter!.Value.ActAs.Should().ContainSingle().Which.Should().Be( + new Party(AlicePartyId), + "a payload carrying many non-party fields must still resolve its submitter from the owner field alone"); } } diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/SuitRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/SuitRoundTripTests.cs index 856288c..a15b2bb 100644 --- a/tests/Daml.Codegen.Testing.Conformance.Tests/SuitRoundTripTests.cs +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/SuitRoundTripTests.cs @@ -17,7 +17,7 @@ public class SuitRoundTripTests [InlineData(Suit.Diamonds)] [InlineData(Suit.Hearts)] [InlineData(Suit.Spades)] - public void every_constructor_round_trips_through_DamlEnum(Suit original) + public void SuitRoundTrip_every_constructor_round_trips_through_DamlEnum(Suit original) { var restored = SuitExtensions.FromDamlEnum(original.ToDamlEnum()); @@ -25,7 +25,7 @@ public void every_constructor_round_trips_through_DamlEnum(Suit original) } [Fact] - public void clubs_serializes_to_bare_string_wire_shape() + public void SuitRoundTrip_clubs_serializes_to_bare_string_wire_shape() { var json = DamlJsonSerializer.Serialize(Suit.Clubs.ToDamlEnum()); @@ -33,7 +33,7 @@ public void clubs_serializes_to_bare_string_wire_shape() } [Fact] - public void spades_round_trips_through_json_deserialization_given_the_wire_type_back() + public void SuitRoundTrip_spades_round_trips_through_json_deserialization_given_the_wire_type_back() { Suit original = Suit.Spades; @@ -45,7 +45,7 @@ public void spades_round_trips_through_json_deserialization_given_the_wire_type_ } [Fact] - public void from_daml_enum_throws_for_an_unrecognized_constructor() + public void FromDamlEnum_throws_for_an_unrecognized_constructor() { var act = () => SuitExtensions.FromDamlEnum(DamlEnum.Create("Joker")); @@ -53,7 +53,7 @@ public void from_daml_enum_throws_for_an_unrecognized_constructor() } [Fact] - public void to_daml_enum_throws_for_a_value_outside_the_declared_range() + public void ToDamlEnum_throws_for_a_value_outside_the_declared_range() { var invalid = (Suit)99; diff --git a/tests/Daml.Codegen.Testing.Conformance.Tests/TypeCornersRoundTripTests.cs b/tests/Daml.Codegen.Testing.Conformance.Tests/TypeCornersRoundTripTests.cs new file mode 100644 index 0000000..a0520c0 --- /dev/null +++ b/tests/Daml.Codegen.Testing.Conformance.Tests/TypeCornersRoundTripTests.cs @@ -0,0 +1,388 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Daml.Runtime.Stdlib; +using AwesomeAssertions; +using Daml.Codegen.Testing.Conformance.Richtypes; +using Xunit; + +namespace Daml.Codegen.Testing.Conformance.Tests; + +public class TypeCornersRoundTripTests +{ + private static TypeCorners Sample( + Box>? nestedNote = null, + Optional>? maybeMaybeNote = null) => new( + Owner: new Party("alice"), + BoxedText: new Box("boxed"), + BoxedProfile: new Box(new Profile("ace", 7)), + Slot: new Slot.Filled(11), + NestedNote: nestedNote, + MaybeMaybeNote: maybeMaybeNote ?? new Optional>.None(), + Crate: new Crate(new Optional.Some("crated")), + QuotaByParty: new Dictionary + { + [new Party("alice")] = 1, + [new Party("bob")] = 2, + }, + LabelByRank: new Dictionary { [1] = "gold", [2] = "silver" }, + RankOrLabel: new Either.Right("runner-up"), + NoteOrRank: new Either, long>.Left(new Optional.Some("noted")), + Pair: new Tuple2("pair", 3), + Triple: new Tuple3("triple", 4, true), + Branch: new Branch("root", new List + { + new Branch("left", new List()), + new Branch("right", new List { new Branch("leaf", new List()) }), + }), + Whole: 42m, + Finest: 0.5m); + + private static DamlRecord SampleRecordWith(string label, DamlValue value) + { + var record = Sample().ToRecord(); + record.GetField(label).Should().NotBeNull(); + return record with + { + Fields = [.. record.Fields.Select(field => field.Label == label ? DamlField.Create(label, value) : field)], + }; + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_every_corner_field() + { + var original = Sample(nestedNote: new Box>(new Optional.Some("inner"))); + + var restored = TypeCorners.FromRecord(original.ToRecord()); + + restored.Should().BeEquivalentTo(original); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_the_parameterized_record_and_variant() + { + var restored = TypeCorners.FromRecord(Sample().ToRecord()); + + restored.BoxedText.Item.Should().Be("boxed"); + restored.BoxedProfile.Item.Should().Be(new Profile("ace", 7)); + restored.Slot.Should().BeOfType.Filled>().Subject.Value.Should().Be(11); + } + + [Fact] + public void ToRecord_wires_the_parameterized_variant_as_a_daml_variant() + { + var slot = Sample().ToRecord().GetRequiredField("slot").As(); + + slot.Constructor.Should().Be("Filled"); + slot.Value.As().Value.Should().Be(11); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_the_vacant_constructor_of_the_parameterized_variant() + { + var original = Sample() with { Slot = new Slot.Vacant() }; + + var restored = TypeCorners.FromRecord(original.ToRecord()); + + restored.Slot.Should().BeOfType.Vacant>(); + } + + [Fact] + public void ToRecord_serializes_non_text_keyed_maps_as_gen_maps() + { + var record = Sample().ToRecord(); + + var quotaByParty = record.GetRequiredField("quotaByParty").As(); + quotaByParty.Entries.Select(e => e.Key.As().Value).Should().BeEquivalentTo("alice", "bob"); + + var labelByRank = record.GetRequiredField("labelByRank").As(); + labelByRank.Entries.Select(e => e.Key.As().Value).Should().BeEquivalentTo([1L, 2L]); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_an_optional_nested_inside_an_optional_through_the_wrapper_record() + { + var outerAndInnerPresent = TypeCorners.FromRecord( + Sample(new Box>(new Optional.Some("inner"))).ToRecord()); + var outerPresentInnerAbsent = TypeCorners.FromRecord( + Sample(new Box>(new Optional.None())).ToRecord()); + var outerAbsent = TypeCorners.FromRecord(Sample(nestedNote: null).ToRecord()); + + outerAndInnerPresent.NestedNote!.Item.GetValueOrThrow().Should().Be("inner"); + outerPresentInnerAbsent.NestedNote!.Item.HasValue.Should().BeFalse(); + outerAbsent.NestedNote.Should().BeNull(); + } + + [Fact] + public void ToRecord_distinguishes_an_absent_wrapper_from_a_wrapper_holding_none() + { + var outerAbsent = Sample(nestedNote: null) + .ToRecord().GetRequiredField("nestedNote").As(); + var innerAbsent = Sample(new Box>(new Optional.None())) + .ToRecord().GetRequiredField("nestedNote").As(); + + outerAbsent.HasValue.Should().BeFalse(); + innerAbsent.HasValue.Should().BeTrue(); + innerAbsent.Value!.As().GetRequiredField("item").As() + .HasValue.Should().BeFalse(); + } + + [Fact] + public void ToRecord_keeps_the_wrapped_optional_on_the_flat_wire_encoding() + { + var innerPresent = Sample(new Box>(new Optional.Some("inner"))) + .ToRecord().GetRequiredField("nestedNote").As(); + + innerPresent.Value!.As().GetRequiredField("item") + .Should().BeOfType(); + } + + public sealed record NestedNoteBox( + [property: DamlFieldAttribute("item")] Optional Item) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("item", Item.ToValue(value => new DamlText(value)))); + } + + [Fact] + public void TypeCornersRoundTrip_decodes_the_wrapped_optional_field_from_its_flat_json_payload() + { + var carried = DamlLfJsonReader.ReadRecord("""{"item":"deep"}"""); + var absent = DamlLfJsonReader.ReadRecord("""{"item":null}"""); + + var innerPresent = TypeCorners.FromRecord(SampleRecordWith("nestedNote", DamlOptional.Some(carried))); + var innerAbsent = TypeCorners.FromRecord(SampleRecordWith("nestedNote", DamlOptional.Some(absent))); + + innerPresent.NestedNote!.Item.GetValueOrThrow().Should().Be("deep"); + innerAbsent.NestedNote!.Item.HasValue.Should().BeFalse(); + } + + [Fact] + public void TypeCornersRoundTrip_rejects_the_array_form_for_the_wrapped_optional_field() + { + var readArrayForm = () => DamlLfJsonReader.ReadRecord("""{"item":["deep"]}"""); + + readArrayForm.Should().Throw( + "the Daml type under this wrapper is a flat Optional Text, so the array form belongs " + + "to a nested chain and must not be accepted here"); + } + + [Fact] + public void ToRecord_renders_the_wrapped_optional_as_flat_json_rather_than_the_array_form() + { + var innerPresent = DamlJsonSerializer.Serialize( + Sample(new Box>(new Optional.Some("inner"))).ToRecord()); + var innerAbsent = DamlJsonSerializer.Serialize( + Sample(new Box>(new Optional.None())).ToRecord()); + + innerPresent.Should().Contain("""{"item":"inner"}""").And.NotContain("""["inner"]"""); + innerAbsent.Should().Contain("""{"item":null}""").And.NotContain("""{"item":[]}"""); + } + + [Fact] + public void TypeCornersRoundTrip_keeps_the_three_inhabitants_of_a_nested_optional_distinct() + { + var outerAbsent = TypeCorners.FromRecord( + Sample(maybeMaybeNote: new Optional>.None()).ToRecord()); + var innerAbsent = TypeCorners.FromRecord( + Sample(maybeMaybeNote: new Optional>.Some(new Optional.None())).ToRecord()); + var present = TypeCorners.FromRecord( + Sample(maybeMaybeNote: new Optional>.Some(new Optional.Some("deep"))).ToRecord()); + + outerAbsent.MaybeMaybeNote.HasValue.Should().BeFalse(); + innerAbsent.MaybeMaybeNote.GetValueOrThrow().HasValue.Should().BeFalse(); + present.MaybeMaybeNote.GetValueOrThrow().GetValueOrThrow().Should().Be("deep"); + } + + [Fact] + public void ToRecord_encodes_a_nested_optional_on_the_array_form_the_participant_accepts() + { + DamlJsonSerializer.Serialize( + Sample(maybeMaybeNote: new Optional>.None()) + .ToRecord().GetRequiredField("maybeMaybeNote")).Should().Be("[]"); + DamlJsonSerializer.Serialize( + Sample(maybeMaybeNote: new Optional>.Some(new Optional.None())) + .ToRecord().GetRequiredField("maybeMaybeNote")).Should().Be("[[]]"); + DamlJsonSerializer.Serialize( + Sample(maybeMaybeNote: new Optional>.Some(new Optional.Some("deep"))) + .ToRecord().GetRequiredField("maybeMaybeNote")).Should().Be("""[["deep"]]"""); + } + + [Fact] + public void ToRecord_leaves_the_case_c_nested_note_on_the_flat_encoding() + { + DamlJsonSerializer.Serialize(Sample().ToRecord().GetRequiredField("nestedNote")) + .Should().NotStartWith("[", + "nestedNote is Optional of Box of Optional Text - the Box between the two Optionals makes " + + "the inner one a flat optional, and the array form there would be a wire regression"); + } + + [Fact] + public void TypeCornersRoundTrip_records_the_short_write_codegen_refuses_to_emit() + { + var substituted = new Crate>( + new Optional>.Some(new Optional.None())); + + var written = DamlJsonSerializer.Serialize( + substituted.ToRecord(item => item.ToValue(text => new DamlText(text))) + .GetRequiredField("item")); + + written.Should().Be("null", + "Crate's field converter is emitted once from its declaration and writes the flat form, so " + + "composing it with an Optional type argument lands one array level short of the chain the " + + "participant accepts - which is why codegen refuses to emit that substitution and this " + + "composition can only be reached by hand"); + + var decodingTheAcceptedForm = () => Crate>.FromRecord( + DamlRecord.Create(DamlField.Create( + "item", DamlOptionalChain.Some(DamlOptionalChain.None))), + value => Optional.FromValue(value, text => text.As().Value)); + + decodingTheAcceptedForm.Should().Throw( + "the strict As of DamlOptional in Optional.FromValue makes the gap loud rather than silent") + .WithMessage("*DamlOptionalChain*DamlOptional*"); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_an_optional_over_a_type_variable_through_the_wrapper() + { + var carried = TypeCorners.FromRecord(Sample().ToRecord()); + var absent = TypeCorners.FromRecord( + (Sample() with { Crate = new Crate(new Optional.None()) }).ToRecord()); + + carried.Crate.Item.GetValueOrThrow().Should().Be("crated"); + absent.Crate.Item.HasValue.Should().BeFalse(); + } + + [Fact] + public void ToRecord_keeps_an_optional_over_a_type_variable_on_the_flat_wire_encoding() + { + var crate = Sample().ToRecord().GetRequiredField("crate").As(); + + crate.GetRequiredField("item").Should().BeOfType(); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_either_in_both_branches() + { + var right = TypeCorners.FromRecord(Sample().ToRecord()); + var left = TypeCorners.FromRecord((Sample() with { RankOrLabel = new Either.Left(9) }).ToRecord()); + + right.RankOrLabel.Should().BeOfType.Right>().Subject.Value.Should().Be("runner-up"); + left.RankOrLabel.Should().BeOfType.Left>().Subject.Value.Should().Be(9); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_an_optional_carried_by_an_either_arm() + { + var carried = TypeCorners.FromRecord(Sample().ToRecord()); + var absent = TypeCorners.FromRecord((Sample() with + { + NoteOrRank = new Either, long>.Left(new Optional.None()), + }).ToRecord()); + var otherArm = TypeCorners.FromRecord((Sample() with + { + NoteOrRank = new Either, long>.Right(5), + }).ToRecord()); + + carried.NoteOrRank.Should().BeOfType, long>.Left>() + .Subject.Value.GetValueOrThrow().Should().Be("noted"); + absent.NoteOrRank.Should().BeOfType, long>.Left>() + .Subject.Value.HasValue.Should().BeFalse(); + otherArm.NoteOrRank.Should().BeOfType, long>.Right>() + .Subject.Value.Should().Be(5); + } + + [Fact] + public void ToRecord_keeps_an_optional_carried_by_an_either_arm_on_the_flat_wire_encoding() + { + var noteOrRank = Sample().ToRecord().GetRequiredField("noteOrRank").As(); + + noteOrRank.Constructor.Should().Be("Left"); + noteOrRank.Value.Should().BeOfType( + "the Optional sits directly under an Either arm, so the array form of a nested chain " + + "there would be a wire regression"); + noteOrRank.Value.As().Value!.As().Value.Should().Be("noted"); + } + + [Fact] + public void ToRecord_wires_tuples_as_positional_records() + { + var record = Sample().ToRecord(); + + var pair = record.GetRequiredField("pair").As(); + pair.GetRequiredField("_1").As().Value.Should().Be("pair"); + pair.GetRequiredField("_2").As().Value.Should().Be(3); + + var triple = record.GetRequiredField("triple").As(); + triple.GetRequiredField("_3").As().Value.Should().BeTrue(); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_a_recursive_record_to_its_full_depth() + { + var restored = TypeCorners.FromRecord(Sample().ToRecord()); + + restored.Branch.Children.Should().HaveCount(2); + restored.Branch.Children[1].Children.Should().ContainSingle() + .Which.Label.Should().Be("leaf"); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_the_scale_zero_corner_from_integral_canonical_text() + { + const string integralCanonicalText = "7"; + DamlNumeric.TryParseCanonical(integralCanonicalText, out var wire).Should().BeTrue(); + + var restored = TypeCorners.FromRecord(SampleRecordWith("whole", wire)); + + restored.Whole.Should().Be(7m); + + const string scaleZeroShapeLostBecauseDecimalCarriesNoScale = "7.0"; + restored.ToRecord().GetRequiredField("whole").As().ToCanonicalString() + .Should().Be(scaleZeroShapeLostBecauseDecimalCarriesNoScale); + } + + [Fact] + public void TypeCornersRoundTrip_round_trips_the_numeric_37_corner_up_to_decimal_precision() + { + const string twentyEightFractionalDigits = "0.1234567890123456789012345678"; + DamlNumeric.TryParseCanonical(twentyEightFractionalDigits, out var wire).Should().BeTrue(); + + var restored = TypeCorners.FromRecord(SampleRecordWith("finest", wire)); + + restored.Finest.Should().Be(0.1234567890123456789012345678m); + restored.ToRecord().GetRequiredField("finest").As().ToCanonicalString() + .Should().Be(twentyEightFractionalDigits); + } + + [Fact] + public void TypeCornersRoundTrip_overflows_on_the_numeric_37_corner_beyond_decimal_precision() + { + var thirtySevenFractionalDigits = "0." + new string('0', 36) + "1"; + DamlNumeric.TryParseCanonical(thirtySevenFractionalDigits, out var wire).Should().BeTrue(); + wire.ToCanonicalString().Should().Be(thirtySevenFractionalDigits, + "the wire type carries the full Numeric 37 corner losslessly, so any loss below is the generated model's"); + + var restore = () => TypeCorners.FromRecord(SampleRecordWith("finest", wire)); + + restore.Should().Throw( + "the emitter maps every Numeric scale to decimal, so the Numeric 37 corner is only " + + "round-trippable up to decimal's 28 fractional digits") + .WithMessage("*" + thirtySevenFractionalDigits + "*"); + } + + [Fact] + public void Rebox_choice_argument_carries_the_parameterized_record() + { + var argument = new TypeCorners.Rebox(new Box("replaced")); + + var restored = TypeCorners.Rebox.FromRecord(argument.ToRecord()); + + restored.Replacement.Item.Should().Be("replaced"); + restored.Should().Be(argument); + } +} diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ActAsIgnoringSubmitterFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ActAsIgnoringSubmitterFakeClient.cs index ecfbe89..93eec48 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ActAsIgnoringSubmitterFakeClient.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ActAsIgnoringSubmitterFakeClient.cs @@ -61,11 +61,13 @@ private void RequireAuthorized(CommandsSubmission submission) public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate => throw new NotSupportedException(); @@ -73,25 +75,54 @@ public Task>> TryCreateAsync( public IAsyncEnumerable> SubscribeAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeLedgerEffectsAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public Task GetLedgerEndAsync( TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/AuthorizingFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/AuthorizingFakeClient.cs index 0c0eb6d..948add4 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/AuthorizingFakeClient.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/AuthorizingFakeClient.cs @@ -62,11 +62,13 @@ private void RequireAuthorized(CommandsSubmission submission, SubmitterInfo subm public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate => throw new NotSupportedException(); @@ -74,25 +76,54 @@ public Task>> TryCreateAsync( public IAsyncEnumerable> SubscribeAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeLedgerEffectsAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public Task GetLedgerEndAsync( TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdConformanceTests.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdConformanceTests.cs new file mode 100644 index 0000000..6f53234 --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdConformanceTests.cs @@ -0,0 +1,197 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Threading.Tasks; +using AwesomeAssertions; +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Xunit; + +namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; + +public sealed class CommandIdConformanceTests +{ + private static readonly SubmitterInfo Submitter = new(new Party("alice")); + + private const string VerbatimContractFragment = + "the caller-supplied commandId must reach the participant unchanged"; + + private const string MintContractFragment = + "an omitted commandId obliges the implementation to mint one"; + + [Fact] + public async Task TryExerciseAsync_verbatim_check_fails_against_a_client_that_mints_over_the_supplied_id() + { + var kit = new CommandIdFixtureKit(new SuppliedCommandIdIgnoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + ShouldBeTheContractFailure(run, VerbatimContractFragment, SuppliedCommandIdIgnoringFakeClient.MintedPrefix); + } + + [Fact] + public async Task TryCreateAsync_verbatim_check_fails_against_a_client_that_mints_over_the_supplied_id() + { + var kit = new CommandIdFixtureKit(new SuppliedCommandIdIgnoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + ShouldBeTheContractFailure(run, VerbatimContractFragment, SuppliedCommandIdIgnoringFakeClient.MintedPrefix); + } + + [Fact] + public async Task TryExerciseAsync_mint_check_fails_against_a_client_that_leaves_the_command_id_unset() + { + var kit = new CommandIdFixtureKit(new UnsetCommandIdFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_mints_a_command_id_when_the_caller_omits_one()); + + ShouldBeTheContractFailure(run, MintContractFragment, ""); + } + + [Fact] + public async Task TryCreateAsync_mint_check_fails_against_a_client_that_leaves_the_command_id_unset() + { + var kit = new CommandIdFixtureKit(new UnsetCommandIdFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_mints_a_command_id_when_the_caller_omits_one()); + + ShouldBeTheContractFailure(run, MintContractFragment, ""); + } + + [Fact] + public async Task TryExerciseAsync_mint_check_passes_against_a_client_that_mints_over_the_supplied_id() + { + var kit = new CommandIdFixtureKit(new SuppliedCommandIdIgnoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_mints_a_command_id_when_the_caller_omits_one()); + + run.Should().BeNull( + "a client that always mints breaks only the verbatim direction; a mint check that also " + + "rejected it would be failing every implementation rather than isolating one fault"); + } + + [Fact] + public async Task TryExerciseAsync_verbatim_check_passes_against_a_client_that_leaves_the_command_id_unset() + { + var kit = new CommandIdFixtureKit(new UnsetCommandIdFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + run.Should().BeNull( + "a client that never mints breaks only the omission direction; a verbatim check that also " + + "rejected it would be failing every implementation rather than isolating one fault"); + } + + [Fact] + public async Task TryCreateAsync_mint_check_passes_against_a_client_that_mints_over_the_supplied_id() + { + var kit = new CommandIdFixtureKit(new SuppliedCommandIdIgnoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_mints_a_command_id_when_the_caller_omits_one()); + + run.Should().BeNull( + "a client that always mints breaks only the verbatim direction; a mint check that also " + + "rejected it would be failing every implementation rather than isolating one fault"); + } + + [Fact] + public async Task TryCreateAsync_verbatim_check_passes_against_a_client_that_leaves_the_command_id_unset() + { + var kit = new CommandIdFixtureKit(new UnsetCommandIdFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + run.Should().BeNull( + "a client that never mints breaks only the omission direction; a verbatim check that also " + + "rejected it would be failing every implementation rather than isolating one fault"); + } + + [Fact] + public async Task TryExerciseAsync_verbatim_check_passes_against_a_client_that_honors_the_contract() + { + var kit = new CommandIdFixtureKit(new CommandIdHonoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + run.Should().BeNull(); + } + + [Fact] + public async Task TryExerciseAsync_mint_check_passes_against_a_client_that_honors_the_contract() + { + var kit = new CommandIdFixtureKit(new CommandIdHonoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryExerciseAsync_mints_a_command_id_when_the_caller_omits_one()); + + run.Should().BeNull(); + } + + [Fact] + public async Task TryCreateAsync_verbatim_check_passes_against_a_client_that_honors_the_contract() + { + var kit = new CommandIdFixtureKit(new CommandIdHonoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_dispatches_the_caller_supplied_commandId_verbatim()); + + run.Should().BeNull(); + } + + [Fact] + public async Task TryCreateAsync_mint_check_passes_against_a_client_that_honors_the_contract() + { + var kit = new CommandIdFixtureKit(new CommandIdHonoringFakeClient()); + + var run = await Record.ExceptionAsync( + () => kit.TryCreateAsync_mints_a_command_id_when_the_caller_omits_one()); + + run.Should().BeNull(); + } + + private static void ShouldBeTheContractFailure( + System.Exception? run, string contractFragment, string offendingValueFragment) + { + run.Should().NotBeNull("the buggy client must fail this check"); + run!.Message.Should().Contain( + contractFragment, + "the failure must come from the command-id assertion itself, not from an unrelated throw " + + "or from the opt-out skip that a null fixture would raise"); + run.Message.Should().Contain( + offendingValueFragment, + "the failure message must quote the command id the client actually recorded, so an " + + "implementer can see what their client did instead of only that something went wrong"); + } + + private sealed class CommandIdFixtureKit(CommandIdFakeClientBase fake) + : LedgerClientConformanceTests + { + protected override ILedgerClient CreateClient() => + throw new System.NotSupportedException("this kit only drives the command-id checks"); + + protected override SubmitterInfo Reader { get; } = Submitter; + + protected override CommandIdConformanceFixture CreateCommandIdFixture() => new( + fake, + (writer, commandId) => writer.TryExerciseAsync( + ExerciseCommand.For( + new ContractId("c1"), new ChoiceName("Archive"), DamlRecord.Create()), + Submitter, + commandId: commandId), + (writer, commandId) => writer.TryCreateAsync( + new ConformanceProbe("alice"), Submitter, commandId: commandId), + () => ValueTask.FromResult(fake.RecordedCommandId)); + } +} diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdFakeClientBase.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdFakeClientBase.cs new file mode 100644 index 0000000..bcb981a --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdFakeClientBase.cs @@ -0,0 +1,101 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Data; +using Daml.Runtime.Contracts; +using Daml.Runtime.Streams; +using Daml.Runtime.Outcomes; + +namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; + +/// +/// The inert surface shared by the command-id fakes: every member the command-id conformance +/// checks never touch throws, and the single slot holding the command_id a submission +/// recorded. Each subclass writes its own and +/// in full, so no command-id behaviour is shared between the +/// conforming fake and the deliberately-buggy ones. +/// +internal abstract class CommandIdFakeClientBase : ILedgerClient +{ + public string? RecordedCommandId { get; protected set; } + + public abstract Task> TryExerciseAsync( + ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default); + + public abstract Task>> TryCreateAsync( + TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + where TTemplate : ITemplate; + + public Task SubmitAndWaitAsync( + CommandsSubmission submission, SubmitterInfo submitter, TimeSpan? timeout = null, + CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public Task> TrySubmitAndWaitForTransactionAsync( + CommandsSubmission submission, SubmitterInfo submitter, TimeSpan? timeout = null, + CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeAsync( + SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => + throw new NotSupportedException(); + + public Task GetLedgerEndAsync( + TimeSpan? timeout = null, CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public void Dispose() { } +} diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdHonoringFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdHonoringFakeClient.cs new file mode 100644 index 0000000..ef6b9bd --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/CommandIdHonoringFakeClient.cs @@ -0,0 +1,40 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Outcomes; + +namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; + +/// +/// Records the caller-supplied commandId verbatim and mints one only when the caller +/// omits it — the contract as documented. Proves the command-id conformance checks pass against +/// a client that honors it, so a failure elsewhere is the fake's bug and not the check's. +/// +internal sealed class CommandIdHonoringFakeClient : CommandIdFakeClientBase +{ + public override Task> TryExerciseAsync( + ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = commandId?.Value ?? Mint(); + return Task.FromResult>(new ExerciseOutcome.One(default!)); + } + + public override Task>> TryCreateAsync( + TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = commandId?.Value ?? Mint(); + return Task.FromResult>>( + new ExerciseOutcome>.One(new ContractId("created-1"))); + } + + private static string Mint() => $"minted-{Guid.NewGuid()}"; +} diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformanceProbe.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformanceProbe.cs index 0c11218..ffd628d 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformanceProbe.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformanceProbe.cs @@ -8,7 +8,7 @@ namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; -public sealed record ConformanceProbe(string Owner) : ITemplate +public sealed record ConformanceProbe(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "ConformanceProbe"); public static string PackageId => "pkg"; @@ -16,5 +16,8 @@ public sealed record ConformanceProbe(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("owner", new DamlText(Owner))); + + public static ConformanceProbe FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformingFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformingFakeClient.cs index ca90d0c..1e1f48e 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformingFakeClient.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/ConformingFakeClient.cs @@ -28,7 +28,7 @@ public async IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { cancellationToken.ThrowIfCancellationRequested(); var effective = activeAtOffset ?? LedgerEnd; @@ -37,7 +37,7 @@ public async IAsyncEnumerable> SubscribeActiveAsync( if (_faultsMidSnapshot) { yield return new AcsSnapshotEntry.Created( - new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(1), + new ContractId("c1"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")]); yield return new AcsSnapshotEntry.StreamError(14, "UNAVAILABLE: transport fault mid-snapshot"); yield break; @@ -46,20 +46,20 @@ public async IAsyncEnumerable> SubscribeActiveAsync( if (effective.Value >= 1) { yield return new AcsSnapshotEntry.Created( - new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(1), + new ContractId("c1"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")]); } if (effective.Value >= 2) { yield return new AcsSnapshotEntry.Created( - new ContractId("c2"), DamlRecord.Create(), LedgerOffset.At(2), + new ContractId("c2"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(2), new SynchronizerId("sync"), [new Party("alice")]); } if (effective.Value >= 3) { - yield return new AcsSnapshotEntry.Unclassified(LedgerOffset.At(3), "UNMAPPED"); + yield return new AcsSnapshotEntry.Unclassified(LedgerOffset.At(3), UnclassifiedKind.Unknown, "UNMAPPED"); } yield return new AcsSnapshotEntry.Checkpoint(new StakeholderResume(effective)); @@ -70,7 +70,7 @@ public async IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { cancellationToken.ThrowIfCancellationRequested(); var lower = (fromOffset ?? LedgerOffset.Begin).Value; @@ -98,7 +98,7 @@ public async IAsyncEnumerable> SubscribeLedgerEffectsAsyn LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { cancellationToken.ThrowIfCancellationRequested(); var lower = (fromOffset ?? LedgerOffset.Begin).Value; @@ -122,10 +122,10 @@ public async IAsyncEnumerable> SubscribeLedgerEffectsAsyn } private static IEnumerable<(long Offset, ContractStreamEvent Event)> SeededEffectsStream() - where T : IDamlType + where T : ITemplate, IDamlRecord { yield return (1, new ContractStreamEvent.Created( - new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(1), + new ContractId("c1"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")])); yield return (2, new ContractStreamEvent.Exercised( new ContractId("c1"), "Archive", DamlUnit.Instance, DamlUnit.Instance, true, @@ -133,13 +133,13 @@ public async IAsyncEnumerable> SubscribeLedgerEffectsAsyn } private static IEnumerable<(long Offset, ContractStreamEvent Event)> SeededStream() - where T : IDamlType + where T : ITemplate, IDamlRecord { yield return (1, new ContractStreamEvent.Created( - new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(1), + new ContractId("c1"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")])); yield return (2, new ContractStreamEvent.Created( - new ContractId("c2"), DamlRecord.Create(), LedgerOffset.At(2), + new ContractId("c2"), T.FromRecord(DamlRecord.Create()), null, LedgerOffset.At(2), new SynchronizerId("sync"), [new Party("alice")])); yield return (3, new ContractStreamEvent.Unclassified(LedgerOffset.At(3), UnclassifiedKind.Unknown, "UNMAPPED")); } @@ -151,6 +151,7 @@ public Task GetLedgerEndAsync( public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); @@ -166,10 +167,40 @@ public Task> TrySubmitAndWaitForTransactionAs public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate => throw new NotSupportedException(); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/HangingSubscriptionConformanceTests.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/HangingSubscriptionConformanceTests.cs index 7817a65..5e83b9d 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/HangingSubscriptionConformanceTests.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/HangingSubscriptionConformanceTests.cs @@ -49,7 +49,7 @@ public async IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { while (true) { @@ -63,14 +63,14 @@ public IAsyncEnumerable> SubscribeLedgerEffectsAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public Task GetLedgerEndAsync( @@ -79,6 +79,7 @@ public Task GetLedgerEndAsync( public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); @@ -94,10 +95,40 @@ public Task> TrySubmitAndWaitForTransactionAs public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate => throw new NotSupportedException(); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SubmitterMergingFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SubmitterMergingFakeClient.cs index b5a8ea1..73ade65 100644 --- a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SubmitterMergingFakeClient.cs +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SubmitterMergingFakeClient.cs @@ -62,11 +62,13 @@ private void RequireAuthorized(CommandsSubmission submission, SubmitterInfo subm public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate => throw new NotSupportedException(); @@ -74,25 +76,54 @@ public Task>> TryCreateAsync( public IAsyncEnumerable> SubscribeAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeLedgerEffectsAsync( SubmitterInfo submitter, LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType => + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); public Task GetLedgerEndAsync( TimeSpan? timeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SuppliedCommandIdIgnoringFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SuppliedCommandIdIgnoringFakeClient.cs new file mode 100644 index 0000000..0572af1 --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/SuppliedCommandIdIgnoringFakeClient.cs @@ -0,0 +1,43 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Outcomes; + +namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; + +/// +/// Mints a fresh command id on every submission, discarding the caller-supplied one — the bug +/// that silently breaks deduplication across a retry that deliberately reuses an id. Used to +/// prove the verbatim checks actually fail against it. It still always records a non-empty id, +/// so it passes the mint checks: the verbatim checks are what single it out. +/// +internal sealed class SuppliedCommandIdIgnoringFakeClient : CommandIdFakeClientBase +{ + internal const string MintedPrefix = "minted-over-supplied-"; + + public override Task> TryExerciseAsync( + ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = Mint(); + return Task.FromResult>(new ExerciseOutcome.One(default!)); + } + + public override Task>> TryCreateAsync( + TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = Mint(); + return Task.FromResult>>( + new ExerciseOutcome>.One(new ContractId("created-1"))); + } + + private static string Mint() => $"{MintedPrefix}{Guid.NewGuid()}"; +} diff --git a/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/UnsetCommandIdFakeClient.cs b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/UnsetCommandIdFakeClient.cs new file mode 100644 index 0000000..ca3c113 --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Testing.Conformance.Tests/UnsetCommandIdFakeClient.cs @@ -0,0 +1,40 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Threading; +using System.Threading.Tasks; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Outcomes; + +namespace Daml.Ledger.Abstractions.Testing.Conformance.Tests; + +/// +/// Forwards the caller-supplied command id but mints nothing when the caller omits one, leaving +/// the participant's command_id unset — the bug that yields a submission which can neither +/// be deduplicated nor correlated to its completion. Used to prove the mint checks actually fail +/// against it. It records the supplied id verbatim, so it passes the verbatim checks: the mint +/// checks are what single it out. +/// +internal sealed class UnsetCommandIdFakeClient : CommandIdFakeClientBase +{ + public override Task> TryExerciseAsync( + ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = commandId?.Value; + return Task.FromResult>(new ExerciseOutcome.One(default!)); + } + + public override Task>> TryCreateAsync( + TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + RecordedCommandId = commandId?.Value; + return Task.FromResult>>( + new ExerciseOutcome>.One(new ContractId("created-1"))); + } +} diff --git a/tests/Daml.Ledger.Abstractions.Tests/LedgerClientExtensionsTests.cs b/tests/Daml.Ledger.Abstractions.Tests/LedgerClientExtensionsTests.cs index 3b80173..b3c289a 100644 --- a/tests/Daml.Ledger.Abstractions.Tests/LedgerClientExtensionsTests.cs +++ b/tests/Daml.Ledger.Abstractions.Tests/LedgerClientExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Daml.Ledger.Abstractions.Tests; /// /// Verifies : the throwing convenience wrappers around -/// . +/// . /// public class LedgerClientExtensionsTests { @@ -31,10 +31,20 @@ public class LedgerClientExtensionsTests private static TransactionResult TransactionCreating(params string[] contractIds) => new("update-id", LedgerOffset.Begin, - contractIds.Select(id => new CreatedContract(id, SampleTemplate.TemplateId, "{}")).ToArray(), + contractIds.Select(CreatedOf).ToArray(), [], new CommandId("cmd-id")); + private static CreatedContract CreatedOf(string contractId) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: SampleTemplate.TemplateId, + Payload: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); + [Fact] public async Task ExerciseAsync_returns_value_when_TryExerciseAsync_returns_One() { @@ -118,6 +128,52 @@ await act.Should().ThrowAsync() .WithMessage("*Many*3*"); } + [Fact] + public async Task ExerciseAsync_forwards_the_timeout_to_TryExerciseAsync() + { + var stub = new StubLedgerClient(new ExerciseOutcome.One(42)); + + await stub.ExerciseAsync( + SampleCommand, new Party("alice"), timeout: TimeSpan.FromSeconds(7), + cancellationToken: TestContext.Current.CancellationToken); + + stub.LastExerciseTimeout.Should().Be(TimeSpan.FromSeconds(7)); + } + + [Fact] + public async Task ExerciseAsync_omitted_timeout_reaches_TryExerciseAsync_as_null() + { + var stub = new StubLedgerClient(new ExerciseOutcome.One(42)); + + await stub.ExerciseAsync( + SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + + stub.LastExerciseTimeout.Should().BeNull(); + } + + [Fact] + public async Task ExerciseAsync_void_forwards_the_timeout_to_TrySubmitAndWaitForTransactionAsync() + { + var stub = new StubLedgerClient(new ExerciseOutcome.One(SampleTransaction)); + + await stub.ExerciseAsync( + SampleCommand, new Party("alice"), timeout: TimeSpan.FromSeconds(7), + cancellationToken: TestContext.Current.CancellationToken); + + stub.LastTransactionTimeout.Should().Be(TimeSpan.FromSeconds(7)); + } + + [Fact] + public async Task ExerciseAsync_void_omitted_timeout_reaches_TrySubmitAndWaitForTransactionAsync_as_null() + { + var stub = new StubLedgerClient(new ExerciseOutcome.One(SampleTransaction)); + + await stub.ExerciseAsync( + SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + + stub.LastTransactionTimeout.Should().BeNull(); + } + [Fact] public async Task ExerciseAsync_void_does_not_throw_when_TrySubmitAndWaitForTransactionAsync_returns_One() { @@ -129,13 +185,14 @@ public async Task ExerciseAsync_void_does_not_throw_when_TrySubmitAndWaitForTran } [Fact] - public async Task ExerciseAsync_void_does_not_throw_when_TrySubmitAndWaitForTransactionAsync_returns_None() + public async Task ExerciseAsync_void_throws_LedgerOperationException_when_TrySubmitSingleAsync_returns_None() { ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.None()); Func act = () => client.ExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); - await act.Should().NotThrowAsync(); + await act.Should().ThrowAsync() + .WithMessage("*None*"); } [Fact] @@ -264,14 +321,15 @@ public async Task ExerciseAsync_void_with_SubmitterInfo_does_not_throw_when_TryS } [Fact] - public async Task ExerciseAsync_void_with_SubmitterInfo_does_not_throw_when_TrySubmitAndWaitForTransactionAsync_returns_None() + public async Task ExerciseAsync_void_with_SubmitterInfo_throws_LedgerOperationException_when_TrySubmitSingleAsync_returns_None() { ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.None()); var submitter = new SubmitterInfo(new Party("alice")); Func act = () => client.ExerciseAsync(SampleCommand, submitter, cancellationToken: TestContext.Current.CancellationToken); - await act.Should().NotThrowAsync(); + await act.Should().ThrowAsync() + .WithMessage("*None*"); } [Fact] @@ -350,7 +408,7 @@ public async Task ExerciseAsync_throws_LedgerOperationException_carrying_the_Dam public async Task ExerciseAsync_throws_LedgerOperationException_carrying_the_InfraError_outcome() { var sourceException = new InvalidOperationException("transport failed"); - ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError(14, "Connection reset", sourceException)); + ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError(14, "Connection reset", SourceException: sourceException)); Func act = () => client.ExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); @@ -362,6 +420,22 @@ public async Task ExerciseAsync_throws_LedgerOperationException_carrying_the_Inf exception.Metadata.Should().BeNull(); } + [Fact] + public async Task ExerciseAsync_throws_LedgerOperationException_carrying_the_InfraError_category_and_status_code_together() + { + ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError( + 400, "Bad Request", DamlErrorCategory.InvalidIndependentOfSystemState)); + + Func act = () => client.ExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + + var exception = (await act.Should().ThrowAsync()).Which; + exception.Category.Should().Be(DamlErrorCategory.InvalidIndependentOfSystemState); + exception.StatusCode.Should().Be(400); + exception.ErrorId.Should().BeNull( + "a classified transport failure carries no recoverable Canton error id, and both facts have to " + + "survive on the one exception rather than the caller having to pick which to discard"); + } + [Fact] public async Task ExerciseAsync_throws_LedgerOperationException_with_null_InnerException_when_InfraError_has_no_SourceException() { @@ -412,7 +486,7 @@ public async Task ExerciseAsync_void_throws_LedgerOperationException_carrying_th public async Task ExerciseAsync_void_throws_LedgerOperationException_carrying_the_InfraError_outcome() { var sourceException = new TimeoutException("deadline transport failure"); - ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError(4, "Deadline exceeded", sourceException)); + ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError(4, "Deadline exceeded", SourceException: sourceException)); Func act = () => client.ExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); @@ -503,6 +577,22 @@ public async Task TryCreateOneByExerciseAsync_propagates_InfraError() .Which.StatusCode.Should().Be(14); } + [Fact] + public async Task TryCreateOneByExerciseAsync_carries_the_InfraError_category_through_the_remap() + { + ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.InfraError( + 400, "Bad Request", DamlErrorCategory.InvalidIndependentOfSystemState)); + + var result = await client.TryCreateOneByExerciseAsync( + SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + + result.Should().BeOfType>.InfraError>() + .Which.Category.Should().Be( + DamlErrorCategory.InvalidIndependentOfSystemState, + "remapping the outcome onto the created contract id without forwarding the category silently " + + "discards a classification the transport determined without a structured Canton error to carry it"); + } + [Fact] public async Task TryCreateManyByExerciseAsync_returns_One_with_empty_list_when_transaction_created_no_TTemplate() { @@ -662,15 +752,15 @@ public async Task DisposeAsync_default_bridge_delegates_to_Dispose() } [Fact] - public async Task Read_paths_accept_an_interface_marker_as_type_argument() + public async Task Read_paths_accept_an_interface_marker_through_its_view_witness() { ILedgerClient client = new StubLedgerClient(new ExerciseOutcome.None()); var exercised = await client.TryCreateOneByExerciseAsync( SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); - var subscription = client.SubscribeAsync( - new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); - var events = new List>(); + var subscription = client.SubscribeAsync( + SampleInterface.View, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + var events = new List>(); await foreach (var evt in subscription) { events.Add(evt); @@ -686,7 +776,8 @@ public async Task Read_paths_accept_an_interface_marker_as_type_argument() /// and TrySubmitAndWaitForTransactionAsync primitives. The outcome is stored /// as object and cast on retrieval so a single non-generic stub can satisfy /// the generic contract for any TResult. The throwing extension wrappers and - /// the Party-actAs overloads both route here. + /// the Party-actAs overloads both route here. Each primitive records the + /// timeout it was handed, so a wrapper that accepts one and drops it is visible. /// private sealed class StubLedgerClient : ILedgerClient { @@ -694,13 +785,21 @@ private sealed class StubLedgerClient : ILedgerClient public StubLedgerClient(object outcome) => _outcome = outcome; + public TimeSpan? LastExerciseTimeout { get; private set; } + + public TimeSpan? LastTransactionTimeout { get; private set; } + public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) - => Task.FromResult((ExerciseOutcome)_outcome); + { + LastExerciseTimeout = timeout; + return Task.FromResult((ExerciseOutcome)_outcome); + } public Task SubmitAndWaitAsync( CommandsSubmission submission, @@ -714,12 +813,16 @@ public Task> TrySubmitAndWaitForTransactionAs SubmitterInfo submitter, TimeSpan? timeout = null, CancellationToken cancellationToken = default) - => Task.FromResult((ExerciseOutcome)_outcome); + { + LastTransactionTimeout = timeout; + return Task.FromResult((ExerciseOutcome)_outcome); + } public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate @@ -730,7 +833,7 @@ public IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord => EmptyAsync>(cancellationToken); public IAsyncEnumerable> SubscribeLedgerEffectsAsync( @@ -738,14 +841,14 @@ public IAsyncEnumerable> SubscribeLedgerEffectsAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord => EmptyAsync>(cancellationToken); public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord => EmptyAsync>(cancellationToken); public Task GetLedgerEndAsync( @@ -755,6 +858,35 @@ public Task GetLedgerEndAsync( public bool Disposed { get; private set; } + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord + => EmptyAsync>(cancellationToken); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord + => EmptyAsync>(cancellationToken); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord + => EmptyAsync>(cancellationToken); + public void Dispose() => Disposed = true; private static async IAsyncEnumerable EmptyAsync( @@ -779,13 +911,20 @@ internal sealed record SampleTemplate : ITemplate public static SampleTemplate FromRecord(DamlRecord record) => new(); } -internal sealed record SampleInterface : IDamlInterface +internal sealed record SampleInterface : IDamlInterface, IHasView { public static Identifier InterfaceId => new("iface-pkg", "Module", "ISample"); public static string PackageId => "iface-pkg"; public static string PackageName => "iface-name"; public static Version PackageVersion => new(1, 0, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(InterfaceId, DamlTypeKind.Interface, PackageName); + public static ViewDescriptor View { get; } = new(); public DamlRecord ToRecord() => DamlRecord.Create(); } + +internal sealed record SampleView : IDamlRecord +{ + public DamlRecord ToRecord() => DamlRecord.Create(); + public static SampleView FromRecord(DamlRecord record) => new(); +} diff --git a/tests/Daml.Ledger.Abstractions.Tests/LedgerClientSubmitterInfoTests.cs b/tests/Daml.Ledger.Abstractions.Tests/LedgerClientSubmitterInfoTests.cs index 8d4fab5..b030535 100644 --- a/tests/Daml.Ledger.Abstractions.Tests/LedgerClientSubmitterInfoTests.cs +++ b/tests/Daml.Ledger.Abstractions.Tests/LedgerClientSubmitterInfoTests.cs @@ -16,10 +16,10 @@ namespace Daml.Ledger.Abstractions.Tests; /// /// Verifies the authorization routing of : the -/// overloads are the primitives implementations override, -/// the Party actAs convenience overloads forward to them with a single -/// act-as party and no read-as parties, and multi-party / read-as submissions carry -/// every party through to the primitive instead of throwing. +/// members are the primitives implementations override, +/// a lone Party argument converts to a submitter carrying that one act-as party +/// and no read-as parties, and multi-party / read-as submissions carry every party +/// through to the primitive instead of throwing. /// public class LedgerClientSubmitterInfoTests { @@ -61,7 +61,7 @@ public async Task ExerciseAsync_void_Party_actAs_forwards_to_transaction_primiti } [Fact] - public async Task TryCreateAsync_Party_actAs_forwards_to_SubmitterInfo_primitive_with_single_actAs_no_readAs() + public async Task TryCreateAsync_Party_argument_converts_to_a_submitter_with_single_actAs_no_readAs() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; @@ -85,7 +85,7 @@ public async Task TryCreateOneByExerciseAsync_Party_actAs_forwards_to_transactio } [Fact] - public async Task SubscribeAsync_Party_actAs_forwards_to_SubmitterInfo_primitive_with_single_actAs_no_readAs() + public async Task SubscribeAsync_Party_argument_converts_to_a_submitter_with_single_actAs_no_readAs() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; @@ -99,7 +99,7 @@ public async Task SubscribeAsync_Party_actAs_forwards_to_SubmitterInfo_primitive } [Fact] - public async Task SubscribeActiveAsync_Party_actAs_forwards_to_SubmitterInfo_primitive_with_single_actAs_no_readAs() + public async Task SubscribeActiveAsync_Party_argument_converts_to_a_submitter_with_single_actAs_no_readAs() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; @@ -239,37 +239,37 @@ public async Task SubscribeAsync_with_multi_party_actAs_carries_all_parties_thro } [Fact] - public async Task TryExerciseAsync_Party_actAs_forwards_timeout_to_the_SubmitterInfo_primitive() + public async Task TryExerciseAsync_Party_argument_converts_to_a_submitter_with_single_actAs_no_readAs() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; - await client.TryExerciseAsync(SampleCommand, new Party("alice"), timeout: TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken); + await client.TryExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); - fake.LastExerciseTimeout.Should().Be(TimeSpan.FromSeconds(5)); + fake.LastExerciseSubmitter!.Value.ActAs.Select(p => p.Id).Should().Equal("alice"); + fake.LastExerciseSubmitter!.Value.ReadAs.Should().BeEmpty(); } [Fact] - public async Task TryExerciseAsync_Party_actAs_defaults_timeout_to_null_at_the_primitive() + public async Task ExerciseAsync_with_result_Party_actAs_forwards_commandId_to_the_SubmitterInfo_primitive() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; - await client.TryExerciseAsync(SampleCommand, new Party("alice"), cancellationToken: TestContext.Current.CancellationToken); + await client.ExerciseAsync(SampleCommand, new Party("alice"), commandId: new CommandId("caller-supplied"), cancellationToken: TestContext.Current.CancellationToken); - fake.LastExerciseSubmitter.Should().NotBeNull(); - fake.LastExerciseTimeout.Should().BeNull(); + fake.LastExerciseCommandId.Should().Be(new CommandId("caller-supplied")); } [Fact] - public async Task TryCreateAsync_Party_actAs_forwards_timeout_to_the_SubmitterInfo_primitive() + public async Task ExerciseAsync_with_result_SubmitterInfo_forwards_commandId_to_the_primitive() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; - await client.TryCreateAsync(new FakeTemplate(), new Party("alice"), timeout: TimeSpan.FromSeconds(7), cancellationToken: TestContext.Current.CancellationToken); + await client.ExerciseAsync(SampleCommand, MultiParty, commandId: new CommandId("caller-supplied"), cancellationToken: TestContext.Current.CancellationToken); - fake.LastCreateTimeout.Should().Be(TimeSpan.FromSeconds(7)); + fake.LastExerciseCommandId.Should().Be(new CommandId("caller-supplied")); } [Fact] @@ -318,34 +318,6 @@ public async Task ExerciseAsync_extension_reaches_the_primitive_with_null_timeou fake.LastExerciseTimeout.Should().BeNull(); } - [Fact] - public async Task SubscribeAsync_Party_actAs_forwards_fromOffset_and_toOffset_to_the_SubmitterInfo_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeAsync(new Party("alice"), fromOffset: LedgerOffset.At(10), toOffset: LedgerOffset.At(42), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeFromOffset.Should().Be(LedgerOffset.At(10)); - fake.LastSubscribeToOffset.Should().Be(LedgerOffset.At(42)); - } - - [Fact] - public async Task SubscribeAsync_Party_actAs_defaults_toOffset_to_null_at_the_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeAsync(new Party("alice"), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeSubmitter.Should().NotBeNull(); - fake.LastSubscribeToOffset.Should().BeNull(); - } - [Fact] public async Task SubscribeAsync_accepts_a_StakeholderResume_and_forwards_its_offset_to_the_SubmitterInfo_primitive() { @@ -362,7 +334,7 @@ public async Task SubscribeAsync_accepts_a_StakeholderResume_and_forwards_its_of } [Fact] - public async Task SubscribeAsync_Party_actAs_StakeholderResume_overload_forwards_the_ticket_offset_to_the_SubmitterInfo_primitive() + public async Task SubscribeAsync_Party_argument_converts_and_the_StakeholderResume_ticket_offset_reaches_the_primitive() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; @@ -377,34 +349,7 @@ public async Task SubscribeAsync_Party_actAs_StakeholderResume_overload_forwards } [Fact] - public async Task SubscribeActiveAsync_Party_actAs_forwards_activeAtOffset_to_the_SubmitterInfo_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeActiveAsync(new Party("alice"), activeAtOffset: LedgerOffset.At(33), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeActiveAtOffset.Should().Be(LedgerOffset.At(33)); - } - - [Fact] - public async Task SubscribeActiveAsync_Party_actAs_defaults_activeAtOffset_to_null_at_the_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeActiveAsync(new Party("alice"), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeActiveSubmitter.Should().NotBeNull(); - fake.LastSubscribeActiveAtOffset.Should().BeNull(); - } - - [Fact] - public async Task SubscribeLedgerEffectsAsync_Party_actAs_forwards_to_SubmitterInfo_primitive_with_single_actAs_no_readAs() + public async Task SubscribeLedgerEffectsAsync_Party_argument_converts_to_a_submitter_with_single_actAs_no_readAs() { var fake = new RecordingLedgerClient(); ILedgerClient client = fake; @@ -445,44 +390,18 @@ public async Task SubscribeLedgerEffectsAsync_with_multi_party_actAs_carries_all fake.LastSubscribeLedgerEffectsSubmitter!.Value.ReadAs.Should().BeEmpty(); } - [Fact] - public async Task SubscribeLedgerEffectsAsync_Party_actAs_forwards_fromOffset_and_toOffset_to_the_SubmitterInfo_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeLedgerEffectsAsync(new Party("alice"), fromOffset: LedgerOffset.At(10), toOffset: LedgerOffset.At(42), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeLedgerEffectsFromOffset.Should().Be(LedgerOffset.At(10)); - fake.LastSubscribeLedgerEffectsToOffset.Should().Be(LedgerOffset.At(42)); - } - - [Fact] - public async Task SubscribeLedgerEffectsAsync_Party_actAs_defaults_toOffset_to_null_at_the_primitive() - { - var fake = new RecordingLedgerClient(); - ILedgerClient client = fake; - - await foreach (var _ in client.SubscribeLedgerEffectsAsync(new Party("alice"), cancellationToken: TestContext.Current.CancellationToken)) - { - } - - fake.LastSubscribeLedgerEffectsSubmitter.Should().NotBeNull(); - fake.LastSubscribeLedgerEffectsToOffset.Should().BeNull(); - } - /// - /// Records the , timeout, and offset bounds each - /// primitive receives so tests can assert that the Party-actAs - /// convenience overloads forward every parameter to the primitive — the right - /// single party, read-as / multi-party submissions carrying every party through, - /// and the per-call timeout / fromOffset / toOffset / - /// activeAtOffset values (or their null defaults). + /// Records what each primitive receives, limited to the values something other than + /// the caller decides: the a Party argument or a + /// multi-party set resolves to, the timeout and commandId the throwing + /// and create-by-exercise extensions compute, and the fromOffset a + /// ticket resolves to on the default interface member. /// private class RecordingLedgerClient : ILedgerClient { + private static readonly TransactionResult SampleTransaction = new( + "update-id", LedgerOffset.Begin, [], [], new CommandId("cmd-id")); + public SubmitterInfo? LastExerciseSubmitter { get; private set; } public SubmitterInfo? LastCreateSubmitter { get; private set; } public SubmitterInfo? LastTransactionSubmitter { get; private set; } @@ -490,23 +409,21 @@ private class RecordingLedgerClient : ILedgerClient public SubmitterInfo? LastSubscribeActiveSubmitter { get; private set; } public SubmitterInfo? LastSubscribeLedgerEffectsSubmitter { get; private set; } public TimeSpan? LastExerciseTimeout { get; private set; } - public TimeSpan? LastCreateTimeout { get; private set; } public TimeSpan? LastTransactionTimeout { get; private set; } + public CommandId? LastExerciseCommandId { get; private set; } public LedgerOffset? LastSubscribeFromOffset { get; private set; } - public LedgerOffset? LastSubscribeToOffset { get; private set; } - public LedgerOffset? LastSubscribeActiveAtOffset { get; private set; } - public LedgerOffset? LastSubscribeLedgerEffectsFromOffset { get; private set; } - public LedgerOffset? LastSubscribeLedgerEffectsToOffset { get; private set; } public Task> TryExerciseAsync( ExerciseCommand command, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { LastExerciseSubmitter = submitter; LastExerciseTimeout = timeout; + LastExerciseCommandId = commandId; return Task.FromResult>(new ExerciseOutcome.One(default(TResult)!)); } @@ -530,19 +447,19 @@ public Task> TrySubmitAndWaitForTransactionAs new HashSet(actAs), readAs.Count > 0 ? new HashSet(readAs) : null); LastTransactionTimeout = timeout; return Task.FromResult>( - new ExerciseOutcome.None()); + new ExerciseOutcome.One(SampleTransaction)); } public Task>> TryCreateAsync( TTemplate payload, SubmitterInfo submitter, string? workflowId = null, + CommandId? commandId = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) where TTemplate : ITemplate { LastCreateSubmitter = submitter; - LastCreateTimeout = timeout; return Task.FromResult>>( new ExerciseOutcome>.None()); } @@ -552,11 +469,10 @@ public IAsyncEnumerable> SubscribeAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { LastSubscribeSubmitter = submitter; LastSubscribeFromOffset = fromOffset; - LastSubscribeToOffset = toOffset; return EmptyAsync>(cancellationToken); } @@ -565,11 +481,9 @@ public IAsyncEnumerable> SubscribeLedgerEffectsAsync( LedgerOffset? fromOffset = null, LedgerOffset? toOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { LastSubscribeLedgerEffectsSubmitter = submitter; - LastSubscribeLedgerEffectsFromOffset = fromOffset; - LastSubscribeLedgerEffectsToOffset = toOffset; return EmptyAsync>(cancellationToken); } @@ -577,10 +491,9 @@ public IAsyncEnumerable> SubscribeActiveAsync( SubmitterInfo submitter, LedgerOffset? activeAtOffset = null, CancellationToken cancellationToken = default) - where T : IDamlType + where T : ITemplate, IDamlRecord { LastSubscribeActiveSubmitter = submitter; - LastSubscribeActiveAtOffset = activeAtOffset; return EmptyAsync>(cancellationToken); } @@ -589,6 +502,35 @@ public Task GetLedgerEndAsync( CancellationToken cancellationToken = default) => Task.FromResult(LedgerOffset.Begin); + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => + throw new NotSupportedException(); + public void Dispose() { } @@ -607,7 +549,7 @@ private static async IAsyncEnumerable EmptyAsync( /// the assertions here are about which submitter reaches the primitive, /// not about template encoding. /// - private sealed record FakeTemplate : ITemplate + private sealed record FakeTemplate : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "Module", "FakeTemplate"); @@ -618,5 +560,7 @@ private sealed record FakeTemplate : ITemplate public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); public DamlRecord ToRecord() => new(TemplateId, []); + + public static FakeTemplate FromRecord(DamlRecord record) => new(); } } diff --git a/tests/Daml.Ledger.Abstractions.Tests/LedgerOperationExceptionTests.cs b/tests/Daml.Ledger.Abstractions.Tests/LedgerOperationExceptionTests.cs index 3849e22..94b1365 100644 --- a/tests/Daml.Ledger.Abstractions.Tests/LedgerOperationExceptionTests.cs +++ b/tests/Daml.Ledger.Abstractions.Tests/LedgerOperationExceptionTests.cs @@ -10,7 +10,7 @@ namespace Daml.Ledger.Abstractions.Tests; public class LedgerOperationExceptionTests { [Fact] - public void message_and_inner_exception_constructor_preserves_both() + public void LedgerOperationException_message_and_inner_exception_constructor_preserves_both() { var inner = new TimeoutException("transport gave up"); @@ -25,7 +25,36 @@ public void message_and_inner_exception_constructor_preserves_both() } [Fact] - public void daml_error_constructor_rejects_null_metadata() + public void LedgerOperationException_infra_error_constructor_leaves_category_null_when_omitted() + { + var exception = new LedgerOperationException("transport failed", 503); + + exception.StatusCode.Should().Be(503); + exception.Category.Should().BeNull(); + exception.ErrorId.Should().BeNull(); + exception.Metadata.Should().BeNull(); + exception.InnerException.Should().BeNull(); + } + + [Fact] + public void LedgerOperationException_infra_error_constructor_keeps_the_category_alongside_the_status_code() + { + var inner = new TimeoutException("transport gave up"); + + var exception = new LedgerOperationException( + "transport failed", + 400, + DamlErrorCategory.InvalidIndependentOfSystemState, + inner); + + exception.StatusCode.Should().Be(400); + exception.Category.Should().Be(DamlErrorCategory.InvalidIndependentOfSystemState); + exception.InnerException.Should().BeSameAs(inner); + exception.ErrorId.Should().BeNull(); + } + + [Fact] + public void LedgerOperationException_daml_error_constructor_rejects_null_metadata() { var act = () => new LedgerOperationException( "exercise failed", @@ -37,7 +66,7 @@ public void daml_error_constructor_rejects_null_metadata() } [Fact] - public void daml_error_constructor_keeps_supplied_metadata() + public void LedgerOperationException_daml_error_constructor_keeps_supplied_metadata() { var metadata = new Dictionary { ["key"] = "value" }; diff --git a/tests/Daml.Ledger.Abstractions.Tests/SingleCommandExtensionsTests.cs b/tests/Daml.Ledger.Abstractions.Tests/SingleCommandExtensionsTests.cs new file mode 100644 index 0000000..5dbba6b --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Tests/SingleCommandExtensionsTests.cs @@ -0,0 +1,342 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime; +using Daml.Runtime.Streams; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Ledger.Abstractions.Tests; + +/// +/// Verifies : the shared single-command submission +/// path that generated exercisers and the hand-written write-path extensions both use. +/// +public class SingleCommandExtensionsTests +{ + private static readonly ExerciseCommand SampleCommand = new( + new Identifier("pkg", "Module", "Template"), + ContractId: new ContractId("cid-1"), + Choice: new ChoiceName("DoIt"), + ChoiceArgument: new DamlRecord(null, [])); + + private static readonly SubmitterInfo Alice = new Party("alice"); + + private static readonly TransactionResult TransactionCreatingOneSampleTemplate = new( + UpdateId: "update-id", + CompletionOffset: LedgerOffset.Begin, + CreatedContracts: [CreatedOf("cid-created")], + ArchivedContractIds: [], + CommandId: new CommandId("cmd-id")); + + private static CreatedContract CreatedOf(string contractId) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: SampleTemplate.TemplateId, + Payload: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); + + [Fact] + public async Task TrySubmitSingleAsync_uses_the_supplied_command_id() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, commandId: new CommandId("caller-supplied"), + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().Be(new CommandId("caller-supplied")); + } + + [Fact] + public async Task TrySubmitSingleAsync_mints_a_command_id_when_none_is_supplied() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().NotBeNull( + "the submission must carry a client-assigned command id rather than leaving command_id unset"); + } + + [Fact] + public async Task TrySubmitSingleAsync_mints_a_distinct_command_id_per_call() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync(SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + var first = writer.LastSubmission!.CommandId; + await writer.TrySubmitSingleAsync(SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().NotBe(first); + } + + [Fact] + public async Task TrySubmitSingleAsync_treats_an_empty_workflow_id_as_absent() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, workflowId: string.Empty, + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.WorkflowId.Should().BeNull( + "workflow_id is a correlation key and an empty one correlates nothing"); + } + + [Fact] + public async Task TrySubmitSingleAsync_treats_a_null_workflow_id_as_absent() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, workflowId: null, + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.WorkflowId.Should().BeNull(); + } + + [Fact] + public async Task TrySubmitSingleAsync_carries_a_non_empty_workflow_id() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, workflowId: "wf-1", + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.WorkflowId.Should().Be(new WorkflowId("wf-1")); + } + + [Fact] + public async Task TrySubmitSingleAsync_forwards_the_timeout() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, timeout: TimeSpan.FromSeconds(3), + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastTimeout.Should().Be(TimeSpan.FromSeconds(3)); + } + + [Fact] + public async Task TrySubmitSingleAsync_carries_the_single_command() + { + var writer = new CapturingWriter(); + + await writer.TrySubmitSingleAsync( + SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.Commands.Should().ContainSingle().Which.Should().Be(SampleCommand); + } + + [Fact] + public async Task TrySubmitSingleAsync_throws_when_the_writer_is_null() + { + ILedgerWriter writer = null!; + + Func act = () => writer.TrySubmitSingleAsync( + SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task TrySubmitSingleAsync_throws_when_the_command_is_null() + { + var writer = new CapturingWriter(); + + Func act = () => writer.TrySubmitSingleAsync( + null!, Alice, cancellationToken: TestContext.Current.CancellationToken); + + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task TrySubmitSingleAsync_forwards_the_cancellation_token() + { + var writer = new CapturingWriter(); + using var cts = new CancellationTokenSource(); + + await writer.TrySubmitSingleAsync(SampleCommand, Alice, cancellationToken: cts.Token); + + writer.LastCancellationToken.Should().Be(cts.Token); + } + + [Fact] + public async Task TrySubmitSingleAsync_rejects_a_default_command_id_rather_than_submitting_it() + { + var writer = new CapturingWriter(); + + Func act = () => writer.TrySubmitSingleAsync( + SampleCommand, Alice, commandId: default(CommandId), cancellationToken: TestContext.Current.CancellationToken); + + await act.Should().ThrowAsync(); + writer.LastSubmission.Should().BeNull(); + } + + [Fact] + public async Task TryCreateOneByExerciseAsync_mints_a_command_id_so_a_retry_can_deduplicate() + { + var writer = new CapturingWriter(); + + await writer.TryCreateOneByExerciseAsync( + SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().NotBeNull(); + } + + [Fact] + public async Task TryCreateOneByExerciseAsync_uses_the_supplied_command_id() + { + var writer = new CapturingWriter(); + + await writer.TryCreateOneByExerciseAsync( + SampleCommand, Alice, commandId: new CommandId("caller-supplied"), + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().Be(new CommandId("caller-supplied")); + } + + [Theory] + [InlineData("TryCreateManyByExerciseAsync", "Party")] + [InlineData("TryCreateManyByExerciseAsync", "SubmitterInfo")] + [InlineData("CreateOneByExerciseAsync", "Party")] + [InlineData("CreateOneByExerciseAsync", "SubmitterInfo")] + [InlineData("CreateManyByExerciseAsync", "Party")] + [InlineData("CreateManyByExerciseAsync", "SubmitterInfo")] + public async Task CreateByExercise_uses_the_supplied_command_id(string method, string submitterShape) + { + var writer = new CapturingWriter( + new ExerciseOutcome.One(TransactionCreatingOneSampleTemplate)); + + await InvokeCreateByExercise( + method, submitterShape, writer, new CommandId("caller-supplied"), TestContext.Current.CancellationToken); + + writer.LastSubmission.Should().NotBeNull("the CreateByExercise path must reach TrySubmitAndWaitForTransactionAsync for the command id to be checkable"); + writer.LastSubmission!.CommandId.Should().Be( + new CommandId("caller-supplied"), + "an overload that drops the caller's command id mints a fresh one instead, so a retry of a lost-but-accepted submission re-executes rather than deduplicating"); + } + + private static Task InvokeCreateByExercise( + string method, + string submitterShape, + ILedgerWriter writer, + CommandId commandId, + CancellationToken cancellationToken) + { + var actAs = new Party("alice"); + SubmitterInfo submitter = actAs; + + return (method, submitterShape) switch + { + ("TryCreateManyByExerciseAsync", "Party") => writer.TryCreateManyByExerciseAsync( + SampleCommand, actAs, commandId: commandId, cancellationToken: cancellationToken), + ("TryCreateManyByExerciseAsync", "SubmitterInfo") => writer.TryCreateManyByExerciseAsync( + SampleCommand, submitter, commandId: commandId, cancellationToken: cancellationToken), + ("CreateOneByExerciseAsync", "Party") => writer.CreateOneByExerciseAsync( + SampleCommand, actAs, commandId: commandId, cancellationToken: cancellationToken), + ("CreateOneByExerciseAsync", "SubmitterInfo") => writer.CreateOneByExerciseAsync( + SampleCommand, submitter, commandId: commandId, cancellationToken: cancellationToken), + ("CreateManyByExerciseAsync", "Party") => writer.CreateManyByExerciseAsync( + SampleCommand, actAs, commandId: commandId, cancellationToken: cancellationToken), + ("CreateManyByExerciseAsync", "SubmitterInfo") => writer.CreateManyByExerciseAsync( + SampleCommand, submitter, commandId: commandId, cancellationToken: cancellationToken), + _ => throw new ArgumentOutOfRangeException(nameof(method), method + " / " + submitterShape), + }; + } + + [Fact] + public async Task ExerciseAsync_forwards_the_command_id_from_an_implicitly_converted_Party_for_a_void_choice() + { + var writer = new CapturingWriter(); + + await writer.ExerciseAsync( + SampleCommand, new Party("alice"), commandId: new CommandId("caller-supplied"), + cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().Be(new CommandId("caller-supplied")); + } + + [Fact] + public async Task ExerciseAsync_mints_a_command_id_for_a_void_choice() + { + var writer = new CapturingWriter(); + + await writer.ExerciseAsync( + SampleCommand, Alice, cancellationToken: TestContext.Current.CancellationToken); + + writer.LastSubmission!.CommandId.Should().NotBeNull(); + } + + private sealed class CapturingWriter : ILedgerWriter + { + private static readonly TransactionResult SampleTransaction = new( + "update-id", LedgerOffset.Begin, [], [], new CommandId("cmd-id")); + + private readonly ExerciseOutcome _outcome; + + public CapturingWriter() + : this(new ExerciseOutcome.One(SampleTransaction)) + { + } + + public CapturingWriter(ExerciseOutcome outcome) => _outcome = outcome; + + public CommandsSubmission? LastSubmission { get; private set; } + + public TimeSpan? LastTimeout { get; private set; } + + public CancellationToken LastCancellationToken { get; private set; } + + public Task> TryExerciseAsync( + ExerciseCommand command, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + => Task.FromResult>(new ExerciseOutcome.One(default(TResult)!)); + + public Task SubmitAndWaitAsync( + CommandsSubmission submission, + SubmitterInfo submitter, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + => Task.FromResult(new SubmitAndWaitResult(new CommandId("cmd-id"), "update-id", LedgerOffset.Begin)); + + public Task> TrySubmitAndWaitForTransactionAsync( + CommandsSubmission submission, + SubmitterInfo submitter, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + { + LastSubmission = submission; + LastTimeout = timeout; + LastCancellationToken = cancellationToken; + return Task.FromResult(_outcome); + } + + public Task>> TryCreateAsync( + TTemplate payload, + SubmitterInfo submitter, + string? workflowId = null, + CommandId? commandId = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default) + where TTemplate : ITemplate + => Task.FromResult>>( + new ExerciseOutcome>.None()); + } +} diff --git a/tests/Daml.Ledger.Abstractions.Tests/StakeholderResumeCompileGuardTests.cs b/tests/Daml.Ledger.Abstractions.Tests/StakeholderResumeCompileGuardTests.cs index 038edc8..5b4e899 100644 --- a/tests/Daml.Ledger.Abstractions.Tests/StakeholderResumeCompileGuardTests.cs +++ b/tests/Daml.Ledger.Abstractions.Tests/StakeholderResumeCompileGuardTests.cs @@ -25,7 +25,7 @@ public sealed class StakeholderResumeCompileGuardTests using Daml.Runtime.Contracts; using Daml.Runtime.Data; - internal sealed record Probe : ITemplate + internal sealed record Probe : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "Module", "Probe"); public static string PackageId => "pkg"; @@ -33,6 +33,24 @@ internal sealed record Probe : ITemplate public static System.Version PackageVersion { get; } = new(1, 0, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); public DamlRecord ToRecord() => DamlRecord.Create(); + public static Probe FromRecord(DamlRecord record) => new(); + } + + internal sealed record ProbeView : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + public static ProbeView FromRecord(DamlRecord record) => new(); + } + + internal sealed record ProbeInterface : IDamlInterface, IHasView + { + public static Identifier InterfaceId { get; } = new("pkg", "Module", "ProbeInterface"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static System.Version PackageVersion { get; } = new(1, 0, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(InterfaceId, DamlTypeKind.Interface, PackageName); + public static ViewDescriptor View { get; } = new(); + public DamlRecord ToRecord() => DamlRecord.Create(); } """; @@ -89,6 +107,61 @@ internal static void Call(ILedgerStreamer streamer, SubmitterInfo submitter, Sta diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Should().BeEmpty(); } + [Fact] + public void SubscribeAsync_accepts_a_StakeholderResume_through_the_view_witness() + { + var diagnostics = Compile(Prelude + """ + internal static class Caller + { + internal static void Call(ILedgerStreamer streamer, SubmitterInfo submitter, StakeholderResume resume) + { + _ = streamer.SubscribeAsync(ProbeInterface.View, submitter, resume); + } + } + """); + + diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Should().BeEmpty( + "the interface family mirrors the template family's resume overload, and both type " + + "parameters have to be inferred from the witness alone"); + } + + [Fact] + public void SubscribeLedgerEffectsAsync_rejects_a_StakeholderResume_through_the_view_witness() + { + var diagnostics = Compile(Prelude + """ + internal static class Caller + { + internal static void Call(ILedgerStreamer streamer, SubmitterInfo submitter, StakeholderResume resume) + { + _ = streamer.SubscribeLedgerEffectsAsync(ProbeInterface.View, submitter, resume); + } + } + """); + + diagnostics.Should().Contain( + d => d.Severity == DiagnosticSeverity.Error && d.Id == "CS1503", + "the ledger-effects shape matches on witnesses rather than stakeholders in the interface " + + "family too, so a snapshot's resume ticket must not cross the basis silently"); + } + + [Fact] + public void SubscribeAsync_rejects_a_partial_type_argument_list_on_the_interface_family() + { + var diagnostics = Compile(Prelude + """ + internal static class Caller + { + internal static void Call(ILedgerStreamer streamer, SubmitterInfo submitter) + { + _ = streamer.SubscribeAsync(submitter); + } + } + """); + + diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).Should().NotBeEmpty( + "naming the marker as a bare type argument is what the witness exists to replace: the " + + "one-parameter overload is the template family, which a marker does not satisfy"); + } + private static IReadOnlyList Compile(string source) { var tree = CSharpSyntaxTree.ParseText(source); diff --git a/tests/Daml.Ledger.Abstractions.Tests/StreamerSnapshotTests.cs b/tests/Daml.Ledger.Abstractions.Tests/StreamerSnapshotTests.cs new file mode 100644 index 0000000..10c12a7 --- /dev/null +++ b/tests/Daml.Ledger.Abstractions.Tests/StreamerSnapshotTests.cs @@ -0,0 +1,393 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Runtime.CompilerServices; +using AwesomeAssertions; +using Daml.Ledger.Abstractions.Extensions; +using Daml.Runtime; +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Streams; +using Xunit; + +namespace Daml.Ledger.Abstractions.Tests; + +/// +/// Behavioural coverage for : the typed +/// projection consumers would otherwise hand-roll, and the incomplete-snapshot shapes it +/// must refuse to hand back as a short list. +/// +public sealed class StreamerSnapshotTests +{ + private static readonly SubmitterInfo Alice = new(new Party("alice")); + + [Fact] + public async Task SnapshotAsync_decodes_created_rows_into_typed_contracts() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + Created("cid-2", "bob", 2), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(2)))); + + var contracts = await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + contracts.Should().HaveCount(2); + contracts[0].Id.Value.Should().Be("cid-1"); + contracts[0].Data.Owner.Should().Be(new Party("alice")); + contracts[1].Data.Owner.Should().Be(new Party("bob")); + } + + [Fact] + public async Task SnapshotAsync_surfaces_the_contract_key_the_snapshot_row_carried() + { + var streamer = new FakeStreamer( + new AcsSnapshotEntry.Created( + new ContractId("cid-1"), + new Probe(new Party("alice"), ProbeGrade.Low), + new ContractKey(DamlRecord.Create(new DamlField("owner", new DamlParty("alice")))), + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(1)))); + + var contracts = await streamer.SnapshotAsync(Probe.Key, Alice, cancellationToken: TestContext.Current.CancellationToken); + + contracts.Should().ContainSingle().Which.Key.Value.Should().Be( + new Party("alice"), + "this is the entry point the key feature documents, so a key dropped anywhere along the " + + "Created to Contract chain reaches the caller as a silently absent key"); + } + + [Fact] + public async Task SnapshotAsync_infers_both_type_arguments_from_the_key_witness() + { + var streamer = new FakeStreamer( + new AcsSnapshotEntry.Created( + new ContractId("cid-1"), + new Probe(new Party("alice"), ProbeGrade.Low), + new ContractKey(DamlRecord.Create(new DamlField("owner", new DamlParty("alice")))), + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(1)))); + + var contracts = await streamer.SnapshotAsync(Probe.Key, Alice, cancellationToken: TestContext.Current.CancellationToken); + + contracts.Should().BeAssignableTo>>( + "the witness argument exists so one argument fixes both type parameters; C# performs no " + + "partial type-argument inference, so a call site spelling neither must still bind"); + } + + [Fact] + public async Task SnapshotAsync_reports_a_keyed_row_that_carried_no_key() + { + var streamer = new FakeStreamer( + new AcsSnapshotEntry.Created( + new ContractId("cid-1"), + new Probe(new Party("alice"), ProbeGrade.Low), + null, + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(1)))); + + var draining = async () => await streamer.SnapshotAsync(Probe.Key, Alice, cancellationToken: TestContext.Current.CancellationToken); + + await draining.Should().ThrowAsync( + "a keyed snapshot whose row carried no key would otherwise return contracts the caller " + + "cannot address by key, which is the short-list failure this convenience prevents"); + } + + [Fact] + public async Task SnapshotAsync_surfaces_the_key_hash_the_snapshot_row_carried() + { + const string ledgerKeyHash = "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA="; + var streamer = new FakeStreamer( + new AcsSnapshotEntry.Created( + new ContractId("cid-1"), + new Probe(new Party("alice"), ProbeGrade.Low), + new ContractKey( + DamlRecord.Create(new DamlField("owner", new DamlParty("alice"))), + Probe.TemplateId) + { + KeyHash = ledgerKeyHash, + }, + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(1)))); + + var contracts = await streamer.SnapshotAsync(Probe.Key, Alice, cancellationToken: TestContext.Current.CancellationToken); + + contracts.Should().ContainSingle().Which.Key.Hash.Should().Be( + ledgerKeyHash, + "this drains the whole Created to Contract chain, so a hash dropped at " + + "any hop reaches the caller as a silently absent hash"); + } + + [Fact] + public async Task SnapshotAsync_returns_an_empty_list_for_an_empty_snapshot() + { + var streamer = new FakeStreamer( + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(7)))); + + var contracts = await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + contracts.Should().BeEmpty(); + } + + [Fact] + public async Task SnapshotAsync_throws_on_a_terminal_stream_error() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.StreamError(14, "unavailable")); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + var thrown = await snapshot.Should().ThrowAsync(); + thrown.Which.StatusCode.Should().Be(14); + thrown.Which.Message.Should().Contain("unavailable"); + } + + [Fact] + public async Task SnapshotAsync_carries_the_stream_errors_classification_onto_the_thrown_exception() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.StreamError( + 14, "unavailable", DamlErrorCategory.TransientServerFailure)); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + var thrown = await snapshot.Should().ThrowAsync(); + thrown.Which.Category.Should().Be( + DamlErrorCategory.TransientServerFailure, + "a classification the transport determined is discarded unless this branch forwards it, " + + "leaving a caller who catches the exception unable to tell a retryable transient fault " + + "from a permanent one with only the status code to go on"); + thrown.Which.StatusCode.Should().Be( + 14, + "the classification has to arrive alongside the status code, not in place of it"); + } + + [Fact] + public async Task SnapshotAsync_carries_the_stream_errors_source_exception_onto_the_thrown_exception() + { + var transportFault = new InvalidOperationException("the channel went away"); + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.StreamError( + 14, "unavailable", DamlErrorCategory.TransientServerFailure, transportFault)); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + var thrown = await snapshot.Should().ThrowAsync(); + thrown.Which.InnerException.Should().BeSameAs( + transportFault, + "the exception the transport caught is lost unless this branch forwards it, leaving a caller " + + "who catches the rethrow with none of the stack the fault actually came from"); + } + + [Fact] + public async Task SnapshotAsync_throws_rather_than_dropping_an_unclassified_row() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.Unclassified(LedgerOffset.At(2), UnclassifiedKind.Unknown, "ACTIVE_CONTRACT"), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(2)))); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + await snapshot.Should().ThrowAsync() + .WithMessage("*ACTIVE_CONTRACT*"); + } + + [Fact] + public async Task SnapshotAsync_reports_an_unclassified_row_that_carries_no_offset() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.Unclassified(null, UnclassifiedKind.DecodeFailure), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(2)))); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + await snapshot.Should().ThrowAsync() + .WithMessage( + "*with no ledger offset*", + "interpolating a null offset would render an empty position and read as 'at offset , so the returned' — the row's absent ledger position has to be said, not blanked"); + } + + [Fact] + public async Task SnapshotAsync_throws_when_the_snapshot_ends_without_its_checkpoint() + { + var streamer = new FakeStreamer(Created("cid-1", "alice", 1)); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + await snapshot.Should().ThrowAsync() + .WithMessage("*without its terminal checkpoint*"); + } + + [Fact] + public async Task SnapshotAsync_throws_on_a_row_the_projector_could_not_decode() + { + var streamer = new FakeStreamer( + Created("cid-1", "alice", 1), + new AcsSnapshotEntry.Unclassified(LedgerOffset.At(2), UnclassifiedKind.DecodeFailure), + new AcsSnapshotEntry.Checkpoint(new StakeholderResume(LedgerOffset.At(2)))); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: TestContext.Current.CancellationToken); + + await snapshot.Should().ThrowAsync( + "a payload the projector could not decode reaches this layer as a DecodeFailure row " + + "rather than a create row, and leaves the snapshot as unusable as any other " + + "unclassified row — which is what publisher-ahead-of-reader drift looks like") + .WithMessage("*DecodeFailure*"); + } + + [Fact] + public async Task SnapshotAsync_reports_caller_cancellation_rather_than_an_in_band_transport_fault() + { + using var cts = new CancellationTokenSource(); + cts.Cancel(); + var streamer = FakeStreamer.IgnoringCancellation(new AcsSnapshotEntry.StreamError(1, "CANCELLED")); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: cts.Token); + + await snapshot.Should().ThrowAsync( + "a transport that maps the cancelled call in-band would otherwise make the caller's " + + "own cancellation read as an infrastructure failure"); + } + + [Fact] + public async Task SnapshotAsync_reports_caller_cancellation_rather_than_a_truncated_snapshot() + { + using var cts = new CancellationTokenSource(); + cts.Cancel(); + var streamer = FakeStreamer.IgnoringCancellation(Created("cid-1", "alice", 1)); + + var snapshot = async () => await streamer.SnapshotAsync(Alice, cancellationToken: cts.Token); + + await snapshot.Should().ThrowAsync( + "a stream cut short by the caller's own token is not a transport truncation"); + } + + private static AcsSnapshotEntry.Created Created(string contractId, string owner, long offset) => + new( + new ContractId(contractId), + new Probe(new Party(owner), ProbeGrade.Low), + null, + LedgerOffset.At(offset), + new SynchronizerId("sync"), + [new Party(owner)]); + + private enum ProbeGrade + { + Low, + High, + } + + private sealed record Probe(Party Owner, ProbeGrade Grade) + : ITemplate, IDamlRecord, IHasKey + { + public static Identifier TemplateId { get; } = new("pkg", "Module", "Probe"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static Version PackageVersion { get; } = new(1, 0, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = key => DamlRecord.Create(new DamlField("owner", key.ToDamlValue())), + KeyDecoder = value => + Party.FromDamlValue(value.As().GetRequiredField("owner").As()), + }; + + public DamlRecord ToRecord() => DamlRecord.Create( + new DamlField("owner", Owner.ToDamlValue()), + new DamlField("grade", new DamlEnum(null, Grade.ToString()))); + + public static Probe FromRecord(DamlRecord record) => + new( + Party.FromDamlValue(record.GetRequiredField("owner").As()), + record.GetRequiredField("grade").As().Constructor switch + { + "Low" => ProbeGrade.Low, + "High" => ProbeGrade.High, + var unknown => throw new ArgumentOutOfRangeException(nameof(record), unknown, null), + }); + } + + private sealed class FakeStreamer(params AcsSnapshotEntry[] entries) : ILedgerStreamer + { + private bool ObservesCancellation { get; init; } = true; + + public static FakeStreamer IgnoringCancellation(params AcsSnapshotEntry[] entries) => + new(entries) { ObservesCancellation = false }; + + public IAsyncEnumerable> SubscribeAsync( + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where T : ITemplate, IDamlRecord => (IAsyncEnumerable>)(object)Replay(cancellationToken); + + public IAsyncEnumerable> SubscribeAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeLedgerEffectsAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? fromOffset = null, + LedgerOffset? toOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => throw new NotSupportedException(); + + public IAsyncEnumerable> SubscribeActiveAsync( + ViewDescriptor view, + SubmitterInfo submitter, + LedgerOffset? activeAtOffset = null, + CancellationToken cancellationToken = default) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord => throw new NotSupportedException(); + + private async IAsyncEnumerable> Replay( + [EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var entry in entries) + { + if (ObservesCancellation) + { + cancellationToken.ThrowIfCancellationRequested(); + } + + yield return entry; + await Task.Yield(); + } + } + } +} diff --git a/tests/Daml.Runtime.Tests/AcsSnapshotEntryTests.cs b/tests/Daml.Runtime.Tests/AcsSnapshotEntryTests.cs index 47b6f70..733b6cb 100644 --- a/tests/Daml.Runtime.Tests/AcsSnapshotEntryTests.cs +++ b/tests/Daml.Runtime.Tests/AcsSnapshotEntryTests.cs @@ -5,6 +5,7 @@ using Daml.Runtime; using Daml.Runtime.Contracts; using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; using Daml.Runtime.Streams; using Xunit; @@ -12,6 +13,8 @@ namespace Daml.Runtime.Tests; public sealed class AcsSnapshotEntryTests { + private const string LedgerKeyHash = "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA="; + [Fact] public void Checkpoint_carries_the_terminal_offset_as_a_StakeholderResume() { @@ -25,7 +28,7 @@ public void Checkpoint_carries_the_terminal_offset_as_a_StakeholderResume() public void Variants_are_distinguishable_via_pattern_match() { AcsSnapshotEntry entry = - new AcsSnapshotEntry.Unclassified(LedgerOffset.At(3), "ACTIVE_CONTRACT"); + new AcsSnapshotEntry.Unclassified(LedgerOffset.At(3), UnclassifiedKind.DecodeFailure); var matched = entry switch { @@ -67,6 +70,23 @@ public void StreamError_StatusCode_is_int_so_no_transport_dep_leaks() error.Message.Should().Be("unavailable"); } + [Fact] + public void StreamError_carries_the_classification_the_transport_determined() + { + var error = new AcsSnapshotEntry.StreamError( + 14, "unavailable", DamlErrorCategory.TransientServerFailure); + + error.Category.Should().Be(DamlErrorCategory.TransientServerFailure); + } + + [Fact] + public void StreamError_leaves_the_classification_null_when_the_transport_determined_none() + { + var error = new AcsSnapshotEntry.StreamError(14, "unavailable"); + + error.Category.Should().BeNull(); + } + [Fact] public void StreamError_with_same_payload_should_be_value_equal() { @@ -79,19 +99,192 @@ public void StreamError_with_same_payload_should_be_value_equal() public void Created_carries_its_contract_and_fields() { var contractId = new ContractId("c1"); - var payload = DamlRecord.Create(); + var payload = new TestTemplate("alice"); var offset = LedgerOffset.At(4); var synchronizerId = new SynchronizerId("sync"); IReadOnlyList witnessParties = [new Party("alice")]; - var entry = new AcsSnapshotEntry.Created(contractId, payload, offset, synchronizerId, witnessParties); + var entry = new AcsSnapshotEntry.Created(contractId, payload, null, offset, synchronizerId, witnessParties); entry.ContractId.Should().Be(contractId); entry.Offset.Should().Be(offset); entry.Payload.Should().Be(payload); } - private sealed record TestTemplate(string Owner) : ITemplate + [Fact] + public void Unclassified_should_expose_offset_and_enumerated_kind() + { + var unclassified = new AcsSnapshotEntry.Unclassified( + LedgerOffset.At(7), UnclassifiedKind.DecodeFailure); + + unclassified.Offset.Should().Be( + LedgerOffset.At(7), + "a projector that does know where the unclassifiable row sat must still report it, so the nullable slot has to round-trip a real offset unchanged"); + unclassified.Kind.Should().Be(UnclassifiedKind.DecodeFailure); + unclassified.RawKind.Should().BeNull(); + } + + [Fact] + public void Unclassified_unknown_kind_preserves_the_raw_descriptor() + { + var unclassified = new AcsSnapshotEntry.Unclassified( + LedgerOffset.At(7), UnclassifiedKind.Unknown, "ACTIVE_CONTRACT"); + + unclassified.Kind.Should().Be(UnclassifiedKind.Unknown); + unclassified.RawKind.Should().Be("ACTIVE_CONTRACT"); + } + + [Fact] + public void Unclassified_rejects_a_raw_descriptor_on_an_enumerated_kind() + { + var act = () => new AcsSnapshotEntry.Unclassified( + LedgerOffset.At(7), UnclassifiedKind.DecodeFailure, "ACTIVE_CONTRACT"); + + act.Should().Throw(); + } + + [Fact] + public void Unclassified_rejects_an_unknown_kind_without_a_raw_descriptor() + { + var act = () => new AcsSnapshotEntry.Unclassified( + LedgerOffset.At(7), UnclassifiedKind.Unknown, null); + + act.Should().Throw(); + } + + [Fact] + public void Unclassified_accepts_a_null_offset_and_preserves_it() + { + var unclassified = new AcsSnapshotEntry.Unclassified( + null, UnclassifiedKind.DecodeFailure); + + unclassified.Offset.Should().BeNull( + "a snapshot row the projector could not place on the ledger has no offset to report, and the only alternative is fabricating LedgerOffset.Begin, which is indistinguishable from a genuine ledger start: a consumer persisting it as resume state checkpoints at the beginning of the ledger and re-reads the entire stream"); + unclassified.Kind.Should().Be(UnclassifiedKind.DecodeFailure); + unclassified.RawKind.Should().BeNull(); + } + + [Fact] + public void Unclassified_without_an_offset_still_rejects_a_raw_descriptor_on_an_enumerated_kind() + { + var act = () => new AcsSnapshotEntry.Unclassified( + null, UnclassifiedKind.DecodeFailure, "ACTIVE_CONTRACT"); + + act.Should().Throw( + "widening Offset to a nullable must not relax the Kind and RawKind invariant: an enumerated kind carrying a raw descriptor stays a construction error"); + } + + [Fact] + public void Unclassified_without_an_offset_still_rejects_an_unknown_kind_without_a_raw_descriptor() + { + var act = () => new AcsSnapshotEntry.Unclassified( + null, UnclassifiedKind.Unknown, null); + + act.Should().Throw( + "widening Offset to a nullable must not relax the Kind and RawKind invariant: Unknown without the transport's raw descriptor stays a construction error"); + } + + [Fact] + public void ToContract_pairs_the_decoded_payload_with_its_contract_id() + { + var created = new AcsSnapshotEntry.Created( + new ContractId("c1"), + new DecodableTemplate(new Party("alice")), + null, + LedgerOffset.At(4), + new SynchronizerId("sync"), + [new Party("alice")]); + + var contract = created.ToContract(); + + contract.Id.Value.Should().Be("c1"); + contract.Data.Owner.Should().Be(new Party("alice")); + } + + [Fact] + public void ToContract_carries_the_contract_key_onto_the_contract() + { + var created = new AcsSnapshotEntry.Created( + new ContractId("c1"), + new DecodableTemplate(new Party("alice")), + new ContractKey(DamlRecord.Create(new DamlField("owner", new DamlParty("alice")))), + LedgerOffset.At(4), + new SynchronizerId("sync"), + [new Party("alice")]); + + var contract = created.ToContract(); + + contract.Key.Value.Should().Be( + new Party("alice"), + "Contract is what the snapshot bridge hands back, so a projection that drops the key here " + + "makes contract.Key unreachable no matter what the transport read off the wire"); + } + + [Fact] + public void ToContract_rejects_a_keyed_projection_of_a_row_carrying_no_key() + { + var created = new AcsSnapshotEntry.Created( + new ContractId("c1"), + new DecodableTemplate(new Party("alice")), + null, + LedgerOffset.At(4), + new SynchronizerId("sync"), + [new Party("alice")]); + + var projecting = () => created.ToContract(); + + projecting.Should().Throw( + "the keyed contract's key is non-nullable, so a row with no key has to fail loudly " + + "rather than reach a caller through a shape whose type says the key is present"); + } + + [Fact] + public void ToContract_carries_the_key_hash_onto_the_contract() + { + var created = new AcsSnapshotEntry.Created( + new ContractId("c1"), + new DecodableTemplate(new Party("alice")), + new ContractKey( + DamlRecord.Create(new DamlField("owner", new DamlParty("alice"))), + DecodableTemplate.TemplateId) + { + KeyHash = LedgerKeyHash, + }, + LedgerOffset.At(4), + new SynchronizerId("sync"), + [new Party("alice")]); + + var contract = created.ToContract(); + + contract.Key.Hash.Should().Be( + LedgerKeyHash, + "Contract is what the snapshot bridge hands back, so a hash dropped here is unreachable " + + "no matter what the transport read off the wire"); + } + + private sealed record DecodableTemplate(Party Owner) + : ITemplate, IDamlRecord, IHasKey + { + public static Identifier TemplateId { get; } = new("pkg", "M", "DecodableTemplate"); + public static string PackageId => "pkg"; + public static string PackageName => "test"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = key => DamlRecord.Create(new DamlField("owner", key.ToDamlValue())), + KeyDecoder = value => + Party.FromDamlValue(value.As().GetRequiredField("owner").As()), + }; + + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", Owner.ToDamlValue())); + + public static DecodableTemplate FromRecord(DamlRecord record) => + new(Party.FromDamlValue(record.GetRequiredField("owner").As())); + } + + private sealed record TestTemplate(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); public static string PackageId => "pkg"; @@ -99,6 +292,9 @@ private sealed record TestTemplate(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", new DamlText(Owner))); + + public static TestTemplate FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } } diff --git a/tests/Daml.Runtime.Tests/ChoiceResultFromCreatedContractsTests.cs b/tests/Daml.Runtime.Tests/ChoiceResultFromCreatedContractsTests.cs index 2171aaa..b920ae2 100644 --- a/tests/Daml.Runtime.Tests/ChoiceResultFromCreatedContractsTests.cs +++ b/tests/Daml.Runtime.Tests/ChoiceResultFromCreatedContractsTests.cs @@ -121,7 +121,17 @@ public static ExerciseOutcome FromCreatedContracts(IEnumerabl } private static CreatedContract Created(string contractId) where T : ITemplate => - new(contractId, T.TemplateId, "{}"); + CreatedOf(contractId, T.TemplateId); + + private static CreatedContract CreatedOf(string contractId, Identifier templateId) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: templateId, + Payload: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); [Fact] public void FromCreatedContracts_should_return_One_when_all_required_slots_match() @@ -222,7 +232,7 @@ public void FromCreatedContracts_should_ignore_unrelated_created_contracts() { Created("agree-cid-1"), Created("swap-cid-1"), - new CreatedContract("foreign-cid", foreignId, "{}"), + CreatedOf("foreign-cid", foreignId), }; var outcome = ExecuteSwapResult.FromCreatedContracts(refs); diff --git a/tests/Daml.Runtime.Tests/CommandIdTests.cs b/tests/Daml.Runtime.Tests/CommandIdTests.cs index 7d16320..f2158fd 100644 --- a/tests/Daml.Runtime.Tests/CommandIdTests.cs +++ b/tests/Daml.Runtime.Tests/CommandIdTests.cs @@ -48,4 +48,44 @@ public void Explicit_conversion_from_string_should_construct() var id = (CommandId)"cmd-1"; id.Value.Should().Be("cmd-1"); } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void FromWire_should_return_null_when_wire_value_is_null_empty_or_whitespace(string? value) + { + (CommandId.FromWire(value) is null).Should().BeTrue(); + } + + [Fact] + public void FromWire_should_round_trip_a_non_blank_wire_value_through_Value() + { + var id = CommandId.FromWire("cmd-1"); + + (id is null).Should().BeFalse(); + id!.Value.Value.Should().Be("cmd-1"); + } + + [Fact] + public void FromWire_should_store_a_padded_value_verbatim() + { + var id = CommandId.FromWire(" cmd-1 "); + + id!.Value.Value.Should().Be(" cmd-1 "); + } + + [Fact] + public void Default_returned_from_a_CommandId_typed_helper_should_wrap_non_null_unlike_FromWire() + { + static CommandId LegacyTransportHelper(string wire) => + wire.Length == 0 ? default : (CommandId)wire; + + CommandId? viaHelper = LegacyTransportHelper(""); + Action readViaHelper = () => _ = viaHelper?.Value; + + (viaHelper is null).Should().BeFalse(); + readViaHelper.Should().Throw(); + (CommandId.FromWire("") is null).Should().BeTrue(); + } } diff --git a/tests/Daml.Runtime.Tests/CommandTypesTests.cs b/tests/Daml.Runtime.Tests/CommandTypesTests.cs index 4dc511d..884c0d3 100644 --- a/tests/Daml.Runtime.Tests/CommandTypesTests.cs +++ b/tests/Daml.Runtime.Tests/CommandTypesTests.cs @@ -316,6 +316,60 @@ public void CommandsSubmission_WithWorkflowId_should_set_workflow_id() result.Commands.Should().BeEquivalentTo(submission.Commands); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t\n")] + public void CommandsSubmission_WithOptionalWorkflowId_should_leave_the_submission_unchanged(string? workflowId) + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command); + + // Act + var result = submission.WithOptionalWorkflowId(workflowId); + + // Assert + result.WorkflowId.Should().BeNull(); + result.Should().Be(submission); + } + + [Fact] + public void CommandsSubmission_WithOptionalWorkflowId_should_set_workflow_id_when_supplied() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command); + + // Act + var result = submission.WithOptionalWorkflowId("workflow-123"); + + // Assert + result.WorkflowId.Should().Be(new WorkflowId("workflow-123")); + result.Commands.Should().BeEquivalentTo(submission.Commands); + } + + [Fact] + public void CommandsSubmission_WithWorkflowId_should_still_store_an_explicit_blank_workflow_id() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command); + + // Act + var result = submission.WithWorkflowId(new WorkflowId(" ")); + + // Assert + result.WorkflowId.Should().Be(new WorkflowId(" ")); + } + [Fact] public void CommandsSubmission_WithCommandId_should_set_command_id() { @@ -455,6 +509,52 @@ public void DisclosedContract_equality_should_distinguish_different_blob_content (left != right).Should().BeTrue(); } + [Fact] + public void DisclosedContract_should_not_observe_mutation_of_the_source_blob_array() + { + var blob = "created-event-blob"u8.ToArray(); + var disclosedContract = new DisclosedContract( + "contract-id-1", new Identifier("pkg", "Module", "Template"), blob); + var hashCodeBeforeMutation = disclosedContract.GetHashCode(); + + blob[0] ^= 0xFF; + + disclosedContract.CreatedEventBlob.ToArray().Should().Equal("created-event-blob"u8.ToArray()); + disclosedContract.GetHashCode().Should().Be(hashCodeBeforeMutation); + } + + [Fact] + public void DisclosedContract_should_stay_findable_in_a_hash_set_after_the_source_blob_array_mutates() + { + var blob = "created-event-blob"u8.ToArray(); + var disclosedContract = new DisclosedContract( + "contract-id-1", new Identifier("pkg", "Module", "Template"), blob); + var disclosedContracts = new HashSet { disclosedContract }; + + blob[0] ^= 0xFF; + + disclosedContracts.Contains(disclosedContract).Should().BeTrue(); + disclosedContracts.Contains(new DisclosedContract( + "contract-id-1", + new Identifier("pkg", "Module", "Template"), + "created-event-blob"u8.ToArray())).Should().BeTrue(); + } + + [Fact] + public void DisclosedContract_with_expression_should_copy_the_replacement_blob_array() + { + var original = new DisclosedContract( + "contract-id-1", + new Identifier("pkg", "Module", "Template"), + "created-event-blob"u8.ToArray()); + var replacementBlob = "replacement-event-blob"u8.ToArray(); + + var replaced = original with { CreatedEventBlob = replacementBlob }; + replacementBlob[0] ^= 0xFF; + + replaced.CreatedEventBlob.ToArray().Should().Equal("replacement-event-blob"u8.ToArray()); + } + [Fact] public void CommandsSubmission_WithSubmitter_should_preserve_disclosed_contracts() { @@ -476,6 +576,157 @@ public void CommandsSubmission_WithSubmitter_should_preserve_disclosed_contracts result.DisclosedContracts.Should().ContainSingle().Which.Should().Be(disclosedContract); } + [Fact] + public void CommandsSubmission_should_leave_min_ledger_time_null_when_omitted() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + + // Act + var submission = CommandsSubmission.Single(command); + + // Assert + submission.MinLedgerTime.Should().BeNull(); + CommandsSubmission.Multiple(command).MinLedgerTime.Should().BeNull(); + new CommandsSubmission([command]).MinLedgerTime.Should().BeNull(); + } + + [Fact] + public void CommandsSubmission_WithMinLedgerTime_should_set_an_absolute_bound() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var instant = new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.Zero); + var submission = CommandsSubmission.Single(command); + + // Act + var result = submission.WithMinLedgerTime(new MinLedgerTime.Absolute(instant)); + + // Assert + result.MinLedgerTime.Should().Be(new MinLedgerTime.Absolute(instant)); + result.MinLedgerTime.Should().BeOfType() + .Which.Value.Should().Be(instant); + submission.MinLedgerTime.Should().BeNull(); + } + + [Fact] + public void CommandsSubmission_WithMinLedgerTime_should_set_a_relative_bound() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command); + + // Act + var result = submission.WithMinLedgerTime( + new MinLedgerTime.Relative(TimeSpan.FromSeconds(30))); + + // Assert + result.MinLedgerTime.Should().BeOfType() + .Which.Value.Should().Be(TimeSpan.FromSeconds(30)); + } + + [Fact] + public void CommandsSubmission_WithMinLedgerTime_should_round_trip_both_arms() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var instant = new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.Zero); + MinLedgerTime[] bounds = + [ + new MinLedgerTime.Absolute(instant), + new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)), + ]; + + // Act + var readBack = bounds + .Select(bound => CommandsSubmission.Single(command).WithMinLedgerTime(bound)) + .Select(submission => submission.MinLedgerTime?.Match( + absolute => $"abs:{absolute:O}", + relative => $"rel:{relative}") ?? "none") + .ToArray(); + + // Assert + readBack.Should().Equal("abs:2026-08-15T09:30:00.0000000+00:00", "rel:00:00:30"); + } + + [Fact] + public void CommandsSubmission_WithMinLedgerTime_with_null_should_clear_the_bound() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command) + .WithMinLedgerTime(new MinLedgerTime.Relative(TimeSpan.FromSeconds(30))); + + // Act + var result = submission.WithMinLedgerTime(null); + + // Assert + result.MinLedgerTime.Should().BeNull(); + } + + [Fact] + public void CommandsSubmission_WithMinLedgerTime_should_preserve_the_other_fields() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var disclosedContract = new DisclosedContract( + "contract-id-1", + new Identifier("pkg", "Module", "Template"), + "created-event-blob"u8.ToArray()); + var submission = CommandsSubmission.Single(command) + .WithWorkflowId(new WorkflowId("workflow-1")) + .WithCommandId(new CommandId("cmd-1")) + .WithActAs(new Party("Alice")) + .WithReadAs(new Party("Bob")) + .WithSynchronizerId(new SynchronizerId("sync-1")) + .WithDisclosedContracts(disclosedContract); + + // Act + var result = submission.WithMinLedgerTime( + new MinLedgerTime.Relative(TimeSpan.FromSeconds(30))); + + // Assert + result.Commands.Should().ContainSingle().Which.Should().Be(command); + result.WorkflowId.Should().Be(new WorkflowId("workflow-1")); + result.CommandId.Should().Be(new CommandId("cmd-1")); + result.ActAs.Should().ContainSingle().Which.Should().Be(new Party("Alice")); + result.ReadAs.Should().ContainSingle().Which.Should().Be(new Party("Bob")); + result.SynchronizerId.Should().Be(new SynchronizerId("sync-1")); + result.DisclosedContracts.Should().ContainSingle().Which.Should().Be(disclosedContract); + result.MinLedgerTime.Should().BeOfType() + .Which.Value.Should().Be(TimeSpan.FromSeconds(30)); + } + + [Fact] + public void CommandsSubmission_WithSubmitter_should_preserve_min_ledger_time() + { + // Arrange + var command = new CreateCommand( + new Identifier("pkg", "Module", "Template"), + DamlRecord.Create()); + var submission = CommandsSubmission.Single(command) + .WithMinLedgerTime(new MinLedgerTime.Relative(TimeSpan.FromSeconds(30))); + + // Act + var result = submission.WithSubmitter(new Party("Alice")); + + // Assert + result.MinLedgerTime.Should().BeOfType() + .Which.Value.Should().Be(TimeSpan.FromSeconds(30)); + } + [Fact] public void CommandsSubmission_should_chain_fluent_methods() { diff --git a/tests/Daml.Runtime.Tests/ContractIdJsonTests.cs b/tests/Daml.Runtime.Tests/ContractIdJsonTests.cs new file mode 100644 index 0000000..6e896c7 --- /dev/null +++ b/tests/Daml.Runtime.Tests/ContractIdJsonTests.cs @@ -0,0 +1,254 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class ContractIdJsonTests +{ + private static readonly JsonSerializerOptions Registered = new JsonSerializerOptions().AddDamlConverters(); + + private sealed record TemplatePayload(ContractId Marker, Party Owner); + + private sealed record OptionalContractIdPayload(ContractId? Marker, string Note); + + [Fact] + public void ContractId_should_serialize_as_json_string() + { + var json = JsonSerializer.Serialize(new ContractId("00abc")); + + json.Should().Be("\"00abc\""); + } + + [Fact] + public void ContractId_should_deserialize_from_json_string() + { + var contractId = JsonSerializer.Deserialize>("\"00abc\""); + + contractId!.Value.Should().Be("00abc"); + } + + [Fact] + public void ContractId_should_round_trip_inside_a_record_payload() + { + var json = "{\"Marker\":\"00abc\",\"Owner\":\"Alice::1220ab\"}"; + + var payload = JsonSerializer.Deserialize(json); + + payload!.Marker.Value.Should().Be("00abc"); + payload.Owner.Id.Should().Be("Alice::1220ab"); + JsonSerializer.Serialize(payload).Should().Be(json); + } + + [Fact] + public void ContractId_should_reject_a_non_string_token() + { + var deserialize = () => JsonSerializer.Deserialize>("{\"value\":\"00abc\"}"); + + deserialize.Should().Throw(); + } + + [Fact] + public void ContractId_should_reject_an_empty_string() + { + var deserialize = () => JsonSerializer.Deserialize>("\"\""); + + deserialize.Should().Throw(); + } + + [Fact] + public void Optional_ContractId_should_round_trip_as_json_null() + { + var json = "{\"Marker\":null,\"Note\":\"none held\"}"; + + var payload = JsonSerializer.Deserialize(json); + + payload!.Marker.Should().BeNull(); + JsonSerializer.Serialize(payload).Should().Be(json); + } + + [Fact] + public void ContractId_should_throw_JsonException_when_non_nullable_field_receives_null() + { + var json = "{\"Marker\":null,\"Owner\":\"Alice::1220ab\"}"; + + var deserialize = () => JsonSerializer.Deserialize(json, Registered); + + deserialize.Should().Throw( + "a null on a non-nullable ContractId field must fail as loudly as it already does on the " + + "Party field beside it, rather than silently yielding a null the caller only discovers " + + "when it dereferences the id"); + } + + [Fact] + public void Optional_ContractId_should_round_trip_as_json_null_under_the_registered_converters() + { + var json = "{\"Marker\":null,\"Note\":\"none held\"}"; + + var payload = JsonSerializer.Deserialize(json, Registered); + + payload!.Marker.Should().BeNull( + "Optional (ContractId T) is a supported Daml shape, so rejecting a null has to key off the " + + "declared nullability — a converter that refuses every null token cannot tell the two " + + "declarations apart, because both are the same reference type at runtime"); + JsonSerializer.Serialize(payload, Registered).Should().Be(json); + } + + [Fact] + public void AddDamlConverters_should_respect_nullable_annotations() + { + var options = new JsonSerializerOptions().AddDamlConverters(); + + options.RespectNullableAnnotations.Should().BeTrue( + "ContractId is a reference type, so nothing but the annotation distinguishes a required " + + "field from an Optional one; the two struct converters reject a null token themselves, and " + + "this is what gives the third member of the family the same posture"); + } + + [Fact] + public void AddDamlConverters_should_register_the_scalar_identity_converters() + { + var options = new JsonSerializerOptions().AddDamlConverters(); + + options.Converters.Should().Contain(c => c is ContractIdJsonConverterFactory); + options.Converters.Select(c => c.GetType().Name).Should().BeEquivalentTo( + ["PartyJsonConverter", "ContractIdJsonConverterFactory", "SynchronizerIdJsonConverter"], + "naming the converters is what catches one silently dropping out of DamlJsonConverters.All, " + + "which a count compared against All itself cannot"); + } + + [Fact] + public void AddDamlConverters_should_keep_the_bare_string_wire_shape_for_a_host_composed_options() + { + var options = new JsonSerializerOptions().AddDamlConverters(); + + JsonSerializer.Serialize(new ContractId("00abc"), options).Should().Be("\"00abc\""); + JsonSerializer.Deserialize>("\"00abc\"", options)!.Value.Should().Be("00abc"); + } + + [Fact] + public void ContractIdJsonConverterFactory_should_reject_an_unrelated_type() + { + var factory = new ContractIdJsonConverterFactory(); + + factory.CanConvert(typeof(Party)).Should().BeFalse(); + var create = () => factory.CreateConverter(typeof(Party), new JsonSerializerOptions()); + create.Should().Throw(); + } + + [Fact] + public void ContractIdJsonConverterFactory_should_reject_the_abstract_erased_base() + { + var factory = new ContractIdJsonConverterFactory(); + + factory.CanConvert(typeof(Daml.Runtime.Contracts.ContractId)).Should().BeFalse( + "the erased base is abstract, so there is nothing for Read to construct"); + } + + [Fact] + public void Codegen_derived_ContractId_should_serialize_as_json_string() + { + var json = JsonSerializer.Serialize(new Marker.ContractId("00abc"), Registered); + + json.Should().Be( + "\"00abc\"", + "codegen emits a per-template T.ContractId deriving from ContractId, and that derived " + + "type is what a consumer DTO declares — matching only the exact closed generic would " + + "leave it on the default object contract and give one value two wire shapes"); + } + + [Fact] + public void Codegen_derived_ContractId_should_deserialize_from_json_string() + { + var contractId = JsonSerializer.Deserialize("\"00abc\"", Registered); + + contractId.Should().BeOfType( + "reading must construct the derived record, not its base"); + contractId!.Value.Should().Be("00abc"); + } + + [Fact] + public void Codegen_derived_ContractId_should_round_trip_inside_a_record_payload() + { + var json = "{\"Marker\":\"00abc\",\"Owner\":\"Alice::1220ab\"}"; + + var payload = JsonSerializer.Deserialize(json, Registered); + + payload!.Marker.Value.Should().Be("00abc"); + JsonSerializer.Serialize(payload, Registered).Should().Be(json); + } + + [Fact] + public void Codegen_derived_ContractId_should_reject_an_empty_string() + { + var deserialize = () => JsonSerializer.Deserialize("\"\"", Registered); + + deserialize.Should().Throw(); + } + + [Fact] + public void Codegen_derived_ContractId_keeps_the_bare_string_shape_without_AddDamlConverters() + { + JsonSerializer.Serialize(new Marker.ContractId("00abc")).Should().Be( + "\"00abc\"", + "System.Text.Json reads [JsonConverter] off the type being converted and does not walk " + + "the base chain, so the attribute on ContractId never reaches the derived record. " + + "The emitter now writes the attribute onto the generated T.ContractId, and this " + + "fixture mirrors that shape, so a consumer DTO declaring the derived type no longer " + + "needs AddDamlConverters to get one wire shape"); + } + + [Fact] + public void Generic_ContractId_declared_property_should_keep_the_bare_string_shape_for_a_derived_instance() + { + var payload = new TemplatePayload(new Marker.ContractId("00abc"), new Party("Alice::1220ab")); + + JsonSerializer.Serialize(payload).Should().Be("{\"Marker\":\"00abc\",\"Owner\":\"Alice::1220ab\"}"); + } + + [Fact] + public void Derived_ContractId_rejecting_a_value_should_surface_as_JsonException() + { + var deserialize = () => JsonSerializer.Deserialize("\"ZZ\"", Registered); + + deserialize.Should().Throw( + "the converter constructs the derived type reflectively, and ConstructorInfo.Invoke " + + "wraps a constructor throw in TargetInvocationException — a caller catching " + + "JsonException, which is the whole System.Text.Json contract, would otherwise miss it"); + } + + private sealed record DerivedContractIdPayload(Marker.ContractId Marker, Party Owner); + + private sealed record FormatCheckedContractId : global::Daml.Runtime.Contracts.ContractId + { + public FormatCheckedContractId(string value) + : base(value) + { + if (!value.StartsWith("00", StringComparison.Ordinal)) + { + throw new ArgumentException("Contract ids start with '00'.", nameof(value)); + } + } + } + + private sealed record Marker : ITemplate + { + public static Identifier TemplateId { get; } = new("pkg", "M", "Marker"); + public static string PackageId => "pkg"; + public static string PackageName => "test"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + + [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Daml.Runtime.Serialization.ContractIdJsonConverterFactory))] + public sealed record ContractId(string Value) + : global::Daml.Runtime.Contracts.ContractId(Value); + } +} diff --git a/tests/Daml.Runtime.Tests/ContractStreamEventTests.cs b/tests/Daml.Runtime.Tests/ContractStreamEventTests.cs index 6f74185..f82fba1 100644 --- a/tests/Daml.Runtime.Tests/ContractStreamEventTests.cs +++ b/tests/Daml.Runtime.Tests/ContractStreamEventTests.cs @@ -4,6 +4,7 @@ using Daml.Runtime; using Daml.Runtime.Contracts; using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; using Daml.Runtime.Streams; using AwesomeAssertions; using Xunit; @@ -12,15 +13,71 @@ namespace Daml.Runtime.Tests; public class ContractStreamEventTests { + private const string LedgerKeyHash = "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA="; + + [Fact] + public void Created_should_carry_the_key_hash_the_transport_read_off_the_wire() + { + var created = new ContractStreamEvent.Created( + new ContractId("c1"), + new TestTemplate("alice"), + new ContractKey(new DamlText("savings"), TestTemplate.TemplateId) { KeyHash = LedgerKeyHash }, + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]); + + created.Key!.KeyHash.Should().Be( + LedgerKeyHash, + "the live stream is the fourth read shape a key travels, so the hash has to survive it " + + "as well as the snapshot shapes"); + } + + [Fact] + public void Created_should_leave_the_key_hash_null_when_the_event_carried_none() + { + var created = new ContractStreamEvent.Created( + new ContractId("c1"), + new TestTemplate("alice"), + new ContractKey(new DamlText("savings"), TestTemplate.TemplateId), + LedgerOffset.At(1), + new SynchronizerId("sync"), + [new Party("alice")]); + + created.Key!.KeyHash.Should().BeNull(); + } + + [Fact] + public void Assigned_should_carry_the_key_of_the_contract_it_reassigns() + { + var key = new ContractKey(new DamlText("savings"), TestTemplate.TemplateId) { KeyHash = LedgerKeyHash }; + + var assigned = new ContractStreamEvent.Assigned( + new ContractId("c1"), + new TestTemplate("alice"), + key, + LedgerOffset.At(4), + new SynchronizerId("src"), + new SynchronizerId("tgt"), + "reassignment-1", + 7L, + [new Party("alice")]); + + assigned.Key.Should().Be( + key, + "an assignment re-emits the whole created contract, so a consumer rebuilding state from " + + "one stream would lose the key at every reassignment if this variant had nowhere to put it"); + assigned.Key!.KeyHash.Should().Be(LedgerKeyHash); + } + [Fact] public void Variants_should_be_distinguishable_via_pattern_match() { ContractStreamEvent[] events = [ - new ContractStreamEvent.Created(new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")]), + new ContractStreamEvent.Created(new ContractId("c1"), new TestTemplate("alice"), null, LedgerOffset.At(1), new SynchronizerId("sync"), [new Party("alice")]), new ContractStreamEvent.Archived(new ContractId("c1"), LedgerOffset.At(2), new SynchronizerId("sync"), [new Party("alice")]), new ContractStreamEvent.Exercised(new ContractId("c1"), "Accept", DamlUnit.Instance, DamlUnit.Instance, true, LedgerOffset.At(3), new SynchronizerId("sync"), [new Party("alice")]), - new ContractStreamEvent.Assigned(new ContractId("c1"), DamlRecord.Create(), LedgerOffset.At(4), new SynchronizerId("src"), new SynchronizerId("tgt"), "reassignment-1", 7L, [new Party("alice")]), + new ContractStreamEvent.Assigned(new ContractId("c1"), new TestTemplate("alice"), null, LedgerOffset.At(4), new SynchronizerId("src"), new SynchronizerId("tgt"), "reassignment-1", 7L, [new Party("alice")]), new ContractStreamEvent.Unassigned(new ContractId("c1"), LedgerOffset.At(5), new SynchronizerId("src"), new SynchronizerId("tgt"), "reassignment-1", 7L, [new Party("alice")]), new ContractStreamEvent.Checkpoint(LedgerOffset.At(6)), new ContractStreamEvent.StreamError(14, "unavailable"), @@ -62,19 +119,40 @@ public void Unclassified_with_same_payload_should_be_value_equal() [Fact] public void StreamError_StatusCode_is_int_so_no_transport_dep_leaks() { - // Held as int so this type doesn't require any consumer to take a - // dep on Grpc.Core (or any other transport library) to switch on it. var err = new ContractStreamEvent.StreamError(14, "transient"); - err.StatusCode.Should().BeOfType(typeof(int)); + + err.StatusCode.Should().BeOfType( + typeof(int), + "holding the status code as an int is what spares every consumer a dependency on " + + "Grpc.Core, or any other transport library, merely to switch on it"); err.StatusCode.Should().Be(14); } + [Fact] + public void StreamError_carries_the_classification_the_transport_determined() + { + var err = new ContractStreamEvent.StreamError( + 14, "transient", DamlErrorCategory.TransientServerFailure); + + err.Category.Should().Be(DamlErrorCategory.TransientServerFailure); + } + + [Fact] + public void StreamError_leaves_the_classification_null_when_the_transport_determined_none() + { + var err = new ContractStreamEvent.StreamError(14, "transient"); + + err.Category.Should().BeNull(); + } + [Fact] public void Unclassified_should_expose_offset_and_enumerated_kind() { var unclassified = new ContractStreamEvent.Unclassified(LedgerOffset.At(7), UnclassifiedKind.DecodeFailure); - unclassified.Offset.Should().Be(LedgerOffset.At(7)); + unclassified.Offset.Should().Be( + LedgerOffset.At(7), + "a projector that does know where the unclassifiable event sat must still report it, so the nullable slot has to round-trip a real offset unchanged"); unclassified.Kind.Should().Be(UnclassifiedKind.DecodeFailure); unclassified.RawKind.Should().BeNull(); } @@ -104,6 +182,35 @@ public void Unclassified_rejects_an_unknown_kind_without_a_raw_descriptor() act.Should().Throw(); } + [Fact] + public void Unclassified_accepts_a_null_offset_and_preserves_it() + { + var unclassified = new ContractStreamEvent.Unclassified(null, UnclassifiedKind.DecodeFailure); + + unclassified.Offset.Should().BeNull( + "an event the projector could not place on the ledger has no offset to report, and the only alternative is fabricating LedgerOffset.Begin, which is indistinguishable from a genuine ledger start: a consumer persisting it as resume state checkpoints at the beginning of the ledger and re-reads the entire stream"); + unclassified.Kind.Should().Be(UnclassifiedKind.DecodeFailure); + unclassified.RawKind.Should().BeNull(); + } + + [Fact] + public void Unclassified_without_an_offset_still_rejects_a_raw_descriptor_on_an_enumerated_kind() + { + var act = () => new ContractStreamEvent.Unclassified(null, UnclassifiedKind.DecodeFailure, "EventCase_1"); + + act.Should().Throw( + "widening Offset to a nullable must not relax the Kind and RawKind invariant: an enumerated kind carrying a raw descriptor stays a construction error"); + } + + [Fact] + public void Unclassified_without_an_offset_still_rejects_an_unknown_kind_without_a_raw_descriptor() + { + var act = () => new ContractStreamEvent.Unclassified(null, UnclassifiedKind.Unknown, null); + + act.Should().Throw( + "widening Offset to a nullable must not relax the Kind and RawKind invariant: Unknown without the transport's raw descriptor stays a construction error"); + } + [Fact] public void Unclassified_with_new_offset_preserves_kind_and_raw_descriptor() { @@ -122,7 +229,7 @@ public void UnclassifiedKind_default_is_Unknown() default(UnclassifiedKind).Should().Be(UnclassifiedKind.Unknown); } - private sealed record TestTemplate(string Owner) : ITemplate + private sealed record TestTemplate(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); public static string PackageId => "pkg"; @@ -130,6 +237,9 @@ private sealed record TestTemplate(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", new DamlText(Owner))); + + public static TestTemplate FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } } diff --git a/tests/Daml.Runtime.Tests/ContractTypesTests.Data.cs b/tests/Daml.Runtime.Tests/ContractTypesTests.Data.cs index 02b4c1c..3867e43 100644 --- a/tests/Daml.Runtime.Tests/ContractTypesTests.Data.cs +++ b/tests/Daml.Runtime.Tests/ContractTypesTests.Data.cs @@ -10,6 +10,8 @@ namespace Daml.Runtime.Tests; public partial class ContractTypesTests { + private const string LedgerKeyHash = "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA="; + [Fact] public void Contract_should_store_id_and_data() { @@ -37,7 +39,8 @@ public void Contract_FromCreatedEvent_should_decode_contract() CreateArguments: createArgs, WitnessParties: [new Party("Bob")], Signatories: [new Party("Bob")], - Observers: []); + Observers: [], + ContractKey: null); var contract = Contract.FromCreatedEvent(createdEvent, TestTemplate.FromRecord); @@ -46,6 +49,39 @@ public void Contract_FromCreatedEvent_should_decode_contract() contract.Data.Amount.Should().Be(200); } + [Fact] + public void Contract_should_declare_no_key_member_on_the_keyless_shape() + { + typeof(Contract).GetProperty("Key").Should().BeNull( + "the keyless shape exists so a template that declares no contract key stops offering " + + "a member that can never be populated; a key belongs to Contract"); + } + + [Fact] + public void Contract_FromCreatedEvent_should_read_a_keyed_event_through_the_keyless_shape() + { + var templateId = TestTemplate.TemplateId; + var createdEvent = new CreatedEvent( + EventId: "event-1", + ContractId: "contract-with-key", + TemplateId: templateId, + CreateArguments: DamlRecord.Create( + DamlField.Create("owner", new DamlParty("Bob")), + DamlField.Create("amount", new DamlInt64(200))), + WitnessParties: [new Party("Bob")], + Signatories: [new Party("Bob")], + Observers: [], + ContractKey: new ContractKey(new DamlText("savings"), templateId)); + + var contract = Contract.FromCreatedEvent(createdEvent, TestTemplate.FromRecord); + + contract.Id.Value.Should().Be("contract-with-key"); + contract.Data.Owner.Should().Be( + new Party("Bob"), + "reading a keyed contract through the keyless shape is the documented fallback when a " + + "caller does not want the key, so the event's key must not make the projection fail"); + } + [Fact] public void Contract_should_support_equality() { @@ -103,7 +139,8 @@ public void CreatedEvent_should_allow_optional_properties_null() CreateArguments: createArgs, WitnessParties: [], Signatories: [], - Observers: []); + Observers: [], + ContractKey: null); @event.ContractKey.Should().BeNull(); @event.CreatedAt.Should().BeNull(); @@ -161,6 +198,163 @@ public void ContractKey_should_allow_null_template_id() contractKey.TemplateId.Should().BeNull(); } + [Fact] + public void ContractKey_should_store_the_ledgers_key_hash() + { + var contractKey = new ContractKey( + new DamlText("my-key"), + new Identifier("pkg", "Module", "Template")) + { + KeyHash = LedgerKeyHash, + }; + + contractKey.KeyHash.Should().Be(LedgerKeyHash); + } + + [Fact] + public void ContractKey_should_default_the_key_hash_to_null() + { + var contractKey = new ContractKey(new DamlText("my-key")); + + contractKey.KeyHash.Should().BeNull(); + } + + [Fact] + public void ContractKey_should_equal_the_same_key_constructed_without_a_hash() + { + var templateId = new Identifier("pkg", "Module", "Template"); + var readOffTheWire = new ContractKey(new DamlText("my-key"), templateId) { KeyHash = LedgerKeyHash }; + var builtByTheCaller = new ContractKey(new DamlText("my-key"), templateId); + + readOffTheWire.Should().Be( + builtByTheCaller, + "the exercise-by-key path builds a key from data the caller already holds and so has no " + + "hash, and it names the same key the ledger reported — equality that split on the hash " + + "would make by-key matching fail quietly depending on where the key came from"); + readOffTheWire.GetHashCode().Should().Be( + builtByTheCaller.GetHashCode(), + "a hash code that split on KeyHash would drop the two into different buckets of a " + + "dictionary keyed by ContractKey, so a lookup would miss even though Equals agrees"); + } + + [Fact] + public void ContractKey_should_not_equal_a_key_naming_a_different_value() + { + var templateId = new Identifier("pkg", "Module", "Template"); + var savings = new ContractKey(new DamlText("savings"), templateId) { KeyHash = LedgerKeyHash }; + var current = new ContractKey(new DamlText("current"), templateId) { KeyHash = LedgerKeyHash }; + + savings.Should().NotBe( + current, + "equality is over the key the record names, so ignoring the hash must not collapse into " + + "ignoring the value as well"); + } + + [Fact] + public void ContractKey_should_not_equal_a_key_naming_a_different_template() + { + var value = new DamlText("my-key"); + var here = new ContractKey(value, new Identifier("pkg", "Module", "Template")); + var elsewhere = new ContractKey(value, new Identifier("pkg", "Module", "Other")); + + here.Should().NotBe(elsewhere); + } + + [Fact] + public void ContractKey_should_expose_the_hash_it_was_given_despite_ignoring_it_for_equality() + { + var templateId = new Identifier("pkg", "Module", "Template"); + var readOffTheWire = new ContractKey(new DamlText("my-key"), templateId) { KeyHash = LedgerKeyHash }; + + readOffTheWire.KeyHash.Should().Be( + LedgerKeyHash, + "leaving the hash out of equality must not mean discarding it — it is the value Canton " + + "indexes keyed contracts by and the reason the slot exists"); + readOffTheWire.ToString().Should().Contain( + LedgerKeyHash, + "the hash stays a visible member of the record, so a diagnostic dump still shows it"); + } + + [Fact] + public void Contract_FromCreatedEvent_should_carry_the_keys_hash() + { + var createdEvent = KeyedCreatedEvent( + new ContractKey(new DamlParty("Alice"), KeyedTestTemplate.TemplateId) + { + KeyHash = LedgerKeyHash, + }); + + var contract = Contract.FromCreatedEvent( + createdEvent, + KeyedTestTemplate.FromRecord); + + contract.Key.Hash.Should().Be( + LedgerKeyHash, + "the hash is what the ledger indexes the contract by, so a projection that rebuilds " + + "the key without it leaves the caller unable to address the contract by key"); + } + + [Fact] + public void Contract_FromCreatedEvent_should_decode_the_key_through_the_templates_witness() + { + var createdEvent = KeyedCreatedEvent( + new ContractKey(new DamlParty("Alice"), KeyedTestTemplate.TemplateId)); + + var contract = Contract.FromCreatedEvent( + createdEvent, + KeyedTestTemplate.FromRecord); + + contract.Key.Value.Should().Be( + new Party("Alice"), + "the keyed shape exists to hand back the decoded key, so a caller never repeats the " + + "cast-and-decode hop the wire-level key would force"); + } + + [Fact] + public void Contract_FromCreatedEvent_should_reject_a_keyed_event_carrying_no_key() + { + var keyless = KeyedCreatedEvent(null); + + var decoding = () => Contract.FromCreatedEvent( + keyless, + KeyedTestTemplate.FromRecord); + + decoding.Should().Throw( + "the keyed shape's key is non-nullable, so an event that carries none has to fail " + + "loudly rather than reach a caller with a key the type says cannot be absent") + .WithMessage("*carried no contract key*"); + } + + private static CreatedEvent KeyedCreatedEvent(ContractKey? key) => new( + EventId: "event-1", + ContractId: "contract-with-key", + TemplateId: KeyedTestTemplate.TemplateId, + CreateArguments: DamlRecord.Create(DamlField.Create("owner", new DamlParty("Alice"))), + WitnessParties: [new Party("Alice")], + Signatories: [new Party("Alice")], + Observers: [], + ContractKey: key); + + private sealed record KeyedTestTemplate(Party Owner) : ITemplate, IHasKey + { + public static Identifier TemplateId => new(TestPackageId, TestModuleName, nameof(KeyedTestTemplate)); + public static string PackageId => TestPackageId; + public static string PackageName => TestPackageName; + public static Version PackageVersion => TestPackageV1; + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = key => key.ToDamlValue(), + KeyDecoder = value => Party.FromDamlValue(value.As()), + }; + + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("owner", Owner.ToDamlValue())); + + public static KeyedTestTemplate FromRecord(DamlRecord record) => + new(Party.FromDamlValue(record.GetRequiredField("owner").As())); + } + [Fact] public void ContractKey_should_support_complex_key_values() { diff --git a/tests/Daml.Runtime.Tests/ContractTypesTests.TemplateInterfaces.cs b/tests/Daml.Runtime.Tests/ContractTypesTests.TemplateInterfaces.cs index fddccbb..9e50f23 100644 --- a/tests/Daml.Runtime.Tests/ContractTypesTests.TemplateInterfaces.cs +++ b/tests/Daml.Runtime.Tests/ContractTypesTests.TemplateInterfaces.cs @@ -1,6 +1,7 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; using AwesomeAssertions; @@ -45,14 +46,19 @@ public void ITemplate_FromRecord_should_deserialize_correctly() template.Amount.Should().Be(750); } - private sealed record KeyedTemplate(Party Owner, string AssetId) : ITemplate, IHasKey + private sealed record KeyedTemplate(Party Owner, string AssetId) : ITemplate, IHasKey { public static Identifier TemplateId => new(TestPackageId, TestModuleName, nameof(KeyedTemplate)); public static string PackageId => TestPackageId; public static string PackageName => TestPackageName; public static Version PackageVersion => TestPackageV1; public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public string Key => AssetId; + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = static key => new DamlText(key), + KeyDecoder = static value => value.As().Value, + }; public DamlRecord ToRecord() => DamlRecord.Create( DamlField.Create("owner", Owner.ToDamlValue()), @@ -63,24 +69,171 @@ public static KeyedTemplate FromRecord(DamlRecord record) => record.GetRequiredField("assetId").As().Value); } - [Fact] - public void IHasKey_should_return_contract_key() + private sealed record PartyKeyedTemplate(Party Steward) : ITemplate, IHasKey + { + public static Identifier TemplateId => new(TestPackageId, TestModuleName, nameof(PartyKeyedTemplate)); + public static string PackageId => TestPackageId; + public static string PackageName => TestPackageName; + public static Version PackageVersion => TestPackageV1; + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = static key => key.ToDamlValue(), + KeyDecoder = static value => Party.FromDamlValue(value.As()), + }; + + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("steward", Steward.ToDamlValue())); + + public static PartyKeyedTemplate FromRecord(DamlRecord record) => + new(Party.FromDamlValue(record.GetRequiredField("steward").As())); + } + + private sealed record KeyFieldTemplate(Party Owner, string Key) + : ITemplate, IHasKey + { + public static Identifier TemplateId => new(TestPackageId, TestModuleName, nameof(KeyFieldTemplate)); + public static string PackageId => TestPackageId; + public static string PackageName => TestPackageName; + public static Version PackageVersion => TestPackageV1; + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + static KeyDescriptor IHasKey.Key { get; } = new() + { + KeyEncoder = static key => new DamlText(key), + KeyDecoder = static value => value.As().Value, + }; + + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("owner", Owner.ToDamlValue()), + DamlField.Create("key", new DamlText(Key))); + + public static KeyFieldTemplate FromRecord(DamlRecord record) => + new(Party.FromDamlValue(record.GetRequiredField("owner").As()), + record.GetRequiredField("key").As().Value); + } + + private sealed record AccountKey(Party Owner, string Number); + + private sealed record RecordKeyedTemplate(Party Owner, string Number, long Balance) + : ITemplate, IHasKey { - var template = new KeyedTemplate(new Party("Alice"), "asset-123"); + public static Identifier TemplateId => new(TestPackageId, TestModuleName, nameof(RecordKeyedTemplate)); + public static string PackageId => TestPackageId; + public static string PackageName => TestPackageName; + public static Version PackageVersion => TestPackageV1; + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public static KeyDescriptor Key { get; } = new() + { + KeyEncoder = static key => DamlRecord.Create( + DamlField.Create("owner", key.Owner.ToDamlValue()), + DamlField.Create("number", new DamlText(key.Number))), + KeyDecoder = static value => new AccountKey( + Party.FromDamlValue(value.As().GetRequiredField("owner").As()), + value.As().GetRequiredField("number").As().Value), + }; + + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("owner", Owner.ToDamlValue()), + DamlField.Create("number", new DamlText(Number)), + DamlField.Create("balance", new DamlInt64(Balance))); - var key = template.Key; + public static RecordKeyedTemplate FromRecord(DamlRecord record) => + new(Party.FromDamlValue(record.GetRequiredField("owner").As()), + record.GetRequiredField("number").As().Value, + record.GetRequiredField("balance").As().Value); + } + + private static TKey DecodeKey(DamlValue value) + where TTemplate : ITemplate, IHasKey + { + return TTemplate.Key.KeyDecoder(value); + } + + private static TKey RoundTripKey(TKey key) + where TTemplate : ITemplate, IHasKey + { + return TTemplate.Key.KeyDecoder(TTemplate.Key.KeyEncoder(key)); + } + + private static ExerciseByKeyCommand ExerciseByKey(TKey key, ChoiceName choice) + where TTemplate : ITemplate, IHasKey + { + return new(TTemplate.TemplateId, TTemplate.Key.KeyEncoder(key), choice, DamlRecord.Create()); + } + + [Fact] + public void IHasKey_should_reach_the_key_descriptor_through_a_generic_constraint() + { + var key = DecodeKey(new DamlText("asset-123")); key.Should().Be("asset-123"); } [Fact] - public void IHasKey_should_be_covariant_with_key_type() + public void IHasKey_should_admit_a_key_type_that_is_not_a_record() + { + var key = DecodeKey(new DamlParty("Alice")); + + key.Should().Be(new Party("Alice")); + } + + [Fact] + public void IHasKey_should_reach_an_explicitly_implemented_descriptor_when_the_payload_names_a_field_Key() { - IHasKey keyedContract = new KeyedTemplate(new Party("Bob"), "asset-456"); + var template = new KeyFieldTemplate(new Party("Bob"), "asset-456"); - var key = keyedContract.Key; + template.Key.Should().Be("asset-456"); + DecodeKey(new DamlText("asset-789")).Should().Be("asset-789"); + } - key.Should().Be("asset-456"); + [Fact] + public void KeyDescriptor_should_round_trip_a_key_through_the_encoder_and_the_decoder() + { + KeyedTemplate.Key.KeyEncoder("asset-123").Should().BeOfType() + .Which.Value.Should().Be("asset-123"); + RoundTripKey("asset-123").Should().Be("asset-123"); + } + + [Fact] + public void KeyDescriptor_should_round_trip_a_record_key_through_the_encoder_and_the_decoder() + { + var key = new AccountKey(new Party("Alice"), "acc-001"); + + RecordKeyedTemplate.Key.KeyEncoder(key).As() + .GetRequiredField("number").As().Value.Should().Be("acc-001"); + RoundTripKey(key).Should().Be(key); + } + + [Fact] + public void KeyDescriptor_should_round_trip_a_bare_Party_key_through_the_encoder_and_the_decoder() + { + var key = new Party("Steward"); + + PartyKeyedTemplate.Key.KeyEncoder(key).Should().BeOfType() + .Which.Value.Should().Be("Steward"); + RoundTripKey(key).Should().Be(key); + } + + [Fact] + public void KeyDescriptor_should_round_trip_an_explicitly_implemented_key_through_the_encoder_and_the_decoder() + { + RoundTripKey("asset-456").Should().Be("asset-456"); + } + + [Fact] + public void KeyEncoder_should_give_generic_code_holding_only_a_key_a_route_to_ExerciseByKeyCommand() + { + var command = ExerciseByKey( + new AccountKey(new Party("Alice"), "acc-001"), + new ChoiceName("Transfer")); + + command.TemplateId.Should().Be(RecordKeyedTemplate.TemplateId); + command.ContractKey.As() + .GetRequiredField("owner").As().Value.Should().Be("Alice"); + RecordKeyedTemplate.Key.KeyDecoder(command.ContractKey).Number.Should().Be("acc-001"); } private interface ITestInterface : IDamlInterface @@ -116,7 +269,6 @@ private sealed record ViewedTemplate(Party Owner, decimal Amount) : ITemplate, I public static string PackageName => TestPackageName; public static Version PackageVersion => TestPackageV1; public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public AssetView View => new(Owner, Amount); public DamlRecord ToRecord() => DamlRecord.Create( DamlField.Create("owner", Owner.ToDamlValue()), @@ -128,25 +280,17 @@ public static ViewedTemplate FromRecord(DamlRecord record) => } [Fact] - public void IHasView_should_return_interface_view() + public void IHasView_carries_no_members() { - var template = new ViewedTemplate(new Party("Charlie"), 1000.50m); - - var view = template.View; - - view.Owner.Should().Be(new Party("Charlie")); - view.Amount.Should().Be(1000.50m); + typeof(IHasView<>).GetMembers().Should().BeEmpty(); } [Fact] - public void IHasView_should_be_covariant_with_view_type() + public void IHasView_links_the_implementing_type_to_its_view_type() { - IHasView viewable = new ViewedTemplate(new Party("Diana"), 2500.00m); - - var view = viewable.View; + var template = new ViewedTemplate(new Party("Diana"), 2500.00m); - view.Owner.Should().Be(new Party("Diana")); - view.Amount.Should().Be(2500.00m); + template.Should().BeAssignableTo>(); } private const string UpgradedPkgId = "upgraded-package"; diff --git a/tests/Daml.Runtime.Tests/CreatedShapeAgreementTests.cs b/tests/Daml.Runtime.Tests/CreatedShapeAgreementTests.cs new file mode 100644 index 0000000..8e9a6a6 --- /dev/null +++ b/tests/Daml.Runtime.Tests/CreatedShapeAgreementTests.cs @@ -0,0 +1,141 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Streams; +using Xunit; +using RuntimeIdentifier = Daml.Runtime.Data.Identifier; + +namespace Daml.Runtime.Tests; + +/// +/// Pins the created-contract shape that AcsSnapshotEntry<T>.Created, +/// ContractStreamEvent<T>.Created and their interface-family counterparts each +/// declare for themselves. All four carry the same created event — snapshot or live stream, +/// template or interface — so a consumer handling any of them switches on one vocabulary. Every shape compared here is read out of the +/// declaration by reflection, so renaming, retyping, reordering, adding or omitting a +/// parameter on one declaration alone fails this class instead of letting a fact reach one +/// path and not the other. +/// +public class CreatedShapeAgreementTests +{ + private const string VacuityReason = + "an empty shape would let every agreement assertion in this class pass without comparing anything"; + + private static readonly Type SnapshotCreated = typeof(AcsSnapshotEntry.Created); + private static readonly Type SubscriptionCreated = typeof(ContractStreamEvent.Created); + private static readonly Type InterfaceSnapshotCreated = + typeof(InterfaceAcsSnapshotEntry.Created); + private static readonly Type InterfaceSubscriptionCreated = + typeof(InterfaceStreamEvent.Created); + + [Fact] + public void CreatedShapeAgreement_the_snapshot_and_subscription_variants_declare_one_shape() + { + DeclaredShape(SnapshotCreated).Should().Be( + DeclaredShape(SubscriptionCreated), + "the snapshot and the subscription Created carry the same created contract, so a parameter " + + "renamed, retyped, reordered, added or dropped on one of them has to be mirrored on the other"); + } + + [Fact] + public void CreatedShapeAgreement_both_variants_carry_the_contract_key() + { + DeclaredShape(SnapshotCreated).Should().Contain( + "ContractKey? Key|", + "the key is a separate wire field the payload record cannot hold, so a created row that omits " + + "the slot leaves the key unreadable on that path no matter what the emitted contract declares"); + DeclaredShape(SubscriptionCreated).Should().Contain("ContractKey? Key|", "the live stream carries the same field"); + } + + [Fact] + public void CreatedShapeAgreement_a_variant_missing_the_key_stops_agreeing() + { + var keyless = typeof(PayloadWithoutKey); + + DeclaredShape(keyless).Should().NotBe( + DeclaredShape(SnapshotCreated), + "a declaration that drops the key has to break the comparison, otherwise the agreement " + + "assertions would hold against the very omission this class exists to catch"); + } + + [Fact] + public void CreatedShapeAgreement_the_interface_family_variants_declare_one_shape() + { + DeclaredShape(InterfaceSnapshotCreated).Should().Be( + DeclaredShape(InterfaceSubscriptionCreated), + "the interface-family snapshot and subscription Created carry the same created contract, " + + "so a parameter renamed, retyped, reordered, added or dropped on one of them has to be " + + "mirrored on the other"); + } + + [Fact] + public void CreatedShapeAgreement_the_interface_family_mirrors_the_template_family_field_for_field() + { + DeclaredShape(InterfaceSubscriptionCreated).Should().Be( + DeclaredShape(SubscriptionCreated).Replace("Probe Payload|", "ProbeView Payload|", StringComparison.Ordinal), + "the interface family is the template family with the view record in the payload slot, so " + + "the two differ in the payload type alone — any other divergence makes a consumer that " + + "handles both families switch on two vocabularies"); + } + + [Fact] + public void CreatedShapeAgreement_reads_a_non_empty_shape_from_every_declaration() + { + DeclaredShape(SnapshotCreated).Should().NotBeEmpty(VacuityReason); + DeclaredShape(SubscriptionCreated).Should().NotBeEmpty(VacuityReason); + DeclaredShape(InterfaceSnapshotCreated).Should().NotBeEmpty(VacuityReason); + DeclaredShape(InterfaceSubscriptionCreated).Should().NotBeEmpty(VacuityReason); + } + + private static string DeclaredShape(Type declaration) + { + var nullability = new NullabilityInfoContext(); + + return string.Concat(declaration.GetConstructors() + .OrderByDescending(constructor => constructor.GetParameters().Length) + .First() + .GetParameters() + .Select(parameter => + $"{parameter.ParameterType.Name}{NullableMarker(nullability, parameter)} {parameter.Name}|")); + } + + private static string NullableMarker(NullabilityInfoContext nullability, ParameterInfo parameter) => + nullability.Create(parameter).WriteState == NullabilityState.Nullable ? "?" : string.Empty; + + private sealed record PayloadWithoutKey(DamlRecord Payload); + + private sealed record Probe : ITemplate, IDamlRecord + { + public static RuntimeIdentifier TemplateId { get; } = new("pkg", "M", "Probe"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + public static Probe FromRecord(DamlRecord record) => new(); + } + + private sealed record ProbeInterface : IDamlInterface, IHasView + { + public static RuntimeIdentifier InterfaceId { get; } = new("pkg", "M", "ProbeInterface"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(InterfaceId, DamlTypeKind.Interface, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + } + + private sealed record ProbeView : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + public static ProbeView FromRecord(DamlRecord record) => new(); + } +} diff --git a/tests/Daml.Runtime.Tests/Daml.Runtime.Tests.csproj b/tests/Daml.Runtime.Tests/Daml.Runtime.Tests.csproj index 68cb812..3973a2e 100644 --- a/tests/Daml.Runtime.Tests/Daml.Runtime.Tests.csproj +++ b/tests/Daml.Runtime.Tests/Daml.Runtime.Tests.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/tests/Daml.Runtime.Tests/DamlJsonSerializerBasicTests.cs b/tests/Daml.Runtime.Tests/DamlJsonSerializerBasicTests.cs index 7d346bc..5febbc3 100644 --- a/tests/Daml.Runtime.Tests/DamlJsonSerializerBasicTests.cs +++ b/tests/Daml.Runtime.Tests/DamlJsonSerializerBasicTests.cs @@ -70,7 +70,7 @@ public void Serialize_should_handle_DamlList() var json = DamlJsonSerializer.Serialize(record); - json.Should().Contain("[1,2,3]"); + json.Should().Contain("[\"1\",\"2\",\"3\"]"); } [Fact] @@ -171,7 +171,7 @@ public void Serialize_should_handle_DamlOptional_Some() var json = DamlJsonSerializer.Serialize(record); - json.Should().Contain("\"maybeValue\":42"); + json.Should().Contain("\"maybeValue\":\"42\""); } [Fact] @@ -188,7 +188,7 @@ public void Serialize_should_handle_DamlTextMap() var json = DamlJsonSerializer.Serialize(record); json.Should().Contain("\"key1\":\"value1\""); - json.Should().Contain("\"key2\":42"); + json.Should().Contain("\"key2\":\"42\""); } [Fact] diff --git a/tests/Daml.Runtime.Tests/DamlJsonSerializerStructuralTests.cs b/tests/Daml.Runtime.Tests/DamlJsonSerializerStructuralTests.cs index 8ef4142..b9c5c08 100644 --- a/tests/Daml.Runtime.Tests/DamlJsonSerializerStructuralTests.cs +++ b/tests/Daml.Runtime.Tests/DamlJsonSerializerStructuralTests.cs @@ -43,7 +43,7 @@ public void Serialize_should_render_DamlGenMap_as_array_of_two_element_arrays() var json = DamlJsonSerializer.Serialize(record); - json.Should().Contain("\"entries\":[[\"Alice\",1],[\"Bob\",2]]"); + json.Should().Contain("\"entries\":[[\"Alice\",\"1\"],[\"Bob\",\"2\"]]"); } [Fact] @@ -223,4 +223,52 @@ public void DeserializeRecord_should_support_json_at_the_DamlLf_depth_limit() act.Should().NotThrow(); } + + [Fact] + public void Serialize_should_render_DamlOptionalChain_none_as_an_empty_array() + { + DamlJsonSerializer.Serialize(DamlOptionalChain.None).Should().Be("[]"); + } + + [Fact] + public void Serialize_should_render_a_chain_carrying_none_as_an_array_of_an_empty_array() + { + DamlJsonSerializer.Serialize(DamlOptionalChain.Some(DamlOptionalChain.None)) + .Should().Be("[[]]"); + } + + [Fact] + public void Serialize_should_render_a_chain_carrying_a_value_as_an_array_of_an_array() + { + DamlJsonSerializer.Serialize( + DamlOptionalChain.Some(DamlOptionalChain.Some(new DamlText("deep")))) + .Should().Be("""[["deep"]]"""); + } + + [Fact] + public void Serialize_should_leave_a_flat_DamlOptional_on_its_existing_encoding() + { + DamlJsonSerializer.Serialize(DamlOptional.None).Should().Be("null"); + DamlJsonSerializer.Serialize(DamlOptional.Some(new DamlText("present"))) + .Should().Be("\"present\""); + } + + [Fact] + public void Serialize_should_never_emit_a_nested_optional_encoding_the_participant_rejected() + { + var rejected = new[] { "null", "[null]", """["deep"]""" }; + + var emitted = new[] + { + DamlJsonSerializer.Serialize(DamlOptionalChain.None), + DamlJsonSerializer.Serialize(DamlOptionalChain.Some(DamlOptionalChain.None)), + DamlJsonSerializer.Serialize( + DamlOptionalChain.Some(DamlOptionalChain.Some(new DamlText("deep")))), + }; + + emitted.Should().HaveCount(3, + "a shorter array would satisfy the disjointness below without serializing anything"); + emitted.Should().NotIntersectWith(rejected, + "the participant answered HTTP 500 to each of these forms in a nested position"); + } } diff --git a/tests/Daml.Runtime.Tests/DamlJsonSerializerValueOverloadsTests.cs b/tests/Daml.Runtime.Tests/DamlJsonSerializerValueOverloadsTests.cs index 4644e75..a621073 100644 --- a/tests/Daml.Runtime.Tests/DamlJsonSerializerValueOverloadsTests.cs +++ b/tests/Daml.Runtime.Tests/DamlJsonSerializerValueOverloadsTests.cs @@ -15,7 +15,7 @@ public class DamlJsonSerializerValueOverloadsTests public static TheoryData Serialize_value_overload_cases() => new() { { "DamlText", new DamlText("hello"), "\"hello\"" }, - { "DamlInt64", new DamlInt64(42), "42" }, + { "DamlInt64", new DamlInt64(42), "\"42\"" }, { "DamlBool_true", new DamlBool(true), "true" }, { "DamlBool_false", new DamlBool(false), "false" }, { "DamlParty", new DamlParty("Alice::12345"), "\"Alice::12345\"" }, @@ -23,8 +23,8 @@ public class DamlJsonSerializerValueOverloadsTests { "DamlUnit", DamlUnit.Instance, "{}" }, { "DamlGenMap", DamlGenMap.Create( (new DamlText("k1"), new DamlInt64(1)), - (new DamlText("k2"), new DamlInt64(2))), "[[\"k1\",1],[\"k2\",2]]" }, - { "DamlOptional_Some", DamlOptional.Some(new DamlInt64(42)), "42" }, + (new DamlText("k2"), new DamlInt64(2))), "[[\"k1\",\"1\"],[\"k2\",\"2\"]]" }, + { "DamlOptional_Some", DamlOptional.Some(new DamlInt64(42)), "\"42\"" }, { "DamlOptional_None", DamlOptional.None, "null" }, }; @@ -111,7 +111,7 @@ public void Serialize_value_overload_should_handle_DamlRecord() var valueJson = DamlJsonSerializer.Serialize((DamlValue)inner); var recordJson = DamlJsonSerializer.Serialize(inner); - valueJson.Should().Be("{\"x\":1}"); + valueJson.Should().Be("{\"x\":\"1\"}"); recordJson.Should().Be(valueJson); } @@ -150,7 +150,7 @@ public void Serialize_value_overload_should_handle_DamlTextMap() var recordJson = DamlJsonSerializer.Serialize( DamlRecord.Create(DamlField.Create("m", textMap))); - valueJson.Should().Be("{\"k\":7}"); + valueJson.Should().Be("{\"k\":\"7\"}"); recordJson.Should().Be($"{{\"m\":{valueJson}}}"); } @@ -163,7 +163,7 @@ public void Serialize_value_overload_should_handle_DamlList() var recordJson = DamlJsonSerializer.Serialize( DamlRecord.Create(DamlField.Create("l", list))); - valueJson.Should().Be("[1,2,3]"); + valueJson.Should().Be("[\"1\",\"2\",\"3\"]"); recordJson.Should().Be($"{{\"l\":{valueJson}}}"); } diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderLimitsTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderLimitsTests.cs new file mode 100644 index 0000000..ca9fa4d --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderLimitsTests.cs @@ -0,0 +1,267 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Daml.Runtime.Stdlib; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderLimitsTests +{ + private const string ThreeOwnersJson = """{"owners":["a::1220ab","b::1220cd","c::1220ef"]}"""; + private static readonly Type OwnerListHolderKnownOnlyAtRuntime = typeof(OwnerListHolder); + + public sealed record OwnerListHolder([property: DamlFieldAttribute("owners")] IReadOnlyList Owners) : IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create( + "owners", + new DamlList(Owners.Select(owner => (DamlValue)owner.ToDamlValue()).ToList()))); + } + + public sealed record NestingHolder([property: DamlFieldAttribute("nested")] IReadOnlyList Nested) : IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create( + "nested", + new DamlList(Nested.Select(child => (DamlValue)child.ToRecord()).ToList()))); + } + + private const int LevelsOverflowingTheDepthBoundAtTwoDepthUnitsEach = 128; + private const int LevelsExactlyFillingTheDepthBound = 64; + + private static string NestedJson(int levels) => + string.Concat(Enumerable.Repeat("""{"nested":[""", levels)) + + string.Concat(Enumerable.Repeat("]}", levels)); + + [Fact] + public void ReadRecord_should_reject_arrays_wider_than_the_configured_limit_from_a_parsed_element() + { + using var document = JsonDocument.Parse(ThreeOwnersJson); + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord(document.RootElement, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON array length*"); + } + + [Fact] + public void ReadRecord_should_reject_arrays_wider_than_the_configured_limit_from_json_text() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord(ThreeOwnersJson, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON array length*"); + } + + [Fact] + public void ReadRecord_should_reject_arrays_wider_than_the_configured_limit_for_a_runtime_type() + { + using var document = JsonDocument.Parse(ThreeOwnersJson); + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord(document.RootElement, OwnerListHolderKnownOnlyAtRuntime, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON array length*"); + } + + [Fact] + public void ReadRecord_should_reject_arrays_wider_than_the_configured_limit_from_json_text_for_a_runtime_type() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord(ThreeOwnersJson, OwnerListHolderKnownOnlyAtRuntime, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON array length*"); + } + + [Fact] + public void ReadRecord_should_decode_an_array_exactly_at_the_configured_limit() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 3); + + var record = DamlLfJsonReader.ReadRecord(ThreeOwnersJson, limits); + + record.GetRequiredField("owners").Should().BeOfType() + .Which.Values.Should().HaveCount(3); + } + + [Fact] + public void ReadRecord_should_reject_json_text_larger_than_the_configured_limit() + { + var limits = new DamlJsonDeserializationLimits(MaxInputCharacters: ThreeOwnersJson.Length - 1); + + var act = () => DamlLfJsonReader.ReadRecord(ThreeOwnersJson, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON input size*"); + } + + [Fact] + public void ReadRecord_should_reject_json_text_larger_than_the_configured_limit_for_a_runtime_type() + { + var limits = new DamlJsonDeserializationLimits(MaxInputCharacters: ThreeOwnersJson.Length - 1); + + var act = () => DamlLfJsonReader.ReadRecord(ThreeOwnersJson, OwnerListHolderKnownOnlyAtRuntime, limits); + + act.Should().Throw().WithMessage("*maximum supported JSON input size*"); + } + + [Fact] + public void ReadRecord_should_reject_value_nesting_beyond_the_supported_depth() + { + var act = () => DamlLfJsonReader.ReadRecord(NestedJson(LevelsOverflowingTheDepthBoundAtTwoDepthUnitsEach)); + + act.Should().Throw().WithMessage("*maximum supported depth*"); + } + + [Fact] + public void ReadRecord_should_decode_value_nesting_exactly_at_the_supported_depth() + { + var record = DamlLfJsonReader.ReadRecord(NestedJson(LevelsExactlyFillingTheDepthBound)); + + record.GetRequiredField("nested").Should().BeOfType(); + } + + [Fact] + public void ReadRecord_should_reject_value_nesting_one_level_beyond_the_supported_depth() + { + var act = () => DamlLfJsonReader.ReadRecord(NestedJson(LevelsExactlyFillingTheDepthBound + 1)); + + act.Should().Throw().WithMessage("*maximum supported depth*"); + } + + [Fact] + public void ReadRecord_should_throw_JsonException_for_duplicate_json_properties() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"owners":["a::1220ab"],"owners":["b::1220cd"]}"""); + + act.Should().Throw(); + } + + public sealed record AttributeMapHolder( + [property: DamlFieldAttribute("attributes")] IReadOnlyDictionary Attributes) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "attributes", + new DamlTextMap(Attributes.ToDictionary(entry => entry.Key, entry => (DamlValue)new DamlText(entry.Value))))); + } + + [Fact] + public void ReadRecord_should_reject_text_maps_wider_than_the_configured_limit() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord( + """{"attributes":{"a":"1","b":"2","c":"3"}}""", limits); + + act.Should().Throw() + .WithMessage("JSON object property count 3 exceeds the maximum supported Daml TextMap entry count of 2"); + } + + [Fact] + public void ReadRecord_should_decode_a_text_map_exactly_at_the_configured_limit() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 3); + + var record = DamlLfJsonReader.ReadRecord( + """{"attributes":{"a":"1","b":"2","c":"3"}}""", limits); + + record.GetRequiredField("attributes").Should().BeOfType() + .Which.Values.Should().HaveCount(3); + } + + public sealed record GenMapBalanceHolder( + [property: DamlFieldAttribute("balances")] IReadOnlyDictionary Balances) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "balances", + new DamlGenMap(Balances + .Select(entry => ((DamlValue)entry.Key.ToDamlValue(), (DamlValue)new DamlInt64(entry.Value))) + .ToList()))); + } + + [Fact] + public void ReadRecord_should_reject_gen_maps_wider_than_the_configured_limit() + { + var limits = new DamlJsonDeserializationLimits(MaxArrayElements: 2); + + var act = () => DamlLfJsonReader.ReadRecord( + """{"balances":[["a::1220ab","1"],["b::1220cd","2"],["c::1220ef","3"]]}""", limits); + + act.Should().Throw().WithMessage("*maximum supported JSON array length*"); + } + + public sealed record NestedMapHolder( + [property: DamlFieldAttribute("nested")] IReadOnlyDictionary Nested) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "nested", + new DamlTextMap(Nested.ToDictionary(entry => entry.Key, entry => (DamlValue)entry.Value.ToRecord())))); + } + + private static string NestedMapJson(int levels) => + string.Concat(Enumerable.Repeat("""{"nested":{"a":""", levels)) + + """{"nested":{}}""" + + string.Concat(Enumerable.Repeat("}}", levels)); + + [Fact] + public void ReadRecord_should_reject_value_nesting_beyond_the_supported_depth_through_a_text_map() + { + var act = () => DamlLfJsonReader.ReadRecord(NestedMapJson(LevelsOverflowingTheDepthBoundAtTwoDepthUnitsEach)); + + act.Should().Throw().WithMessage("*maximum supported depth*"); + } + + public sealed record NestedStdlibMapHolder( + [property: DamlFieldAttribute("nested")] Map Nested) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "nested", + Nested.ToRecord(key => new DamlText(key), child => child.ToRecord()))); + } + + private const int StdlibMapLevelsExactlyFillingTheDepthBound = 42; + + private static string NestedStdlibMapJson(int levels) => + string.Concat(Enumerable.Repeat("""{"nested":{"map":[["a",""", levels)) + + """{"nested":{"map":[]}}""" + + string.Concat(Enumerable.Repeat("]]}}", levels)); + + [Fact] + public void ReadRecord_should_decode_stdlib_map_nesting_exactly_at_the_supported_depth() + { + var record = DamlLfJsonReader.ReadRecord( + NestedStdlibMapJson(StdlibMapLevelsExactlyFillingTheDepthBound)); + + record.GetRequiredField("nested").Should().BeOfType(); + } + + [Fact] + public void ReadRecord_should_reject_stdlib_map_nesting_one_level_beyond_the_supported_depth() + { + var act = () => DamlLfJsonReader.ReadRecord( + NestedStdlibMapJson(StdlibMapLevelsExactlyFillingTheDepthBound + 1)); + + act.Should().Throw().WithMessage("*maximum supported depth*"); + } + + [Fact] + public void ReadRecord_should_decode_a_document_over_the_size_limit_when_the_caller_already_parsed_it() + { + using var document = JsonDocument.Parse(ThreeOwnersJson); + var limits = new DamlJsonDeserializationLimits(MaxInputCharacters: 1); + + var record = DamlLfJsonReader.ReadRecord(document.RootElement, limits); + + record.GetRequiredField("owners").Should().BeOfType() + .Which.Values.Should().Equal( + new DamlParty("a::1220ab"), + new DamlParty("b::1220cd"), + new DamlParty("c::1220ef")); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderRepeatedDecodeTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderRepeatedDecodeTests.cs new file mode 100644 index 0000000..ff4a1d8 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderRepeatedDecodeTests.cs @@ -0,0 +1,94 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderRepeatedDecodeTests +{ + public sealed record GenericBox( + [property: DamlFieldAttribute("content")] TContent Content) : IDamlRecord + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + [Fact] + public void ReadRecord_should_decode_each_instantiation_of_a_generic_record_by_its_own_field_shape() + { + var countBox = DamlLfJsonReader.ReadRecord>("""{"content":"42"}"""); + var flagBox = DamlLfJsonReader.ReadRecord>("""{"content":true}"""); + + countBox.GetRequiredField("content").Should().BeOfType().Which.Value.Should().Be(42L); + flagBox.GetRequiredField("content").Should().BeOfType().Which.Value.Should().BeTrue(); + } + + public sealed record ScoredProfile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("score")] long Score) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("nickname", new DamlText(Nickname)), + DamlField.Create("score", new DamlInt64(Score))); + } + + [Fact] + public void ReadRecord_should_produce_equal_records_across_repeated_decodes_of_one_type() + { + const string json = """{"nickname":"nick","score":"7"}"""; + + var first = DamlLfJsonReader.ReadRecord(json); + var second = DamlLfJsonReader.ReadRecord(json); + + second.Should().Be(first); + second.Should().Be(new ScoredProfile("nick", 7L).ToRecord()); + } + + [Fact] + public void ReadRecord_should_order_fields_by_declaration_rather_than_by_json_property_order() + { + var record = DamlLfJsonReader.ReadRecord("""{"score":"7","nickname":"nick"}"""); + + record.Should().Be(new ScoredProfile("nick", 7L).ToRecord()); + } + + [Fact] + public void ReadRecord_should_still_refuse_a_missing_field_once_the_type_has_been_decoded_before() + { + DamlLfJsonReader.ReadRecord("""{"nickname":"nick","score":"7"}"""); + + var act = () => DamlLfJsonReader.ReadRecord("""{"nickname":"nick"}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'ScoredProfile.score' is missing from the JSON object"); + } + + public sealed record RaceProfile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("score")] long Score) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("nickname", new DamlText(Nickname)), + DamlField.Create("score", new DamlInt64(Score))); + } + + [Fact] + public void ReadRecord_should_decode_one_type_concurrently_from_its_first_touch_onwards() + { + const string json = """{"nickname":"racer","score":"9"}"""; + var expected = new RaceProfile("racer", 9L).ToRecord(); + + var records = Enumerable.Range(0, 64) + .AsParallel() + .WithDegreeOfParallelism(8) + .Select(_ => DamlLfJsonReader.ReadRecord(json)) + .ToList(); + + records.Should().AllSatisfy(record => record.Should().Be(expected)); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderScalarTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderScalarTests.cs new file mode 100644 index 0000000..88c79f5 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderScalarTests.cs @@ -0,0 +1,286 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderScalarTests +{ + public sealed record TextHolder([property: DamlFieldAttribute("name")] string Name) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("name", new DamlText(Name))); + } + + [Fact] + public void ReadRecord_should_decode_a_text_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"name":"hello"}"""); + + record.GetRequiredField("name").Should().BeOfType().Which.Value.Should().Be("hello"); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_text_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"name":""}"""); + + record.GetRequiredField("name").Should().BeOfType().Which.Value.Should().Be(""); + } + + public sealed record FlagHolder([property: DamlFieldAttribute("flag")] bool Flag) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("flag", new DamlBool(Flag))); + } + + [Fact] + public void ReadRecord_should_decode_a_bool_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"flag":true}"""); + + record.GetRequiredField("flag").Should().BeOfType().Which.Value.Should().BeTrue(); + } + + [Fact] + public void ReadRecord_should_decode_a_false_bool_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"flag":false}"""); + + record.GetRequiredField("flag").Should().BeOfType().Which.Value.Should().BeFalse(); + } + + [Fact] + public void ReadRecord_should_reject_a_bool_field_encoded_as_a_json_number() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"flag":1}"""); + + act.Should().Throw().WithMessage("Expected JSON boolean at 'FlagHolder.flag' but found Number"); + } + + [Fact] + public void ReadRecord_should_name_a_json_boolean_when_rejecting_a_bool_field_encoded_as_a_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"flag":"true"}"""); + + act.Should().Throw() + .WithMessage("Expected JSON boolean at 'FlagHolder.flag' but found String"); + } + + public sealed record CountHolder([property: DamlFieldAttribute("count")] long Count) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("count", new DamlInt64(Count))); + } + + [Fact] + public void ReadRecord_should_decode_a_positive_int64_field_from_its_wire_string_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"count":"42"}"""); + + record.GetRequiredField("count").Should().BeOfType().Which.Value.Should().Be(42L); + } + + [Fact] + public void ReadRecord_should_decode_a_negative_int64_field_from_its_wire_string_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"count":"-1"}"""); + + record.GetRequiredField("count").Should().BeOfType().Which.Value.Should().Be(-1L); + } + + [Fact] + public void ReadRecord_should_reject_an_int64_field_encoded_as_a_json_number_instead_of_the_observed_wire_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"count":42}"""); + + act.Should().Throw().WithMessage("Expected JSON String at 'CountHolder.count' but found Number"); + } + + [Fact] + public void ReadRecord_should_reject_a_malformed_int64_field() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"count":"not-a-number"}"""); + + act.Should().Throw() + .WithMessage("Value 'not-a-number' at 'CountHolder.count' is not a valid Daml Int64"); + } + + [Fact] + public void ReadRecord_should_decode_a_zero_int64_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"count":"0"}"""); + + record.GetRequiredField("count").Should().BeOfType().Which.Value.Should().Be(0L); + } + + [Fact] + public void ReadRecord_should_reject_an_int64_field_with_a_leading_plus_sign() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"count":"+42"}"""); + + act.Should().Throw() + .WithMessage("Value '+42' at 'CountHolder.count' is not a valid Daml Int64"); + } + + [Fact] + public void ReadRecord_should_reject_an_int64_field_with_leading_zeroes() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"count":"007"}"""); + + act.Should().Throw() + .WithMessage("Value '007' at 'CountHolder.count' is not a valid Daml Int64"); + } + + [Fact] + public void ReadRecord_should_echo_only_a_bounded_prefix_of_an_oversized_malformed_value() + { + var oversizedValue = new string('9', 5_000); + + var act = () => DamlLfJsonReader.ReadRecord($$"""{"count":"{{oversizedValue}}"}"""); + + act.Should().Throw() + .WithMessage($"Value '{new string('9', 64)}…' at 'CountHolder.count' is not a valid Daml Int64"); + } + + [Fact] + public void ReadRecord_should_not_split_a_surrogate_pair_when_eliding_an_oversized_malformed_value() + { + var surrogateStraddlingValue = new string('9', 63) + "😀😀"; + + var act = () => DamlLfJsonReader.ReadRecord($$"""{"count":"{{surrogateStraddlingValue}}"}"""); + + act.Should().Throw() + .WithMessage($"Value '{new string('9', 63)}…' at 'CountHolder.count' is not a valid Daml Int64"); + } + + public sealed record AmountHolder([property: DamlFieldAttribute("amount")] decimal Amount) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("amount", new DamlNumeric(Amount))); + } + + [Fact] + public void ReadRecord_should_decode_a_numeric_field_at_scale_ten() + { + var record = DamlLfJsonReader.ReadRecord("""{"amount":"42.5000000000"}"""); + + record.GetRequiredField("amount").Should().BeOfType() + .Which.Value.Should().Be(42.5m); + } + + [Fact] + public void ReadRecord_should_decode_a_numeric_field_at_scale_two() + { + var record = DamlLfJsonReader.ReadRecord("""{"amount":"1.25"}"""); + + record.GetRequiredField("amount").Should().BeOfType() + .Which.Value.Should().Be(1.25m); + } + + [Fact] + public void ReadRecord_should_reject_a_malformed_numeric_field() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"amount":"not-a-number"}"""); + + act.Should().Throw(); + } + + public sealed record IssuedHolder([property: DamlFieldAttribute("issued")] DateOnly Issued) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("issued", new DamlDate(Issued))); + } + + [Fact] + public void ReadRecord_should_decode_a_date_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"issued":"2026-07-31"}"""); + + record.GetRequiredField("issued").Should().BeOfType() + .Which.Value.Should().Be(new DateOnly(2026, 7, 31)); + } + + [Fact] + public void ReadRecord_should_reject_a_malformed_date_field() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"issued":"31-07-2026"}"""); + + act.Should().Throw(); + } + + [Fact] + public void ReadRecord_should_decode_a_timestamp_field_with_a_microsecond_fraction() + { + var record = DamlLfJsonReader.ReadRecord("""{"recordedAt":"2026-07-31T12:34:56.123456Z"}"""); + + var expected = new DateTimeOffset(2026, 7, 31, 12, 34, 56, TimeSpan.Zero).AddTicks(1_234_560); + record.GetRequiredField("recordedAt").Should().BeOfType() + .Which.Value.Should().Be(expected); + } + + [Fact] + public void ReadRecord_should_decode_a_timestamp_field_without_a_fraction() + { + var record = DamlLfJsonReader.ReadRecord("""{"recordedAt":"2026-07-31T12:34:56Z"}"""); + + var expected = new DateTimeOffset(2026, 7, 31, 12, 34, 56, TimeSpan.Zero); + record.GetRequiredField("recordedAt").Should().BeOfType() + .Which.Value.Should().Be(expected); + } + + [Fact] + public void ReadRecord_should_decode_a_timestamp_field_with_a_millisecond_fraction() + { + var record = DamlLfJsonReader.ReadRecord("""{"recordedAt":"2023-06-15T12:30:45.123Z"}"""); + + var expected = new DateTimeOffset(2023, 6, 15, 12, 30, 45, TimeSpan.Zero).AddTicks(1_230_000); + record.GetRequiredField("recordedAt").Should().BeOfType() + .Which.Value.Should().Be(expected); + } + + [Fact] + public void ReadRecord_should_reject_a_malformed_timestamp_field() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"recordedAt":"not-a-timestamp"}"""); + + act.Should().Throw(); + } + + public sealed record ReferencedTemplate(long Placeholder) : ITemplate + { + public static Identifier TemplateId => new("test-package-id", "Test.Module", nameof(ReferencedTemplate)); + public static string PackageId => "test-package-id"; + public static string PackageName => "test-package-name"; + public static Version PackageVersion => new(1, 0, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("placeholder", new DamlInt64(Placeholder))); + } + + public sealed record ReferenceHolder([property: DamlFieldAttribute("reference")] ContractId Reference) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("reference", new DamlContractId(Reference.Value))); + } + + private const string ReferenceContractId = + "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612"; + + [Fact] + public void ReadRecord_should_decode_a_contract_id_field() + { + var record = DamlLfJsonReader.ReadRecord($$"""{"reference":"{{ReferenceContractId}}"}"""); + + record.GetRequiredField("reference").Should().BeOfType() + .Which.Value.Should().Be(ReferenceContractId); + } + + [Fact] + public void ReadRecord_should_reject_a_contract_id_field_encoded_as_a_json_number() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"reference":123}"""); + + act.Should().Throw().WithMessage("Expected JSON String at 'ReferenceHolder.reference' but found Number"); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderStdlibGenericsTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderStdlibGenericsTests.cs new file mode 100644 index 0000000..2f3ad91 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderStdlibGenericsTests.cs @@ -0,0 +1,916 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Daml.Runtime.Stdlib; +using Daml.Runtime.Streams; +using Xunit; +using RuntimeIdentifier = Daml.Runtime.Data.Identifier; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderStdlibGenericsTests +{ + public sealed record PairHolder( + [property: DamlFieldAttribute("pair")] Tuple2 Pair) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "pair", + Pair.ToRecord(count => new DamlInt64(count), label => new DamlText(label)))); + } + + [Fact] + public void ReadRecord_should_decode_a_tuple2_field_from_its_wire_record_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"pair":{"_1":"42","_2":"gold"}}"""); + + record.GetRequiredField("pair").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlInt64(42L)), + new DamlField("_2", new DamlText("gold"))); + } + + [Fact] + public void ReadRecord_should_leave_a_decoded_tuple2_without_a_record_id() + { + var record = DamlLfJsonReader.ReadRecord("""{"pair":{"_1":"42","_2":"gold"}}"""); + + record.GetRequiredField("pair").Should().BeOfType().Which.RecordId.Should().BeNull(); + } + + [Fact] + public void ReadRecord_should_reject_a_tuple2_field_missing_a_component() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"pair":{"_1":"42"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'PairHolder.pair._2' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_tuple2_field_encoded_as_a_json_array() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"pair":["42","gold"]}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'PairHolder.pair' but found Array"); + } + + [Fact] + public void ReadRecord_should_report_the_component_path_when_a_tuple2_component_has_the_wrong_wire_shape() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"pair":{"_1":42,"_2":"gold"}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'PairHolder.pair._1' but found Number"); + } + + public sealed record TripleHolder( + [property: DamlFieldAttribute("triple")] Tuple3 Triple) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "triple", + Triple.ToRecord( + count => new DamlInt64(count), + label => new DamlText(label), + active => new DamlBool(active)))); + } + + [Fact] + public void ReadRecord_should_decode_a_tuple3_field_from_its_wire_record_form() + { + var record = DamlLfJsonReader.ReadRecord( + """{"triple":{"_1":"42","_2":"gold","_3":true}}"""); + + record.GetRequiredField("triple").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlInt64(42L)), + new DamlField("_2", new DamlText("gold")), + new DamlField("_3", new DamlBool(true))); + } + + [Fact] + public void ReadRecord_should_reject_a_tuple3_field_missing_its_last_component() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"triple":{"_1":"42","_2":"gold"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'TripleHolder.triple._3' is missing from the JSON object"); + } + + public sealed record OptionalPairHolder( + [property: DamlFieldAttribute("pair")] Tuple2> Pair) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "pair", + Pair.ToRecord( + count => new DamlInt64(count), + label => label.ToValue(text => new DamlText(text))))); + } + + [Fact] + public void ReadRecord_should_decode_a_present_optional_tuple2_component_from_its_bare_wire_value() + { + var record = DamlLfJsonReader.ReadRecord("""{"pair":{"_1":"42","_2":"gold"}}"""); + + record.GetRequiredField("pair").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlInt64(42L)), + new DamlField("_2", DamlOptional.Some(new DamlText("gold")))); + } + + [Fact] + public void ReadRecord_should_decode_an_absent_optional_tuple2_component_from_json_null() + { + var record = DamlLfJsonReader.ReadRecord("""{"pair":{"_1":"42","_2":null}}"""); + + record.GetRequiredField("pair").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlInt64(42L)), + new DamlField("_2", DamlOptional.None)); + } + + public sealed record Profile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("level")] long Level) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("nickname", new DamlText(Nickname)), + DamlField.Create("level", new DamlInt64(Level))); + } + + public sealed record ProfilePairHolder( + [property: DamlFieldAttribute("pair")] Tuple2 Pair) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "pair", + Pair.ToRecord(profile => profile.ToRecord(), label => new DamlText(label)))); + } + + [Fact] + public void ReadRecord_should_decode_a_generated_record_carried_by_a_tuple2_component() + { + var record = DamlLfJsonReader.ReadRecord( + """{"pair":{"_1":{"nickname":"nick","level":"3"},"_2":"gold"}}"""); + + record.GetRequiredField("pair").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", DamlRecord.Create( + DamlField.Create("nickname", new DamlText("nick")), + DamlField.Create("level", new DamlInt64(3L)))), + new DamlField("_2", new DamlText("gold"))); + } + + public sealed record PairListHolder( + [property: DamlFieldAttribute("pairs")] IReadOnlyList> Pairs) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "pairs", + new DamlList(Pairs + .Select(pair => (DamlValue)pair.ToRecord(count => new DamlInt64(count), label => new DamlText(label))) + .ToList()))); + } + + [Fact] + public void ReadRecord_should_decode_tuple2_elements_nested_inside_a_list() + { + var record = DamlLfJsonReader.ReadRecord( + """{"pairs":[{"_1":"1","_2":"a"},{"_1":"2","_2":"b"}]}"""); + + record.GetRequiredField("pairs").Should().BeOfType().Which.Values.Should().Equal( + DamlRecord.Create(new DamlField("_1", new DamlInt64(1L)), new DamlField("_2", new DamlText("a"))), + DamlRecord.Create(new DamlField("_1", new DamlInt64(2L)), new DamlField("_2", new DamlText("b")))); + } + + public sealed record ChoiceHolder( + [property: DamlFieldAttribute("choice")] Either Choice) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "choice", + Choice.ToValue(label => new DamlText(label), count => new DamlInt64(count)))); + } + + [Fact] + public void ReadRecord_should_decode_an_either_left_arm_from_its_wire_variant_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"choice":{"tag":"Left","value":"gold"}}"""); + + var variant = record.GetRequiredField("choice").Should().BeOfType().Which; + variant.Constructor.Should().Be("Left"); + variant.Value.Should().Be(new DamlText("gold")); + } + + [Fact] + public void ReadRecord_should_decode_an_either_right_arm_from_its_wire_variant_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"choice":{"tag":"Right","value":"42"}}"""); + + var variant = record.GetRequiredField("choice").Should().BeOfType().Which; + variant.Constructor.Should().Be("Right"); + variant.Value.Should().Be(new DamlInt64(42L)); + } + + [Fact] + public void ReadRecord_should_reject_an_unknown_either_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"choice":{"tag":"Middle","value":"42"}}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml variant constructor 'Middle' at 'ChoiceHolder.choice'; " + + "expected one of Left, Right"); + } + + [Fact] + public void ReadRecord_should_reject_an_either_field_without_a_tag() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"choice":{"value":"gold"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml variant field 'ChoiceHolder.choice.tag' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_an_either_field_missing_its_value() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"choice":{"tag":"Left"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml variant field 'ChoiceHolder.choice.value' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_an_either_field_encoded_as_a_bare_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"choice":"Left"}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'ChoiceHolder.choice' but found String"); + } + + public sealed record EitherPairHolder( + [property: DamlFieldAttribute("choice")] Either, string> Choice) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "choice", + Choice.ToValue( + pair => pair.ToRecord(count => new DamlInt64(count), label => new DamlText(label)), + label => new DamlText(label)))); + } + + [Fact] + public void ReadRecord_should_decode_a_tuple2_carried_by_an_either_arm() + { + var record = DamlLfJsonReader.ReadRecord( + """{"choice":{"tag":"Left","value":{"_1":"7","_2":"gold"}}}"""); + + var variant = record.GetRequiredField("choice").Should().BeOfType().Which; + variant.Constructor.Should().Be("Left"); + variant.Value.Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlInt64(7L)), + new DamlField("_2", new DamlText("gold"))); + } + + public sealed record OptionalChoiceHolder( + [property: DamlFieldAttribute("choice")] Either> Choice) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "choice", + Choice.ToValue( + label => new DamlText(label), + note => note.ToValue(text => new DamlText(text))))); + } + + [Fact] + public void ReadRecord_should_decode_a_present_optional_carried_by_an_either_arm() + { + var record = DamlLfJsonReader.ReadRecord( + """{"choice":{"tag":"Right","value":"gold"}}"""); + + var variant = record.GetRequiredField("choice").Should().BeOfType().Which; + variant.Constructor.Should().Be("Right"); + variant.Value.Should().Be(DamlOptional.Some(new DamlText("gold"))); + } + + [Fact] + public void ReadRecord_should_decode_an_absent_optional_carried_by_an_either_arm() + { + var record = DamlLfJsonReader.ReadRecord( + """{"choice":{"tag":"Right","value":null}}"""); + + var variant = record.GetRequiredField("choice").Should().BeOfType().Which; + variant.Constructor.Should().Be("Right"); + variant.Value.Should().Be(DamlOptional.None); + } + + public sealed record EitherUnitHolder( + [property: DamlFieldAttribute("outcome")] Either Outcome) : IDamlRecord + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + [Fact] + public void ReadRecord_should_decode_an_either_left_arm_carrying_daml_unit() + { + var record = DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":"Left","value":{}}}"""); + + var variant = record.GetRequiredField("outcome").Should().BeOfType().Which; + variant.Constructor.Should().Be("Left"); + variant.Value.Should().Be(DamlUnit.Instance); + } + + public abstract record Result + { + public sealed record Ok(TValue Value) : Result + { + public string Tag => "Ok"; + } + } + + public sealed record ResultHolder( + [property: DamlFieldAttribute("result")] Result Outcome) : IDamlRecord + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + [Fact] + public void ReadRecord_should_still_refuse_a_generic_variant_outside_the_stdlib_types() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"result":{"tag":"Ok","value":"gold"}}"""); + + act.Should().Throw() + .WithMessage("*at 'ResultHolder.result' lies outside the Daml type mapping*"); + } + + public sealed record Box( + [property: DamlFieldAttribute("item")] TItem Item) : IDamlRecord + where TItem : notnull + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + public sealed record AnnotatedBox( + [property: DamlFieldAttribute("item")] TItem Item, + [property: DamlFieldAttribute("note")] string? Note) : IDamlRecord + where TItem : notnull + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + public sealed record UnconstrainedBox( + [property: DamlFieldAttribute("item")] TItem Item) : IDamlRecord + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + [Fact] + public void ReadRecord_should_read_a_notnull_type_parameter_slot_as_required_at_a_reference_type_instantiation() + { + var record = DamlLfJsonReader.ReadRecord>("""{"item":"gold"}"""); + + record.GetRequiredField("item").Should().Be(new DamlText("gold")); + } + + [Fact] + public void ReadRecord_should_read_a_notnull_type_parameter_slot_as_required_at_a_value_type_instantiation() + { + var record = DamlLfJsonReader.ReadRecord>("""{"item":"42"}"""); + + record.GetRequiredField("item").Should().Be(new DamlInt64(42L)); + } + + [Fact] + public void ReadRecord_should_keep_an_annotated_optional_slot_optional_beside_a_notnull_type_parameter() + { + var record = DamlLfJsonReader.ReadRecord>("""{"item":"gold","note":null}"""); + + record.GetRequiredField("item").Should().Be(new DamlText("gold")); + record.GetRequiredField("note").Should().Be(DamlOptional.None); + } + + [Fact] + public void ReadRecord_should_wrap_the_unconstrained_type_parameter_slot_the_emitter_no_longer_produces() + { + var record = DamlLfJsonReader.ReadRecord>("""{"item":"gold"}"""); + + record.GetRequiredField("item").Should().Be( + DamlOptional.Some(new DamlText("gold")), + "an unconstrained type parameter reports NullabilityState.Nullable at every reference-type " + + "instantiation, and inventing that Optional is exactly what the emitted notnull constraint prevents"); + } + + [Fact] + public void ReadRecord_should_reject_a_null_in_a_notnull_type_parameter_slot_that_an_unconstrained_slot_absorbs() + { + var act = () => DamlLfJsonReader.ReadRecord>("""{"item":null}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'Box`1.item' but found Null"); + + DamlLfJsonReader.ReadRecord>("""{"item":null}""") + .GetRequiredField("item").Should().Be( + DamlOptional.None, + "the very same payload decodes to an absent Optional once the type parameter loses its " + + "notnull constraint, so the rejection above is caused by the constraint and not by the payload"); + } + + public sealed record WrappedOptionalHolder( + [property: DamlFieldAttribute("note")] Optional Note) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "note", + Note.ToValue(value => new DamlText(value)))); + } + + [Fact] + public void ReadRecord_should_decode_a_wrapped_optional_field_carrying_a_value() + { + var record = DamlLfJsonReader.ReadRecord("""{"note":"deep"}"""); + + record.GetRequiredField("note").Should().Be(DamlOptional.Some(new DamlText("deep"))); + } + + [Fact] + public void ReadRecord_should_decode_a_wrapped_optional_field_carrying_nothing() + { + var record = DamlLfJsonReader.ReadRecord("""{"note":null}"""); + + record.GetRequiredField("note").Should().Be(DamlOptional.None); + } + + [Fact] + public void ReadRecord_should_round_trip_a_wrapped_optional_field_through_the_generated_shape() + { + var carried = DamlLfJsonReader.ReadRecord("""{"note":"deep"}"""); + var absent = DamlLfJsonReader.ReadRecord("""{"note":null}"""); + + Optional.FromValue(carried.GetRequiredField("note"), v => ((DamlText)v).Value) + .Should().Be(new Optional.Some("deep")); + Optional.FromValue(absent.GetRequiredField("note"), v => ((DamlText)v).Value) + .Should().Be(new Optional.None()); + } + + public sealed record TagSetHolder( + [property: DamlFieldAttribute("tags")] Set Tags) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tags", + Tags.ToRecord(tag => new DamlText(tag)))); + } + + private const string TwoTagSetJson = """{"tags":{"map":[["a",{}],["b",{}]]}}"""; + + [Fact] + public void ReadRecord_should_decode_a_set_field_from_its_wire_record_form() + { + var record = DamlLfJsonReader.ReadRecord(TwoTagSetJson); + + record.GetRequiredField("tags").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("map", DamlGenMap.Create( + (new DamlText("a"), DamlUnit.Instance), + (new DamlText("b"), DamlUnit.Instance)))); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_set_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"tags":{"map":[]}}"""); + + record.GetRequiredField("tags").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("map", DamlGenMap.Create())); + } + + [Fact] + public void ReadRecord_should_hand_a_decoded_set_field_to_the_stdlib_shape() + { + var record = DamlLfJsonReader.ReadRecord(TwoTagSetJson); + + Set.FromRecord((DamlRecord)record.GetRequiredField("tags"), value => ((DamlText)value).Value) + .Elements.Should().BeEquivalentTo(["a", "b"]); + } + + [Fact] + public void ReadRecord_should_reject_a_set_field_missing_its_map() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tags":{}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'TagSetHolder.tags.map' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_set_field_encoded_as_a_bare_entry_array() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tags":[["a",{}]]}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'TagSetHolder.tags' but found Array"); + } + + [Fact] + public void ReadRecord_should_reject_a_set_whose_map_is_not_an_entry_array() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tags":{"map":{"a":{}}}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Array at 'TagSetHolder.tags.map' but found Object"); + } + + [Fact] + public void ReadRecord_should_reject_a_set_carrying_the_same_element_twice() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tags":{"map":[["a",{}],["a",{}]]}}"""); + + act.Should().Throw() + .WithMessage("Duplicate key at 'TagSetHolder.tags.map[1]' in a Daml Set"); + } + + [Fact] + public void ReadRecord_should_reject_a_set_whose_element_carries_a_value_other_than_unit() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tags":{"map":[["a","b"]]}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'TagSetHolder.tags.map[0].value' but found String"); + } + + [Fact] + public void ReadRecord_should_decode_a_set_field_into_the_record_the_stdlib_shape_writes() + { + var decoded = DamlLfJsonReader.ReadRecord(TwoTagSetJson) + .GetRequiredField("tags").Should().BeOfType().Which; + + decoded.RecordId.Should().BeNull(); + decoded.Should().Be(new Set(["a", "b"]).ToRecord(tag => new DamlText(tag))); + } + + public sealed record ProfileSetHolder( + [property: DamlFieldAttribute("profiles")] Set Profiles) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "profiles", + Profiles.ToRecord(profile => profile.ToRecord()))); + } + + [Fact] + public void ReadRecord_should_decode_a_generated_record_carried_by_a_set_element() + { + var record = DamlLfJsonReader.ReadRecord( + """{"profiles":{"map":[[{"nickname":"nick","level":"3"},{}]]}}"""); + + var entry = record.GetRequiredField("profiles").Should().BeOfType() + .Which.GetRequiredField("map").Should().BeOfType() + .Which.Entries.Should().ContainSingle().Which; + entry.Key.Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("nickname", new DamlText("nick")), + new DamlField("level", new DamlInt64(3L))); + entry.Value.Should().Be(DamlUnit.Instance); + } + + public sealed record HistoryHolder( + [property: DamlFieldAttribute("history")] NonEmpty History) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "history", + History.ToRecord(entry => new DamlText(entry)))); + } + + private const string HeadAndTailHistoryJson = """{"history":{"hd":"a","tl":["b","c"]}}"""; + + [Fact] + public void ReadRecord_should_decode_a_non_empty_field_from_its_wire_record_form() + { + var record = DamlLfJsonReader.ReadRecord(HeadAndTailHistoryJson); + + record.GetRequiredField("history").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("hd", new DamlText("a")), + new DamlField("tl", new DamlList([new DamlText("b"), new DamlText("c")]))); + } + + [Fact] + public void ReadRecord_should_decode_a_non_empty_field_carrying_only_a_head() + { + var record = DamlLfJsonReader.ReadRecord("""{"history":{"hd":"a","tl":[]}}"""); + + record.GetRequiredField("history").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("hd", new DamlText("a")), + new DamlField("tl", new DamlList([]))); + } + + [Fact] + public void ReadRecord_should_hand_a_decoded_non_empty_field_to_the_stdlib_shape() + { + var record = DamlLfJsonReader.ReadRecord(HeadAndTailHistoryJson); + + NonEmpty.FromRecord((DamlRecord)record.GetRequiredField("history"), value => ((DamlText)value).Value) + .All.Should().Equal("a", "b", "c"); + } + + [Fact] + public void ReadRecord_should_decode_a_non_empty_field_into_the_record_the_stdlib_shape_writes() + { + var decoded = DamlLfJsonReader.ReadRecord(HeadAndTailHistoryJson) + .GetRequiredField("history").Should().BeOfType().Which; + + decoded.RecordId.Should().BeNull(); + decoded.Should().Be(new NonEmpty("a", ["b", "c"]).ToRecord(entry => new DamlText(entry))); + } + + [Fact] + public void ReadRecord_should_reject_a_non_empty_field_missing_its_head() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"history":{"tl":["b"]}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'HistoryHolder.history.hd' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_non_empty_field_missing_its_tail() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"history":{"hd":"a"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'HistoryHolder.history.tl' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_non_empty_field_whose_tail_is_not_an_array() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"history":{"hd":"a","tl":"b"}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Array at 'HistoryHolder.history.tl' but found String"); + } + + public sealed record PairHistoryHolder( + [property: DamlFieldAttribute("history")] NonEmpty> History) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "history", + History.ToRecord(pair => pair.ToRecord(count => new DamlInt64(count), label => new DamlText(label))))); + } + + [Fact] + public void ReadRecord_should_decode_tuple2_elements_carried_by_a_non_empty_field() + { + var record = DamlLfJsonReader.ReadRecord( + """{"history":{"hd":{"_1":"1","_2":"a"},"tl":[{"_1":"2","_2":"b"}]}}"""); + + record.GetRequiredField("history").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("hd", DamlRecord.Create( + new DamlField("_1", new DamlInt64(1L)), + new DamlField("_2", new DamlText("a")))), + new DamlField("tl", new DamlList([ + DamlRecord.Create( + new DamlField("_1", new DamlInt64(2L)), + new DamlField("_2", new DamlText("b")))]))); + } + + public sealed record TallyHolder( + [property: DamlFieldAttribute("tally")] Map Tally) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tally", + Tally.ToRecord(label => new DamlText(label), count => new DamlInt64(count)))); + } + + private const string TwoEntryTallyJson = """{"tally":{"map":[["alice","1"],["bob","2"]]}}"""; + + [Fact] + public void ReadRecord_should_decode_a_stdlib_map_field_from_its_wire_record_form() + { + var record = DamlLfJsonReader.ReadRecord(TwoEntryTallyJson); + + record.GetRequiredField("tally").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("map", DamlGenMap.Create( + (new DamlText("alice"), new DamlInt64(1L)), + (new DamlText("bob"), new DamlInt64(2L))))); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_stdlib_map_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"tally":{"map":[]}}"""); + + record.GetRequiredField("tally").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("map", DamlGenMap.Create())); + } + + [Fact] + public void ReadRecord_should_hand_a_decoded_stdlib_map_field_to_the_stdlib_shape() + { + var record = DamlLfJsonReader.ReadRecord(TwoEntryTallyJson); + + Map.FromRecord( + (DamlRecord)record.GetRequiredField("tally"), + key => ((DamlText)key).Value, + value => ((DamlInt64)value).Value) + .Entries.Should().Equal( + new KeyValuePair("alice", 1L), + new KeyValuePair("bob", 2L)); + } + + [Fact] + public void ReadRecord_should_decode_a_stdlib_map_field_into_the_record_the_stdlib_shape_writes() + { + var decoded = DamlLfJsonReader.ReadRecord(TwoEntryTallyJson) + .GetRequiredField("tally").Should().BeOfType().Which; + + decoded.RecordId.Should().BeNull(); + decoded.Should().Be(new Map([ + new KeyValuePair("alice", 1L), + new KeyValuePair("bob", 2L)]) + .ToRecord(label => new DamlText(label), count => new DamlInt64(count))); + } + + [Fact] + public void ReadRecord_should_reject_a_stdlib_map_carrying_the_same_key_twice() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tally":{"map":[["alice","1"],["alice","2"]]}}"""); + + act.Should().Throw() + .WithMessage("Duplicate key at 'TallyHolder.tally.map[1]' in a Daml Map"); + } + + [Fact] + public void ReadRecord_should_reject_a_stdlib_map_field_missing_its_map() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tally":{}}"""); + + act.Should().Throw() + .WithMessage("Required Daml field 'TallyHolder.tally.map' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_stdlib_map_field_encoded_as_a_text_map_object() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"tally":{"map":{"alice":"1"}}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Array at 'TallyHolder.tally.map' but found Object"); + } + + public sealed record ProfileTallyMapHolder( + [property: DamlFieldAttribute("tally")] Map> Tally) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tally", + Tally.ToRecord( + profile => profile.ToRecord(), + note => note.ToValue(text => new DamlText(text))))); + } + + [Fact] + public void ReadRecord_should_decode_the_key_and_value_types_of_a_stdlib_map_field() + { + var record = DamlLfJsonReader.ReadRecord( + """{"tally":{"map":[[{"nickname":"nick","level":"3"},"gold"],[{"nickname":"nack","level":"4"},null]]}}"""); + + record.GetRequiredField("tally").Should().BeOfType() + .Which.GetRequiredField("map").Should().BeOfType() + .Which.Entries.Should().Equal( + (DamlRecord.Create( + new DamlField("nickname", new DamlText("nick")), + new DamlField("level", new DamlInt64(3L))), + (DamlValue)DamlOptional.Some(new DamlText("gold"))), + (DamlRecord.Create( + new DamlField("nickname", new DamlText("nack")), + new DamlField("level", new DamlInt64(4L))), + DamlOptional.None)); + } + + public sealed record BothMapShapesHolder( + [property: DamlFieldAttribute("wrapped")] Map Wrapped, + [property: DamlFieldAttribute("primitive")] IReadOnlyDictionary Primitive) : IDamlRecord + { + public DamlRecord ToRecord() => + throw new NotSupportedException("Reader-shape stand-ins in this suite are decode-only."); + } + + [Fact] + public void ReadRecord_should_keep_the_genmap_primitive_out_of_the_stdlib_map_record_wrapper() + { + var record = DamlLfJsonReader.ReadRecord( + """{"wrapped":{"map":[["alice","1"]]},"primitive":{"alice":"1"}}"""); + + record.GetRequiredField("wrapped").Should().BeOfType() + .Which.GetRequiredField("map").Should().Be( + DamlGenMap.Create((new DamlText("alice"), new DamlInt64(1L)))); + record.GetRequiredField("primitive").Should().Be( + DamlTextMap.Create(("alice", new DamlInt64(1L)))); + } + + [Fact] + public void ReadRecord_should_decode_the_genmap_primitive_in_its_array_form_beside_a_stdlib_map_field() + { + var record = DamlLfJsonReader.ReadRecord( + """{"wrapped":{"map":[["alice","1"]]},"primitive":[["alice","1"]]}"""); + + record.GetRequiredField("wrapped").Should().BeOfType() + .Which.GetRequiredField("map").Should().Be( + DamlGenMap.Create((new DamlText("alice"), new DamlInt64(1L)))); + record.GetRequiredField("primitive").Should().Be( + DamlGenMap.Create((new DamlText("alice"), new DamlInt64(1L)))); + } + + public sealed record TagsByOwnerHolder( + [property: DamlFieldAttribute("tags")] Map> Tags) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tags", + Tags.ToRecord(owner => new DamlText(owner), tags => tags.ToRecord(tag => new DamlText(tag))))); + } + + [Fact] + public void ReadRecord_should_decode_a_set_carried_by_a_stdlib_map_value() + { + var record = DamlLfJsonReader.ReadRecord( + """{"tags":{"map":[["alice",{"map":[["gold",{}],["silver",{}]]}],["bob",{"map":[]}]]}}"""); + + record.GetRequiredField("tags").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("map", DamlGenMap.Create( + (new DamlText("alice"), DamlRecord.Create(DamlField.Create("map", DamlGenMap.Create( + (new DamlText("gold"), DamlUnit.Instance), + (new DamlText("silver"), DamlUnit.Instance))))), + (new DamlText("bob"), DamlRecord.Create(DamlField.Create("map", DamlGenMap.Create())))))); + } + + private sealed record PinnedTemplate : ITemplate, IDamlRecord + { + public static RuntimeIdentifier TemplateId { get; } = new("pkg", "M", "PinnedTemplate"); + public static string PackageId => "pkg"; + public static string PackageName => "pinned"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + public static PinnedTemplate FromRecord(DamlRecord record) => new(); + } + + private sealed record PinnedView : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + public static PinnedView FromRecord(DamlRecord record) => new(); + } + + private sealed record PinnedInterface : IDamlInterface, IHasView + { + public static RuntimeIdentifier InterfaceId { get; } = new("pkg", "M", "PinnedInterface"); + public static string PackageId => "pkg"; + public static string PackageName => "pinned"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(InterfaceId, DamlTypeKind.Interface, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + } + + private sealed record InterfaceConstrainedGenericHolder( + Contract Contract, + AcsSnapshotEntry SnapshotEntry, + ContractStreamEvent StreamEvent, + InterfaceStreamEvent InterfaceStreamEvent, + InterfaceAcsSnapshotEntry InterfaceSnapshotEntry, + ViewDescriptor ViewDescriptor, + ContractId ContractId); + + private sealed record ContractIdHolder( + [property: DamlFieldAttribute("contractId")] ContractId ContractId) : IDamlRecord + { + public DamlRecord ToRecord() => throw new NotSupportedException("decode-only shape"); + } + + [Fact] + public void ReadRecord_should_treat_every_interface_constrained_generic_slot_as_non_optional() + { + var nullability = new NullabilityInfoContext(); + var slots = typeof(InterfaceConstrainedGenericHolder).GetProperties(); + + slots.Should().NotBeEmpty( + "a holder that lost its properties would leave the projection below empty and pass " + + "while exercising no interface-constrained generic at all"); + + var nullableSlots = slots + .Where(property => nullability.Create(property).ReadState == NullabilityState.Nullable) + .Select(property => property.Name); + + nullableSlots.Should().BeEmpty( + "an interface-constrained generic reports NullabilityState.Unknown, and the reader's " + + "optionality predicate tests for Nullable rather than for not-NotNull"); + } + + [Fact] + public void ReadRecord_should_reject_rather_than_silently_absorb_a_null_in_an_interface_constrained_generic_slot() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"contractId":null}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'ContractIdHolder.contractId' but found Null"); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderStructuralTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderStructuralTests.cs new file mode 100644 index 0000000..c4036e0 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderStructuralTests.cs @@ -0,0 +1,748 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderStructuralTests +{ + public sealed record NoteHolder([property: DamlFieldAttribute("note")] string? Note) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "note", + Note is null ? DamlOptional.None : DamlOptional.Some(new DamlText(Note)))); + } + + [Fact] + public void ReadRecord_should_decode_a_present_optional_field_from_its_bare_wire_value() + { + var record = DamlLfJsonReader.ReadRecord("""{"note":"present"}"""); + + record.GetRequiredField("note").Should().BeOfType() + .Which.Value.Should().BeOfType().Which.Value.Should().Be("present"); + } + + [Fact] + public void ReadRecord_should_decode_an_absent_optional_field_from_json_null() + { + var record = DamlLfJsonReader.ReadRecord("""{"note":null}"""); + + record.GetRequiredField("note").Should().BeOfType() + .Which.Should().Be(DamlOptional.None); + } + + public sealed record LevelHolder([property: DamlFieldAttribute("level")] long? Level) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "level", + Level is null ? DamlOptional.None : DamlOptional.Some(new DamlInt64(Level.Value)))); + } + + [Fact] + public void ReadRecord_should_decode_a_present_optional_value_type_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"level":"3"}"""); + + record.GetRequiredField("level").Should().BeOfType() + .Which.Value.Should().BeOfType().Which.Value.Should().Be(3L); + } + + [Fact] + public void ReadRecord_should_decode_an_absent_optional_value_type_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"level":null}"""); + + record.GetRequiredField("level").Should().BeOfType() + .Which.Should().Be(DamlOptional.None); + } + + public sealed record NoteListHolder([property: DamlFieldAttribute("notes")] IReadOnlyList Notes) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "notes", + new DamlList(Notes + .Select(note => (DamlValue)(note is null ? DamlOptional.None : DamlOptional.Some(new DamlText(note)))) + .ToList()))); + } + + [Fact] + public void ReadRecord_should_decode_optionals_nested_inside_a_list() + { + var record = DamlLfJsonReader.ReadRecord("""{"notes":["present",null]}"""); + + record.GetRequiredField("notes").Should().BeOfType() + .Which.Values.Should().Equal( + DamlOptional.Some(new DamlText("present")), + DamlOptional.None); + } + + public sealed record AttributesHolder( + [property: DamlFieldAttribute("attributes")] IReadOnlyDictionary Attributes) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "attributes", + new DamlTextMap(Attributes.ToDictionary(entry => entry.Key, entry => (DamlValue)new DamlText(entry.Value))))); + } + + [Fact] + public void ReadRecord_should_decode_a_text_map_field_from_its_wire_object_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"attributes":{"a":"1"}}"""); + + record.GetRequiredField("attributes").Should().BeOfType() + .Which.Should().Be(DamlTextMap.Create(("a", new DamlText("1")))); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_text_map_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"attributes":{}}"""); + + record.GetRequiredField("attributes").Should().BeOfType() + .Which.Values.Should().BeEmpty(); + } + + [Fact] + public void ReadRecord_should_reject_duplicate_text_map_keys_in_a_caller_parsed_document() + { + using var document = JsonDocument.Parse("""{"attributes":{"a":"1","a":"2"}}"""); + + var act = () => DamlLfJsonReader.ReadRecord(document.RootElement); + + act.Should().Throw() + .WithMessage("Duplicate key 'a' at 'AttributesHolder.attributes' in a Daml TextMap"); + } + + [Fact] + public void ReadRecord_should_bracket_the_map_key_when_reporting_an_error_inside_a_text_map_value() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"attributes":{"a.b":5}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'AttributesHolder.attributes['a.b']' but found Number"); + } + + [Fact] + public void ReadRecord_should_escape_a_quote_inside_a_bracketed_map_key() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"attributes":{"o'brien":5}}"""); + + act.Should().Throw() + .WithMessage(@"Expected JSON String at 'AttributesHolder.attributes['o\'brien']' but found Number"); + } + + [Fact] + public void ReadRecord_should_escape_a_backslash_inside_a_bracketed_map_key() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"attributes":{"a\\b":5}}"""); + + act.Should().Throw() + .WithMessage(@"Expected JSON String at 'AttributesHolder.attributes['a\\b']' but found Number"); + } + + [Fact] + public void ReadRecord_should_escape_a_backslash_that_precedes_a_quote_inside_a_bracketed_map_key() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"attributes":{"a\\'b":5}}"""); + + act.Should().Throw() + .WithMessage(@"Expected JSON String at 'AttributesHolder.attributes['a\\\'b']' but found Number"); + } + + [Fact] + public void ReadRecord_should_elide_an_oversized_map_key_in_a_bracketed_path() + { + var oversizedKey = new string('k', 70); + + var act = () => DamlLfJsonReader.ReadRecord( + $$$"""{"attributes":{"{{{oversizedKey}}}":5}}"""); + + act.Should().Throw() + .WithMessage( + $"Expected JSON String at 'AttributesHolder.attributes['{new string('k', 64)}…']' but found Number"); + } + + private const string WireParty = + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b"; + + public sealed record GenMapHolder( + [property: DamlFieldAttribute("genMap")] IReadOnlyDictionary GenMap) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "genMap", + new DamlGenMap(GenMap + .Select(entry => ((DamlValue)entry.Key.ToDamlValue(), (DamlValue)new DamlInt64(entry.Value))) + .ToList()))); + } + + [Fact] + public void ReadRecord_should_decode_a_gen_map_field_from_its_wire_pair_array_form() + { + var record = DamlLfJsonReader.ReadRecord($$"""{"genMap":[["{{WireParty}}","7"]]}"""); + + record.GetRequiredField("genMap").Should().BeOfType() + .Which.Should().Be(DamlGenMap.Create((new DamlParty(WireParty), new DamlInt64(7)))); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_gen_map_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"genMap":[]}"""); + + record.GetRequiredField("genMap").Should().BeOfType() + .Which.Entries.Should().BeEmpty(); + } + + [Fact] + public void ReadRecord_should_reject_a_gen_map_entry_that_is_not_a_key_value_pair() + { + var act = () => DamlLfJsonReader.ReadRecord($$"""{"genMap":[["{{WireParty}}"]]}"""); + + act.Should().Throw() + .WithMessage("Expected a two-element key/value pair at 'GenMapHolder.genMap[0]' but found 1 element(s)"); + } + + [Fact] + public void ReadRecord_should_reject_a_gen_map_with_a_duplicate_key() + { + var act = () => DamlLfJsonReader.ReadRecord( + $$"""{"genMap":[["{{WireParty}}","1"],["{{WireParty}}","2"]]}"""); + + act.Should().Throw() + .WithMessage("Duplicate key at 'GenMapHolder.genMap[1]' in a Daml GenMap"); + } + + [Fact] + public void ReadRecord_should_reject_a_gen_map_entry_that_is_not_an_array() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"genMap":["nope"]}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Array at 'GenMapHolder.genMap[0]' but found String"); + } + + [Fact] + public void ReadRecord_should_reject_a_gen_map_field_encoded_as_a_json_object() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"genMap":{"a":"1"}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON array of entry pairs (GenMap) at 'GenMapHolder.genMap' but found Object"); + } + + [Fact] + public void ReadRecord_should_name_both_map_wire_forms_when_rejecting_a_string_keyed_map() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"attributes":"nope"}"""); + + act.Should().Throw() + .WithMessage("Expected JSON object (TextMap) or array of entry pairs (GenMap) " + + "at 'AttributesHolder.attributes' but found String"); + } + + public sealed record UnitHolder([property: DamlFieldAttribute("unitField")] DamlUnit UnitField) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("unitField", UnitField)); + } + + [Fact] + public void ReadRecord_should_decode_a_unit_field_from_its_wire_empty_object_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"unitField":{}}"""); + + record.GetRequiredField("unitField").Should().BeOfType() + .Which.Should().BeSameAs(DamlUnit.Instance); + } + + [Fact] + public void ReadRecord_should_reject_a_unit_field_encoded_as_a_json_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"unitField":"nope"}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'UnitHolder.unitField' but found String"); + } + + public enum Suit + { + Clubs, + Diamonds, + Hearts, + Spades + } + + public sealed record SuitHolder([property: DamlFieldAttribute("suit")] Suit Suit) : IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create("suit", DamlEnum.Create(Suit.ToString()))); + } + + [Fact] + public void ReadRecord_should_decode_an_enum_field_from_its_bare_wire_string() + { + var record = DamlLfJsonReader.ReadRecord("""{"suit":"Hearts"}"""); + + record.GetRequiredField("suit").Should().BeOfType() + .Which.Should().Be(DamlEnum.Create("Hearts")); + } + + [Fact] + public void ReadRecord_should_reject_an_unknown_enum_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"suit":"Wands"}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml enum constructor 'Wands' at 'SuitHolder.suit'; " + + "expected one of Clubs, Diamonds, Hearts, Spades"); + } + + [Fact] + public void ReadRecord_should_reject_an_enum_field_encoded_as_a_json_number() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"suit":2}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'SuitHolder.suit' but found Number"); + } + + public enum Zigzag + { + Zig, + Alpha + } + + public sealed record ZigzagHolder([property: DamlFieldAttribute("zigzag")] Zigzag Zigzag) : IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create("zigzag", DamlEnum.Create(Zigzag.ToString()))); + } + + [Fact] + public void ReadRecord_should_sort_the_expected_set_when_rejecting_an_unknown_enum_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"zigzag":"Zag"}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml enum constructor 'Zag' at 'ZigzagHolder.zigzag'; " + + "expected one of Alpha, Zig"); + } + + public sealed record OutcomeWin( + [property: DamlFieldAttribute("prize")] decimal Prize, + [property: DamlFieldAttribute("tier")] string Tier) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("prize", new DamlNumeric(Prize)), + DamlField.Create("tier", new DamlText(Tier))); + } + + public abstract record Outcome : IDamlVariant + { + public abstract string Tag { get; } + + public abstract DamlVariant ToVariant(); + + public sealed record Win(OutcomeWin Value) : Outcome + { + public override string Tag => "Win"; + + public override DamlVariant ToVariant() => DamlVariant.Create("Win", Value.ToRecord()); + } + + public sealed record Pending : Outcome + { + public override string Tag => "Pending"; + + public override DamlVariant ToVariant() => DamlVariant.Create("Pending", DamlUnit.Instance); + } + } + + public sealed record OutcomeHolder([property: DamlFieldAttribute("outcome")] Outcome Outcome) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("outcome", Outcome.ToVariant())); + } + + [Fact] + public void ReadRecord_should_decode_a_tagged_variant_arm_with_its_record_payload() + { + var record = DamlLfJsonReader.ReadRecord( + """{"outcome":{"tag":"Win","value":{"prize":"1.25","tier":"gold"}}}"""); + + var variant = record.GetRequiredField("outcome").Should().BeOfType().Which; + variant.Constructor.Should().Be("Win"); + variant.Value.Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("prize", new DamlNumeric(1.25m)), + new DamlField("tier", new DamlText("gold"))); + } + + [Fact] + public void ReadRecord_should_decode_a_nullary_variant_arm_from_its_empty_object_value() + { + var record = DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":"Pending","value":{}}}"""); + + var variant = record.GetRequiredField("outcome").Should().BeOfType().Which; + variant.Constructor.Should().Be("Pending"); + variant.Value.Should().BeSameAs(DamlUnit.Instance); + } + + [Fact] + public void ReadRecord_should_reject_an_unknown_variant_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":"Draw","value":{}}}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml variant constructor 'Draw' at 'OutcomeHolder.outcome'; " + + "expected one of Pending, Win"); + } + + [Fact] + public void ReadRecord_should_reject_a_variant_without_a_tag() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":{"value":{}}}"""); + + act.Should().Throw() + .WithMessage("Required Daml variant field 'OutcomeHolder.outcome.tag' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_variant_tag_that_is_not_a_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":5,"value":{}}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON String at 'OutcomeHolder.outcome.tag' but found Number"); + } + + [Fact] + public void ReadRecord_should_reject_a_nullary_variant_value_that_is_not_an_object() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":"Pending","value":"nope"}}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'OutcomeHolder.outcome.value' but found String"); + } + + [Fact] + public void ReadRecord_should_reject_a_variant_missing_its_value_field() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":{"tag":"Pending"}}"""); + + act.Should().Throw() + .WithMessage("Required Daml variant field 'OutcomeHolder.outcome.value' is missing from the JSON object"); + } + + [Fact] + public void ReadRecord_should_reject_a_variant_field_encoded_as_a_bare_string() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"outcome":"Pending"}"""); + + act.Should().Throw() + .WithMessage("Expected JSON Object at 'OutcomeHolder.outcome' but found String"); + } + + public sealed record Profile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("level")] long Level) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create( + DamlField.Create("nickname", new DamlText(Nickname)), + DamlField.Create("level", new DamlInt64(Level))); + } + + public sealed record ProfileHolder([property: DamlFieldAttribute("profile")] Profile Profile) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("profile", Profile.ToRecord())); + } + + [Fact] + public void ReadRecord_should_decode_a_nested_record_field_keyed_by_field_name() + { + var record = DamlLfJsonReader.ReadRecord("""{"profile":{"nickname":"nick","level":"3"}}"""); + + record.GetRequiredField("profile").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("nickname", new DamlText("nick")), + new DamlField("level", new DamlInt64(3L))); + } + + public sealed record TagsHolder([property: DamlFieldAttribute("tags")] IReadOnlyList Tags) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tags", + new DamlList(Tags.Select(tag => (DamlValue)new DamlText(tag)).ToList()))); + } + + [Fact] + public void ReadRecord_should_decode_a_list_field_from_its_wire_array_form() + { + var record = DamlLfJsonReader.ReadRecord("""{"tags":["x","y"]}"""); + + record.GetRequiredField("tags").Should().BeOfType() + .Which.Values.Should().Equal(new DamlText("x"), new DamlText("y")); + } + + [Fact] + public void ReadRecord_should_decode_an_empty_list_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"tags":[]}"""); + + record.GetRequiredField("tags").Should().BeOfType().Which.Values.Should().BeEmpty(); + } + + public sealed record ProfileTallyHolder( + [property: DamlFieldAttribute("tally")] IReadOnlyDictionary Tally) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create( + "tally", + new DamlGenMap(Tally + .Select(entry => ((DamlValue)entry.Key.ToRecord(), (DamlValue)new DamlInt64(entry.Value))) + .ToList()))); + } + + [Fact] + public void ReadRecord_should_decode_a_gen_map_keyed_by_a_record() + { + var record = DamlLfJsonReader.ReadRecord( + """{"tally":[[{"nickname":"nick","level":"3"},"7"]]}"""); + + var entry = record.GetRequiredField("tally").Should().BeOfType() + .Which.Entries.Should().ContainSingle().Which; + entry.Key.Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("nickname", new DamlText("nick")), + new DamlField("level", new DamlInt64(3L))); + entry.Value.Should().Be(new DamlInt64(7L)); + } + + public abstract record Reading : IDamlVariant + { + public abstract string Tag { get; } + + public abstract DamlVariant ToVariant(); + + public sealed record Measured(decimal Value) : Reading + { + public override string Tag => "Measured"; + + public override DamlVariant ToVariant() => DamlVariant.Create("Measured", new DamlNumeric(Value)); + } + } + + public sealed record ReadingHolder([property: DamlFieldAttribute("reading")] Reading Reading) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("reading", Reading.ToVariant())); + } + + [Fact] + public void ReadRecord_should_decode_a_variant_arm_carrying_a_scalar_payload() + { + var record = DamlLfJsonReader.ReadRecord("""{"reading":{"tag":"Measured","value":"1.25"}}"""); + + var variant = record.GetRequiredField("reading").Should().BeOfType().Which; + variant.Constructor.Should().Be("Measured"); + variant.Value.Should().BeOfType().Which.Value.Should().Be(1.25m); + } + + public abstract record Shape : IDamlVariant + { + public abstract string Tag { get; } + + public abstract DamlVariant ToVariant(); + + public sealed record Shape_(string Value) : Shape + { + public override string Tag => "Shape"; + + public override DamlVariant ToVariant() => DamlVariant.Create("Shape", new DamlText(Value)); + } + + public sealed record Blank : Shape + { + public override string Tag => "Blank"; + + public override DamlVariant ToVariant() => DamlVariant.Create("Blank", DamlUnit.Instance); + } + } + + public sealed record ShapeHolder([property: DamlFieldAttribute("shape")] Shape Shape) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("shape", Shape.ToVariant())); + } + + [Fact] + public void ReadRecord_should_decode_a_variant_arm_whose_csharp_name_was_disambiguated_from_its_wire_tag() + { + var record = DamlLfJsonReader.ReadRecord("""{"shape":{"tag":"Shape","value":"round"}}"""); + + var variant = record.GetRequiredField("shape").Should().BeOfType().Which; + variant.Constructor.Should().Be("Shape"); + variant.Value.Should().BeOfType().Which.Value.Should().Be("round"); + } + + [Fact] + public void ReadRecord_should_list_wire_tags_rather_than_csharp_names_for_an_unknown_variant_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"shape":{"tag":"Round","value":{}}}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml variant constructor 'Round' at 'ShapeHolder.shape'; " + + "expected one of Blank, Shape"); + } + + public abstract record Armless : IDamlVariant + { + public abstract DamlVariant ToVariant(); + } + + public sealed record ArmlessHolder([property: DamlFieldAttribute("armless")] Armless Armless) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("armless", Armless.ToVariant())); + } + + [Fact] + public void ReadRecord_should_refuse_a_variant_type_that_declares_no_arms() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"armless":{"tag":"Whatever","value":{}}}"""); + + act.Should().Throw() + .WithMessage($"Type '{typeof(Armless)}' at 'ArmlessHolder.armless' declares no variant arms; " + + "pass a generated variant whose constructors are nested types carrying a Tag property."); + } + + public abstract record Untagged : IDamlVariant + { + public abstract DamlVariant ToVariant(); + + public sealed record Only : Untagged + { + public override DamlVariant ToVariant() => DamlVariant.Create("Only", DamlUnit.Instance); + } + } + + public sealed record UntaggedHolder([property: DamlFieldAttribute("untagged")] Untagged Untagged) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("untagged", Untagged.ToVariant())); + } + + [Fact] + public void ReadRecord_should_refuse_a_variant_arm_that_carries_no_wire_tag() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"untagged":{"tag":"Only","value":{}}}"""); + + act.Should().Throw() + .WithMessage($"Variant arm '{typeof(Untagged.Only)}' at 'UntaggedHolder.untagged' exposes no readable " + + "Tag property, so its wire constructor cannot be determined; pass a generated variant."); + } + + public abstract record Cursed : IDamlVariant + { + public abstract DamlVariant ToVariant(); + + public sealed record Broken : Cursed + { + public string Tag => throw new InvalidOperationException("this fixture's tag getter always throws"); + + public override DamlVariant ToVariant() => DamlVariant.Create("Broken", DamlUnit.Instance); + } + } + + public sealed record CursedHolder([property: DamlFieldAttribute("cursed")] Cursed Cursed) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("cursed", Cursed.ToVariant())); + } + + [Fact] + public void ReadRecord_should_refuse_a_variant_arm_whose_tag_getter_throws() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"cursed":{"tag":"Broken","value":{}}}"""); + + act.Should().Throw() + .WithMessage($"Variant arm '{typeof(Cursed.Broken)}' at 'CursedHolder.cursed' exposes no readable " + + "Tag property, so its wire constructor cannot be determined; pass a generated variant."); + } + + public sealed record DirectionHolder([property: DamlFieldAttribute("direction")] Direction Direction) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("direction", Direction.ToDamlEnum())); + } + + [Fact] + public void ReadRecord_should_decode_an_enum_constructor_whose_wire_name_differs_from_its_csharp_member() + { + var record = DamlLfJsonReader.ReadRecord("""{"direction":"U$u0020Turn"}"""); + + record.GetRequiredField("direction").Should().BeOfType() + .Which.Should().Be(DamlEnum.Create("U$u0020Turn")); + } + + [Fact] + public void ReadRecord_should_decode_an_enum_constructor_whose_wire_name_survives_sanitization() + { + var record = DamlLfJsonReader.ReadRecord("""{"direction":"Forward"}"""); + + record.GetRequiredField("direction").Should().BeOfType() + .Which.Should().Be(DamlEnum.Create("Forward")); + } + + [Fact] + public void ReadRecord_should_list_wire_constructors_rather_than_csharp_members_for_an_unknown_enum_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"direction":"Sideways"}"""); + + act.Should().Throw() + .WithMessage("Unknown Daml enum constructor 'Sideways' at 'DirectionHolder.direction'; " + + "expected one of Forward, U$u0020Turn"); + } + + public sealed record CadenceHolder([property: DamlFieldAttribute("cadence")] Cadence Cadence) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("cadence", Cadence.ToDamlEnum())); + } + + [Fact] + public void ReadRecord_should_refuse_an_enum_whose_companion_cannot_name_every_wire_constructor() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"cadence":"Steady"}"""); + + act.Should().Throw() + .WithMessage($"Enum '{typeof(Cadence)}' at 'CadenceHolder.cadence' has a companion whose ToDamlEnum " + + "fails for member 'Broken', so its wire constructors cannot be determined; " + + "pass a generated Daml enum."); + } +} + +public enum Direction +{ + Forward, + U_u0020Turn +} + +public static class DirectionExtensions +{ + public static DamlEnum ToDamlEnum(this Direction value) => + value switch + { + Direction.Forward => DamlEnum.Create("Forward"), + Direction.U_u0020Turn => DamlEnum.Create("U$u0020Turn"), + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; +} + +public enum Cadence +{ + Steady, + Broken +} + +public static class CadenceExtensions +{ + public static DamlEnum ToDamlEnum(this Cadence value) => + value switch + { + Cadence.Steady => DamlEnum.Create("Steady"), + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderTests.cs new file mode 100644 index 0000000..ca67a48 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderTests.cs @@ -0,0 +1,100 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderTests +{ + private const string OwnerJson = """{"owner":"alice::1220ab"}"""; + private const string OwnerParty = "alice::1220ab"; + private static readonly Type PartyHolderKnownOnlyAtRuntime = typeof(PartyHolder); + + public sealed record PartyHolder([property: DamlFieldAttribute("owner")] Party Owner) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("owner", Owner.ToDamlValue())); + } + + public sealed record PartyHolderEnvelope([property: DamlFieldAttribute("holder")] PartyHolder Holder) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("holder", Holder.ToRecord())); + } + + private static void ShouldCarryTheOwnerParty(DamlRecord record) + { + record.RecordId.Should().BeNull(); + record.Fields.Should().ContainSingle().Which.Label.Should().Be("owner"); + record.GetRequiredField("owner").Should().BeOfType() + .Which.Value.Should().Be(OwnerParty); + } + + [Fact] + public void ReadRecord_should_arm_a_party_field_when_given_json_text_and_a_type_argument() + { + ShouldCarryTheOwnerParty(DamlLfJsonReader.ReadRecord(OwnerJson)); + } + + [Fact] + public void ReadRecord_should_arm_a_party_field_when_given_a_parsed_element_and_a_type_argument() + { + using var document = JsonDocument.Parse(OwnerJson); + + ShouldCarryTheOwnerParty(DamlLfJsonReader.ReadRecord(document.RootElement)); + } + + [Fact] + public void ReadRecord_should_arm_a_party_field_when_given_json_text_and_a_runtime_type() + { + ShouldCarryTheOwnerParty(DamlLfJsonReader.ReadRecord(OwnerJson, PartyHolderKnownOnlyAtRuntime)); + } + + [Fact] + public void ReadRecord_should_arm_a_party_field_when_given_a_parsed_element_and_a_runtime_type() + { + using var document = JsonDocument.Parse(OwnerJson); + + ShouldCarryTheOwnerParty(DamlLfJsonReader.ReadRecord(document.RootElement, PartyHolderKnownOnlyAtRuntime)); + } + + [Fact] + public void ReadRecord_should_arm_a_party_field_nested_inside_a_record_field() + { + var record = DamlLfJsonReader.ReadRecord("""{"holder":{"owner":"alice::1220ab"}}"""); + + record.Fields.Should().ContainSingle().Which.Label.Should().Be("holder"); + ShouldCarryTheOwnerParty(record.GetRequiredField("holder").Should().BeOfType().Which); + } + + public sealed record HoldingView([property: DamlFieldAttribute("owner")] Party Owner) : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("owner", Owner.ToDamlValue())); + } + + public interface IHolding : IDamlInterface, IHasView; + + [Fact] + public void ReadRecord_should_refuse_an_interface_marker_and_point_at_its_view_type() + { + var act = () => DamlLfJsonReader.ReadRecord(OwnerJson, typeof(IHolding)); + + act.Should().Throw() + .WithMessage($"Type '{typeof(IHolding)}' at 'IHolding' is a Daml interface marker, which has no wire " + + "record of its own; read the interface's view type instead."); + } + + [Fact] + public void ReadRecord_should_refuse_a_type_argument_that_is_not_a_daml_record() + { + var act = () => DamlLfJsonReader.ReadRecord(OwnerJson, typeof(int)); + + act.Should().Throw() + .WithMessage($"Type '{typeof(int)}' at 'Int32' is not a generated Daml record; " + + "pass a concrete type implementing IDamlRecord whose properties carry DamlFieldAttribute."); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlLfJsonReaderWireSamplesTests.cs b/tests/Daml.Runtime.Tests/DamlLfJsonReaderWireSamplesTests.cs new file mode 100644 index 0000000..024e43d --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlLfJsonReaderWireSamplesTests.cs @@ -0,0 +1,487 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Daml.Runtime.Stdlib; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class DamlLfJsonReaderWireSamplesTests +{ + private const string WireTypesPackageId = "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200"; + private const string WireTypesPackageName = "wiretypes23"; + private const string WireTypesModuleName = "WireTypes"; + + internal static readonly string CorpusDirectory = + Path.Combine(AppContext.BaseDirectory, "wire-samples", "data"); + + private static readonly string[] FactConsumedWireSamples = + [ + "acs_wildcard_verbose_true.json", + "acs_wildcard_verbose_false.json", + "probe_nested_optional_matrix.json", + ]; + + [Theory] + [MemberData(nameof(SupportedWireSamplePayloads))] + public void ReadRecord_should_decode_captured_payloads_inside_the_reader_type_mapping( + string fileName, string payloadPath, string shapeName, (string Label, Type DamlType)[] expectedFields) + { + using var document = LoadWireSample(fileName); + var payload = ResolvePayload(document.RootElement, payloadPath); + + var record = DamlLfJsonReader.ReadRecord(payload, DeclaredShapes[shapeName]); + + record.RecordId.Should().BeNull(); + record.Fields.Select(field => (field.Label, DamlType: field.Value.GetType())) + .Should().Equal(expectedFields); + } + + [Theory] + [MemberData(nameof(UnsupportedWireSamplePayloads))] + public void ReadRecord_should_pin_the_first_gap_for_captured_payloads_outside_the_reader_type_mapping( + string fileName, string payloadPath, string shapeName, string expectedGap) + { + using var document = LoadWireSample(fileName); + var payload = ResolvePayload(document.RootElement, payloadPath); + + var act = () => DamlLfJsonReader.ReadRecord(payload, DeclaredShapes[shapeName]); + + act.Should().Throw().WithMessage($"*{expectedGap}*"); + } + + [Fact] + public void ReadRecord_should_decode_the_captured_tuple_key_as_a_stdlib_tuple_field() + { + using var document = LoadWireSample("create_keyed_contract_key.json"); + var capturedKey = ResolvePayload( + document.RootElement, "response/transaction/events/0/CreatedEvent/contractKey"); + + var record = DamlLfJsonReader.ReadRecord( + """{"key":""" + capturedKey.GetRawText() + "}"); + + record.GetRequiredField("key").Should().BeOfType().Which.Fields.Should().Equal( + new DamlField("_1", new DamlParty(capturedKey.GetProperty("_1").GetString()!)), + new DamlField("_2", new DamlText(capturedKey.GetProperty("_2").GetString()!))); + } + + [Fact] + public void WireSamplesCorpus_should_show_identical_contract_entries_for_verbose_true_and_false() + { + using var verboseTrue = LoadWireSample("acs_wildcard_verbose_true.json"); + using var verboseFalse = LoadWireSample("acs_wildcard_verbose_false.json"); + + var trueEntries = verboseTrue.RootElement.GetProperty("response").EnumerateArray() + .Select(entry => entry.GetProperty("contractEntry")).ToList(); + var falseEntries = verboseFalse.RootElement.GetProperty("response").EnumerateArray() + .Select(entry => entry.GetProperty("contractEntry")).ToList(); + + trueEntries.Should().HaveSameCount(falseEntries); + foreach (var (verboseEntry, terseEntry) in trueEntries.Zip(falseEntries)) + { + JsonElement.DeepEquals(verboseEntry, terseEntry).Should().BeTrue( + "the corpus README records that the verbose flag leaves ACS payloads untouched"); + } + } + + [Fact] + public void WireSamplesCorpus_should_keep_the_nested_optional_probe_matrix_self_consistent() + { + using var document = LoadWireSample("probe_nested_optional_matrix.json"); + + var acceptedCandidates = new List(); + foreach (var candidate in document.RootElement.GetProperty("response").EnumerateObject()) + { + if (candidate.Value.GetProperty("accepted").GetBoolean()) + { + acceptedCandidates.Add(candidate.Name); + JsonElement.DeepEquals( + candidate.Value.GetProperty("sent"), + candidate.Value.GetProperty("echoed")) + .Should().BeTrue($"the participant echoed accepted candidate '{candidate.Name}' back verbatim"); + } + else + { + candidate.Value.GetProperty("error").GetString().Should().NotBeNullOrEmpty(); + candidate.Value.TryGetProperty("echoed", out _).Should().BeFalse(); + } + } + + acceptedCandidates.Should().Equal("empty_array", "array_of_empty_array", "array_of_array_of_text"); + } + + [Fact] + public void WireSamplesCorpus_should_fail_when_a_capture_is_not_consumed_by_these_tests() + { + var consumed = FileNamesOf(SupportedWireSamplePayloads) + .Concat(FileNamesOf(UnsupportedWireSamplePayloads)) + .Concat(FactConsumedWireSamples) + .Distinct(); + + var captured = Directory.EnumerateFiles(CorpusDirectory, "*.json").Select(Path.GetFileName); + + captured.Should().BeEquivalentTo( + consumed, + "every capture under tests/wire-samples/data must be exercised by a reader test"); + } + + [Fact] + public void ReadRecord_should_decode_the_captured_nested_optional_encodings_as_a_chain() + { + using var matrix = LoadWireSample("probe_nested_optional_matrix.json"); + var someNone = ResolvePayload(matrix.RootElement, "response/array_of_empty_array/sent"); + + var record = DamlLfJsonReader.ReadRecord( + """{"nestedNote":""" + someNone.GetRawText() + "}"); + + record.GetRequiredField("nestedNote") + .Should().Be(DamlOptionalChain.Some(DamlOptionalChain.None)); + } + + [Fact] + public void ReadRecord_should_distinguish_the_three_accepted_nested_optional_encodings() + { + DamlLfJsonReader.ReadRecord("""{"nestedNote":[]}""") + .GetRequiredField("nestedNote").Should().Be(DamlOptionalChain.None); + DamlLfJsonReader.ReadRecord("""{"nestedNote":[[]]}""") + .GetRequiredField("nestedNote").Should().Be(DamlOptionalChain.Some(DamlOptionalChain.None)); + DamlLfJsonReader.ReadRecord("""{"nestedNote":[["deep"]]}""") + .GetRequiredField("nestedNote") + .Should().Be(DamlOptionalChain.Some(DamlOptionalChain.Some(new DamlText("deep")))); + } + + [Fact] + public void ReadRecord_should_reject_every_nested_optional_encoding_the_participant_rejected() + { + var rejected = new[] + { + ("null", "Expected JSON Array at 'NestedNoteHolder.nestedNote' but found Null"), + ("[null]", "Expected JSON Array at 'NestedNoteHolder.nestedNote[0]' but found Null"), + ("""["deep"]""", "Expected JSON Array at 'NestedNoteHolder.nestedNote[0]' but found String"), + }; + + foreach (var (encoding, message) in rejected) + { + var act = () => DamlLfJsonReader.ReadRecord( + """{"nestedNote":""" + encoding + "}"); + + act.Should().Throw($"the participant rejected {encoding} with HTTP 500") + .WithMessage(message); + } + } + + [Fact] + public void ReadRecord_should_reject_a_nested_optional_level_carrying_more_than_one_element() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"nestedNote":[[],[]]}"""); + + act.Should().Throw().WithMessage( + "A nested Daml Optional at 'NestedNoteHolder.nestedNote' encodes as an array of at most " + + "one element but found 2"); + } + + [Fact] + public void ReadRecord_should_name_the_chain_level_that_failed() + { + var outerLevel = () => DamlLfJsonReader.ReadRecord("""{"deepNote":[null]}"""); + var innerLevel = () => DamlLfJsonReader.ReadRecord("""{"deepNote":[[null]]}"""); + + outerLevel.Should().Throw().WithMessage( + "Expected JSON Array at 'DeepNoteHolder.deepNote[0]' but found Null"); + innerLevel.Should().Throw().WithMessage( + "Expected JSON Array at 'DeepNoteHolder.deepNote[0][0]' but found Null"); + } + + [Fact] + public void ReadRecord_should_name_the_chain_level_carrying_more_than_one_element() + { + var act = () => DamlLfJsonReader.ReadRecord("""{"deepNote":[[[],[]]]}"""); + + act.Should().Throw().WithMessage( + "A nested Daml Optional at 'DeepNoteHolder.deepNote[0]' encodes as an array of at most " + + "one element but found 2"); + } + + [Fact] + public void ReadRecord_should_keep_a_flat_wrapper_slot_on_the_flat_encoding() + { + DamlLfJsonReader.ReadRecord("""{"note":null}""") + .GetRequiredField("note").Should().Be(DamlOptional.None); + DamlLfJsonReader.ReadRecord("""{"note":"present"}""") + .GetRequiredField("note").Should().Be(DamlOptional.Some(new DamlText("present"))); + } + + [Fact] + public void ReadRecord_should_read_a_wrapper_reached_through_a_generic_at_exactly_its_own_level() + { + var item = DamlLfJsonReader.ReadRecord("""{"boxed":{"item":"deep"}}""") + .GetRequiredField("boxed").As() + .GetRequiredField("item"); + + item.Should().Be( + DamlOptional.Some(new DamlText("deep")), + "the notnull constraint the emitter puts on every generated type parameter keeps the " + + "slot's read-state non-nullable, so only the wrapper the field actually carries is read"); + + Optional.FromValue(item, value => value.As().Value) + .Should().Be(new Optional.Some("deep"), + "the generated FromRecord a caller hands this to has to be able to consume it"); + } + + [Fact] + public void ReadRecord_should_read_every_level_of_a_chain_as_a_chain_level() + { + var innerAbsent = DamlLfJsonReader.ReadRecord("""{"nestedNote":[[]]}""") + .GetRequiredField("nestedNote").As(); + + innerAbsent.Value.Should().BeOfType( + "the interior level of a chain is CLR-identical to a flat wrapper and can only be " + + "reached by recursion from the root; deciding it structurally reads it as flat"); + } + + private static IEnumerable FileNamesOf(IEnumerable rows) => + rows.Select(row => (string)row.GetData()[0]!); + + internal static JsonDocument LoadWireSample(string fileName) => + JsonDocument.Parse(File.ReadAllText(Path.Combine(CorpusDirectory, fileName))); + + internal static JsonElement ResolvePayload(JsonElement root, string payloadPath) + { + var current = root; + foreach (var segment in payloadPath.Split('/')) + { + current = int.TryParse(segment, out var index) ? current[index] : current.GetProperty(segment); + } + return current; + } + + private static readonly (string Label, Type DamlType)[] WireRecordFields = + [ + ("owner", typeof(DamlParty)), + ("count", typeof(DamlInt64)), + ("amount", typeof(DamlNumeric)), + ("label", typeof(DamlText)), + ("active", typeof(DamlBool)), + ("asOf", typeof(DamlDate)), + ("observedAt", typeof(DamlTimestamp)), + ("note", typeof(DamlOptional)), + ("nestedNote", typeof(DamlOptionalChain)), + ("tags", typeof(DamlList)), + ("attributes", typeof(DamlTextMap)), + ("genMap", typeof(DamlGenMap)), + ("unitField", typeof(DamlUnit)), + ("marker", typeof(DamlContractId)), + ("holdingCid", typeof(DamlContractId)), + ("holdingCids", typeof(DamlList)), + ("profile", typeof(DamlRecord)), + ("outcome", typeof(DamlVariant)), + ("suit", typeof(DamlEnum)), + ("fee", typeof(DamlNumeric)), + ]; + + public static TheoryData SupportedWireSamplePayloads => new() + { + { "create_marker.json", "response/transaction/events/0/CreatedEvent/createArgument", nameof(Marker), [("owner", typeof(DamlParty))] }, + { "acs_wildcard_verbose_false.json", "response/0/contractEntry/JsActiveContract/createdEvent/createArgument", nameof(Marker), [("owner", typeof(DamlParty))] }, + { "acs_wildcard_verbose_true.json", "response/0/contractEntry/JsActiveContract/createdEvent/createArgument", nameof(Marker), [("owner", typeof(DamlParty))] }, + { "exercise_ping.json", "response/transaction/events/0/ExercisedEvent/choiceArgument", nameof(Ping), [] }, + { "create_asset_numeric_edges.json", "response/transaction/events/0/CreatedEvent/createArgument", nameof(Asset), [("issuer", typeof(DamlParty)), ("amount", typeof(DamlNumeric))] }, + { "create_keyed_contract_key.json", "response/transaction/events/0/CreatedEvent/createArgument", nameof(Keyed), [("owner", typeof(DamlParty)), ("label", typeof(DamlText))] }, + { "create_keyed_contract_key.json", "response/transaction/events/0/CreatedEvent/contractKey", nameof(KeyedKey), [("_1", typeof(DamlParty)), ("_2", typeof(DamlText))] }, + { "create_wirerecord_empty.json", "response/transaction/events/0/CreatedEvent/createArgument", nameof(WireRecord), WireRecordFields }, + { "create_wirerecord_populated.json", "response/transaction/events/0/CreatedEvent/createArgument", nameof(WireRecord), WireRecordFields }, + { "exercise_describe.json", "response/transaction/events/0/ExercisedEvent/choiceArgument", nameof(Describe), [("probe", typeof(DamlOptional))] }, + { "exercise_relabel.json", "response/transaction/events/0/ExercisedEvent/choiceArgument", nameof(Relabel), [("newLabel", typeof(DamlText))] }, + { "acs_interface_view_holding.json", "response/0/contractEntry/JsActiveContract/createdEvent/interfaceViews/0/viewValue", nameof(HoldingView), [("amount", typeof(DamlNumeric))] }, + }; + + public static TheoryData UnsupportedWireSamplePayloads => new() + { + { "exercise_describe.json", "response/transaction/events/0/ExercisedEvent/exerciseResult", nameof(Outcome), "at 'Outcome' is not a generated Daml record" }, + { "exercise_ping.json", "response/transaction/events/0/ExercisedEvent/exerciseResult", nameof(Unit), "at 'Unit' is not a generated Daml record" }, + }; + + internal static readonly IReadOnlyDictionary DeclaredShapes = new Dictionary + { + [nameof(Marker)] = typeof(Marker), + [nameof(Ping)] = typeof(Ping), + [nameof(Asset)] = typeof(Asset), + [nameof(Keyed)] = typeof(Keyed), + [nameof(KeyedKey)] = typeof(KeyedKey), + [nameof(WireRecord)] = typeof(WireRecord), + [nameof(Relabel)] = typeof(Relabel), + [nameof(Describe)] = typeof(Describe), + [nameof(Outcome)] = typeof(Outcome), + [nameof(Unit)] = typeof(Unit), + [nameof(HoldingView)] = typeof(HoldingView), + }; + + private static NotSupportedException DecodeOnlyShape() => + new("Wire-sample shapes are decode-only stand-ins for the corpus's generated types."); + + public sealed record Marker([property: DamlFieldAttribute("owner")] Party Owner) : IDamlRecord, IDamlType + { + public static DamlTypeDescriptor DamlTypeId { get; } = new( + new Identifier(WireTypesPackageId, WireTypesModuleName, nameof(Marker)), + DamlTypeKind.Template, + WireTypesPackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("owner", Owner.ToDamlValue())); + } + + public sealed record Ping : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + } + + public interface IHolding : IDamlType + { + static DamlTypeDescriptor IDamlType.DamlTypeId => + new(new Identifier(WireTypesPackageId, WireTypesModuleName, "Holding"), + DamlTypeKind.Interface, + WireTypesPackageName); + } + + public sealed record Asset( + [property: DamlFieldAttribute("issuer")] Party Issuer, + [property: DamlFieldAttribute("amount")] decimal Amount) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record Keyed( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("label")] string Label) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record KeyedKey( + [property: DamlFieldAttribute("_1")] Party Maintainer, + [property: DamlFieldAttribute("_2")] string Label) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record TupleKeyHolder( + [property: DamlFieldAttribute("key")] Tuple2 Key) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record Profile( + [property: DamlFieldAttribute("nickname")] string Nickname, + [property: DamlFieldAttribute("level")] long Level) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record HoldingView( + [property: DamlFieldAttribute("amount")] decimal Amount) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record WinDetails( + [property: DamlFieldAttribute("prize")] decimal Prize, + [property: DamlFieldAttribute("tier")] string Tier) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public abstract record Outcome : IDamlVariant + { + public DamlVariant ToVariant() => throw DecodeOnlyShape(); + + public sealed record Win(WinDetails Details) : Outcome + { + public string Tag => "Win"; + } + + public sealed record Pending : Outcome + { + public string Tag => "Pending"; + } + } + + public enum Suit + { + Clubs, + Diamonds, + Hearts, + Spades, + } + + public sealed record WireRecord( + [property: DamlFieldAttribute("owner")] Party Owner, + [property: DamlFieldAttribute("count")] long Count, + [property: DamlFieldAttribute("amount")] decimal Amount, + [property: DamlFieldAttribute("label")] string Label, + [property: DamlFieldAttribute("active")] bool Active, + [property: DamlFieldAttribute("asOf")] DateOnly AsOf, + [property: DamlFieldAttribute("observedAt")] DateTimeOffset ObservedAt, + [property: DamlFieldAttribute("note")] string? Note, + [property: DamlFieldAttribute("nestedNote")] Optional> NestedNote, + [property: DamlFieldAttribute("tags")] IReadOnlyList Tags, + [property: DamlFieldAttribute("attributes")] IReadOnlyDictionary Attributes, + [property: DamlFieldAttribute("genMap")] IReadOnlyDictionary GenMap, + [property: DamlFieldAttribute("unitField")] DamlUnit UnitField, + [property: DamlFieldAttribute("marker")] ContractId Marker, + [property: DamlFieldAttribute("holdingCid")] ContractId HoldingCid, + [property: DamlFieldAttribute("holdingCids")] IReadOnlyList> HoldingCids, + [property: DamlFieldAttribute("profile")] Profile Profile, + [property: DamlFieldAttribute("outcome")] Outcome Outcome, + [property: DamlFieldAttribute("suit")] Suit Suit, + [property: DamlFieldAttribute("fee")] decimal Fee) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record Relabel( + [property: DamlFieldAttribute("newLabel")] string NewLabel) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record Describe( + [property: DamlFieldAttribute("probe")] long? Probe) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record NestedNoteHolder( + [property: DamlFieldAttribute("nestedNote")] Optional> NestedNote) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record DeepNoteHolder( + [property: DamlFieldAttribute("deepNote")] Optional>> DeepNote) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record FlatNoteHolder( + [property: DamlFieldAttribute("note")] Optional Note) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record Boxed( + [property: DamlFieldAttribute("item")] TA Item) : IDamlRecord + where TA : notnull + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } + + public sealed record BoxedNoteHolder( + [property: DamlFieldAttribute("boxed")] Boxed> Boxed) : IDamlRecord + { + public DamlRecord ToRecord() => throw DecodeOnlyShape(); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlRecordFacet.cs b/tests/Daml.Runtime.Tests/DamlRecordFacet.cs new file mode 100644 index 0000000..7a7fdff --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlRecordFacet.cs @@ -0,0 +1,12 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Data; + +namespace Daml.Runtime.Tests; + +internal static class DamlRecordFacet +{ + internal static T Materialize(DamlRecord record) where T : IDamlRecord => + T.FromRecord(record); +} diff --git a/tests/Daml.Runtime.Tests/DamlRecordGenericInterfaceTests.cs b/tests/Daml.Runtime.Tests/DamlRecordGenericInterfaceTests.cs new file mode 100644 index 0000000..164c834 --- /dev/null +++ b/tests/Daml.Runtime.Tests/DamlRecordGenericInterfaceTests.cs @@ -0,0 +1,33 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Data; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Runtime.Tests; + +/// +/// Tests for : record-shaped values whose concrete +/// type can be materialized from a through the static +/// abstract factory, without reflection. +/// +public class DamlRecordGenericInterfaceTests +{ + private sealed record Badge(string Name) : IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create("name", new DamlText(Name))); + + public static Badge FromRecord(DamlRecord record) => + new(record.GetRequiredField("name").As().Value); + } + + [Fact] + public void IDamlRecord_TSelf_materializes_the_concrete_type_through_the_static_abstract_factory() + { + var record = new Badge("issuer-badge").ToRecord(); + + DamlRecordFacet.Materialize(record).Should().Be(new Badge("issuer-badge")); + } +} diff --git a/tests/Daml.Runtime.Tests/DamlTypesTests.Scalars.cs b/tests/Daml.Runtime.Tests/DamlTypesTests.Scalars.cs index a26ae4f..6477a4f 100644 --- a/tests/Daml.Runtime.Tests/DamlTypesTests.Scalars.cs +++ b/tests/Daml.Runtime.Tests/DamlTypesTests.Scalars.cs @@ -110,6 +110,33 @@ public void ToCanonicalString_round_trips_more_fractional_digits_than_decimal_ho "29 fractional digits exceed decimal's 28-digit limit and must not silently round"); } + [Fact] + public void Value_narrows_a_scale_padded_value_that_fits_decimal() + { + const string paddedToScale37 = "1.5000000000000000000000000000000000000"; + + DamlNumeric.TryParseCanonical(paddedToScale37, out var value).Should().BeTrue(); + value.Value.Should().Be(1.5m, + "a participant pads every Numeric 37 field out to its declared scale, so the padding must not decide representability"); + } + + [Fact] + public void Value_narrows_a_scale_padded_zero() + { + const string paddedZero = "0.0000000000000000000000000000000000000"; + + DamlNumeric.TryParseCanonical(paddedZero, out var value).Should().BeTrue(); + value.Value.Should().Be(0m); + } + + [Fact] + public void Value_keeps_the_trailing_zeros_of_a_mantissa_decimal_already_holds() + { + DamlNumeric.TryParseCanonical("1.50", out var value).Should().BeTrue(); + value.Value.Scale.Should().Be(2, + "stripping is a fallback for mantissas decimal cannot otherwise hold, not a normalisation of every value"); + } + [Theory] [InlineData("nope")] [InlineData("1e5")] diff --git a/tests/Daml.Runtime.Tests/DamlValueExtensionsTests.cs b/tests/Daml.Runtime.Tests/DamlValueExtensionsTests.cs index 7aca1f2..3243dd6 100644 --- a/tests/Daml.Runtime.Tests/DamlValueExtensionsTests.cs +++ b/tests/Daml.Runtime.Tests/DamlValueExtensionsTests.cs @@ -194,6 +194,14 @@ public void AsOptional_wraps_bare_value_as_Some() new DamlInt64(42).AsOptional().Should().Be(DamlOptional.Some(new DamlInt64(42))); } + [Fact] + public void AsOptional_rejects_a_DamlOptionalChain_instead_of_wrapping_it_as_Some() + { + var act = () => DamlOptionalChain.Some(new DamlText("deep")).AsOptional(); + + act.Should().Throw().WithMessage("*DamlOptionalChain*"); + } + [Fact] public void FromDamlValue_should_convert_DamlParty_to_nullable_Party() { diff --git a/tests/Daml.Runtime.Tests/DamlValueStructuralEqualityTests.cs b/tests/Daml.Runtime.Tests/DamlValueStructuralEqualityTests.cs index b3f4c79..55cc471 100644 --- a/tests/Daml.Runtime.Tests/DamlValueStructuralEqualityTests.cs +++ b/tests/Daml.Runtime.Tests/DamlValueStructuralEqualityTests.cs @@ -78,6 +78,58 @@ public void DamlRecord_equals_compares_nested_collections_structurally() left.GetHashCode().Should().Be(right.GetHashCode()); } + [Fact] + public void DamlRecord_stays_findable_in_a_set_after_the_producer_mutates_its_field_list() + { + var fields = new List { DamlField.Create("amount", new DamlNumeric(100.5m)) }; + var record = new DamlRecord(null, fields); + var set = new HashSet { record }; + var hashBefore = record.GetHashCode(); + + fields.Add(DamlField.Create("issuer", new DamlParty("Alice::1220abcd"))); + + set.Should().Contain(record); + record.GetHashCode().Should().Be(hashBefore); + record.Fields.Should().ContainSingle(); + } + + [Fact] + public void DamlRecord_stays_usable_as_a_DamlGenMap_key_after_the_producer_mutates_its_field_list() + { + var fields = new List { DamlField.Create("id", new DamlText("k")) }; + var key = new DamlRecord(null, fields); + var map = DamlGenMap.Create((key, new DamlInt64(1))); + var set = new HashSet { map }; + var hashBefore = map.GetHashCode(); + + fields.Add(DamlField.Create("extra", new DamlInt64(9))); + + set.Should().Contain(map); + map.GetHashCode().Should().Be(hashBefore); + map.Entries[0].Key.Should().Be(DamlRecord.Create(DamlField.Create("id", new DamlText("k")))); + } + + [Fact] + public void DamlRecord_copies_fields_supplied_through_a_with_expression() + { + var fields = new List { DamlField.Create("amount", new DamlNumeric(100.5m)) }; + var record = SampleRecord() with { Fields = fields }; + var hashBefore = record.GetHashCode(); + + fields.Add(DamlField.Create("issuer", new DamlParty("Alice::1220abcd"))); + + record.Fields.Should().ContainSingle(); + record.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void DamlRecord_rejects_a_null_field_list_at_the_producer() + { + Action act = () => _ = new DamlRecord(null, null!); + + act.Should().Throw().WithParameterName("Fields"); + } + [Fact] public void DamlOptional_equals_compares_nested_collections_structurally() { diff --git a/tests/Daml.Runtime.Tests/EventCollectionCopyTests.cs b/tests/Daml.Runtime.Tests/EventCollectionCopyTests.cs new file mode 100644 index 0000000..7dd1e41 --- /dev/null +++ b/tests/Daml.Runtime.Tests/EventCollectionCopyTests.cs @@ -0,0 +1,552 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Commands; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Xunit; +using RuntimeIdentifier = Daml.Runtime.Data.Identifier; + +namespace Daml.Runtime.Tests; + +/// +/// Pins how the event records in Daml.Runtime.Contracts take their collection +/// members. All of them reject a null one with +/// naming the parameter, at the primary constructor and on init so with +/// expressions are covered too. Only the four whose equality reads the contents also copy: +/// is a read-only view, not an immutable collection, so +/// without the copy a producer that keeps its backing list could mutate an +/// already-constructed value's hash code and make it unfindable in a set or dictionary that +/// already holds it. The records that keep record-synthesized equality compare that member +/// by reference, so no hash can be corrupted there and they borrow rather than copy. +/// +public class EventCollectionCopyTests +{ + private static readonly RuntimeIdentifier FooTemplateId = new("test-pkg", "Acme.Foo", "FooBar"); + + [Fact] + public void CreatedContract_copies_the_party_lists_it_is_constructed_from() + { + var witnesses = new List { new("alice") }; + var signatories = new List { new("alice") }; + var observers = new List { new("bob") }; + var created = MakeCreated(witnesses, signatories, observers); + var hashBefore = created.GetHashCode(); + + witnesses.Add(new Party("mallory")); + signatories.Clear(); + observers.Add(new Party("mallory")); + + created.WitnessParties.Should().ContainSingle().Which.Id.Should().Be("alice"); + created.Signatories.Should().ContainSingle().Which.Id.Should().Be("alice"); + created.Observers.Should().ContainSingle().Which.Id.Should().Be("bob"); + created.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void CreatedContract_copies_interface_ids_supplied_through_a_with_expression() + { + var interfaceIds = new List { new("pkg", "Acme.Foo", "IThing") }; + var created = MakeCreated() with { InterfaceIds = interfaceIds }; + var hashBefore = created.GetHashCode(); + + interfaceIds.Add(new RuntimeIdentifier("pkg", "Acme.Foo", "IOther")); + + created.InterfaceIds.Should().ContainSingle(); + created.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void CreatedContract_stays_findable_in_a_set_after_the_producer_mutates_its_list() + { + var witnesses = new List { new("alice") }; + var created = MakeCreated(witnesses); + var set = new HashSet { created }; + + witnesses.Add(new Party("mallory")); + + set.Should().Contain(created); + } + + [Fact] + public void TransactionResult_copies_the_lists_it_is_constructed_from() + { + var createdContracts = new List { MakeCreated() }; + var archived = new List { "00a" }; + var result = new TransactionResult( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: createdContracts, + ArchivedContractIds: archived, + CommandId: null); + var hashBefore = result.GetHashCode(); + + createdContracts.Clear(); + archived.Add("00b"); + + result.CreatedContracts.Should().ContainSingle(); + result.ArchivedContractIds.Should().ContainSingle().Which.Should().Be("00a"); + result.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void TransactionResult_copies_exercised_events_supplied_through_a_with_expression() + { + var exercised = new List { MakeExercised() }; + var result = MakeResult() with { ExercisedEvents = exercised }; + var hashBefore = result.GetHashCode(); + + exercised.Add(MakeExercised()); + + result.ExercisedEvents.Should().ContainSingle(); + result.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void ExercisedEvent_copies_the_party_lists_it_is_constructed_from() + { + var acting = new List { new("alice") }; + var witnesses = new List { new("alice") }; + var exercised = MakeExercised(acting, witnesses); + var hashBefore = exercised.GetHashCode(); + + acting.Add(new Party("mallory")); + witnesses.Clear(); + + exercised.ActingParties.Should().ContainSingle(); + exercised.WitnessParties.Should().ContainSingle(); + exercised.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void ExercisedEvent_copies_caught_exceptions_supplied_through_a_with_expression() + { + var caught = new List { MakeCaught() }; + var exercised = MakeExercised() with { CaughtExceptions = caught }; + var hashBefore = exercised.GetHashCode(); + + caught.Add(MakeCaught()); + + exercised.CaughtExceptions.Should().ContainSingle(); + exercised.GetHashCode().Should().Be(hashBefore); + } + + [Fact] + public void CaughtException_copies_the_metadata_it_is_constructed_from() + { + var metadata = new Dictionary { ["required"] = "100" }; + var caught = MakeCaught(metadata); + var hashBefore = caught.GetHashCode(); + + metadata["required"] = "200"; + metadata["available"] = "40"; + + caught.Metadata.Should().ContainSingle(); + caught.Metadata["required"].Should().Be("100"); + caught.GetHashCode().Should().Be(hashBefore); + } + + [Theory] + [InlineData("WitnessParties")] + [InlineData("Signatories")] + [InlineData("Observers")] + public void CreatedContract_rejects_a_null_party_list_at_the_producer(string parameterName) + { + Action act = () => _ = new CreatedContract( + EventId: "evt", + ContractId: "00c", + TemplateId: new RuntimeIdentifier("test-pkg", "Acme.Foo", "FooBar"), + Payload: DamlRecord.Create(), + WitnessParties: parameterName == "WitnessParties" ? null! : [], + Signatories: parameterName == "Signatories" ? null! : [], + Observers: parameterName == "Observers" ? null! : []); + + act.Should().Throw().WithParameterName(parameterName); + } + + [Theory] + [InlineData("ActingParties")] + [InlineData("WitnessParties")] + public void ExercisedEvent_rejects_a_null_party_list_at_the_producer(string parameterName) + { + Action act = () => _ = new ExercisedEvent( + ContractId: "00c", + TemplateId: new RuntimeIdentifier("test-pkg", "Acme.Foo", "FooBar"), + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: parameterName == "ActingParties" ? null! : [], + WitnessParties: parameterName == "WitnessParties" ? null! : []); + + act.Should().Throw().WithParameterName(parameterName); + } + + [Fact] + public void CaughtException_rejects_null_metadata_at_the_producer() + { + Action act = () => _ = new CaughtException( + ErrorId: "Acme.Errors:InsufficientFunds", + Message: "not enough funds", + Metadata: null!); + + act.Should().Throw().WithParameterName("Metadata"); + } + + [Theory] + [InlineData("WitnessParties")] + [InlineData("Signatories")] + [InlineData("Observers")] + public void CreatedEvent_rejects_a_null_party_list_at_the_producer(string parameterName) + { + Action act = () => _ = new CreatedEvent( + EventId: "evt", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: parameterName == "WitnessParties" ? null! : [], + Signatories: parameterName == "Signatories" ? null! : [], + Observers: parameterName == "Observers" ? null! : [], + ContractKey: null); + + act.Should().Throw().WithParameterName(parameterName); + } + + [Theory] + [InlineData("WitnessParties")] + [InlineData("Signatories")] + [InlineData("Observers")] + public void CreatedEvent_rejects_a_null_party_list_from_a_with_expression(string parameterName) + { + var created = MakeCreatedEvent(); + + Action act = () => _ = parameterName switch + { + "WitnessParties" => created with { WitnessParties = null! }, + "Signatories" => created with { Signatories = null! }, + _ => created with { Observers = null! }, + }; + + act.Should().Throw().WithParameterName(parameterName); + } + + [Fact] + public void ArchivedEvent_rejects_null_witness_parties_at_the_producer() + { + Action act = () => _ = new ArchivedEvent( + EventId: "evt", + ContractId: "00c", + TemplateId: FooTemplateId, + WitnessParties: null!); + + act.Should().Throw().WithParameterName("WitnessParties"); + } + + [Fact] + public void ArchivedEvent_rejects_null_witness_parties_from_a_with_expression() + { + var archived = MakeArchivedEvent(); + + Action act = () => _ = archived with { WitnessParties = null! }; + + act.Should().Throw().WithParameterName("WitnessParties"); + } + + [Theory] + [InlineData("WitnessParties")] + [InlineData("Signatories")] + [InlineData("Observers")] + public void TreeEventCreated_rejects_a_null_party_list_at_the_producer(string parameterName) + { + Action act = () => _ = new TreeEvent.Created( + EventId: "evt", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: parameterName == "WitnessParties" ? null! : [], + Signatories: parameterName == "Signatories" ? null! : [], + Observers: parameterName == "Observers" ? null! : []); + + act.Should().Throw().WithParameterName(parameterName); + } + + [Theory] + [InlineData("WitnessParties")] + [InlineData("Signatories")] + [InlineData("Observers")] + [InlineData("InterfaceIds")] + public void TreeEventCreated_rejects_a_null_list_from_a_with_expression(string parameterName) + { + var created = MakeTreeCreated(); + + Action act = () => _ = parameterName switch + { + "WitnessParties" => created with { WitnessParties = null! }, + "Signatories" => created with { Signatories = null! }, + "Observers" => created with { Observers = null! }, + _ => created with { InterfaceIds = null! }, + }; + + act.Should().Throw().WithParameterName(parameterName); + } + + [Fact] + public void TreeEventCreated_defaults_interface_ids_to_an_empty_list() + { + MakeTreeCreated().InterfaceIds.Should().BeEmpty(); + } + + [Theory] + [InlineData("ActingParties")] + [InlineData("WitnessParties")] + [InlineData("ChildEvents")] + public void TreeEventExercised_rejects_a_null_list_at_the_producer(string parameterName) + { + Action act = () => _ = new TreeEvent.Exercised( + EventId: "evt", + ContractId: "00c", + TemplateId: FooTemplateId, + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: parameterName == "ActingParties" ? null! : [], + WitnessParties: parameterName == "WitnessParties" ? null! : [], + ChildEvents: parameterName == "ChildEvents" ? null! : []); + + act.Should().Throw().WithParameterName(parameterName); + } + + [Theory] + [InlineData("ActingParties")] + [InlineData("WitnessParties")] + [InlineData("ChildEvents")] + public void TreeEventExercised_rejects_a_null_list_from_a_with_expression(string parameterName) + { + var exercised = MakeTreeExercised(); + + Action act = () => _ = parameterName switch + { + "ActingParties" => exercised with { ActingParties = null! }, + "WitnessParties" => exercised with { WitnessParties = null! }, + _ => exercised with { ChildEvents = null! }, + }; + + act.Should().Throw().WithParameterName(parameterName); + } + + [Fact] + public void TransactionTree_rejects_null_root_events_at_the_producer() + { + Action act = () => _ = new TransactionTree( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + RootEvents: null!); + + act.Should().Throw().WithParameterName("RootEvents"); + } + + [Fact] + public void TransactionTree_rejects_null_root_events_from_a_with_expression() + { + var tree = MakeTree(); + + Action act = () => _ = tree with { RootEvents = null! }; + + act.Should().Throw().WithParameterName("RootEvents"); + } + + [Fact] + public void CreatedEvent_borrows_the_party_lists_it_is_constructed_from() + { + var witnesses = new List { new("alice") }; + var signatories = new List { new("alice") }; + var observers = new List { new("bob") }; + + var created = new CreatedEvent( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: witnesses, + Signatories: signatories, + Observers: observers, + ContractKey: null); + + created.WitnessParties.Should().BeSameAs(witnesses); + created.Signatories.Should().BeSameAs(signatories); + created.Observers.Should().BeSameAs(observers); + } + + [Fact] + public void ArchivedEvent_borrows_the_witness_parties_it_is_constructed_from() + { + var witnesses = new List { new("alice") }; + + var archived = new ArchivedEvent( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + WitnessParties: witnesses); + + archived.WitnessParties.Should().BeSameAs(witnesses); + } + + [Fact] + public void TreeEventCreated_borrows_the_lists_it_is_constructed_from() + { + var witnesses = new List { new("alice") }; + var signatories = new List { new("alice") }; + var observers = new List { new("bob") }; + var interfaceIds = new List { new("pkg", "Acme.Foo", "IThing") }; + + var created = new TreeEvent.Created( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: witnesses, + Signatories: signatories, + Observers: observers) with + { + InterfaceIds = interfaceIds, + }; + + created.WitnessParties.Should().BeSameAs(witnesses); + created.Signatories.Should().BeSameAs(signatories); + created.Observers.Should().BeSameAs(observers); + created.InterfaceIds.Should().BeSameAs(interfaceIds); + } + + [Fact] + public void TreeEventExercised_borrows_the_lists_it_is_constructed_from() + { + var acting = new List { new("alice") }; + var witnesses = new List { new("alice") }; + var children = new List { MakeTreeCreated() }; + + var exercised = new TreeEvent.Exercised( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: acting, + WitnessParties: witnesses, + ChildEvents: children); + + exercised.ActingParties.Should().BeSameAs(acting); + exercised.WitnessParties.Should().BeSameAs(witnesses); + exercised.ChildEvents.Should().BeSameAs(children); + } + + [Fact] + public void TransactionTree_borrows_the_root_events_it_is_constructed_from() + { + var roots = new List { MakeTreeCreated() }; + var replacement = new List { MakeTreeCreated() }; + + var tree = new TransactionTree( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + RootEvents: roots); + + tree.RootEvents.Should().BeSameAs(roots); + (tree with { RootEvents = replacement }).RootEvents.Should().BeSameAs(replacement); + } + + private static CreatedContract MakeCreated( + IReadOnlyList? witnesses = null, + IReadOnlyList? signatories = null, + IReadOnlyList? observers = null) => + new( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: new RuntimeIdentifier("test-pkg", "Acme.Foo", "FooBar"), + Payload: DamlRecord.Create(), + WitnessParties: witnesses ?? [new Party("alice")], + Signatories: signatories ?? [new Party("alice")], + Observers: observers ?? []); + + private static TransactionResult MakeResult() => + new( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: [], + ArchivedContractIds: [], + CommandId: null); + + private static ExercisedEvent MakeExercised( + IReadOnlyList? acting = null, + IReadOnlyList? witnesses = null) => + new( + ContractId: "00c", + TemplateId: new RuntimeIdentifier("test-pkg", "Acme.Foo", "FooBar"), + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: acting ?? [new Party("alice")], + WitnessParties: witnesses ?? [new Party("alice")]); + + private static CaughtException MakeCaught(IReadOnlyDictionary? metadata = null) => + new( + ErrorId: "Acme.Errors:InsufficientFunds", + Message: "not enough funds", + Metadata: metadata ?? new Dictionary { ["required"] = "100" }); + + private static CreatedEvent MakeCreatedEvent() => + new( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: [], + ContractKey: null); + + private static ArchivedEvent MakeArchivedEvent() => + new( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + WitnessParties: [new Party("alice")]); + + private static TreeEvent.Created MakeTreeCreated() => + new( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + CreateArguments: DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); + + private static TreeEvent.Exercised MakeTreeExercised() => + new( + EventId: "evt-00c", + ContractId: "00c", + TemplateId: FooTemplateId, + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: [new Party("alice")], + WitnessParties: [new Party("alice")], + ChildEvents: []); + + private static TransactionTree MakeTree() => + new( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + RootEvents: []); +} diff --git a/tests/Daml.Runtime.Tests/ExerciseOutcomeProjectionTests.cs b/tests/Daml.Runtime.Tests/ExerciseOutcomeProjectionTests.cs index aaa8872..901bfd7 100644 --- a/tests/Daml.Runtime.Tests/ExerciseOutcomeProjectionTests.cs +++ b/tests/Daml.Runtime.Tests/ExerciseOutcomeProjectionTests.cs @@ -98,7 +98,7 @@ public void ProjectCommitted_maps_DamlError_preserving_every_field() public void ProjectCommitted_maps_InfraError_preserving_status_code_message_and_source_exception() { var sourceException = new InvalidOperationException("transport failed"); - var outcome = new ExerciseOutcome.InfraError(14, "network down", sourceException); + var outcome = new ExerciseOutcome.InfraError(14, "network down", SourceException: sourceException); var result = outcome.ProjectCommitted(_ => new ExerciseOutcome.One(0)); @@ -108,6 +108,21 @@ public void ProjectCommitted_maps_InfraError_preserving_status_code_message_and_ infraError.SourceException.Should().BeSameAs(sourceException); } + [Fact] + public void ProjectCommitted_carries_the_InfraError_category_across_the_projection() + { + var outcome = new ExerciseOutcome.InfraError( + 400, "bad request", DamlErrorCategory.InvalidIndependentOfSystemState); + + var result = outcome.ProjectCommitted(_ => new ExerciseOutcome.One(0)); + + result.Should().BeOfType.InfraError>() + .Which.Category.Should().Be( + DamlErrorCategory.InvalidIndependentOfSystemState, + "a projection that re-wraps the outcome without forwarding the category silently discards a " + + "classification the transport determined without a structured Canton error to carry it"); + } + [Fact] public void ProjectCommitted_throws_when_projector_is_null() { diff --git a/tests/Daml.Runtime.Tests/ExerciseOutcomeTests.cs b/tests/Daml.Runtime.Tests/ExerciseOutcomeTests.cs index 3aa2fb5..d0b3c00 100644 --- a/tests/Daml.Runtime.Tests/ExerciseOutcomeTests.cs +++ b/tests/Daml.Runtime.Tests/ExerciseOutcomeTests.cs @@ -24,7 +24,7 @@ public void One_carries_contract_id_payload() [Fact] public void One_carries_transaction_result_payload() { - var result = new TransactionResult("u1", LedgerOffset.At(1), [], [], default); + var result = new TransactionResult("u1", LedgerOffset.At(1), [], [], null); var outcome = new ExerciseOutcome.One(result); outcome.Result.Should().BeSameAs(result); @@ -107,13 +107,32 @@ public void InfraError_carries_status_code_message_and_source_exception() // StatusCode is `int` (cast `(int)Grpc.Core.StatusCode.DeadlineExceeded` at the // gRPC client construction site) so this type stays free of any transport-library dep. var sourceException = new InvalidOperationException("transport failed"); - var outcome = new ExerciseOutcome>.InfraError(StatusCodes.DeadlineExceeded, "deadline", sourceException); + var outcome = new ExerciseOutcome>.InfraError(StatusCodes.DeadlineExceeded, "deadline", SourceException: sourceException); outcome.StatusCode.Should().Be(StatusCodes.DeadlineExceeded); outcome.Message.Should().Be("deadline"); outcome.SourceException.Should().BeSameAs(sourceException); } + [Fact] + public void InfraError_leaves_the_category_null_when_it_is_not_supplied() + { + var outcome = new ExerciseOutcome.InfraError(StatusCodes.Unavailable, "network down"); + + outcome.Category.Should().BeNull(); + } + + [Fact] + public void InfraError_carries_a_category_determined_without_a_structured_error() + { + var outcome = new ExerciseOutcome.InfraError( + StatusCodes.PermissionDenied, "permission denied", DamlErrorCategory.AuthorizationChecksFailed); + + outcome.Category.Should().Be(DamlErrorCategory.AuthorizationChecksFailed); + outcome.StatusCode.Should().Be(StatusCodes.PermissionDenied); + outcome.SourceException.Should().BeNull(); + } + [Fact] public void InfraError_works_for_transaction_result_payload() { @@ -157,6 +176,7 @@ private static class StatusCodes { public const int Unavailable = 14; public const int DeadlineExceeded = 4; + public const int PermissionDenied = 7; } private sealed record SwapChoiceResult( diff --git a/tests/Daml.Runtime.Tests/ExercisedEventEqualityTests.cs b/tests/Daml.Runtime.Tests/ExercisedEventEqualityTests.cs new file mode 100644 index 0000000..6a4b96e --- /dev/null +++ b/tests/Daml.Runtime.Tests/ExercisedEventEqualityTests.cs @@ -0,0 +1,141 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Xunit; +using RuntimeIdentifier = Daml.Runtime.Data.Identifier; + +namespace Daml.Runtime.Tests; + +/// +/// Pins that and compare by +/// content rather than by the identity of their collection members, which is what makes +/// equality structural all the way down rather than only at +/// the list level. +/// +public class ExercisedEventEqualityTests +{ + [Fact] + public void Two_independently_built_events_describing_the_same_exercise_are_equal() + { + var first = MakeExercised(); + var second = MakeExercised(); + + first.ActingParties.Should().NotBeSameAs(second.ActingParties); + first.WitnessParties.Should().NotBeSameAs(second.WitnessParties); + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void Events_with_different_acting_parties_are_not_equal() + { + var first = MakeExercised(actingParties: [new Party("alice")]); + var second = MakeExercised(actingParties: [new Party("bob")]); + + first.Should().NotBe(second); + } + + [Fact] + public void Events_with_acting_parties_in_a_different_order_are_not_equal() + { + var first = MakeExercised(actingParties: [new Party("alice"), new Party("bob")]); + var second = MakeExercised(actingParties: [new Party("bob"), new Party("alice")]); + + first.Should().NotBe(second); + } + + [Fact] + public void Acting_and_witness_parties_are_hashed_with_a_length_separator() + { + var split = MakeExercised( + actingParties: [new Party("alice")], + witnessParties: [new Party("bob")]); + var merged = MakeExercised( + actingParties: [new Party("alice"), new Party("bob")], + witnessParties: []); + + split.Should().NotBe(merged); + split.GetHashCode().Should().NotBe(merged.GetHashCode()); + } + + [Fact] + public void Events_with_equal_caught_exceptions_built_separately_are_equal() + { + var first = MakeExercised() with { CaughtExceptions = [MakeCaught()] }; + var second = MakeExercised() with { CaughtExceptions = [MakeCaught()] }; + + first.CaughtExceptions[0].Should().NotBeSameAs(second.CaughtExceptions[0]); + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void Events_with_different_caught_exceptions_are_not_equal() + { + var first = MakeExercised() with { CaughtExceptions = [MakeCaught()] }; + var second = MakeExercised() with { CaughtExceptions = [MakeCaught(errorId: "Acme.Errors:Expired")] }; + + first.Should().NotBe(second); + } + + [Fact] + public void Caught_exceptions_compare_metadata_independently_of_insertion_order() + { + var first = MakeCaught(metadata: new Dictionary + { + ["required"] = "100", + ["available"] = "40", + }); + var second = MakeCaught(metadata: new Dictionary + { + ["available"] = "40", + ["required"] = "100", + }); + + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void Caught_exceptions_with_different_metadata_values_are_not_equal() + { + var first = MakeCaught(metadata: new Dictionary { ["required"] = "100" }); + var second = MakeCaught(metadata: new Dictionary { ["required"] = "200" }); + + first.Should().NotBe(second); + } + + [Fact] + public void Caught_exceptions_with_a_metadata_key_the_other_lacks_are_not_equal() + { + var first = MakeCaught(metadata: new Dictionary { ["required"] = "100" }); + var second = MakeCaught(metadata: new Dictionary { ["available"] = "100" }); + + first.Should().NotBe(second); + } + + private static ExercisedEvent MakeExercised( + IReadOnlyList? actingParties = null, + IReadOnlyList? witnessParties = null) => + new( + ContractId: "00c", + TemplateId: new RuntimeIdentifier("test-pkg", "Acme.Foo", "FooBar"), + InterfaceId: null, + ChoiceName: "DoThing", + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: actingParties ?? [new Party("alice")], + WitnessParties: witnessParties ?? [new Party("alice")]); + + private static CaughtException MakeCaught( + string errorId = "Acme.Errors:InsufficientFunds", + IReadOnlyDictionary? metadata = null) => + new( + ErrorId: errorId, + Message: "not enough funds", + Metadata: metadata ?? new Dictionary { ["required"] = "100" }); +} diff --git a/tests/Daml.Runtime.Tests/MinLedgerTimeTests.cs b/tests/Daml.Runtime.Tests/MinLedgerTimeTests.cs new file mode 100644 index 0000000..60a460b --- /dev/null +++ b/tests/Daml.Runtime.Tests/MinLedgerTimeTests.cs @@ -0,0 +1,118 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Commands; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class MinLedgerTimeTests +{ + [Fact] + public void Absolute_should_carry_the_instant_verbatim() + { + var instant = new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.FromHours(2)); + + var bound = new MinLedgerTime.Absolute(instant); + + bound.Value.Should().Be(instant); + bound.Value.Offset.Should().Be(TimeSpan.FromHours(2)); + } + + [Fact] + public void Relative_should_carry_the_delay_verbatim() + { + var bound = new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)); + + bound.Value.Should().Be(TimeSpan.FromSeconds(30)); + } + + [Fact] + public void Relative_should_accept_a_zero_delay() + { + var bound = new MinLedgerTime.Relative(TimeSpan.Zero); + + bound.Value.Should().Be(TimeSpan.Zero); + } + + [Theory] + [InlineData(-1)] + [InlineData(-30)] + public void Relative_should_throw_when_the_delay_is_negative(int seconds) + { + Action act = () => _ = new MinLedgerTime.Relative(TimeSpan.FromSeconds(seconds)); + + act.Should().Throw(); + } + + [Fact] + public void Absolute_should_compare_by_instant() + { + var instant = new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.Zero); + + new MinLedgerTime.Absolute(instant).Should().Be(new MinLedgerTime.Absolute(instant)); + new MinLedgerTime.Absolute(instant) + .Should().NotBe(new MinLedgerTime.Absolute(instant.AddSeconds(1))); + } + + [Fact] + public void Relative_should_compare_by_delay() + { + new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)) + .Should().Be(new MinLedgerTime.Relative(TimeSpan.FromSeconds(30))); + new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)) + .Should().NotBe(new MinLedgerTime.Relative(TimeSpan.FromSeconds(31))); + } + + [Fact] + public void Match_should_apply_the_handler_for_the_arm_it_carries() + { + MinLedgerTime absolute = new MinLedgerTime.Absolute( + new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.Zero)); + MinLedgerTime relative = new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)); + + Describe(absolute).Should().Be("abs:2026-08-15T09:30:00.0000000+00:00"); + Describe(relative).Should().Be("rel:00:00:30"); + } + + [Fact] + public void Match_should_leave_the_other_arms_handler_unapplied() + { + var absoluteApplied = 0; + var relativeApplied = 0; + + _ = new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)).Match( + _ => ++absoluteApplied, + _ => ++relativeApplied); + + absoluteApplied.Should().Be(0); + relativeApplied.Should().Be(1); + } + + [Fact] + public void Match_should_throw_when_a_handler_is_null() + { + MinLedgerTime bound = new MinLedgerTime.Relative(TimeSpan.FromSeconds(30)); + + Action withoutAbsolute = () => bound.Match(null!, _ => "rel"); + Action withoutRelative = () => bound.Match(_ => "abs", null!); + + withoutAbsolute.Should().Throw(); + withoutRelative.Should().Throw(); + + MinLedgerTime absoluteBound = new MinLedgerTime.Absolute( + new DateTimeOffset(2026, 8, 15, 9, 30, 0, TimeSpan.Zero)); + + Action absoluteWithoutAbsoluteHandler = () => absoluteBound.Match(null!, _ => "rel"); + Action absoluteWithoutRelativeHandler = () => absoluteBound.Match(_ => "abs", null!); + + absoluteWithoutAbsoluteHandler.Should().Throw(); + absoluteWithoutRelativeHandler.Should().Throw(); + } + + private static string Describe(MinLedgerTime bound) => + bound.Match( + absolute => $"abs:{absolute:O}", + relative => $"rel:{relative}"); +} diff --git a/tests/Daml.Runtime.Tests/PartyJsonTests.cs b/tests/Daml.Runtime.Tests/PartyJsonTests.cs index f551584..bc86ba2 100644 --- a/tests/Daml.Runtime.Tests/PartyJsonTests.cs +++ b/tests/Daml.Runtime.Tests/PartyJsonTests.cs @@ -114,6 +114,19 @@ public void Party_should_throw_JsonException_when_non_nullable_field_receives_nu act.Should().Throw(); } + [Fact] + public void Party_should_name_the_offending_value_when_the_id_is_whitespace() + { + var json = "{\"operator\":\" \",\"marketId\":\"BTC-USD\"}"; + + var act = () => JsonSerializer.Deserialize(json, CaseInsensitiveOptions); + + act.Should().Throw().WithMessage( + "*' '*", + "echoing the rejected value is what makes a whitespace id diagnosable in a PQS row, and the " + + "sibling SynchronizerId converter has always done it"); + } + [Fact] public void Party_should_deserialize_null_inside_nullable_wrapper() { diff --git a/tests/Daml.Runtime.Tests/ReassignmentPairingFieldTypingTests.cs b/tests/Daml.Runtime.Tests/ReassignmentPairingFieldTypingTests.cs index a6eafe0..fb8551d 100644 --- a/tests/Daml.Runtime.Tests/ReassignmentPairingFieldTypingTests.cs +++ b/tests/Daml.Runtime.Tests/ReassignmentPairingFieldTypingTests.cs @@ -37,7 +37,8 @@ public void Assigned_should_carry_ReassignmentId_and_ReassignmentCounter() { var ev = new ContractStreamEvent.Assigned( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(6), Source, Target, @@ -68,7 +69,7 @@ public void Reassignment_fields_are_string_and_long_positioned_between_Target_an parameters["WitnessParties"].Position.Should().Be(parameters["ReassignmentCounter"].Position + 1); } - private sealed record TestTemplate(string Owner) : ITemplate + private sealed record TestTemplate(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); public static string PackageId => "pkg"; @@ -76,6 +77,9 @@ private sealed record TestTemplate(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", new DamlText(Owner))); + + public static TestTemplate FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } } diff --git a/tests/Daml.Runtime.Tests/RecordedAtHolder.cs b/tests/Daml.Runtime.Tests/RecordedAtHolder.cs new file mode 100644 index 0000000..ae154bf --- /dev/null +++ b/tests/Daml.Runtime.Tests/RecordedAtHolder.cs @@ -0,0 +1,12 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; + +namespace Daml.Runtime.Tests; + +public sealed record RecordedAtHolder([property: DamlFieldAttribute("recordedAt")] DateTimeOffset RecordedAt) : IDamlRecord +{ + public DamlRecord ToRecord() => DamlRecord.Create(DamlField.Create("recordedAt", new DamlTimestamp(RecordedAt))); +} diff --git a/tests/Daml.Runtime.Tests/SerializedWireShapeMatchesCaptureTests.cs b/tests/Daml.Runtime.Tests/SerializedWireShapeMatchesCaptureTests.cs new file mode 100644 index 0000000..bac2c9e --- /dev/null +++ b/tests/Daml.Runtime.Tests/SerializedWireShapeMatchesCaptureTests.cs @@ -0,0 +1,150 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +/// +/// Holds the writer answerable to real participant output: every capture the typed reader decodes +/// is read back and re-serialized, and the result must be the payload the participant sent. +/// +public class SerializedWireShapeMatchesCaptureTests +{ + [Theory] + [MemberData(nameof(DecodedWireSamplePayloads))] + public void Serialize_should_reproduce_the_captured_payload( + string fileName, string payloadPath, string shapeName) + { + using var document = DamlLfJsonReaderWireSamplesTests.LoadWireSample(fileName); + var captured = DamlLfJsonReaderWireSamplesTests.ResolvePayload(document.RootElement, payloadPath); + + var record = DamlLfJsonReader.ReadRecord( + captured, DamlLfJsonReaderWireSamplesTests.DeclaredShapes[shapeName]); + using var written = JsonDocument.Parse(DamlJsonSerializer.Serialize(record)); + + DisagreementsBetween(captured, written.RootElement, shapeName).Should().BeEmpty( + "the participant's own payload is the wire grammar the writer has to speak"); + } + + /// + /// A capture whose payloads the typed reader cannot decode, so the writer cannot be held + /// against it. Naming one here is the only way to leave it out of the gate below. + /// + private static readonly string[] CapturesCarryingNoDecodableRecord = + [ + "probe_nested_optional_matrix.json", + ]; + + /// + /// Anchored on the corpus directory, not on the member this class's theory data is projected + /// from: comparing a projection against its own source can never fail. Payload-level totality + /// is structural instead — loops over every supported + /// row with no filter — so what needs a gate is a capture landing on disk that no writer + /// assertion ever reaches. + /// + [Fact] + public void SerializedWireShapeMatchesCapture_should_hold_the_writer_against_every_capture_on_disk() + { + var capturesTheWriterIsHeldAgainst = FileNamesOf(DecodedWireSamplePayloads).Distinct(); + + capturesTheWriterIsHeldAgainst.Concat(CapturesCarryingNoDecodableRecord) + .Should().BeEquivalentTo( + Directory.EnumerateFiles(DamlLfJsonReaderWireSamplesTests.CorpusDirectory, "*.json") + .Select(Path.GetFileName), + "a capture the writer is never held against would let the two halves of the " + + "grammar drift apart again"); + } + + public static TheoryData DecodedWireSamplePayloads + { + get + { + var payloads = new TheoryData(); + foreach (ITheoryDataRow row in DamlLfJsonReaderWireSamplesTests.SupportedWireSamplePayloads) + { + var data = row.GetData(); + payloads.Add((string)data[0]!, (string)data[1]!, (string)data[2]!); + } + return payloads; + } + } + + private static IEnumerable FileNamesOf(IEnumerable rows) => + rows.Select(row => (string)row.GetData()[0]!); + + private static IReadOnlyList DisagreementsBetween( + JsonElement captured, JsonElement written, string path) + { + if (captured.ValueKind != written.ValueKind) + { + return [$"{path}: captured {captured.ValueKind} '{captured.GetRawText()}' " + + $"but wrote {written.ValueKind} '{written.GetRawText()}'"]; + } + + return captured.ValueKind switch + { + JsonValueKind.Object => ObjectDisagreements(captured, written, path), + JsonValueKind.Array => ArrayDisagreements(captured, written, path), + JsonValueKind.String => StringDisagreements(captured, written, path), + _ => captured.GetRawText() == written.GetRawText() + ? [] + : [$"{path}: captured '{captured.GetRawText()}' but wrote '{written.GetRawText()}'"], + }; + } + + private static IReadOnlyList ObjectDisagreements( + JsonElement captured, JsonElement written, string path) + { + var capturedNames = captured.EnumerateObject().Select(property => property.Name).ToList(); + var writtenNames = written.EnumerateObject().Select(property => property.Name).ToList(); + if (capturedNames.Count != writtenNames.Count || capturedNames.Except(writtenNames).Any()) + { + return [$"{path}: captured fields [{string.Join(", ", capturedNames)}] " + + $"but wrote [{string.Join(", ", writtenNames)}]"]; + } + + return [.. capturedNames.SelectMany(name => DisagreementsBetween( + captured.GetProperty(name), written.GetProperty(name), $"{path}.{name}"))]; + } + + private static IReadOnlyList ArrayDisagreements( + JsonElement captured, JsonElement written, string path) + { + if (captured.GetArrayLength() != written.GetArrayLength()) + { + return [$"{path}: captured {captured.GetArrayLength()} elements " + + $"but wrote {written.GetArrayLength()}"]; + } + + return [.. captured.EnumerateArray().Zip(written.EnumerateArray()) + .SelectMany((pair, index) => + DisagreementsBetween(pair.First, pair.Second, $"{path}[{index}]"))]; + } + + /// + /// A participant pads a Numeric out to its declared scale; the declared scale is not on the + /// wire, so the writer emits the canonical unpadded form of the same value. Every other + /// string-valued arm has to reproduce the capture verbatim. + /// + private static IReadOnlyList StringDisagreements( + JsonElement captured, JsonElement written, string path) + { + var capturedText = captured.GetString()!; + var writtenText = written.GetString()!; + if (capturedText == writtenText) + { + return []; + } + + var agreesAsNumeric = DamlNumeric.TryParseCanonical(capturedText, out var capturedNumeric) + && DamlNumeric.TryParseCanonical(writtenText, out var writtenNumeric) + && capturedNumeric.Equals(writtenNumeric); + + return agreesAsNumeric ? [] : [$"{path}: captured '{capturedText}' but wrote '{writtenText}'"]; + } +} diff --git a/tests/Daml.Runtime.Tests/StdlibTests.cs b/tests/Daml.Runtime.Tests/StdlibTests.cs index aa88793..79b87fe 100644 --- a/tests/Daml.Runtime.Tests/StdlibTests.cs +++ b/tests/Daml.Runtime.Tests/StdlibTests.cs @@ -16,6 +16,14 @@ public class StdlibTests { #region RelTime + [Fact] + public void RelTime_satisfies_the_generic_IDamlRecord_facet() + { + var original = new RelTime(60_000_000); + + DamlRecordFacet.Materialize(original.ToRecord()).Should().Be(original); + } + [Fact] public void RelTime_should_round_trip_through_ToRecord_FromRecord() { @@ -208,7 +216,35 @@ public void Set_should_round_trip_a_populated_collection() var record = original.ToRecord(s => new DamlText(s)); var recovered = Set.FromRecord(record, v => v.As().Value); - recovered.Elements.Should().BeEquivalentTo(original.Elements); + recovered.Should().Be(original); + } + + [Fact] + public void Set_should_not_observe_mutations_to_the_source_collection() + { + var source = new HashSet { "a", "b" }; + var set = new Set(source); + var hashBeforeMutation = set.GetHashCode(); + + source.Add("c"); + + set.Count.Should().Be(2); + set.Contains("c").Should().BeFalse(); + set.GetHashCode().Should().Be(hashBeforeMutation); + set.Should().Be(new Set(["a", "b"])); + } + + [Fact] + public void Set_with_expression_should_keep_the_defensive_copy() + { + var source = new HashSet { "a" }; + var set = new Set(source); + + var copied = set with { }; + source.Add("b"); + + copied.Count.Should().Be(1); + copied.Should().Be(new Set(["a"])); } [Fact] @@ -275,10 +311,7 @@ public void NonEmpty_should_round_trip_singleton() var record = original.ToRecord(i => new DamlInt64(i)); var recovered = NonEmpty.FromRecord(record, v => v.As().Value); - // Record equality on the wrapping IReadOnlyList is reference-based, so - // we compare the structural contents instead. - recovered.Hd.Should().Be(42L); - recovered.Tl.Should().BeEmpty(); + recovered.Should().Be(original); } [Fact] @@ -289,11 +322,36 @@ public void NonEmpty_should_round_trip_with_tail_elements() var record = original.ToRecord(i => new DamlInt64(i)); var recovered = NonEmpty.FromRecord(record, v => v.As().Value); - recovered.Hd.Should().Be(original.Hd); - recovered.Tl.Should().Equal(original.Tl); + recovered.Should().Be(original); recovered.All.Should().Equal(1L, 2L, 3L, 4L); } + [Fact] + public void NonEmpty_should_not_observe_mutations_to_the_source_tail() + { + var tail = new List { 2L }; + var nonEmpty = new NonEmpty(1L, tail); + var hashBeforeMutation = nonEmpty.GetHashCode(); + + tail.Add(3L); + + nonEmpty.Tl.Should().Equal(2L); + nonEmpty.All.Should().Equal(1L, 2L); + nonEmpty.GetHashCode().Should().Be(hashBeforeMutation); + nonEmpty.Should().Be(new NonEmpty(1L, [2L])); + } + + [Fact] + public void NonEmpty_with_expression_should_copy_the_replacement_tail() + { + var replacement = new List { 2L }; + var nonEmpty = new NonEmpty(1L, []) with { Tl = replacement }; + + replacement.Add(3L); + + nonEmpty.Tl.Should().Equal(2L); + } + [Fact] public void NonEmpty_ToRecord_should_emit_hd_and_tl_field_names() { @@ -335,9 +393,33 @@ public void Map_should_round_trip_with_string_keys_and_int_values() v => v.As().Value, v => v.As().Value); - // Record equality on IReadOnlyList> is reference-based, - // so we compare contents. - recovered.Entries.Should().Equal(original.Entries); + recovered.Should().Be(original); + } + + [Fact] + public void Map_should_not_observe_mutations_to_the_source_entries() + { + var source = new List> { new("alice", 1L) }; + var map = new Map(source); + var hashBeforeMutation = map.GetHashCode(); + + source.Add(new KeyValuePair("bob", 2L)); + + map.Count.Should().Be(1); + map.GetHashCode().Should().Be(hashBeforeMutation); + map.Should().Be(new Map([new KeyValuePair("alice", 1L)])); + } + + [Fact] + public void Map_with_expression_should_copy_the_replacement_entries() + { + var replacement = new List> { new("alice", 1L) }; + var map = new Map([]) with { Entries = replacement }; + + replacement.Add(new KeyValuePair("bob", 2L)); + + map.Count.Should().Be(1); + map.Entries.Should().Equal(new List> { new("alice", 1L) }); } [Fact] @@ -499,4 +581,147 @@ public void Either_Right_should_round_trip_through_value() } #endregion + + #region Optional + + private static DamlValue Optional_ToText(string value) => new DamlText(value); + + private static string Optional_FromText(DamlValue value) => ((DamlText)value).Value; + + [Fact] + public void Optional_Some_should_report_a_value_and_carry_it() + { + Optional optional = new Optional.Some("deep"); + + optional.HasValue.Should().BeTrue(); + optional.GetValueOrDefault().Should().Be("deep"); + optional.GetValueOrThrow().Should().Be("deep"); + optional.TryGetValue(out var carried).Should().BeTrue(); + carried.Should().Be("deep"); + } + + [Fact] + public void Optional_None_should_report_no_value_and_throw_on_demand() + { + Optional optional = new Optional.None(); + + optional.HasValue.Should().BeFalse(); + optional.GetValueOrDefault().Should().BeNull(); + optional.TryGetValue(out var carried).Should().BeFalse(); + carried.Should().BeNull(); + optional.Invoking(o => o.GetValueOrThrow()).Should().Throw(); + } + + [Fact] + public void Optional_Match_should_select_the_arm_and_return_its_projection() + { + Optional some = new Optional.Some("deep"); + Optional none = new Optional.None(); + + some.Match(value => $"some:{value}", () => "none").Should().Be("some:deep"); + none.Match(value => $"some:{value}", () => "none").Should().Be("none"); + } + + [Fact] + public void Optional_Match_should_reject_a_null_handler_on_either_arm() + { + Optional some = new Optional.Some("deep"); + Optional none = new Optional.None(); + + some.Invoking(o => o.Match(null!, () => "none")).Should().Throw(); + some.Invoking(o => o.Match(value => value, null!)).Should().Throw(); + none.Invoking(o => o.Match(null!, () => "none")).Should().Throw(); + none.Invoking(o => o.Match(value => value, null!)).Should().Throw(); + } + + [Fact] + public void Optional_should_compare_by_value_and_distinguish_the_arms() + { + new Optional.Some("deep").Should().Be(new Optional.Some("deep")); + new Optional.Some("deep").Should().NotBe(new Optional.Some("other")); + new Optional.None().Should().Be(new Optional.None()); + ((Optional)new Optional.None()).Should().NotBe(new Optional.Some("deep")); + } + + [Fact] + public void Optional_should_be_usable_as_a_dictionary_key() + { + var byOptional = new Dictionary, long> + { + [new Optional.Some("deep")] = 1, + [new Optional.None()] = 2, + }; + + byOptional[new Optional.Some("deep")].Should().Be(1); + byOptional[new Optional.None()].Should().Be(2); + } + + [Fact] + public void Optional_should_round_trip_both_arms_through_the_flat_wire_form() + { + Optional some = new Optional.Some("deep"); + Optional none = new Optional.None(); + + some.ToValue(Optional_ToText).Should().Be(DamlOptional.Some(new DamlText("deep"))); + none.ToValue(Optional_ToText).Should().Be(DamlOptional.None); + Optional.FromValue(some.ToValue(Optional_ToText), Optional_FromText).Should().Be(some); + Optional.FromValue(none.ToValue(Optional_ToText), Optional_FromText).Should().Be(none); + } + + [Fact] + public void Optional_FromValue_should_reject_a_value_that_is_not_a_daml_optional() + { + var act = () => Optional.FromValue(new DamlText("deep"), Optional_FromText); + + act.Should().Throw(); + } + + [Fact] + public void Optional_ToChainValue_should_produce_the_chain_node_and_leave_ToValue_flat() + { + Optional some = new Optional.Some("deep"); + Optional none = new Optional.None(); + + some.ToChainValue(Optional_ToText).Should().Be(DamlOptionalChain.Some(new DamlText("deep"))); + none.ToChainValue(Optional_ToText).Should().Be(DamlOptionalChain.None); + some.ToValue(Optional_ToText).Should().Be(DamlOptional.Some(new DamlText("deep"))); + } + + [Fact] + public void Optional_should_round_trip_both_arms_through_the_chain_wire_form() + { + Optional some = new Optional.Some("deep"); + Optional none = new Optional.None(); + + Optional.FromChainValue(some.ToChainValue(Optional_ToText), Optional_FromText).Should().Be(some); + Optional.FromChainValue(none.ToChainValue(Optional_ToText), Optional_FromText).Should().Be(none); + } + + [Fact] + public void Optional_FromChainValue_should_reject_a_flat_DamlOptional() + { + var act = () => Optional.FromChainValue(DamlOptional.Some(new DamlText("deep")), Optional_FromText); + + act.Should().Throw().WithMessage("*Cannot cast DamlOptional to DamlOptionalChain*"); + } + + [Fact] + public void Optional_FromValue_should_reject_a_chain_node() + { + var act = () => Optional.FromValue(DamlOptionalChain.Some(new DamlText("deep")), Optional_FromText); + + act.Should().Throw().WithMessage("*Cannot cast DamlOptionalChain to DamlOptional*"); + } + + [Fact] + public void Optional_should_nest_without_collapsing_the_inner_arm() + { + Optional> someNone = new Optional>.Some(new Optional.None()); + Optional> none = new Optional>.None(); + + someNone.Should().NotBe(none); + someNone.GetValueOrThrow().HasValue.Should().BeFalse(); + } + + #endregion } diff --git a/tests/Daml.Runtime.Tests/StreamWitnessPartiesGuardTests.cs b/tests/Daml.Runtime.Tests/StreamWitnessPartiesGuardTests.cs new file mode 100644 index 0000000..e01ea1e --- /dev/null +++ b/tests/Daml.Runtime.Tests/StreamWitnessPartiesGuardTests.cs @@ -0,0 +1,203 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using Daml.Runtime; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Streams; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Runtime.Tests; + +/// +/// Closes a gap in the borrowed-collection guard: Contracts/ event records guard every +/// collection member at both the primary constructor and the init accessor, but +/// the four Streams/ event/entry unions had WitnessParties members with no +/// guard at all. Discovers every closed variant of the four families through reflection +/// rather than a hand-listed set, so a future variant that forgets the guard fails this +/// test instead of shipping a silent downstream. +/// +public class StreamWitnessPartiesGuardTests +{ + public static TheoryData WitnessPartiesVariants() + { + var data = new TheoryData(); + foreach (var variant in DiscoverVariants()) + { + data.Add(variant); + } + + return data; + } + + [Theory] + [MemberData(nameof(WitnessPartiesVariants))] + public void Constructor_rejects_a_null_WitnessParties(Type recordType) + { + var ctor = recordType.GetConstructors().Single(); + var parameters = ctor.GetParameters(); + var witnessIndex = Array.FindIndex(parameters, p => p.Name == "WitnessParties"); + var args = parameters.Select(p => DummyValue(p.ParameterType)).ToArray(); + args[witnessIndex] = null; + + var act = () => ctor.Invoke(args); + + act.Should().Throw() + .WithInnerException() + .Which.ParamName.Should().Be("WitnessParties"); + } + + [Theory] + [MemberData(nameof(WitnessPartiesVariants))] + public void Init_accessor_rejects_a_null_WitnessParties(Type recordType) + { + var ctor = recordType.GetConstructors().Single(); + var args = ctor.GetParameters().Select(p => DummyValue(p.ParameterType)).ToArray(); + var instance = ctor.Invoke(args); + var setter = recordType.GetProperty("WitnessParties")!.GetSetMethod(nonPublic: true)!; + + var act = () => setter.Invoke(instance, [null]); + + act.Should().Throw() + .WithInnerException() + .Which.ParamName.Should().Be("WitnessParties"); + } + + /// + /// Proves the sweep is not vacuous by asserting the exact member count, + /// rather than only "more than zero" — a discovery bug that silently dropped every + /// variant would otherwise pass both theories above by finding nothing to test. + /// + [Fact] + public void Sweep_finds_exactly_the_twelve_unguarded_members() + { + DiscoverVariants().Should().HaveCount(12); + } + + private static IEnumerable DiscoverVariants() + { + Type[] families = + [ + typeof(ContractStreamEvent), + typeof(InterfaceStreamEvent), + typeof(AcsSnapshotEntry), + typeof(InterfaceAcsSnapshotEntry), + ]; + + foreach (var family in families) + { + foreach (var nested in family.GetNestedTypes(BindingFlags.Public)) + { + if (!nested.IsSealed) + { + continue; + } + + var closed = nested.IsGenericTypeDefinition + ? nested.MakeGenericType(family.GetGenericArguments()) + : nested; + + var ctor = closed.GetConstructors().SingleOrDefault(); + if (ctor is not null && ctor.GetParameters().Any(p => p.Name == "WitnessParties")) + { + yield return closed; + } + } + } + } + + private static object? DummyValue(Type type) + { + if (type == typeof(string)) + { + return "s"; + } + + if (type == typeof(bool)) + { + return true; + } + + if (type == typeof(long)) + { + return 1L; + } + + if (type == typeof(DamlValue)) + { + return DamlUnit.Instance; + } + + if (type == typeof(LedgerOffset)) + { + return LedgerOffset.At(1); + } + + if (type == typeof(SynchronizerId)) + { + return new SynchronizerId("sync"); + } + + if (type == typeof(IReadOnlyList)) + { + return new List { new("alice") }; + } + + if (type == typeof(ContractKey)) + { + return new ContractKey(DamlUnit.Instance); + } + + if (type == typeof(TestTemplate)) + { + return new TestTemplate(); + } + + if (type == typeof(TestView)) + { + return new TestView(); + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ContractId<>)) + { + return Activator.CreateInstance(type, "00c"); + } + + throw new NotSupportedException( + $"No dummy-value factory registered for parameter type '{type}'. Add one so this " + + "sweep can keep exercising every WitnessParties-carrying variant."); + } + + private sealed record TestTemplate : ITemplate, IDamlRecord + { + public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); + public static string PackageId => "pkg"; + public static string PackageName => "test"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + + public static TestTemplate FromRecord(DamlRecord record) => new(); + } + + private interface TestInterface : IDamlInterface, IHasView + { + static Identifier IDamlInterface.InterfaceId => new("pkg", "M", "TestInterface"); + static string IDamlInterface.PackageId => "pkg"; + static string IDamlInterface.PackageName => "test"; + static Version IDamlInterface.PackageVersion => new(0, 1, 0); + static DamlTypeDescriptor IDamlType.DamlTypeId => + new(new Identifier("pkg", "M", "TestInterface"), DamlTypeKind.Interface, "test"); + } + + private sealed record TestView : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + + public static TestView FromRecord(DamlRecord record) => new(); + } +} diff --git a/tests/Daml.Runtime.Tests/SynchronizerIdFieldTypingTests.cs b/tests/Daml.Runtime.Tests/SynchronizerIdFieldTypingTests.cs index fab753e..4a0e7fd 100644 --- a/tests/Daml.Runtime.Tests/SynchronizerIdFieldTypingTests.cs +++ b/tests/Daml.Runtime.Tests/SynchronizerIdFieldTypingTests.cs @@ -22,7 +22,8 @@ public void Created_SynchronizerId_should_be_typed_as_SynchronizerId() { var ev = new ContractStreamEvent.Created( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(1), Domain, [Alice]); @@ -63,7 +64,8 @@ public void Assigned_Source_should_be_typed_as_SynchronizerId() { var ev = new ContractStreamEvent.Assigned( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(4), Source, Target, @@ -79,7 +81,8 @@ public void Assigned_Target_should_be_typed_as_SynchronizerId() { var ev = new ContractStreamEvent.Assigned( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(4), Source, Target, @@ -120,7 +123,7 @@ public void Unassigned_Target_should_be_typed_as_SynchronizerId() ev.Target.Should().BeOfType().And.Be(Target); } - private sealed record TestTemplate(string Owner) : ITemplate + private sealed record TestTemplate(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); public static string PackageId => "pkg"; @@ -128,6 +131,9 @@ private sealed record TestTemplate(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", new DamlText(Owner))); + + public static TestTemplate FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } } diff --git a/tests/Daml.Runtime.Tests/SynchronizerIdTests.cs b/tests/Daml.Runtime.Tests/SynchronizerIdTests.cs index c6fdb40..7359ba0 100644 --- a/tests/Daml.Runtime.Tests/SynchronizerIdTests.cs +++ b/tests/Daml.Runtime.Tests/SynchronizerIdTests.cs @@ -78,6 +78,48 @@ public void Explicit_conversion_from_string_should_construct() sid.Id.Should().Be(Canton34Id); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void FromWire_should_return_null_when_wire_id_is_null_empty_or_whitespace(string? id) + { + (SynchronizerId.FromWire(id) is null).Should().BeTrue(); + } + + [Fact] + public void FromWire_should_round_trip_a_non_blank_wire_id_through_Id() + { + var sid = SynchronizerId.FromWire(Canton35Id); + + (sid is null).Should().BeFalse(); + sid!.Value.Id.Should().Be(Canton35Id); + } + + [Fact] + public void FromWire_should_store_a_padded_id_verbatim() + { + var padded = $" {Canton35Id} "; + + var sid = SynchronizerId.FromWire(padded); + + sid!.Value.Id.Should().Be(padded); + } + + [Fact] + public void Default_returned_from_a_SynchronizerId_typed_helper_should_wrap_non_null_unlike_FromWire() + { + static SynchronizerId LegacyTransportHelper(string wire) => + wire.Length == 0 ? default : (SynchronizerId)wire; + + SynchronizerId? viaHelper = LegacyTransportHelper(""); + Action readViaHelper = () => _ = viaHelper?.Id; + + (viaHelper is null).Should().BeFalse(); + readViaHelper.Should().Throw(); + (SynchronizerId.FromWire("") is null).Should().BeTrue(); + } + [Fact] public void Equality_should_be_value_based() { diff --git a/tests/Daml.Runtime.Tests/TimestampWireShapeAgreementTests.cs b/tests/Daml.Runtime.Tests/TimestampWireShapeAgreementTests.cs new file mode 100644 index 0000000..3a2db49 --- /dev/null +++ b/tests/Daml.Runtime.Tests/TimestampWireShapeAgreementTests.cs @@ -0,0 +1,75 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json; +using AwesomeAssertions; +using Daml.Runtime.Data; +using Daml.Runtime.Serialization; +using Xunit; + +namespace Daml.Runtime.Tests; + +public class TimestampWireShapeAgreementTests +{ + public static TheoryData ToleratedWireTimestampShapes() => new() + { + { "2026-01-01T12:34:56Z", new DateTimeOffset(2026, 1, 1, 12, 34, 56, TimeSpan.Zero), TimeSpan.Zero }, + { "2026-01-01T12:34:56.123456Z", new DateTimeOffset(2026, 1, 1, 12, 34, 56, TimeSpan.Zero).AddTicks(1_234_560), TimeSpan.Zero }, + { "2026-01-01T12:34:56.1234567Z", new DateTimeOffset(2026, 1, 1, 12, 34, 56, TimeSpan.Zero).AddTicks(1_234_567), TimeSpan.Zero }, + { "2026-01-01T12:34:56+02:00", new DateTimeOffset(2026, 1, 1, 10, 34, 56, TimeSpan.Zero), TimeSpan.Zero }, + { "2026-01-01T12:34:56.123456-05:00", new DateTimeOffset(2026, 1, 1, 17, 34, 56, TimeSpan.Zero).AddTicks(1_234_560), TimeSpan.Zero }, + { "2026-01-01T12:34:56", new DateTimeOffset(2026, 1, 1, 12, 34, 56, TimeSpan.Zero), TimeSpan.Zero }, + }; + + public static TheoryData WireTimestampShapesOutsideTheGrammar() => + [ + "2026-01-01T12:34:56z", + "2026-01-01T12:34:56.12345678Z", + "2026-01-01", + "2026-01-01 12:34:56", + "2026-01-01T12:34Z", + "2026-01-01T12:34:56+02", + "2026-1-1T12:34:56Z", + " 2026-01-01T12:34:56Z", + ]; + + [Theory] + [MemberData(nameof(ToleratedWireTimestampShapes))] + public void ReadRecord_should_decode_every_tolerated_wire_timestamp_shape(string wire, DateTimeOffset expectedInstant, TimeSpan expectedOffset) + { + var record = DamlLfJsonReader.ReadRecord($$"""{"recordedAt":"{{wire}}"}"""); + + var timestamp = record.GetRequiredField("recordedAt").Should().BeOfType().Which.Value; + timestamp.Should().Be(expectedInstant); + timestamp.Offset.Should().Be(expectedOffset); + } + + [Theory] + [MemberData(nameof(ToleratedWireTimestampShapes))] + public void DeserializeRecord_should_decode_every_tolerated_wire_timestamp_shape(string wire, DateTimeOffset expectedInstant, TimeSpan expectedOffset) + { + var record = DamlJsonSerializer.DeserializeRecord($$"""{"recordedAt":"{{wire}}"}"""); + + var timestamp = record.GetRequiredField("recordedAt").Should().BeOfType().Which.Value; + timestamp.Should().Be(expectedInstant); + timestamp.Offset.Should().Be(expectedOffset); + } + + [Theory] + [MemberData(nameof(WireTimestampShapesOutsideTheGrammar))] + public void ReadRecord_should_reject_a_wire_timestamp_outside_the_tolerated_shapes(string wire) + { + var act = () => DamlLfJsonReader.ReadRecord($$"""{"recordedAt":"{{wire}}"}"""); + + act.Should().Throw(); + } + + [Theory] + [MemberData(nameof(WireTimestampShapesOutsideTheGrammar))] + public void DeserializeRecord_should_not_infer_a_timestamp_outside_the_tolerated_shapes(string wire) + { + var record = DamlJsonSerializer.DeserializeRecord($$"""{"recordedAt":"{{wire}}"}"""); + + record.GetRequiredField("recordedAt").Should().NotBeOfType(); + } +} diff --git a/tests/Daml.Runtime.Tests/TransactionResultTests.cs b/tests/Daml.Runtime.Tests/TransactionResultTests.cs index 23060d9..0a2590a 100644 --- a/tests/Daml.Runtime.Tests/TransactionResultTests.cs +++ b/tests/Daml.Runtime.Tests/TransactionResultTests.cs @@ -1,6 +1,8 @@ // Copyright 2026 Peaceful Studio OÜ // SPDX-License-Identifier: Apache-2.0 +using System.Reflection; +using System.Runtime.CompilerServices; using Daml.Runtime.Commands; using Daml.Runtime.Contracts; using Daml.Runtime.Data; @@ -123,7 +125,25 @@ public void CommandId_round_trips_on_the_transaction_result() ArchivedContractIds: [], CommandId: new CommandId("cmd-42")); - result.CommandId.Value.Should().Be("cmd-42"); + result.CommandId?.Value.Should().Be("cmd-42"); + } + + [Fact] + public void CommandId_is_absent_and_readable_when_the_participant_recorded_none() + { + var result = new TransactionResult( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: [], + ArchivedContractIds: [], + CommandId: null); + + var readCommandIdValue = () => result.CommandId?.Value; + + readCommandIdValue.Should().NotThrow( + "an absent command id must be readable rather than detonate on first access"); + readCommandIdValue().Should().BeNull(); + result.CommandId.Should().BeNull(); } [Fact] @@ -134,7 +154,7 @@ public void ExercisedEvents_defaults_to_empty_when_not_set() CompletionOffset: LedgerOffset.At(1), CreatedContracts: [], ArchivedContractIds: [], - CommandId: default); + CommandId: null); result.ExercisedEvents.Should().NotBeNull(); result.ExercisedEvents.Should().BeEmpty(); @@ -161,7 +181,7 @@ public void ExercisedEvents_round_trips_when_initialized() CompletionOffset: LedgerOffset.At(1), CreatedContracts: [], ArchivedContractIds: [], - CommandId: default) + CommandId: null) { ExercisedEvents = [exercised], }; @@ -181,9 +201,9 @@ public void ExercisedEvents_with_expression_preserves_other_fields() var original = new TransactionResult( UpdateId: "u1", CompletionOffset: LedgerOffset.At(5), - CreatedContracts: [new CreatedContract("00a", FooBar.TemplateId, "{}")], + CreatedContracts: [MakeCreated("00a")], ArchivedContractIds: ["00b"], - CommandId: default); + CommandId: null); var exercised = new ExercisedEvent( ContractId: "00c", @@ -229,7 +249,7 @@ public void ExercisedEvent_supports_inherited_interface_choice() [Fact] public void CreatedContract_InterfaceIds_defaults_to_empty_when_not_set() { - var contract = new CreatedContract("00alice", FooBar.TemplateId, "{}"); + var contract = MakeCreated("00alice"); contract.InterfaceIds.Should().NotBeNull(); contract.InterfaceIds.Should().BeEmpty(); @@ -240,10 +260,7 @@ public void CreatedContract_InterfaceIds_round_trips_a_single_interface_id() { var holding = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.HoldingV1", "Holding"); - var contract = new CreatedContract("00alice", FooBar.TemplateId, "{}") - { - InterfaceIds = [holding], - }; + var contract = MakeCreated("00alice") with { InterfaceIds = [holding] }; contract.InterfaceIds.Should().ContainSingle().Which.Should().Be(holding); } @@ -254,16 +271,402 @@ public void CreatedContract_InterfaceIds_round_trips_multiple_interface_ids() var holding = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.HoldingV1", "Holding"); var factory = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.AllocationFactoryV1", "AllocationFactory"); - var contract = new CreatedContract("00alice", FooBar.TemplateId, "{}") - { - InterfaceIds = [holding, factory], - }; + var contract = MakeCreated("00alice") with { InterfaceIds = [holding, factory] }; contract.InterfaceIds.Should().HaveCount(2); contract.InterfaceIds[0].Should().Be(holding); contract.InterfaceIds[1].Should().Be(factory); } + [Fact] + public void CreatedContract_equality_compares_structurally_equal_payloads_as_equal() + { + var first = MakeCreated("00alice", payload: DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))); + var second = MakeCreated("00alice", payload: DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))); + + first.Should().Be(second); + (first == second).Should().BeTrue(); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void CreatedContract_equality_compares_structurally_equal_interface_ids_as_equal() + { + var holding = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.HoldingV1", "Holding"); + var first = MakeCreated("00alice") with { InterfaceIds = new List { holding } }; + var second = MakeCreated("00alice") with { InterfaceIds = new List { holding } }; + + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_interface_ids() + { + var contract = MakeCreated("00alice"); + var withInterface = contract with + { + InterfaceIds = [new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.HoldingV1", "Holding")], + }; + + contract.Should().NotBe(withInterface); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_contract_ids() + { + var first = MakeCreated("00alice"); + var second = first with { ContractId = "00bob" }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_template_ids() + { + var first = MakeCreated("00alice"); + var second = MakeCreated("00alice", templateId: new RuntimeIdentifier("other-pkg", "Other.Mod", "Other")); + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_when_interface_id_order_differs() + { + var holding = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.HoldingV1", "Holding"); + var factory = new RuntimeIdentifier("splice-pkg", "Splice.Api.Token.AllocationFactoryV1", "AllocationFactory"); + var first = MakeCreated("00alice") with { InterfaceIds = [holding, factory] }; + var second = MakeCreated("00alice") with { InterfaceIds = [factory, holding] }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_payloads() + { + var first = MakeCreated("00alice", payload: DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))); + var second = MakeCreated("00alice", payload: DamlRecord.Create(DamlField.Create("owner", new DamlParty("bob")))); + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_deconstructs_into_every_mirrored_field() + { + var payload = DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice"))); + var key = new ContractKey(payload, FooBar.TemplateId); + var createdAt = new DateTimeOffset(2026, 8, 24, 9, 30, 0, TimeSpan.Zero); + var contract = MakeCreated("00alice", payload: payload) with + { + ContractKey = key, + CreatedAt = createdAt, + }; + + var (eventId, contractId, templateId, deconstructed, witnesses, signatories, observers, contractKey, at) = + contract; + + eventId.Should().Be("evt-00alice"); + contractId.Should().Be("00alice"); + templateId.Should().Be(FooBar.TemplateId); + deconstructed.Should().BeSameAs(payload); + witnesses.Should().ContainSingle().Which.Id.Should().Be("alice"); + signatories.Should().ContainSingle().Which.Id.Should().Be("alice"); + observers.Should().BeEmpty(); + contractKey.Should().Be(key); + at.Should().Be(createdAt); + } + + [Fact] + public void CreatedContract_mirrors_TreeEvent_Created_parameter_for_parameter() + { + var mirror = typeof(CreatedContract).GetConstructors().Single().GetParameters(); + var source = typeof(TreeEvent.Created).GetConstructors().Single().GetParameters(); + + mirror.Select(p => p.ParameterType).Should().Equal(source.Select(p => p.ParameterType)); + mirror.Select(p => p.HasDefaultValue).Should().Equal(source.Select(p => p.HasDefaultValue)); + + const string TreeSideCreateArgumentsName = "CreateArguments"; + const string FlattenedSideCreateArgumentsName = "Payload"; + var expectedNamesWithTheOneRenamedSlot = source.Select(p => + p.Name == TreeSideCreateArgumentsName ? FlattenedSideCreateArgumentsName : p.Name); + + mirror.Select(p => p.Name).Should().Equal(expectedNamesWithTheOneRenamedSlot); + } + + [Fact] + public void CreatedContract_mirrors_TreeEvent_Created_in_its_init_only_slots_too() + { + var mirror = InitOnlySlotsOutsideTheConstructor(typeof(CreatedContract)); + var source = InitOnlySlotsOutsideTheConstructor(typeof(TreeEvent.Created)); + + mirror.Should().NotBeEmpty( + "both sides emptying out would make the comparison below pass vacuously"); + mirror.Should().Equal(source); + } + + /// + /// The init-only half of the mirror. The constructor comparison above cannot see these + /// slots, and only fails to + /// compile for a slot it actually forwards — so a new init-only member on one side alone + /// would leave both green while falsifying the field-for-field claim in the doc comments. + /// + private static IReadOnlyList<(string Name, Type Type)> InitOnlySlotsOutsideTheConstructor(Type type) + { + var constructorParameterNames = type.GetConstructors().Single() + .GetParameters() + .Select(parameter => parameter.Name!) + .ToHashSet(StringComparer.Ordinal); + + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => !constructorParameterNames.Contains(property.Name)) + .Where(IsInitOnly) + .OrderBy(property => property.Name, StringComparer.Ordinal) + .Select(property => (property.Name, property.PropertyType)) + .ToList(); + } + + private static bool IsInitOnly(PropertyInfo property) => + property.SetMethod is { } setter + && setter.ReturnParameter.GetRequiredCustomModifiers().Contains(typeof(IsExternalInit)); + + [Fact] + public void CreatedContract_leaves_key_and_created_at_absent_when_not_supplied() + { + var contract = MakeCreated("00alice"); + + contract.ContractKey.Should().BeNull(); + contract.CreatedAt.Should().BeNull(); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_keys() + { + var first = MakeCreated("00alice") with + { + ContractKey = new ContractKey(DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))), + }; + var second = MakeCreated("00alice") with + { + ContractKey = new ContractKey(DamlRecord.Create(DamlField.Create("owner", new DamlParty("bob")))), + }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_compares_structurally_equal_keys_as_equal() + { + var createdAt = new DateTimeOffset(2026, 8, 24, 9, 30, 0, TimeSpan.Zero); + var first = MakeCreated("00alice") with + { + ContractKey = new ContractKey(DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))), + CreatedAt = createdAt, + }; + var second = MakeCreated("00alice") with + { + ContractKey = new ContractKey(DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice")))), + CreatedAt = createdAt, + }; + + first.ContractKey.Should().NotBeSameAs(second.ContractKey); + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_created_at() + { + var first = MakeCreated("00alice") with + { + CreatedAt = new DateTimeOffset(2026, 8, 24, 9, 30, 0, TimeSpan.Zero), + }; + var second = MakeCreated("00alice") with + { + CreatedAt = new DateTimeOffset(2026, 8, 24, 9, 31, 0, TimeSpan.Zero), + }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_event_ids() + { + var first = MakeCreated("00alice"); + var second = first with { EventId = "evt-other" }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_compares_stakeholder_lists_element_wise() + { + var first = MakeCreated("00alice") with + { + WitnessParties = new List { new("alice"), new("bob") }, + Signatories = new List { new("alice") }, + Observers = new List { new("bob") }, + }; + var second = MakeCreated("00alice") with + { + WitnessParties = new List { new("alice"), new("bob") }, + Signatories = new List { new("alice") }, + Observers = new List { new("bob") }, + }; + + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_signatories() + { + var first = MakeCreated("00alice") with { Signatories = [new Party("alice")] }; + var second = MakeCreated("00alice") with { Signatories = [new Party("bob")] }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_when_witness_order_differs() + { + var first = MakeCreated("00alice") with + { + WitnessParties = [new Party("alice"), new Party("bob")], + }; + var second = MakeCreated("00alice") with + { + WitnessParties = [new Party("bob"), new Party("alice")], + }; + + first.Should().NotBe(second); + } + + [Fact] + public void CreatedContract_equality_distinguishes_contracts_with_different_observers() + { + var first = MakeCreated("00alice") with { Observers = [] }; + var second = MakeCreated("00alice") with { Observers = [new Party("bob")] }; + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_compares_created_contracts_element_wise() + { + var first = MakeTransaction(("00alice", FooBar.TemplateId)); + var second = MakeTransaction(("00alice", FooBar.TemplateId)); + + first.CreatedContracts.Should().NotBeSameAs(second.CreatedContracts); + first.Should().Be(second); + (first == second).Should().BeTrue(); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_with_different_created_contracts() + { + var first = MakeTransaction(("00alice", FooBar.TemplateId)); + var second = MakeTransaction(("00bob", FooBar.TemplateId)); + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_when_created_order_differs() + { + var first = MakeTransaction(("00alice", FooBar.TemplateId), ("00bob", FooBar.TemplateId)); + var second = MakeTransaction(("00bob", FooBar.TemplateId), ("00alice", FooBar.TemplateId)); + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_compares_archived_contract_ids_element_wise() + { + var first = MakeResult(archived: new List { "00a", "00b" }); + var second = MakeResult(archived: new List { "00a", "00b" }); + + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_when_archived_order_differs() + { + var first = MakeResult(archived: ["00a", "00b"]); + var second = MakeResult(archived: ["00b", "00a"]); + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_compares_ExercisedEvents_by_list_content_not_by_list_identity() + { + var first = MakeResult(exercised: new List { MakeExercised("DoThing") }); + var second = MakeResult(exercised: new List { MakeExercised("DoThing") }); + + first.ExercisedEvents.Should().NotBeSameAs(second.ExercisedEvents); + first.ExercisedEvents[0].Should().NotBeSameAs(second.ExercisedEvents[0]); + first.Should().Be(second); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + + [Fact] + public void TransactionResult_equality_distinguishes_ExercisedEvents_lists_of_different_length() + { + var first = MakeResult(exercised: new List { MakeExercised("DoThing") }); + var second = MakeResult( + exercised: new List { MakeExercised("DoThing"), MakeExercised("DoThing") }); + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_when_exercised_order_differs() + { + var doThing = MakeExercised("DoThing"); + var doOther = MakeExercised("DoOther"); + var first = MakeResult(exercised: new List { doThing, doOther }); + var second = MakeResult(exercised: new List { doOther, doThing }); + + first.Should().NotBe(second); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_with_different_update_ids() + { + var result = MakeResult(); + + (result with { UpdateId = "u2" }).Should().NotBe(result); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_with_different_completion_offsets() + { + var result = MakeResult(); + + (result with { CompletionOffset = LedgerOffset.At(99) }).Should().NotBe(result); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_with_different_command_ids() + { + var result = MakeResult(); + + (result with { CommandId = new CommandId("cmd-1") }).Should().NotBe(result); + (result with { CommandId = new CommandId("cmd-1") }) + .Should().NotBe(result with { CommandId = new CommandId("cmd-2") }); + } + + [Fact] + public void TransactionResult_equality_distinguishes_results_with_different_archived_contract_ids() + { + var first = MakeResult(archived: ["00a"]); + var second = MakeResult(archived: ["00b"]); + + first.Should().NotBe(second); + } + [Fact] public void Single_matches_created_contract_via_interface_view() { @@ -319,19 +722,57 @@ public void TrySingle_throws_when_two_contracts_match_by_interface_view() act.Should().Throw(); } + private static CreatedContract MakeCreated( + string contractId, + RuntimeIdentifier? templateId = null, + DamlRecord? payload = null) => + new( + EventId: $"evt-{contractId}", + ContractId: contractId, + TemplateId: templateId ?? FooBar.TemplateId, + Payload: payload ?? DamlRecord.Create(), + WitnessParties: [new Party("alice")], + Signatories: [new Party("alice")], + Observers: []); + + private static TransactionResult MakeResult( + IReadOnlyList? archived = null, + IReadOnlyList? exercised = null) => + new( + UpdateId: "u1", + CompletionOffset: LedgerOffset.At(1), + CreatedContracts: [], + ArchivedContractIds: archived ?? [], + CommandId: null) + { + ExercisedEvents = exercised ?? [], + }; + + private static ExercisedEvent MakeExercised(string choiceName) => + new( + ContractId: "00c", + TemplateId: FooBar.TemplateId, + InterfaceId: null, + ChoiceName: choiceName, + ChoiceArgument: DamlUnit.Instance, + ExerciseResult: DamlUnit.Instance, + Consuming: true, + ActingParties: [new Party("alice")], + WitnessParties: [new Party("alice")]); + private static TransactionResult MakeTransaction(params (string ContractId, RuntimeIdentifier TemplateId)[] created) { var contracts = new List(); foreach (var (cid, tid) in created) { - contracts.Add(new CreatedContract(cid, tid, "{}")); + contracts.Add(MakeCreated(cid, tid)); } return new TransactionResult( UpdateId: "u1", CompletionOffset: LedgerOffset.At(1), CreatedContracts: contracts, ArchivedContractIds: [], - CommandId: default); + CommandId: null); } private static TransactionResult MakeTransactionWithInterfaces( @@ -340,14 +781,14 @@ private static TransactionResult MakeTransactionWithInterfaces( var contracts = new List(); foreach (var (cid, tid, interfaceIds) in created) { - contracts.Add(new CreatedContract(cid, tid, "{}") { InterfaceIds = interfaceIds }); + contracts.Add(MakeCreated(cid, tid) with { InterfaceIds = interfaceIds }); } return new TransactionResult( UpdateId: "u1", CompletionOffset: LedgerOffset.At(1), CreatedContracts: contracts, ArchivedContractIds: [], - CommandId: default); + CommandId: null); } private sealed record FooBar(string Owner) : ITemplate diff --git a/tests/Daml.Runtime.Tests/TransactionTreeTests.cs b/tests/Daml.Runtime.Tests/TransactionTreeTests.cs index 0fceec0..2e41962 100644 --- a/tests/Daml.Runtime.Tests/TransactionTreeTests.cs +++ b/tests/Daml.Runtime.Tests/TransactionTreeTests.cs @@ -15,7 +15,7 @@ public class TransactionTreeTests private static readonly RuntimeIdentifier FooTemplateId = new("test-pkg", "Acme.Foo", "FooBar"); [Fact] - public void root_events_preserve_transaction_order() + public void TransactionTree_root_events_preserve_transaction_order() { var first = MakeCreated("00first"); var second = MakeCreated("00second"); @@ -28,7 +28,7 @@ public void root_events_preserve_transaction_order() } [Fact] - public void child_events_are_reachable_from_exercised_node() + public void TransactionTree_child_events_are_reachable_from_exercised_node() { var child = MakeCreated("00child"); var exercise = MakeExercised("00parent", children: [child]); @@ -37,7 +37,7 @@ public void child_events_are_reachable_from_exercised_node() } [Fact] - public void descendant_events_is_empty_for_created_event() + public void DescendantEvents_is_empty_for_created_event() { var created = MakeCreated("00solo"); @@ -45,7 +45,7 @@ public void descendant_events_is_empty_for_created_event() } [Fact] - public void descendant_events_is_empty_for_exercised_event_without_children() + public void DescendantEvents_is_empty_for_exercised_event_without_children() { var exercise = MakeExercised("00leaf", children: []); @@ -53,7 +53,7 @@ public void descendant_events_is_empty_for_exercised_event_without_children() } [Fact] - public void descendant_events_enumerates_nested_children_depth_first() + public void DescendantEvents_enumerates_nested_children_depth_first() { var grandchild = MakeCreated("00grandchild"); var innerExercise = MakeExercised("00inner", children: [grandchild]); @@ -69,7 +69,7 @@ public void descendant_events_enumerates_nested_children_depth_first() } [Fact] - public void descendant_events_handles_deeply_nested_trees_without_stack_overflow() + public void DescendantEvents_handles_deeply_nested_trees_without_stack_overflow() { const int depth = 5000; var leaf = MakeCreated("leaf"); @@ -88,7 +88,7 @@ public void descendant_events_handles_deeply_nested_trees_without_stack_overflow } [Fact] - public void descendant_events_enumerates_many_siblings_in_declared_order() + public void DescendantEvents_enumerates_many_siblings_in_declared_order() { var children = Enumerable.Range(0, 50) .Select(i => (TreeEvent)MakeCreated($"00child-{i:D2}")) @@ -101,7 +101,7 @@ public void descendant_events_enumerates_many_siblings_in_declared_order() } [Fact] - public void descendant_events_preserves_pre_order_across_branching_and_depth() + public void DescendantEvents_preserves_pre_order_across_branching_and_depth() { const int chainLength = 200; TreeEvent tree = MakeCreated("leaf"); @@ -135,7 +135,7 @@ private static List ExpectedPreOrder(IReadOnlyList events) } [Fact] - public void all_events_enumerates_roots_and_descendants_in_pre_order() + public void AllEvents_enumerates_roots_and_descendants_in_pre_order() { var child = MakeCreated("00child"); var rootExercise = MakeExercised("00root-exercise", children: [child]); @@ -151,7 +151,7 @@ public void all_events_enumerates_roots_and_descendants_in_pre_order() } [Fact] - public void all_events_throws_when_tree_is_null() + public void AllEvents_throws_when_tree_is_null() { TransactionTree tree = null!; @@ -161,7 +161,7 @@ public void all_events_throws_when_tree_is_null() } [Fact] - public void to_transaction_result_flattens_created_events_with_serialized_payload() + public void ToTransactionResult_flattens_created_events_with_the_create_arguments_as_payload() { var created = MakeCreated("00alice"); var tree = new TransactionTree("u1", LedgerOffset.At(5), [created]); @@ -173,21 +173,21 @@ public void to_transaction_result_flattens_created_events_with_serialized_payloa result.CreatedContracts.Should().ContainSingle(); result.CreatedContracts[0].ContractId.Should().Be("00alice"); result.CreatedContracts[0].TemplateId.Should().Be(FooTemplateId); - result.CreatedContracts[0].Payload.Should().Contain("alice"); + result.CreatedContracts[0].Payload.Should().BeSameAs(created.CreateArguments); } [Fact] - public void to_transaction_result_defaults_command_id_since_tree_carries_none() + public void ToTransactionResult_leaves_command_id_absent_since_tree_carries_none() { var tree = new TransactionTree("u1", LedgerOffset.At(1),[MakeCreated("00alice")]); var result = tree.ToTransactionResult(); - result.CommandId.Should().Be(default(CommandId)); + result.CommandId.Should().BeNull(); } [Fact] - public void to_transaction_result_preserves_interface_ids_on_created_contracts() + public void ToTransactionResult_preserves_interface_ids_on_created_contracts() { var interfaceId = new RuntimeIdentifier("test-pkg", "Acme.Foo", "IAsset"); var created = MakeCreated("00iface") with { InterfaceIds = [interfaceId] }; @@ -200,7 +200,26 @@ public void to_transaction_result_preserves_interface_ids_on_created_contracts() } [Fact] - public void to_transaction_result_flattens_nested_exercised_events_in_pre_order() + public void ToTransactionResult_forwards_every_created_field_to_the_flattened_contract() + { + var key = new ContractKey(DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice"))), FooTemplateId); + var createdAt = new DateTimeOffset(2026, 8, 24, 9, 30, 0, TimeSpan.Zero); + var created = MakeCreated("00alice") with { ContractKey = key, CreatedAt = createdAt }; + var tree = new TransactionTree("u1", LedgerOffset.At(5), [created]); + + var result = tree.ToTransactionResult(); + + var flattened = result.CreatedContracts.Should().ContainSingle().Which; + flattened.EventId.Should().Be("evt-00alice"); + flattened.WitnessParties.Should().Equal(new Party("alice"), new Party("bob"), new Party("carol")); + flattened.Signatories.Should().Equal(new Party("alice")); + flattened.Observers.Should().Equal(new Party("bob")); + flattened.ContractKey.Should().Be(key); + flattened.CreatedAt.Should().Be(createdAt); + } + + [Fact] + public void ToTransactionResult_flattens_nested_exercised_events_in_pre_order() { var childCreate = MakeCreated("00child"); var innerExercise = MakeExercised("00inner", children: [childCreate], choiceName: "Inner"); @@ -213,7 +232,7 @@ public void to_transaction_result_flattens_nested_exercised_events_in_pre_order( } [Fact] - public void to_transaction_result_collects_archived_contract_ids_from_consuming_exercises() + public void ToTransactionResult_collects_archived_contract_ids_from_consuming_exercises() { var consuming = MakeExercised("00consumed", children: [], consuming: true); var nonConsuming = MakeExercised("00untouched", children: [], consuming: false); @@ -225,7 +244,7 @@ public void to_transaction_result_collects_archived_contract_ids_from_consuming_ } [Fact] - public void to_transaction_result_throws_when_tree_is_null() + public void ToTransactionResult_throws_when_tree_is_null() { TransactionTree tree = null!; @@ -240,9 +259,9 @@ private static TreeEvent.Created MakeCreated(string contractId) => ContractId: contractId, TemplateId: FooTemplateId, CreateArguments: DamlRecord.Create(DamlField.Create("owner", new DamlParty("alice"))), - WitnessParties: [new Party("alice")], + WitnessParties: [new Party("alice"), new Party("bob"), new Party("carol")], Signatories: [new Party("alice")], - Observers: []); + Observers: [new Party("bob")]); private static TreeEvent.Exercised MakeExercised( string contractId, diff --git a/tests/Daml.Runtime.Tests/TransportFailureShapeAgreementTests.cs b/tests/Daml.Runtime.Tests/TransportFailureShapeAgreementTests.cs new file mode 100644 index 0000000..69818e4 --- /dev/null +++ b/tests/Daml.Runtime.Tests/TransportFailureShapeAgreementTests.cs @@ -0,0 +1,168 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using AwesomeAssertions; +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using Daml.Runtime.Outcomes; +using Daml.Runtime.Streams; +using Xunit; +using RuntimeIdentifier = Daml.Runtime.Data.Identifier; + +namespace Daml.Runtime.Tests; + +/// +/// Pins the transport-failure shape — a transport status code, a message, a category and the +/// source exception — that AcsSnapshotEntry<T>.StreamError, +/// ContractStreamEvent<T>.StreamError, +/// InterfaceAcsSnapshotEntry<TInterface, TView>.StreamError, +/// InterfaceStreamEvent<TInterface, TView>.StreamError and +/// ExerciseOutcome<T>.InfraError each declare for themselves. Every shape +/// compared here is read out of the declaration by reflection, so renaming, retyping, +/// reordering or adding a parameter on one declaration alone fails this class instead of +/// letting the five drift apart. +/// +public class TransportFailureShapeAgreementTests +{ + private const string VacuityReason = + "an empty shape would let every agreement assertion in this class pass without comparing anything"; + + private static readonly Type SnapshotStreamError = typeof(AcsSnapshotEntry.StreamError); + private static readonly Type SubscriptionStreamError = typeof(ContractStreamEvent.StreamError); + private static readonly Type OutcomeInfraError = typeof(ExerciseOutcome.InfraError); + private static readonly Type InterfaceSnapshotStreamError = + typeof(InterfaceAcsSnapshotEntry.StreamError); + private static readonly Type InterfaceSubscriptionStreamError = + typeof(InterfaceStreamEvent.StreamError); + + [Fact] + public void TransportFailureShapeAgreement_the_snapshot_and_subscription_variants_declare_one_shape() + { + DeclaredShape(SnapshotStreamError).Should().Be( + DeclaredShape(SubscriptionStreamError), + "the snapshot and the subscription StreamError carry the same transport failure, so a parameter " + + "renamed, retyped, reordered or added on one of them has to be mirrored on the other"); + } + + [Fact] + public void TransportFailureShapeAgreement_the_interface_family_variants_declare_that_same_shape() + { + DeclaredShape(InterfaceSnapshotStreamError).Should().Be( + DeclaredShape(SnapshotStreamError), + "an interface subscription reports a transport failure with the same shape a template " + + "subscription does, so a consumer handling both families reads one vocabulary"); + DeclaredShape(InterfaceSubscriptionStreamError).Should().Be( + DeclaredShape(SubscriptionStreamError), + "the live interface stream carries the same transport failure shape"); + } + + [Fact] + public void TransportFailureShapeAgreement_the_outcome_variant_opens_with_that_same_shape() + { + DeclaredShape(OutcomeInfraError).Should().StartWith( + DeclaredShape(SubscriptionStreamError), + "the infrastructure-failure outcome leads with the same transport failure ahead of its own extra " + + "parameters, so renaming, retyping or reordering the leading ones on either side alone has to fail"); + } + + [Fact] + public void TransportFailureShapeAgreement_every_stream_variant_ends_with_the_source_exception() + { + var sourceException = + $"{typeof(Exception)}? {nameof(ExerciseOutcome.InfraError.SourceException)}|"; + + const string reason = + "a stream consumer deciding retry policy needs the transport exception the write path " + + "already hands it, carried last so the parameter order matches the outcome variant"; + + DeclaredShape(SnapshotStreamError).Should().EndWith(sourceException, reason); + DeclaredShape(SubscriptionStreamError).Should().EndWith(sourceException, reason); + DeclaredShape(InterfaceSnapshotStreamError).Should().EndWith(sourceException, reason); + DeclaredShape(InterfaceSubscriptionStreamError).Should().EndWith(sourceException, reason); + } + + [Fact] + public void TransportFailureShapeAgreement_a_renamed_parameter_stops_agreeing() + { + var renamedDeclaration = new StatusCodeRenamed(14, "unavailable").GetType(); + + DeclaredShape(renamedDeclaration).Should().NotBe( + DeclaredShape(SnapshotStreamError), + "renaming a single parameter has to break the comparison, otherwise the agreement assertions " + + "would hold against a one-sided edit and catch nothing"); + } + + [Fact] + public void TransportFailureShapeAgreement_a_parameter_name_that_extends_a_pinned_one_stops_agreeing() + { + var extendedDeclaration = new MessageRenamedToMessageDetail(14, "unavailable").GetType(); + + DeclaredShape(extendedDeclaration).Should().NotStartWith( + DeclaredShape(SnapshotStreamError), + "a parameter name that merely extends the pinned one has to fall outside the prefix comparison, " + + "otherwise a one-sided rename would slip past the outcome-variant assertion"); + } + + [Fact] + public void TransportFailureShapeAgreement_reads_a_non_empty_shape_from_every_declaration() + { + DeclaredShape(SnapshotStreamError).Should().NotBeEmpty(VacuityReason); + DeclaredShape(SubscriptionStreamError).Should().NotBeEmpty(VacuityReason); + DeclaredShape(OutcomeInfraError).Should().NotBeEmpty(VacuityReason); + } + + private static string DeclaredShape(Type declaration) + { + var nullability = new NullabilityInfoContext(); + + return string.Concat(declaration.GetConstructors() + .OrderByDescending(constructor => constructor.GetParameters().Length) + .First() + .GetParameters() + .Select(parameter => + $"{parameter.ParameterType}{NullableMarker(nullability, parameter)} {parameter.Name}|")); + } + + private static string NullableMarker(NullabilityInfoContext nullability, ParameterInfo parameter) => + nullability.Create(parameter).WriteState == NullabilityState.Nullable ? "?" : string.Empty; + + private sealed record StatusCodeRenamed(int Status, string Message); + + private sealed record MessageRenamedToMessageDetail( + int StatusCode, + string MessageDetail, + DamlErrorCategory? Category = null, + Exception? SourceException = null); + + private sealed record Probe : ITemplate, IDamlRecord + { + public static RuntimeIdentifier TemplateId { get; } = new("pkg", "M", "Probe"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(TemplateId, DamlTypeKind.Template, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + public static Probe FromRecord(DamlRecord record) => new(); + } + + private sealed record ProbeInterface : IDamlInterface, IHasView + { + public static RuntimeIdentifier InterfaceId { get; } = new("pkg", "M", "ProbeInterface"); + public static string PackageId => "pkg"; + public static string PackageName => "probe"; + public static Version PackageVersion { get; } = new(0, 1, 0); + public static DamlTypeDescriptor DamlTypeId { get; } = + new(InterfaceId, DamlTypeKind.Interface, PackageName); + + public DamlRecord ToRecord() => DamlRecord.Create(); + } + + private sealed record ProbeView : IDamlRecord + { + public DamlRecord ToRecord() => DamlRecord.Create(); + public static ProbeView FromRecord(DamlRecord record) => new(); + } +} diff --git a/tests/Daml.Runtime.Tests/ViewDescriptorTests.cs b/tests/Daml.Runtime.Tests/ViewDescriptorTests.cs new file mode 100644 index 0000000..229deb1 --- /dev/null +++ b/tests/Daml.Runtime.Tests/ViewDescriptorTests.cs @@ -0,0 +1,88 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using Daml.Runtime.Contracts; +using Daml.Runtime.Data; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Runtime.Tests; + +/// +/// Tests for : the pure type witness a +/// generated interface marker exposes as its static View property, pairing the +/// marker with its view record so generic call sites infer both type parameters from +/// one argument. +/// +public class ViewDescriptorTests +{ + private const string TestPackageId = "witness-package-id"; + private const string TestPackageName = "witness-package"; + private const string TestModuleName = "Witness.Module"; + + private interface ITestHolding : IDamlInterface, IHasView + { + static Identifier IDamlInterface.InterfaceId => new(TestPackageId, TestModuleName, "TestHolding"); + static string IDamlInterface.PackageId => TestPackageId; + static string IDamlInterface.PackageName => TestPackageName; + static Version IDamlInterface.PackageVersion => new(1, 0, 0); + static DamlTypeDescriptor global::Daml.Runtime.IDamlType.DamlTypeId => + new(new Identifier(TestPackageId, TestModuleName, "TestHolding"), DamlTypeKind.Interface, TestPackageName); + + static ViewDescriptor View { get; } = new(); + + decimal Amount { get; } + } + + private sealed record TestHoldingView(decimal Amount) : ITestHolding, IDamlRecord + { + public DamlRecord ToRecord() => + DamlRecord.Create(DamlField.Create("amount", new DamlNumeric(Amount))); + + public static TestHoldingView FromRecord(DamlRecord record) => + new(record.GetRequiredField("amount").As().Value); + } + + private static (Type Interface, Type View) InferredPair(ViewDescriptor descriptor) + where TInterface : IDamlInterface, IHasView + where TView : IDamlRecord + { + descriptor.Should().NotBeNull(); + return (typeof(TInterface), typeof(TView)); + } + + private static DamlTypeDescriptor IdentityOf() where T : IDamlType => T.DamlTypeId; + + [Fact] + public void ViewDescriptor_infers_both_type_parameters_from_the_marker_witness() + { + var pair = InferredPair(ITestHolding.View); + + pair.Should().Be((typeof(ITestHolding), typeof(TestHoldingView))); + } + + [Fact] + public void ViewDescriptor_view_record_answers_with_the_interface_identity() + { + var identity = IdentityOf(); + + identity.Should().Be(IdentityOf()); + identity.Kind.Should().Be(DamlTypeKind.Interface); + } + + [Fact] + public void ViewDescriptor_view_fields_are_readable_through_a_marker_typed_variable() + { + ITestHolding marker = new TestHoldingView(42.5m); + + marker.Amount.Should().Be(42.5m); + } + + [Fact] + public void ViewDescriptor_view_record_round_trips_through_the_record_facet() + { + var record = new TestHoldingView(7.25m).ToRecord(); + + DamlRecordFacet.Materialize(record).Should().Be(new TestHoldingView(7.25m)); + } +} diff --git a/tests/Daml.Runtime.Tests/WitnessPartiesTypedTests.cs b/tests/Daml.Runtime.Tests/WitnessPartiesTypedTests.cs index 51ddf99..e68f3a7 100644 --- a/tests/Daml.Runtime.Tests/WitnessPartiesTypedTests.cs +++ b/tests/Daml.Runtime.Tests/WitnessPartiesTypedTests.cs @@ -20,7 +20,8 @@ public void Created_WitnessParties_should_be_Party_list() { var ev = new ContractStreamEvent.Created( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(1), new SynchronizerId("sync"), [Alice, Bob]); @@ -64,7 +65,8 @@ public void Assigned_WitnessParties_should_be_Party_list() { var ev = new ContractStreamEvent.Assigned( new ContractId("c1"), - DamlRecord.Create(), + new TestTemplate("alice"), + null, LedgerOffset.At(4), new SynchronizerId("src"), new SynchronizerId("tgt"), @@ -92,7 +94,7 @@ public void Unassigned_WitnessParties_should_be_Party_list() ev.WitnessParties.Should().ContainSingle().Which.Should().Be(Alice); } - private sealed record TestTemplate(string Owner) : ITemplate + private sealed record TestTemplate(string Owner) : ITemplate, IDamlRecord { public static Identifier TemplateId { get; } = new("pkg", "M", "TestTemplate"); public static string PackageId => "pkg"; @@ -100,6 +102,9 @@ private sealed record TestTemplate(string Owner) : ITemplate public static Version PackageVersion { get; } = new(0, 1, 0); public static DamlTypeDescriptor DamlTypeId { get; } = new(TemplateId, DamlTypeKind.Template, PackageName); - public DamlRecord ToRecord() => DamlRecord.Create(); + public DamlRecord ToRecord() => DamlRecord.Create(new DamlField("owner", new DamlText(Owner))); + + public static TestTemplate FromRecord(DamlRecord record) => + new((record.GetField("owner") as DamlText)?.Value ?? string.Empty); } } diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index b8d5b39..58412c3 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -2,10 +2,26 @@ Exe - $(NoWarn);CS1591 + + false + + + $(NoWarn);CA1707;CA1822;NU1603 true --coverage --coverage-output-format cobertura --coverage-output $(MSBuildProjectName).cobertura.xml --coverage-settings $(MSBuildThisFileDirectory)../coverage.settings.xml + + + diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index dfd45e9..0c77203 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -1,12 +1,14 @@ true + true - - - - - + + + + + + diff --git a/tests/Shared/TestNamingConventionTests.cs b/tests/Shared/TestNamingConventionTests.cs new file mode 100644 index 0000000..8fd5ae8 --- /dev/null +++ b/tests/Shared/TestNamingConventionTests.cs @@ -0,0 +1,51 @@ +// Copyright 2026 Peaceful Studio OÜ +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using System.Text.RegularExpressions; +using AwesomeAssertions; +using Xunit; + +namespace Daml.Testing.Conventions; + +/// +/// Compiled into every test project from tests/Shared, so each assembly +/// lints its own test method names against +/// PascalCaseSubject_snake_case_description. +/// +public class TestNamingConventionTests +{ + private static readonly Regex PascalCaseSubjectThenDescription = + new(@"^[A-Z][A-Za-z0-9]*(?:_[A-Za-z0-9]+)+$", RegexOptions.CultureInvariant); + + [Fact] + public void TestNamingConvention_every_test_method_leads_with_a_PascalCase_subject() + { + var offenders = TestMethodNames() + .Where(name => !PascalCaseSubjectThenDescription.IsMatch(name.Split('.')[^1])) + .ToList(); + + offenders.Should().BeEmpty( + "a test name is PascalCaseSubject_snake_case_description — the subject is the member or type under " + + "test, or the test class name minus its Tests suffix, and at least one underscore-separated clause " + + "describes the scenario"); + } + + [Fact] + public void TestNamingConvention_lints_a_non_empty_set_of_test_methods() + { + TestMethodNames().Should().NotBeEmpty( + "a lint that discovers no test methods passes vacuously and would never catch a regression"); + } + + private static IReadOnlyList TestMethodNames() => + Assembly.GetExecutingAssembly() + .GetTypes() + .SelectMany(type => type.GetMethods( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + .Where(method => method.GetCustomAttributes(inherit: true).Any()) + .Select(method => $"{method.DeclaringType!.FullName}.{method.Name}") + .Distinct(StringComparer.Ordinal) + .Order(StringComparer.Ordinal) + .ToList(); +} diff --git a/tests/wire-samples/README.md b/tests/wire-samples/README.md new file mode 100644 index 0000000..5e6017f --- /dev/null +++ b/tests/wire-samples/README.md @@ -0,0 +1,122 @@ + + + +# LF-JSON wire samples + +Raw responses from a live Canton participant, captured so that decoder work is +built against observed encodings rather than against a specification or a +remembered table. Every file under `data/` is a verbatim server response plus +the Daml type it came from and the request that produced it. + +Captured 2026-07-31 against canton-localnet (Splice 0.6.14, Canton **3.5.10**), +`a-validator-1` slot, from a DAR built on Daml SDK **3.5.2** targeting +**Daml-LF 2.3**. + +## Why LF 2.3 + +The capture model targets LF 2.3 rather than the 2.1 the shipped fixtures use, +because contract keys require LF >= 2.3 — on 2.1 the compiler rejects them with +`Contract Keys not supported on current lf version (2.1), feature supported in +from 2.3`. Capturing at 2.3 covers the key-bearing shapes as well; it does not +change the `--target=2.1` pin that `conformance/` and `samples/` build with. + +The table below is nonetheless authoritative for the LF 2.1 code this repo +ships. The whole model was rebuilt at `--target=2.1` (minus `Keyed`, which 2.1 +cannot express), recaptured against the same participant, and diffed field by +field against the 2.3 set: **every encoding is identical**. The only difference +across the two capture sets is the `Keyed` contract's presence in the 2.3 ACS. +`contractKey` and `contractKeyHash` appear only on the key-bearing contract, not +on unkeyed ones, in either version. Scope: this covers the shapes the capture +model expresses; it is not a proof that no 2.3-only encoding exists for a type +the model omits. + +## Layout + +| Path | What | +|---|---| +| `daml/` | The capture model — one template per shape family | +| `data/` | The captures | + +## Observed encodings + +| Daml type | Wire form | Sample | +|---|---|---| +| `Int64` | JSON **string** | `"42"`, `"-1"` | +| `Numeric n` | string at the **declared** scale | `Numeric 10` -> `"42.5000000000"`; `Numeric 2` -> `"1.25"` | +| `Text` | string | `"hello"`, `""` | +| `Bool` | JSON boolean | `true` | +| `Date` | string | `"2026-07-31"` | +| `Time` | string, **6 fractional digits or none** | `"2026-07-31T12:34:56.123456Z"`, `"2026-07-31T12:34:56Z"` | +| `Party`, `ContractId a` | string | `"wire87…::1220…"` | +| `Optional a` (`a` not optional) | `Some` is the **bare value**, `None` is `null` | `"present"` / `null` | +| `Optional (Optional a)` | one array level **per** `Optional` | `None` -> `[]`, `Some None` -> `[[]]`, `Some (Some "deep")` -> `[["deep"]]` | +| `[a]` | array | `["x","y"]`, `[]` | +| `TextMap a` | object | `{"a":"1"}`, `{}` | +| `Map k v` (GenMap) | array of `[key, value]` pairs | `[["party::1220…","7"]]`, `[]` | +| `()` | empty object | `{}` | +| record | object keyed by field name | `{"nickname":"nick","level":"3"}` | +| variant, tagged arm | `{"tag":…,"value":…}` | `{"tag":"Win","value":{"prize":"1.25","tier":"gold"}}` | +| variant, nullary arm | `value` is an **empty object** | `{"tag":"Pending","value":{}}` | +| enum | **bare string** | `"Hearts"` | + +Interface views arrive as `CreatedEvent.interfaceViews[]`, each entry carrying +`interfaceId`, `viewStatus`, `implementationPackageId`, and the record itself +under `viewValue` (`data/acs_interface_view_holding.json`). + +Contract keys arrive as `CreatedEvent.contractKey` alongside a base64 +`contractKeyHash`. A tuple key encodes as a **record**, not an array: +`{"_1":"party::1220…","_2":"alpha"}` (`data/create_keyed_contract_key.json`). + +On the exercise path, `ExercisedEvent.choiceArgument` is the argument record +with no wrapper, and `exerciseResult` follows the ordinary encoding for its +type — a `ContractId` result is a string, a variant result is `{"tag":…}`, and a +`Unit` result is `{}`. A no-argument choice sends `{}`, an **empty record** +rather than a unit value. + +One finding that removes work rather than adds it, and one the codegen now +depends on: + +- **`verbose` does not change the payload.** Requesting the same ACS with + `verbose: true` and `verbose: false` returns byte-identical events; only the + opaque `streamContinuationToken` differs. There is no verbose-form variant for + a reader to handle. (`data/acs_wildcard_verbose_{true,false}.json`) +- **Nested `Optional` reaches this repo's codegen, and the array encoding is + what makes it representable.** A Daml `Optional (Optional a)` generates an + `Optional>`, and every level *of that chain* is written and read + in the array form the table records. The rule is scoped to the chain, not to + the runtime at large: a single-level `Optional a` keeps the flat + `null`-or-bare-value form, and so does an `Optional` separated from another by + an intervening type, such as `Optional (Box (Optional Text))`. The two travel + as different wire nodes for that reason. + `data/probe_nested_optional_matrix.json` is the pin: one create per candidate + encoding, showing the participant accept `[]`, `[[]]` and `[["deep"]]` and + reject `null`, `[null]` and `["deep"]` with HTTP 500. `[null]` is the one to + watch — it is the mixed form, array at the outer level and flat at the inner, + which is what an encoder taught the chain encoding at only one level emits for + `Some None`. It looks plausible, and the participant refuses it. + +## Recapture + +The scripted rig that produced these files was removed when this branch was +reworked down to evidence. It stays retrievable: + +```bash +git fetch origin pull/728/head +git checkout bda87db -- tests/wire-samples/capture/ +``` + +A recapture allocates a fresh party, so contract ids, party ids and offsets +differ from the ones recorded here — the encodings are what these files pin, +not the identifiers. + +To redo the LF 2.1 comparison, copy `daml/` with `--target=2.1` in `daml.yaml`, +a distinct `name:`, and the `Keyed` template deleted; build it; then run the rig +with `CAPTURE_DAR`, `CAPTURE_PKG` and `CAPTURE_OUT` pointed at the 2.1 artifacts +and a scratch output directory. Compare the two sets by walking both JSON trees +and asserting leaf values under `createArgument`, `choiceArgument`, +`exerciseResult`, `contractKey` and `viewValue` — party ids and contract ids +aside — while comparing only shape elsewhere, since offsets, update ids, record +times and package hashes differ by construction. Group active-contract entries +by qualified template name first; ACS ordering is not guaranteed. Validate any +such comparison against a deliberately mutated copy before trusting a clean run, +or a silently vacuous walk will report agreement it never checked. diff --git a/tests/wire-samples/daml/WireTypes.daml b/tests/wire-samples/daml/WireTypes.daml new file mode 100644 index 0000000..17b82ae --- /dev/null +++ b/tests/wire-samples/daml/WireTypes.daml @@ -0,0 +1,96 @@ +-- Copyright 2026 Peaceful Studio OÜ +-- SPDX-License-Identifier: Apache-2.0 +module WireTypes where + +import DA.Map (Map) +import DA.TextMap (TextMap) + +data Profile = Profile + with + nickname : Text + level : Int + deriving (Eq, Show) + +data Outcome = Win with prize : Numeric 2; tier : Text + | Pending + deriving (Eq, Show) + +data Suit = Clubs | Diamonds | Hearts | Spades + deriving (Eq, Show) + +template Marker + with + owner : Party + where + signatory owner + +data HoldingView = HoldingView + with + amount : Decimal + deriving (Eq, Show) + +interface Holding where + viewtype HoldingView + +template Asset + with + issuer : Party + amount : Decimal + where + signatory issuer + + interface instance Holding for Asset where + view = HoldingView with amount + +template Keyed + with + owner : Party + label : Text + where + signatory owner + key (owner, label) : (Party, Text) + maintainer key._1 + +template WireRecord + with + owner : Party + count : Int + amount : Numeric 10 + label : Text + active : Bool + asOf : Date + observedAt : Time + note : Optional Text + nestedNote : Optional (Optional Text) + tags : [Text] + attributes : TextMap Text + genMap : Map Party Int + unitField : () + marker : ContractId Marker + holdingCid : ContractId Holding + holdingCids : [ContractId Holding] + profile : Profile + outcome : Outcome + suit : Suit + fee : Numeric 2 + where + signatory owner + + nonconsuming choice Relabel : ContractId WireRecord + with + newLabel : Text + controller owner + do + create this with label = newLabel + + nonconsuming choice Describe : Outcome + with + probe : Optional Int + controller owner + do + pure outcome + + nonconsuming choice Ping : () + controller owner + do + pure () diff --git a/tests/wire-samples/daml/daml.yaml b/tests/wire-samples/daml/daml.yaml new file mode 100644 index 0000000..7343528 --- /dev/null +++ b/tests/wire-samples/daml/daml.yaml @@ -0,0 +1,12 @@ +# Copyright 2026 Peaceful Studio OÜ +# SPDX-License-Identifier: Apache-2.0 +sdk-version: 3.5.2 +name: wiretypes23 +source: . +version: 0.0.1 +dependencies: + - daml-prim + - daml-stdlib +build-options: + - --target=2.3 + - -Wno-upgrade-interfaces diff --git a/tests/wire-samples/data/acs_interface_view_holding.json b/tests/wire-samples/data/acs_interface_view_holding.json new file mode 100644 index 0000000..d09edc5 --- /dev/null +++ b/tests/wire-samples/data/acs_interface_view_holding.json @@ -0,0 +1,150 @@ +{ + "damlType": "WireTypes:Holding interface view \u2014 HoldingView { amount : Decimal } \u2014 over WireTypes:Asset", + "capturedBy": "POST /v2/state/active-contracts with an InterfaceFilter, includeInterfaceView=true; see CreatedEvent.interfaceViews", + "response": [ + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6045, + "nodeId": 0, + "contractId": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "10.0000000000" + }, + "createdEventBlob": "", + "interfaceViews": [ + { + "interfaceId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Holding", + "viewStatus": { + "code": 0, + "message": "", + "details": [] + }, + "viewValue": { + "amount": "10.0000000000" + }, + "implementationPackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200" + } + ], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.895701Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImg0SBOGyHpA=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6048, + "nodeId": 0, + "contractId": "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "20.5000000000" + }, + "createdEventBlob": "", + "interfaceViews": [ + { + "interfaceId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Holding", + "viewStatus": { + "code": 0, + "message": "", + "details": [] + }, + "viewValue": { + "amount": "20.5000000000" + }, + "implementationPackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200" + } + ], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.245698Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImw0SBOGyHpA=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6051, + "nodeId": 0, + "contractId": "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "0.0000000001" + }, + "createdEventBlob": "", + "interfaceViews": [ + { + "interfaceId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Holding", + "viewStatus": { + "code": 0, + "message": "", + "details": [] + }, + "viewValue": { + "amount": "0.0000000001" + }, + "implementationPackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200" + } + ], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.598713Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInA0SBOGyHpA=" + } + ] +} \ No newline at end of file diff --git a/tests/wire-samples/data/acs_wildcard_verbose_false.json b/tests/wire-samples/data/acs_wildcard_verbose_false.json new file mode 100644 index 0000000..d84869a --- /dev/null +++ b/tests/wire-samples/data/acs_wildcard_verbose_false.json @@ -0,0 +1,328 @@ +{ + "damlType": "Active contract set, all templates visible to the capture party", + "capturedBy": "POST /v2/state/active-contracts, verbose=False, activeAtOffset=6062", + "note": "Paired with its verbose counterpart so the reader can be checked against both.", + "response": [ + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6042, + "nodeId": 0, + "contractId": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Marker", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.467878Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImQ0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6045, + "nodeId": 0, + "contractId": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "10.0000000000" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.895701Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImg0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6048, + "nodeId": 0, + "contractId": "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "20.5000000000" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.245698Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImw0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6051, + "nodeId": 0, + "contractId": "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "0.0000000001" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.598713Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInA0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6054, + "nodeId": 0, + "contractId": "000e9d5ee703268a949c5801b054d547965fccde02a5836c235e2701bb148e1e54ca12122079f4bec1a4103c7476241b5b9348007040e29a29c060ae3dead008fa2563eac7", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Keyed", + "contractKey": { + "_1": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "_2": "alpha" + }, + "contractKeyHash": "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA=", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "label": "alpha" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.981209Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInQ0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6057, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "42", + "amount": "42.5000000000", + "label": "hello", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56.123456Z", + "note": "present", + "nestedNote": [ + [ + "deep" + ] + ], + "tags": [ + "x", + "y" + ], + "attributes": { + "a": "1", + "b": "2" + }, + "genMap": [ + [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "7" + ] + ], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Win", + "value": { + "prize": "1.25", + "tier": "gold" + } + }, + "suit": "Hearts", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.335103Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMIng0SBFB8xH8=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6060, + "nodeId": 0, + "contractId": "00d9da1d84476970e611d9111d5fb9f0e4f12be1653596f1a76ff896aad63eb598ca1212206d391292e808b44d71fd515f6faf4031994c273a6a39563177aca10a6ead92b6", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "-1", + "amount": "42.5000000000", + "label": "", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56Z", + "note": null, + "nestedNote": [], + "tags": [], + "attributes": {}, + "genMap": [], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Pending", + "value": {} + }, + "suit": "Clubs", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.691889Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInw0SBFB8xH8=" + } + ] +} \ No newline at end of file diff --git a/tests/wire-samples/data/acs_wildcard_verbose_true.json b/tests/wire-samples/data/acs_wildcard_verbose_true.json new file mode 100644 index 0000000..c1364e2 --- /dev/null +++ b/tests/wire-samples/data/acs_wildcard_verbose_true.json @@ -0,0 +1,328 @@ +{ + "damlType": "Active contract set, all templates visible to the capture party", + "capturedBy": "POST /v2/state/active-contracts, verbose=True, activeAtOffset=6062", + "note": "Paired with its verbose counterpart so the reader can be checked against both.", + "response": [ + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6042, + "nodeId": 0, + "contractId": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Marker", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.467878Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImQ0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6045, + "nodeId": 0, + "contractId": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "10.0000000000" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.895701Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImg0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6048, + "nodeId": 0, + "contractId": "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "20.5000000000" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.245698Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMImw0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6051, + "nodeId": 0, + "contractId": "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "0.0000000001" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.598713Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInA0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6054, + "nodeId": 0, + "contractId": "000e9d5ee703268a949c5801b054d547965fccde02a5836c235e2701bb148e1e54ca12122079f4bec1a4103c7476241b5b9348007040e29a29c060ae3dead008fa2563eac7", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Keyed", + "contractKey": { + "_1": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "_2": "alpha" + }, + "contractKeyHash": "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA=", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "label": "alpha" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.981209Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInQ0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6057, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "42", + "amount": "42.5000000000", + "label": "hello", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56.123456Z", + "note": "present", + "nestedNote": [ + [ + "deep" + ] + ], + "tags": [ + "x", + "y" + ], + "attributes": { + "a": "1", + "b": "2" + }, + "genMap": [ + [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "7" + ] + ], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Win", + "value": { + "prize": "1.25", + "tier": "gold" + } + }, + "suit": "Hearts", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.335103Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMIng0SBDQg2lc=" + }, + { + "workflowId": "", + "contractEntry": { + "JsActiveContract": { + "createdEvent": { + "offset": 6060, + "nodeId": 0, + "contractId": "00d9da1d84476970e611d9111d5fb9f0e4f12be1653596f1a76ff896aad63eb598ca1212206d391292e808b44d71fd515f6faf4031994c273a6a39563177aca10a6ead92b6", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "-1", + "amount": "42.5000000000", + "label": "", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56Z", + "note": null, + "nestedNote": [], + "tags": [], + "attributes": {}, + "genMap": [], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Pending", + "value": {} + }, + "suit": "Clubs", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.691889Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + }, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "reassignmentCounter": 0 + } + }, + "streamContinuationToken": "CgMInw0SBDQg2lc=" + } + ] +} \ No newline at end of file diff --git a/tests/wire-samples/data/create_asset_numeric_edges.json b/tests/wire-samples/data/create_asset_numeric_edges.json new file mode 100644 index 0000000..75856da --- /dev/null +++ b/tests/wire-samples/data/create_asset_numeric_edges.json @@ -0,0 +1,50 @@ +{ + "damlType": "WireTypes:Asset { issuer : Party, amount : Decimal } at amount = 0.0000000001 (smallest representable at scale 10)", + "capturedBy": "create, ACS_DELTA. Sibling captures in the same run used 10.0 and 20.5", + "response": { + "transaction": { + "updateId": "12203161f236859fce7f9c2f547d6932929eddabb0f2f7454468d730874f0f64955b", + "commandId": "12a6ba733cf94c3894ad50d78a63d367", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:02.598713Z", + "events": [ + { + "CreatedEvent": { + "offset": 6051, + "nodeId": 0, + "contractId": "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Asset", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "issuer": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "amount": "0.0000000001" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.598713Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6051, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-47deeb8289e6ca6270968d10e432bc4d-f4b8d1020aace1c6-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:02.608082Z", + "externalTransactionHash": null, + "paidTrafficCost": 2708 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/create_keyed_contract_key.json b/tests/wire-samples/data/create_keyed_contract_key.json new file mode 100644 index 0000000..c415c8f --- /dev/null +++ b/tests/wire-samples/data/create_keyed_contract_key.json @@ -0,0 +1,53 @@ +{ + "damlType": "WireTypes:Keyed { owner : Party, label : Text } with key (Party, Text) \u2014 requires LF >= 2.3", + "capturedBy": "create, ACS_DELTA; see CreatedEvent.contractKey and .contractKeyHash", + "response": { + "transaction": { + "updateId": "12207e960f872fec3ddd7ae00983df6f6b530131e928207bf018039fe9e99dcc611f", + "commandId": "1bea30ffaa454d20b5bfcbbad6dbfa52", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:02.981209Z", + "events": [ + { + "CreatedEvent": { + "offset": 6054, + "nodeId": 0, + "contractId": "000e9d5ee703268a949c5801b054d547965fccde02a5836c235e2701bb148e1e54ca12122079f4bec1a4103c7476241b5b9348007040e29a29c060ae3dead008fa2563eac7", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Keyed", + "contractKey": { + "_1": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "_2": "alpha" + }, + "contractKeyHash": "6CgQL9eNNqIjS5cB6/kK1IsqdxjcgXl/3kxSiUEkiBA=", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "label": "alpha" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:02.981209Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6054, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-e9554caa94d6c6ddd725a8502c8e7f1f-865497fee5a9394c-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:02.995431Z", + "externalTransactionHash": null, + "paidTrafficCost": 2774 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/create_marker.json b/tests/wire-samples/data/create_marker.json new file mode 100644 index 0000000..1312385 --- /dev/null +++ b/tests/wire-samples/data/create_marker.json @@ -0,0 +1,49 @@ +{ + "damlType": "WireTypes:Marker { owner : Party } \u2014 the minimal single-field record", + "capturedBy": "POST /v2/commands/submit-and-wait-for-transaction, TRANSACTION_SHAPE_ACS_DELTA", + "response": { + "transaction": { + "updateId": "1220c66d985d785609a290e982d1bef6cc2ff9419dc8e99e2f8e7ec876122504dc2b", + "commandId": "8ee31bbc5f9943309d8377a967ba919a", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:01.467878Z", + "events": [ + { + "CreatedEvent": { + "offset": 6042, + "nodeId": 0, + "contractId": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:Marker", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:01.467878Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6042, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-b3340a5c0f357190524e44e7b2c4794c-952e97cb0096263e-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:01.480257Z", + "externalTransactionHash": null, + "paidTrafficCost": 2695 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/create_wirerecord_empty.json b/tests/wire-samples/data/create_wirerecord_empty.json new file mode 100644 index 0000000..2dd2f6f --- /dev/null +++ b/tests/wire-samples/data/create_wirerecord_empty.json @@ -0,0 +1,78 @@ +{ + "damlType": "WireTypes:WireRecord at the empty/absent end: negative Int64, empty Text, Optional None, Optional (Optional Text) = None, empty List, empty TextMap, empty GenMap, nullary variant arm (Pending), Timestamp with NO fractional digits", + "capturedBy": "create, ACS_DELTA", + "response": { + "transaction": { + "updateId": "12201fac09ab7e82d4b57073a8c8405fc843f8b44aff3e62cd53a6ad1644330988ab", + "commandId": "f804b25684a142cb9a671bfa633f327f", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:03.691889Z", + "events": [ + { + "CreatedEvent": { + "offset": 6060, + "nodeId": 0, + "contractId": "00d9da1d84476970e611d9111d5fb9f0e4f12be1653596f1a76ff896aad63eb598ca1212206d391292e808b44d71fd515f6faf4031994c273a6a39563177aca10a6ead92b6", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "-1", + "amount": "42.5000000000", + "label": "", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56Z", + "note": null, + "nestedNote": [], + "tags": [], + "attributes": {}, + "genMap": [], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Pending", + "value": {} + }, + "suit": "Clubs", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.691889Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6060, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-311847efe65e79e5242da14d249b5168-af978aac51c752b7-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:03.704208Z", + "externalTransactionHash": null, + "paidTrafficCost": 3125 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/create_wirerecord_populated.json b/tests/wire-samples/data/create_wirerecord_populated.json new file mode 100644 index 0000000..a36726c --- /dev/null +++ b/tests/wire-samples/data/create_wirerecord_populated.json @@ -0,0 +1,96 @@ +{ + "damlType": "WireTypes:WireRecord, every field populated: Int64, Numeric 10, Numeric 2, Text, Bool, Date, Timestamp (6 fractional digits), Optional Some, Optional (Optional Text) = Some (Some _), List, TextMap, GenMap, Unit, ContractId (template and interface), nested record, tagged variant (Win), enum (Hearts)", + "capturedBy": "create, ACS_DELTA", + "response": { + "transaction": { + "updateId": "1220c4d6435a893d9963f1ff414198639207c4c6f10dd97c16f86ff9c75c38d2af89", + "commandId": "8cd6b2a59dbf4a558f261a96ecbb8c7b", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:03.335103Z", + "events": [ + { + "CreatedEvent": { + "offset": 6057, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "42", + "amount": "42.5000000000", + "label": "hello", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56.123456Z", + "note": "present", + "nestedNote": [ + [ + "deep" + ] + ], + "tags": [ + "x", + "y" + ], + "attributes": { + "a": "1", + "b": "2" + }, + "genMap": [ + [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "7" + ] + ], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Win", + "value": { + "prize": "1.25", + "tier": "gold" + } + }, + "suit": "Hearts", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:03.335103Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6057, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-8a111ebe832c3f2bae27ded40f9775ac-10e306f611a4718a-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:03.347625Z", + "externalTransactionHash": null, + "paidTrafficCost": 3199 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/exercise_describe.json b/tests/wire-samples/data/exercise_describe.json new file mode 100644 index 0000000..c1f3c76 --- /dev/null +++ b/tests/wire-samples/data/exercise_describe.json @@ -0,0 +1,54 @@ +{ + "damlType": "WireTypes:WireRecord:Describe \u2014 choice argument holds an Optional Int64; result is a variant", + "capturedBy": "POST /v2/commands/submit-and-wait-for-transaction, TRANSACTION_SHAPE_LEDGER_EFFECTS; see ExercisedEvent.choiceArgument and ExercisedEvent.exerciseResult", + "response": { + "transaction": { + "updateId": "1220c518ca39871684078db25d1d57531711ad0ad948a81a15b53c6fe720ccfa6e18", + "commandId": "a3b26d83fbd44bbf9fd2cbfd987a7ee2", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:04.401572Z", + "events": [ + { + "ExercisedEvent": { + "offset": 6066, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "interfaceId": null, + "choice": "Describe", + "choiceArgument": { + "probe": "9" + }, + "actingParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "consuming": false, + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "lastDescendantNodeId": 0, + "exerciseResult": { + "tag": "Win", + "value": { + "prize": "1.25", + "tier": "gold" + } + }, + "packageName": "wiretypes23", + "implementedInterfaces": [], + "acsDelta": false + } + } + ], + "offset": 6066, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-be3eb73a70aec6d827fa6bf0374fbda4-654f6528b9cf9c0d-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:04.414235Z", + "externalTransactionHash": null, + "paidTrafficCost": 3238 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/exercise_ping.json b/tests/wire-samples/data/exercise_ping.json new file mode 100644 index 0000000..6fda66a --- /dev/null +++ b/tests/wire-samples/data/exercise_ping.json @@ -0,0 +1,46 @@ +{ + "damlType": "WireTypes:WireRecord:Ping \u2014 no-argument choice; result is Unit", + "capturedBy": "POST /v2/commands/submit-and-wait-for-transaction, TRANSACTION_SHAPE_LEDGER_EFFECTS; see ExercisedEvent.choiceArgument and ExercisedEvent.exerciseResult", + "response": { + "transaction": { + "updateId": "122090561c93fba2c3781052b4a841b4c4d175cc683615b19fd10067608657b3a8f0", + "commandId": "eff6b3d96f3d4035b92d52ca402e1706", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:04.767213Z", + "events": [ + { + "ExercisedEvent": { + "offset": 6069, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "interfaceId": null, + "choice": "Ping", + "choiceArgument": {}, + "actingParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "consuming": false, + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "lastDescendantNodeId": 0, + "exerciseResult": {}, + "packageName": "wiretypes23", + "implementedInterfaces": [], + "acsDelta": false + } + } + ], + "offset": 6069, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-ed908a11b9d4ffe634028f322c9c59f9-75711923479ce39e-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:04.778439Z", + "externalTransactionHash": null, + "paidTrafficCost": 3247 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/exercise_relabel.json b/tests/wire-samples/data/exercise_relabel.json new file mode 100644 index 0000000..3961358 --- /dev/null +++ b/tests/wire-samples/data/exercise_relabel.json @@ -0,0 +1,121 @@ +{ + "damlType": "WireTypes:WireRecord:Relabel \u2014 choice argument is a record; result is a ContractId", + "capturedBy": "POST /v2/commands/submit-and-wait-for-transaction, TRANSACTION_SHAPE_LEDGER_EFFECTS; see ExercisedEvent.choiceArgument and ExercisedEvent.exerciseResult", + "response": { + "transaction": { + "updateId": "1220147ae0c530b39d850f4bc41d64087fdd469cd1105a637a4749ae9e3f16fce7e4", + "commandId": "21df0cfc06b54d279c3f56178eb4ee9d", + "workflowId": "", + "effectiveAt": "2026-07-31T09:23:04.102423Z", + "events": [ + { + "ExercisedEvent": { + "offset": 6063, + "nodeId": 0, + "contractId": "00f9c54b4cf1ac0609a616f88cbeb9a58bf38b08977edaa81c1136b96f552d118eca121220ea362ddb26f079d3438340bbe63d976a4774b3be09cc5b5c883e74c08fe8e1da", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "interfaceId": null, + "choice": "Relabel", + "choiceArgument": { + "newLabel": "relabelled" + }, + "actingParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "consuming": false, + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "lastDescendantNodeId": 1, + "exerciseResult": "005397d332aa2c418bdbdef59cfa7427cf810f855ec6afb4b753d30253b374a2a7ca12122095fdfbe1332d35a094c3e917dbf8217808d3829f2c613f09820be15059ffbdcf", + "packageName": "wiretypes23", + "implementedInterfaces": [], + "acsDelta": false + } + }, + { + "CreatedEvent": { + "offset": 6063, + "nodeId": 1, + "contractId": "005397d332aa2c418bdbdef59cfa7427cf810f855ec6afb4b753d30253b374a2a7ca12122095fdfbe1332d35a094c3e917dbf8217808d3829f2c613f09820be15059ffbdcf", + "templateId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200:WireTypes:WireRecord", + "contractKey": null, + "contractKeyHash": "", + "createArgument": { + "owner": "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "count": "42", + "amount": "42.5000000000", + "label": "relabelled", + "active": true, + "asOf": "2026-07-31", + "observedAt": "2026-07-31T12:34:56.123456Z", + "note": "present", + "nestedNote": [ + [ + "deep" + ] + ], + "tags": [ + "x", + "y" + ], + "attributes": { + "a": "1", + "b": "2" + }, + "genMap": [ + [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b", + "7" + ] + ], + "unitField": {}, + "marker": "00e658d5467611d5231f1a4efafd299efb6e78f9f3971f4fb270773c65f8da64e7ca121220f1dcf68bdb90b3eb833c1ff85f6efc86e701399b3e88b358a8d24abc40fa9612", + "holdingCid": "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "holdingCids": [ + "00c35a913ec4d5ed3d9c407da7dcaf99ecb7956c3930ebe89dd3370075d4de7c7aca121220ccb3a953e2fda3f2c353f831a26edb3ce46c5817aede6d177416de7a5e5a3770", + "00f7f5461eca4cc8707b5e263aa771b2a1d40284b3baaca1e1f39461156fd83411ca121220d2dd2f2edf4cedfa68b26f4aac381d68f58defaaffc7a66c3f65a27902b5d724", + "007cb6a42546f70bc2da5003d129f364064c019ff3f96d84dfcb94f78127d3fc45ca121220de8fe4776bad93c9df08267466e5b9066255724c92de9b2f33e26048d9f29923" + ], + "profile": { + "nickname": "nick", + "level": "3" + }, + "outcome": { + "tag": "Win", + "value": { + "prize": "1.25", + "tier": "gold" + } + }, + "suit": "Hearts", + "fee": "1.25" + }, + "createdEventBlob": "", + "interfaceViews": [], + "witnessParties": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "signatories": [ + "wiree3ed3454::1220141a01c00ef277c31ca4eb0e82ee3de7f790eb25f3787f8195f117af8668bf3b" + ], + "observers": [], + "createdAt": "2026-07-31T09:23:04.102423Z", + "packageName": "wiretypes23", + "representativePackageId": "f7230672ba7d77a5adba89f092d50b7c957d0eb5030c1e4f96706dcca93f2200", + "acsDelta": true + } + } + ], + "offset": 6063, + "synchronizerId": "global-domain::122081089699cb37a4179170d18c311527ed5e248cf3cc9f11ac73f5a19d1a4cd7b4", + "traceContext": { + "traceparent": "00-f1c0ac2d7e2c89f9d2433ca145507165-6601e045cba4f207-03", + "tracestate": null + }, + "recordTime": "2026-07-31T09:23:04.114198Z", + "externalTransactionHash": null, + "paidTrafficCost": 3417 + } + } +} \ No newline at end of file diff --git a/tests/wire-samples/data/probe_nested_optional_matrix.json b/tests/wire-samples/data/probe_nested_optional_matrix.json new file mode 100644 index 0000000..945306c --- /dev/null +++ b/tests/wire-samples/data/probe_nested_optional_matrix.json @@ -0,0 +1,53 @@ +{ + "damlType": "WireTypes:WireRecord.nestedNote : Optional (Optional Text)", + "capturedBy": "One create per candidate encoding; accepted candidates are echoed back from CreatedEvent.createArgument of the same transaction.", + "note": "The encodings this repo's codegen writes and reads for a nested Optional chain: one array level per Optional. Of the rejected candidates only null is flat throughout; [null] and [\"deep\"] are mixed, taking the array form at the outer level and the flat form at the inner one.", + "response": { + "null": { + "sent": null, + "accepted": false, + "error": "HTTP Error 500: Internal Server Error" + }, + "empty_array": { + "sent": [], + "accepted": true, + "echoed": [] + }, + "array_of_null": { + "sent": [ + null + ], + "accepted": false, + "error": "HTTP Error 500: Internal Server Error" + }, + "array_of_empty_array": { + "sent": [ + [] + ], + "accepted": true, + "echoed": [ + [] + ] + }, + "array_of_array_of_text": { + "sent": [ + [ + "deep" + ] + ], + "accepted": true, + "echoed": [ + [ + "deep" + ] + ] + }, + "array_of_text": { + "sent": [ + "deep" + ], + "accepted": false, + "error": "HTTP Error 500: Internal Server Error" + } + } +} \ No newline at end of file From 35248aa45178c032e4620e2d69ffafd2fd7c61c6 Mon Sep 17 00:00:00 2001 From: monsieurleberre Date: Wed, 2 Sep 2026 08:20:00 +0100 Subject: [PATCH 2/4] docs(changelog): add the 0.5.0-preview.1 section --- CHANGELOG.md | 1013 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1012 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c462b..d8150c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This changelog tracks the packages published from this repo together, because they are versioned in lockstep: - `Daml.Codegen.CSharp` — C# emitter library (NuGet package) +- `Daml.Codegen.Intermediate` — intermediate DAR contract (protobuf types + shared Daml model) - `Daml.Runtime` — runtime types referenced by generated code - `Daml.Ledger.Abstractions` — transport-agnostic ledger client contract (`ILedgerClient`, composed of `ILedgerWriter`/`ILedgerReader`/`ILedgerStreamer`) @@ -36,6 +37,1013 @@ because they are versioned in lockstep: ### Security +## [0.5.0-preview.1] — 2026-09-01 + +### Added + +- `Daml.Codegen.Intermediate` — a new package carrying the intermediate DAR contract on its + own: the C# types generated from `intermediate_dar.proto` plus the shared Daml model + (`DarModel`, `DamlPackage`, `DamlTemplate`, `DamlType`, `PackageVersionParser`, …). + Producers of the intermediate representation can now depend on the contract alone instead + of on the whole emitter. Versioned in lockstep with the other packages. +- **Typed interface/view read surface** (`Daml.Runtime`, `Daml.Ledger.Abstractions`): + `ViewDescriptor` is a pure type witness pairing a Daml interface marker + with its view record, exposed by every generated marker through a static `View` property. + `InterfaceStreamEvent` and `InterfaceAcsSnapshotEntry` + are the interface-family counterparts of `ContractStreamEvent` and `AcsSnapshotEntry`, + carrying the interface's server-computed view record rather than the implementing template's + own record. `ILedgerStreamer.SubscribeAsync`, `SubscribeActiveAsync` and + `SubscribeLedgerEffectsAsync` gain interface-family overloads of the same name, each taking + the witness as its first argument — `client.SubscribeAsync(IHolding.View, parties)` — so both + type parameters are inferred from one argument. A mismatched marker/view pair does not + compile. A matching event delivered without a view is surfaced as `Unclassified` with + `UnclassifiedKind.InterfaceViewUnavailable`, unchanged. + + Two gaps ride with them, both deliberate. There is no interface-family counterpart to + `StreamerSnapshot.SnapshotAsync`: it materializes `IReadOnlyList>`, and + `Contract` is a template shape that cannot hold a view record, so draining + `SubscribeActiveAsync(view, …)` means restating its throw-on-`StreamError`/`Unclassified`/ + missing-checkpoint policy yourself. And `LedgerClientConformanceTests` drives only + the template family, so an implementation of the three new members can be wrong and still + pass the kit; see that package's README. +- Generated interface markers now mirror their view's fields as instance get-only properties, + and the generated view record implements its marker, so a view answers with the *interface's* + identity in identity-keyed generic code and its fields are readable through a marker-typed + variable. `==` between marker-typed variables is reference equality, noted on the marker's + doc-comment; concrete view records keep record value equality. The enrichment is conditional: + where the view record is declared in a different package than its interface, is shared by two + interfaces, or carries a field whose C# name would not agree on both sides of the mirror, the + interface still gets the `View` witness, but the record is not stamped with the marker and the + marker mirrors no fields. +- **Contract keys reach the read surface.** `KeyDescriptor` (`Daml.Runtime`) + pairs a keyed template with its key type and carries the key codec as `KeyEncoder`/`KeyDecoder`, + the way `Choice` carries `ArgumentEncoder`/`ResultDecoder`. Generated + keyed templates carry the `IHasKey` facet and expose the descriptor through a + static `Key` witness, so a generic method constrained on the facet can decode a contract key + from a template type alone and encode one back to the `DamlValue` `ExerciseByKeyCommand` takes, + with no reflection. `TKey` is unconstrained, so a template keyed by a bare `Party` + (`key steward : Party`) gets `KeyDescriptor` on the same footing as a + record-keyed template's `KeyDescriptor`. Where a template's own payload + field maps to the C# member name `Key`, or the template itself is named `Key`, the witness is + emitted as an explicit interface implementation only — reachable through the facet, invisible + on the template type — and codegen logs a warning naming the template and, where there is one, + the field. +- `StreamerSnapshot.SnapshotAsync(T.Key, submitter)` (`Daml.Ledger.Abstractions`): a + descriptor-taking overload returning `IReadOnlyList>` with every key decoded, + so `await client.SnapshotAsync(Account.Key, alice)` hands back contracts whose `Key.Value` is + an `AccountKey`. Passing the template's `Key` witness fixes both type parameters from one + argument — C# performs no partial type-argument inference, so the pair has to travel together. + A create row that carried no key is reported as `LedgerOperationException`, consistent with the + short-list failures the keyless overload already prevents. +- Generated code emits a by-key command builder for every choice on a key-bearing template — + `ByKeyCommand(key, argument)`, returning an `ExerciseByKeyCommand`, beside the existing + `Command(contractId, argument)`. It is a plain static on the template record rather + than an extension method: the key's C# type may be `string` or `Party`, and extending those + would put the method on every value of that type in the consuming project. +- `ContractKey.KeyHash` (`Daml.Runtime`): the ledger's hash of the key, the value Canton indexes + keyed contracts by. It travels beside the key on both wire formats (`contractKeyHash` on the + JSON encoding, `contract_key_hash` on gRPC) and is held as the base64 text the JSON encoding + uses. An init-only property rather than a positional parameter, so `Deconstruct` keeps its two + parameters. A `null` there is a stated absence — the created event carried no hash, or the key + was built by a caller rather than read off the wire. Nothing in this repository computes it: + the hash is the ledger's, and a transport that reads one off a created event populates it. On + a decoded active contract the same hash is reached as `contract.Key.Hash` (see + `ContractKey` under *Changed — BREAKING*). +- **`ContractKey` equality ignores `KeyHash`.** Two keys are the same key when they name the same + value of the same template, so a key read off the wire — which carries a hash — equals the same + key a caller constructed to exercise by it, which does not. `Equals`/`GetHashCode` are written + out rather than compiler-generated for this reason; the generated pair would have compared the + hash and made by-key matching fail quietly depending on where the key came from. `KeyHash` stays + in `ToString()`, so a diagnostic dump still shows it. +- **`DamlLfJsonReader.ReadRecord` (`Daml.Runtime`): type-directed LF-JSON decoding.** Decodes a + JSON object against a generated record type — supplied as `T` or as a runtime `Type`, read from + JSON text or from an already-parsed `JsonElement` — into a `DamlRecord` whose fields carry their + true Daml types (a `Party` field arrives as `DamlParty` rather than the `DamlText` an untyped + decode yields), ready to hand to the generated `FromRecord`. Coverage is the full observed + LF-JSON wire encoding: + - Scalars — `Text`, `Bool`, `Int64` as its wire string, `Numeric` at the declared scale, `Date`, + `Timestamp` (up to seven fractional digits, with `Z`, a numeric offset, or no zone designator, + an unzoned value read as UTC), and `ContractId`. + - Structure — nested records, lists, `TextMap`, `GenMap` (its array of key/value pairs), + `Optional` (`Some` as the bare value, `None` as `null`), variants and enums resolved by the + wire names the emitter writes rather than their C# identifiers, so sanitized or disambiguated + constructors decode correctly, and `Unit`. + - Hand-written stdlib generics — `Tuple2`/`Tuple3` from their wire record form + (`{"_1":…,"_2":…}`, the shape a captured tuple contract key shows) and `Either` from its + `Left`/`Right` tagged-variant form, each component decoded from the CLR generic argument, so + `Optional` components, nested records, lists of tuples and tuples carried by an `Either` arm + all decode in those positions. Generic variants other than `Either` are still refused with + `NotSupportedException`. + + A wire-shape mismatch is reported as `JsonException` carrying the dotted property path. The + reader honours `DamlJsonDeserializationLimits`; the `JsonElement` overloads apply the + array-breadth and value-depth caps but not the input-size cap, which was the caller's to apply + when they parsed. +- `IDamlRecord` (`Daml.Runtime`): self-typed record facet whose + `static abstract TSelf FromRecord(DamlRecord)` factory lets a generated type be materialized + through a `where T : IDamlRecord` constraint instead of by reflection. Generated templates + and non-generic records declare it, as does the hand-written `RelTime`; a non-generic record's + base list becomes `: IDamlRecord` (the plain `IDamlRecord` still holds via + inheritance). Generic records are unchanged. +- `Optional` (`Daml.Runtime.Stdlib`): the representation for a Daml `Optional` in the positions + C# nullable syntax cannot carry — over a type parameter, as a type argument to a generated + generic, and as a `GenMap` key. A closed `Some`/`None` record hierarchy with `Match`, `HasValue`, + and the `GetValueOrDefault`/`GetValueOrThrow`/`TryGetValue` bridges back to idiomatic C#, + mirroring the wire-side `DamlOptional`. +- **Nested Daml `Optional` is generated instead of refused** (`Daml.Codegen.CSharp`). A Daml + `Optional (Optional Text)` becomes a C# `Optional>` — every level of the chain + carried by the wrapper, none of it by C# nullability — so `None`, `Some None` and `Some (Some x)` + stay three distinct values. Where the nesting is written directly in the Daml type, codegen + previously failed the run outright with `NotSupportedException: Codegen does not support nested + Optional types`, so for those shapes this is new capability rather than a changed one: no package + containing them generated before this release. Nesting that arises only by *substitution* is a + different story and did generate before — a field typed `Crate (Optional Text)` against + `data Crate a = Crate with item : Optional a` is nested once substituted, and it generated + silently while writing bytes the participant rejects. Those shapes are now refused instead; see + the breaking entry below for exactly which. A flat `Optional Text` is unchanged and still + generates `string?`, and so does an `Optional` separated from another by an intervening type: + `Optional (Box (Optional Text))` is still `Box>?`. Only a directly nested chain + moves. +- `DamlOptionalChain` (`Daml.Runtime.Data`): the wire node for one level of a directly nested + Optional chain, with `Optional.ToChainValue`/`Optional.FromChainValue` + (`Daml.Runtime.Stdlib`) bridging it to the typed wrapper. It is a separate node from + `DamlOptional` because the two carry different encodings for the same shape: a flat optional + writes JSON `null` or its bare value, while every level of a nested chain writes the array form + — `[]` when absent, `[v]` when present — which is what a participant accepts in a nested + position. For the same reason `DamlValueExtensions.AsOptional` now throws `InvalidCastException` + on a chain level rather than wrapping it as a further `Some`: the level is already an Optional, + in the other encoding. Reaching that throw requires holding a chain, and nothing produced one + before this release. + + One asymmetry rides with it. `DamlJsonSerializer.Deserialize` and `DeserializeRecord` are + untyped: with no schema to consult they reconstruct a chain's array form as a `DamlList`, the + same way they already reconstruct a `TextMap` as a `DamlRecord` and a `Party` as a `DamlText`. + So a `DeserializeRecord` result handed to a generated `FromRecord` throws `InvalidCastException: + Cannot cast DamlList to DamlOptionalChain` on a nested-Optional field, where every other field + type would have decoded. Read such a record with the type-directed `DamlLfJsonReader.ReadRecord` + instead, which has the schema and produces the chain. +- Generated code: every `Async` flavour now carries a `SubmitterInfo` overload beside its + ergonomic single-`Party` shape — the value-returning exerciser on `ContractId` (including + `Archive`), the `T.Contract` sibling of the create-bearing exerciser, and the interface-choice + exerciser. `readAs` and multi-party `actAs` are therefore expressible everywhere the fluent path + reaches, instead of only on the create-bearing `ContractId` overload; a submission needing + either no longer has to drop to a hand-built `ExerciseCommand`. Existing single-`Party` call + sites bind to the same overload as before. +- `ContractIdJsonConverterFactory` and `DamlJsonConverters` (`Daml.Runtime`): the + `System.Text.Json` converters for contract ids, with `DamlJsonConverters.All` and + `options.AddDamlConverters()` registering `Party`, `ContractId` and `SynchronizerId` in one call. + The factory matches the per-template `T.ContractId` record codegen derives from `ContractId` + as well as the closed generic itself, so a property declared as either takes the same wire shape. + A property declared as `ContractId` converts with no registration (`ContractId` carries the + factory as a `[JsonConverter]` attribute, as `Party` and `SynchronizerId` already did); a + property declared as the derived `T.ContractId` — including `T.Contract.Id` — does need + `options.AddDamlConverters()`, because `System.Text.Json` reads `[JsonConverter]` off the + declared type and does not walk its base chain. A contract-id string the target type rejects is + reported as `JsonException`. Consumers that hand-rolled a reflective `JsonConverterFactory` for + contract ids can delete it. +- `ILedgerStreamer.SnapshotAsync()` (`Daml.Ledger.Abstractions`): drains an active-contract-set + snapshot into `IReadOnlyList>`, decoding each row through `T.FromRecord`. Throws + `LedgerOperationException` rather than returning a short list when the snapshot faults, carries + an unclassified row, carries a create row that does not decode into `T`, or ends without its + terminal checkpoint; cancelling the token surfaces as `OperationCanceledException` even when the + transport reports the cancelled call in-band. It consumes and discards the checkpoint's + `StakeholderResume` ticket, so callers needing the gapless snapshot-to-stream handover stay on + `SubscribeActiveAsync`. +- `SingleCommandExtensions.TrySubmitSingleAsync` (`Daml.Ledger.Abstractions`): the single-command + submission path shared by generated exercisers and the hand-written write-path extensions. It + builds the `CommandsSubmission` — single command, caller's command id or a freshly minted one, + caller's workflow id when non-empty — and submits it, so that shape lives in one versioned place + instead of being re-emitted into every consumer's generated code. +- `commandId` parameter on the `CreateByExercise` overloads and on the void + `ThrowingExercise.ExerciseAsync` overloads (`Daml.Ledger.Abstractions`), so a caller can reuse an + id across a retry of a lost-but-accepted submission and have the ledger deduplicate it. + Generated exercisers already accepted one; these did not. Note that a minted id is never reported + back on a failed submission, so an application-level retry is deduplicable only when the caller + supplies and retains the id itself. +- Every write-path extension that accepts a `commandId` now rejects a supplied-but- + `default(CommandId)` with an `ArgumentException` naming the parameter, rather than forwarding it. + `CommandId` is a struct whose `Value` throws on a default instance, so an uninitialised one + previously travelled to the transport and surfaced there as an `InvalidOperationException` out of + a method whose contract is to return a structured outcome instead of throwing. Omitting the + argument still mints an id — only an explicitly passed default is rejected. +- `timeout` parameter on both `ThrowingExercise.ExerciseAsync` overloads + (`Daml.Ledger.Abstractions`), forwarded to the underlying `TryExerciseAsync` / + `TrySubmitSingleAsync` call. The throwing forms were the only unary write-path extensions that + could not carry a per-call deadline, so reaching for one meant dropping down to the structured + `Try*` form. It sits after `commandId` and before `cancellationToken`, matching the + `CreateByExercise` overloads; every existing call site passes `cancellationToken` by name, so the + added parameter does not rebind one. Binary compatibility breaks as it does for the other + parameter additions this cycle; publish in lockstep. +- **`Daml.Ledger.Abstractions.Testing.Conformance`**: an opt-in command-id deduplication check. + Override `CreateCommandIdFixture()` on `LedgerClientConformanceTests` to prove your + `ILedgerWriter` implementation honours the `commandId` obligation that + `TryExerciseAsync`/`TryCreateAsync` document — a caller-supplied id reaches the participant + verbatim, and a fresh one is minted only when the caller omits it, never leaving `command_id` + unset. Both directions are checked separately, since a client that mints over the supplied id and + one that never mints are distinct faults. Leaving it at its `null` default skips the four new + `[Fact]`s, matching the existing opt-in pattern for `CreateWriteFixture()`. +- `CommandsSubmission.WithOptionalWorkflowId(string?)` (`Daml.Runtime`): applies a workflow id when + the caller supplies a non-empty one and returns the submission unchanged otherwise, so the + null/empty branch lives on the submission type instead of being restated at every call site. + Generated exercisers and the `Daml.Ledger.Abstractions` extensions both route through it. +- `CommandId.FromWire(string?)` and `SynchronizerId.FromWire(string?)` (`Daml.Runtime`): project an + optional wire field, returning `null` for a null, empty, or whitespace value and a constructed id + otherwise — the supported way to express "the wire carried none". Hand-rolled fallbacks returning + `default` from a helper typed `CommandId` wrap as a non-null `CommandId?` and still throw on + `?.Value` (`?.Id` for `SynchronizerId`); the factory's only absence value is the literal `null`, + so replace such helpers with `FromWire`. +- `CodeGenOptions.PublishesReferencedPackages` (`Daml.Codegen.CSharp`): declares that this run also + produces the NuGet packages for the other DAR packages the generated code references, so their + ``s are pinned to the exact version this run produces instead of floating over + the generations published against their intrinsic version. Defaults to `false`; the CLI sets it + from `--release-counters`. +- **Supported Daml-LF input is now declared as 2.1 through 2.3**, each carried by a conformance + fixture. Nothing in the toolchain enforced 2.1 before; the declaration was narrower than the + capability. C# is emitted from all three fixtures and the result pinned, so every declared + version is proven on the emit path as well as the read one. Emitting 2.3 needs a 3.5-line Daml + SDK — the fixtures build on 3.5.2. +- A contract-key conformance corpus ships in `Daml.Codegen.Testing.Conformance` (namespace + `Daml.Codegen.Testing.Conformance.Contractkeys`, DAR reachable via + `ConformanceCorpus.OpenDar(ConformancePackage.ContractKeys)`), built at Daml-LF 2.3 — the + earliest 2.x version that can express a contract key. It covers a record key built from several + payload fields, a record key whose field comes from a nested projection, a record key built by a + function in another module, and a bare `Party` key. `ConformanceCorpus.OpenDar()` still returns + the rich-types DAR. +- A default-target conformance corpus ships in `Daml.Codegen.Testing.Conformance` (namespace + `Daml.Codegen.Testing.Conformance.Defaulttarget`, DAR reachable via + `ConformanceCorpus.OpenDar(ConformancePackage.DefaultTarget)`), built with no `--target` so it + carries the Daml-LF version damlc emits for a project that requests none. +- The conformance corpus (`Daml.Codegen.Testing.Conformance`, and the DAR it embeds) gains a + `TypeCorners` template covering the type-system corners the corpus previously omitted: + parameterized records and variants (`Box`, `Slot`) instantiated in a template payload, `GenMap` + keyed by `Party` and by `Int`, `Either`, `Tuple2`/`Tuple3`, a recursive record (`Branch`), an + `Optional` nested inside an `Optional` through a wrapper record, and the `Numeric` scale extremes + 0 and 37. The `Holding` interface gains choices (`Describe`, `Reissue`) alongside its view. + Consumers round-tripping the corpus against a participant get the wider surface; the corpus + package id changes accordingly. +- `Crate` (`Daml.Codegen.Testing.Conformance`, namespace + `Daml.Codegen.Testing.Conformance.Richtypes`): a parameterized record whose one field is an + `Optional` over the record's own type variable, held as `Optional`. It is the first such + shape in the shipped corpus, so the position C# nullable syntax cannot spell is now covered end + to end rather than only in the emitter's unit tests. `TypeCorners` carries one as `crate`. +- NuGet packages now ship a SourceLink'd symbols package (`.snupkg`) beside each `.nupkg`, embed + repository/commit metadata for source-stepping in debuggers, and are gated by NuGet package + validation at pack time; release builds are compiled with `ContinuousIntegrationBuild` for + deterministic, path-normalized PDBs. +- The DAR → IntermediateDar tool (the JVM helper) is now packaged for standalone publication as + `daml-dar-to-proto`: each GitHub release attaches `daml-dar-to-proto-.jar` and the + matching `intermediate_dar-.proto`, so non-C# SDKs can decode a `.dar` to an + Intermediate DAR without the `dpm codegen-cs` bundle. The jar gains a `--version` flag printing + the release version it was built as (`0.0.0-dev` for non-release builds). + +### Changed — BREAKING + +- **The read surface's payloads are typed** (`Daml.Runtime`). `ContractStreamEvent.Created`, + `ContractStreamEvent.Assigned` and `AcsSnapshotEntry.Created` now carry the payload as `T` + rather than a raw `DamlRecord`, and the two unions constrain `T` to the template family + (`where T : ITemplate, IDamlRecord`) instead of the broad `IDamlType`. Subscribing to a Daml + interface moves to the new interface-family overloads, whose payload is the interface's view + record. There is no deprecation period: a C# signature does not include its constraints, so the + old and the new could not overload under one name. Template call sites recompile unchanged after + regenerating; a marker call site takes a one-line fix — pass the marker's `View` witness instead + of naming the marker as a type argument, and read `InterfaceStreamEvent<,>`/ + `InterfaceAcsSnapshotEntry<,>` in place of the template unions. A payload that no longer decodes + is the projector's business now: it surfaces as `Unclassified` with + `UnclassifiedKind.DecodeFailure` instead of throwing out of the consumer's own `FromRecord` call. +- **`ILedgerStreamer` implementations must add three members** — the interface-family + `SubscribeAsync`, `SubscribeActiveAsync` and `SubscribeLedgerEffectsAsync` — and tighten the + three template-family members' `where T : IDamlType` to `where T : ITemplate, IDamlRecord`. + The resume overloads keep their forwarding default implementations. A transport now decodes the + payload before yielding it, which is what moves the decode off every consumer's `switch`. +- **`LedgerClientConformanceTests` constrains `TProbe` to the template family** + (`Daml.Ledger.Abstractions.Testing.Conformance`), matching the surface it exercises. An adopter's + probe type gains `IDamlRecord` — a `static FromRecord` — which a generated template + already carries. +- **`Daml.Ledger.Abstractions.Testing.Conformance` now requires xunit.v3 `4.0.0`** (was `3.2.2`). + The kit references `xunit.v3.extensibility.core` and `xunit.v3.assert` directly, and the `[Fact]` + methods a consumer inherits live on its `LedgerClientConformanceTests` base, so the kit's + xunit major is the consumer's — a consumer test project must move to xunit.v3 `4.0.0` in the same + step. This also moves Microsoft.Testing.Platform `2.0.2` to `2.3.3`, which the pinned coverage + extension already required. +- **Contract keys move off the payload and onto the active contract, typed.** `IHasKey` and + the `Key` member are no longer emitted on the template payload type: a payload is what a caller + constructs locally to build a create command, so it cannot know the key of a contract that does + not exist yet, and the accessor was a stub that threw `NotImplementedException`. Read + `contract.Key` instead of `contract.Data.Key`. The key *type* and its serialization are + unchanged, so caller-constructed key values and `ExerciseByKeyCommand` are untouched. +- **`Contract` loses its `ContractKey? Key` slot, and keyed contracts move to a new + `Contract`** (`Daml.Runtime`). The old slot was the third *positional* parameter, so its + removal breaks twice: `Deconstruct` drops from three parameters to two, and the + compiler-generated equality members stop comparing the key. A template that declares no contract + key stops offering a member that could never be populated; a keyed template's contracts are now + `Contract(ContractId Id, T Data, ContractKey Key)`, whose key is + **non-nullable**. `Contract.FromCreatedEvent` throws `InvalidOperationException` when + the event carries no key, rather than handing back a shape whose type says the key is present. +- **The ledger's key hash is retyped, not dropped.** The new `ContractKey(TKey Value, + string? Hash)` pairs the decoded key with the ledger's hash of it, so `contract.Key.Value` is + `TKey` — no `As()` hop — and `contract.Key.Hash` still reaches the hash. The hash is + Canton-computed over the key and the template id, is not derivable client-side, and no projected + shape exposes the raw `CreatedEvent`, so dropping the slot outright would have destroyed that + access rather than relocating it. Callers reading `contract.Key!.KeyHash` become + `contract.Key.Hash`; callers reading `contract.Key!.Value` as a wire `DamlValue` become + `contract.Key.Value` typed. +- **The generated nested `Contract.Key` becomes non-nullable and gains the hash** + (`Daml.Codegen.CSharp`). A keyed template's nested contract already carried a *typed* key, but as + `required TKey? Key`; it is now `required ContractKey Key`, and its `FromCreatedEvent` + throws when the created event carries no key instead of assigning `null`. A keyless template's + generated output is byte-identical to before. Callers become `contract.Key.Value` for the key and + `contract.Key.Hash` for the hash, and a call site that constructed the contract with `Key = null` + must now supply a `ContractKey`. +- **`IHasKey` becomes `IHasKey`, and its member becomes a `static abstract` + `KeyDescriptor Key`** (`Daml.Runtime`). The facet previously carried an instance + `TKey Key { get; }`, which told generic code the key's type but not how to decode one; a read + surface holding only `TKey` had nowhere to get the decode from, so it could not admit an + unconstrained key type. The descriptor now carries the decode and the pair of types travels + together, so a generic call site infers both type parameters from one argument and `TKey` needs + no `IDamlRecord` constraint — a bare `Party` key is admitted on the same footing as a + record key. Reached through a `static abstract` member rather than a bare static, so generic code + constrained on the facet finds it without reflection, the same shape `ITemplate.TemplateId` uses. + Hand-written implementors give the facet the implementing type as its first argument and replace + the instance property with a static `Key` descriptor. Where the payload already has a member + named `Key`, implement the descriptor explicitly + (`static KeyDescriptor IHasKey.Key { get; } = new() { … }`), which leaves the + name free on the type. +- **`AcsSnapshotEntryExtensions.ToContract()` returns a keyless `Contract`** + (`Daml.Runtime`). A keyed projection goes through the new `ToContract()`, which decodes + the row's key through the template's `Key` witness and throws `InvalidOperationException` when + the row carried none. +- **The four runtime shapes that carry a created contract gain a mandatory `ContractKey? Key` + parameter.** `AcsSnapshotEntry.Created` and `ContractStreamEvent.Created` each take it + immediately after their payload, grouping contract identity, data and key ahead of the ledger + metadata. The parameter is positional rather than an optional init-only property, so this is a + source break and a binary break: every projector constructing one of these shapes — every + `ILedgerClient` implementation included — has to pass the key the created event carried, or an + explicit `null` for a template that has none. The wire-level `ContractKey` travels rather than a + generated key type, which `Daml.Runtime` cannot name; decode it with the hop generated code + already performs, `TKey.FromRecord(key.Value.As())`. An optional slot would have let + every existing projector keep compiling while silently never populating the key. +- **`ContractStreamEvent.Assigned` gains a mandatory `ContractKey? Key`**, positioned + immediately after `Payload` to match the shapes that already carry one. An assignment re-emits + the whole created contract — Canton's assigned event wraps a full created event, key included — + so this variant was the one create-carrying shape with nowhere to put a key, and a consumer + rebuilding state from a single stream lost the key at every reassignment. +- **`CreatedEvent.ContractKey` loses its `= null` default** and becomes a mandatory parameter + (`DateTimeOffset? CreatedAt` stays optional after it). This is the single entry point feeding + every downstream key slot, so a transport could omit it entirely, compile clean, and feed `null` + into every `required` slot below — exactly the failure the mandatory downstream slots were + introduced to prevent. Callers now state the absence: `ContractKey: null` for a template that + declares no key. +- **`ILedgerWriter.TryExerciseAsync` and `ILedgerWriter.TryCreateAsync` take a + new optional `CommandId? commandId = null`** immediately after `workflowId` + (`Daml.Ledger.Abstractions`), so a caller can reuse an id across a retry of a lost-but-accepted + submission and have the ledger deduplicate it — the same parameter the generated exercisers + already accept. Any external implementation of `ILedgerWriter` must add the parameter to both + methods and honour it. Callers are unaffected at the source level unless they passed `timeout` + positionally, which now fails to compile rather than rebinding silently — `TimeSpan` and + `CancellationToken` cannot convert to `CommandId`, whose own conversions are explicit and + `string`-only. Binary compatibility does break: an assembly compiled against the previous package + bakes the old signature into its call sites, so pinning an old `Daml.Ledger.Abstractions` + alongside newly generated code raises `MissingMethodException` at runtime. Publish in lockstep. + The two value-returning `ThrowingExercise.ExerciseAsync` overloads gain the same + parameter and forward it, and the `CreateByExercise` overloads and the void + `ThrowingExercise.ExerciseAsync` overloads take it too — there the break is positional, since + `commandId` precedes `timeout`. Both fail to compile rather than rebinding silently. +- **`CreateAsync` no longer takes a submitter when every signatory is a payload field** — the + emitted wrapper derives the `SubmitterInfo` from the payload's `Party` properties, so the + parameter is removed rather than made optional. It is dropped only when the analysis yields at + least one payload field: a template with any non-payload signatory keeps the explicit + `SubmitterInfo` parameter, and so does one whose signatory clause resolves statically to an empty + list. Callers that passed a submitter equal to the payload's signatories drop the argument; a + caller that passed a different `actAs` was asserting an authority the template does not grant. A + caller that passed a `SubmitterInfo` carrying `readAs` was over-asserting nothing, though — + `readAs` is disclosure, not authority — and the emitted wrapper gives it no way to set one. That + call moves to `ILedgerWriter.TryCreateAsync(payload, submitter, workflowId, commandId, + timeout, cancellationToken)`, which still takes the whole `SubmitterInfo`. +- **The `Party actAs` overloads are removed**, both the emitted ones and their six hand-written + counterparts in `CreateByExercise` and `ThrowingExercise`. A positional `Party` call site keeps + compiling — the implicit `Party` → `SubmitterInfo` conversion still binds — but a call passing + `actAs:` as a named argument does not, and the change is binary-breaking either way. Pass a + `SubmitterInfo`, or let a `Party` convert. The named-controller parameters emitted for + create-bearing choices (`owner`, `custodian`, `provider`, `steward`) are a different family and + are unchanged. +- **The six single-`Party` convenience overloads on `PartyOverloads` + (`Daml.Ledger.Abstractions`) are removed**, and the class with them: the `TryExerciseAsync` / + `TryCreateAsync` pair over `ILedgerWriter` and the four `SubscribeAsync` (offset and + `StakeholderResume` forms), `SubscribeActiveAsync` and `SubscribeLedgerEffectsAsync` members over + `ILedgerStreamer`. No positional call ever reached them: `SubmitterInfo` declares an implicit + conversion from `Party`, which makes the interface member itself applicable to a `Party` + argument, and C# considers extension methods only when no instance member is. So + `client.SubscribeAsync(alice, ...)`, `writer.TryCreateAsync(payload, alice, ...)` and every + other positional call site compile and behave exactly as before — they always bound to the + interface member. Only a caller who named the argument, `actAs: alice`, reached the extension, + and that spelling no longer compiles: drop the name, or use the interface's own parameter name, + `submitter: alice`. Binary compatibility breaks for an assembly compiled against a call to one of + the six. +- **`SingleCommandSubmission` is renamed to `SingleCommandExtensions`** + (`Daml.Ledger.Abstractions`), aligning it with the `