Edit source

C# interop and safety

By-reference calls

Provider metadata preserves C# parameter modes:

import { Int32 } from "@tsonic/dotnet/System.js";
import { out } from "@tsonic/csharp/lang.js";
import type { int } from "@tsonic/csharp/types.js";

let value: int = 0;
const parsed = Int32.TryParse("42", out(value));

The target accepts out(value) only when the selected parameter is writable and the argument identifies exact storage. It does not infer by-reference mode from the method name.

Attributes

Use the neutral attribute builder with provider-backed attribute types:

attribute<Controller>()
  .method((controller) => controller.handle)
  .add(AuthorizeAttribute);

Selectors identify exact source declarations. The .NET provider resolves the attribute constructor and legal argument values; the C# planner emits Roslyn attribute nodes. Marker calls erase from runtime code.

Safe typed locations

let value: int32 = 1;
const location = addressOf(value);
storePointer(location, loadPointer(location) + 1);

The C# target lowers this to the closed Tsonic.CSharp.Runtime.Location<int> carrier. It preserves local, parameter, field, property, array-element, and projected value-type storage identity when the exact location can be proven.

Native C# pointers

import { loadNativePointer, unsafeContext } from "@tsonic/core/lang.js";
import type { NativePointer, int32 } from "@tsonic/core/types.js";

export function read(pointer: NativePointer<int32>): int32 {
  return unsafeContext(loadNativePointer(pointer));
}

Native pointer representation, unsafe lexical context, declaration-level requires-unsafe, and project permission are separate controls. The target emits AllowUnsafeBlocks only when the sealed target program contains an authorized unsafe requirement. Other marker use does not imply unsafe output.

languageDialect and memorySafetyRules independently select the C# language and memory-safety specifications. Preview memory-safety rules require the C# 15 preview dialect.

Generators and resources

Ordinary representable generators use native C# iterators:

export function* values(): Generator<int32, void, unknown> {
  yield 1;
  yield 2;
}

Bidirectional and asynchronous generator contracts that cannot be represented by native iterator syntax use closed runtime protocols. using, await using, for await...of, cleanup, completion, injected next(value), return, and throw behavior are selected from exact generator/resource evidence. Native C# restrictions such as illegal yield placement produce deterministic target diagnostics rather than a hand-written state-machine rewrite.