Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright 2026 Peaceful Studio OÜ
# SPDX-License-Identifier: Apache-2.0

# EditorConfig - https://editorconfig.org
root = true

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build-oci-codegen-cs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jvm-helper-jar
path: jvm-helper/target/scala-2.13/daml-codegen-jvm-helper.jar
path: jvm-helper/target/scala-2.13/daml-dar-to-proto.jar
retention-days: 1
if-no-files-found: error

Expand Down Expand Up @@ -231,7 +231,7 @@ jobs:
BUNDLE="bundle/${RID}"
mkdir -p "${BUNDLE}/bin"
cp "publish/${RID}/${EXE_NAME}" "${BUNDLE}/bin/${EXE_NAME}"
cp jvm-helper-artifact/daml-codegen-jvm-helper.jar "${BUNDLE}/bin/daml-codegen-jvm-helper.jar"
cp jvm-helper-artifact/daml-dar-to-proto.jar "${BUNDLE}/bin/daml-dar-to-proto.jar"
cp "scripts/bundle-entrypoint/${ENTRYPOINT_NAME}" "${BUNDLE}/${ENTRYPOINT_NAME}"
cp "scripts/bundle-entrypoint/${MANIFEST_NAME}" "${BUNDLE}/component.yaml"
if [ "${BUNDLE_OS}" != "windows" ]; then
Expand Down Expand Up @@ -343,7 +343,7 @@ jobs:
"component.yaml:644"
"${ENTRYPOINT_NAME}:755"
"bin/${EXE_NAME}:755"
"bin/daml-codegen-jvm-helper.jar:644"
"bin/daml-dar-to-proto.jar:644"
)

ANNOTATIONS_JSON="${RUNNER_TEMP}/oras-annotations-${RID}.json"
Expand Down Expand Up @@ -400,7 +400,7 @@ jobs:
"component.yaml:${COMPONENT_FILE_MEDIA_TYPE}" \
"${ENTRYPOINT_NAME}:${COMPONENT_FILE_MEDIA_TYPE}" \
"bin/${EXE_NAME}:${COMPONENT_FILE_MEDIA_TYPE}" \
"bin/daml-codegen-jvm-helper.jar:${COMPONENT_FILE_MEDIA_TYPE}")
"bin/daml-dar-to-proto.jar:${COMPONENT_FILE_MEDIA_TYPE}")
popd > /dev/null
echo "ref=${REF}" >> "${GITHUB_OUTPUT}"
echo "digest=${digest}" >> "${GITHUB_OUTPUT}"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nuget-pack-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jvm-helper-jar
path: jvm-helper/target/scala-2.13/daml-codegen-jvm-helper.jar
path: jvm-helper/target/scala-2.13/daml-dar-to-proto.jar
retention-days: 1
if-no-files-found: error

Expand Down Expand Up @@ -157,7 +157,7 @@ jobs:
run: |
set -euo pipefail
dotnet publish src/Daml.Codegen.CSharp.Cli -c Release -o bundle/bin
cp jvm-helper-artifact/daml-codegen-jvm-helper.jar "bundle/bin/daml-codegen-jvm-helper.jar"
cp jvm-helper-artifact/daml-dar-to-proto.jar "bundle/bin/daml-dar-to-proto.jar"
cp "scripts/bundle-entrypoint/${ENTRYPOINT}" "bundle/${ENTRYPOINT}"
if [ "${RUNNER_OS}" != "Windows" ]; then
chmod +x "bundle/${ENTRYPOINT}" "bundle/bin/${EXE}"
Expand Down
5 changes: 4 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright 2026 Peaceful Studio OÜ
# SPDX-License-Identifier: Apache-2.0

repos:
- repo: local
hooks:
Expand All @@ -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)(/|$)
1,013 changes: 1,012 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

89 changes: 86 additions & 3 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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<TInterface, TView>` pairs an interface marker with its
view record, `Choice<TTemplate, TArg, TResult>` 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.
Expand Down
1 change: 1 addition & 0 deletions Daml.Codegen.CSharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Folder Name="/src/">
<Project Path="src/Daml.Codegen.CSharp.Cli/Daml.Codegen.CSharp.Cli.csproj" />
<Project Path="src/Daml.Codegen.CSharp/Daml.Codegen.CSharp.csproj" />
<Project Path="src/Daml.Codegen.Intermediate/Daml.Codegen.Intermediate.csproj" />
<Project Path="src/Daml.Codegen.Testing.Conformance/Daml.Codegen.Testing.Conformance.csproj" />
<Project Path="src/Daml.Ledger.Abstractions.Testing.Conformance/Daml.Ledger.Abstractions.Testing.Conformance.csproj" />
<Project Path="src/Daml.Ledger.Abstractions/Daml.Ledger.Abstractions.csproj" />
Expand Down
18 changes: 14 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<NoWarn>$(NoWarn);CA1062;CA2225;CA1711;CA1721;CA1000;CA1720;CA1305;CA1024;CA1707;CA1822;CA1850;CA1852;CA1859</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CA1062;CA1852;CA1859</NoWarn>

<!-- Packaging rigor. Pack-time only, so inert for IsPackable=false projects. SourceLink for
GitHub is implicit in the .NET SDK; ContinuousIntegrationBuild=true is passed by
publish.yaml, never locally. -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EnablePackageValidation>true</EnablePackageValidation>

<!-- Package metadata -->
<Authors>Peaceful Studio OÜ</Authors>
Expand All @@ -20,9 +30,9 @@
<PackageIcon>icon.png</PackageIcon>

<!-- Versioning -->
<Version>0.4.1-preview.1</Version>
<AssemblyVersion>0.4.1.0</AssemblyVersion>
<FileVersion>0.4.1.0</FileVersion>
<Version>0.5.0-preview.1</Version>
<AssemblyVersion>0.5.0.0</AssemblyVersion>
<FileVersion>0.5.0.0</FileVersion>

<!-- Package output directory -->
<PackageOutputPath>$(MSBuildThisFileDirectory)output/nuget</PackageOutputPath>
Expand Down
18 changes: 10 additions & 8 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
</PropertyGroup>
<ItemGroup>
<!-- Core dependencies -->
<PackageVersion Include="Google.Protobuf" Version="3.35.1" />
<PackageVersion Include="Google.Protobuf" Version="3.36.1" />
<!-- Grpc.Tools >= 2.69.0 ships a linux_arm64 protoc that segfaults (grpc/grpc#38538;
fix attempt grpc/grpc#41994 shipped in 2.81.x without resolving it); 2.68.1 is the
last release whose linux_arm64 protoc works. Bump only when grpc/grpc#38538 is closed. -->
<PackageVersion Include="Grpc.Tools" Version="2.68.1" />
<!-- Logging -->
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<!-- CLI tooling -->
<PackageVersion Include="System.CommandLine" Version="2.0.9" />
<PackageVersion Include="Spectre.Console" Version="0.57.0" />
<!-- Code generation -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.2.2" />
<PackageVersion Include="xunit.v3.assert" Version="3.2.2" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.11" />
<PackageVersion Include="Spectre.Console" Version="0.57.2" />
<!-- Conformance kit -->
<PackageVersion Include="xunit.v3.extensibility.core" Version="4.0.0" />
<PackageVersion Include="xunit.v3.assert" Version="4.0.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.6.0" />
</ItemGroup>
</Project>
Loading
Loading