Understand a diagnostic and repair the code
A useful diagnostic explains which intention is missing or which contract the program would violate. The examples below show invalid source, its reason and a repair. Rule identifiers link to the specification; they are not assigned compiler diagnostic codes or promises of exact message text.
Fragments assume the enclosing action and inputs described in each section. Invalid examples are intentional. Repairs use the existing language and normal .NET APIs.
Named loop and jump destinations
| Invalid intent | Required explanation and repair |
|---|---|
| break customers_loop without an enclosing named loop | Identify the unresolved loop target; name an enclosing loop or use an unqualified break for the innermost loop |
| continue completed_loop after that loop has ended | A named loop transfer requires an enclosing active loop, not a previous declaration |
| goto customers_loop when only a loop name is present | Goto requires a mark; use named break/continue for loop control |
| goto missing with no matching mark | Identify the missing destination; do not silently create a mark |
| A nested callable targets its caller's loop or mark | Identify the callable boundary; return an ordinary result and let the caller transfer control |
| A concurrent worker names a loop outside its containing concurrent loop | Identify the concurrency boundary; naming does not widen the existing legal transfer targets |
| A transfer bypasses initialization or required cleanup | Identify the violated scope/lifetime rule; a mark or loop name does not waive it |
Forward versus backward direction alone is not an error: both are supported for goto. Target kind, visibility, assignment and lifetime legality matter. These are specification diagnostic requirements, not assigned compiler codes. See named loops and marks.
Names differing only in case
Invalid: two locals declared in the same scope:
let customerCount = 1
let CustomerCount = 2
Both declarations name the same symbol. Capitalization cannot create a second variable. If the intention is to update the existing count, assign it:
let customerCount = 1
customerCount = 2
If two different concepts are needed, give them different names, such as customerCount and activeCustomerCount. The same case-insensitive identity applies to imports and CLR member binding. A parameter and member that collide require explicit arguments/self qualification; changing capitalization does not resolve that ambiguity. See CS-NAME-010/011 and the rename exercise.
A local's type cannot change after initialization
Invalid: a nonnullable Integer local later receives absence:
let count as Integer = 0
count = nothing
The initializer establishes nonnullable Integer. A later assignment does not retroactively infer a different type. If absence is part of this variable's meaning, declare the compatible type:
let count as Global.System.Nullable <of Integer> = 0
count = nothing
The repaired local starts at zero and can later become absent. Starting with let count as Integer = nothing also infers real Nullable<Integer>, but starts absent instead. An action that may return an absent Integer similarly needs a compatible result contract; the local inference rule does not silently change a fixed CLR method signature. String/reference values can already contain nothing. See CS-NULL-006–008.
A type argument does not satisfy its constraints
This fragment uses the constrained import-draft factory.
Invalid: String does not provide the declared base/interface/constructor capabilities:
let factory = new ImportDraftFactory <of String>()
A similarly named method would not satisfy the actual interface relationship either. Choose a type that declares the required relationships:
let factory = new ImportDraftFactory <of CustomerImportDraft>()
let draft = factory.Create(batchId, 7)
The repair assumes a supplied Guid batchId and the CustomerImportDraft declaration from that example. Draft has that actual type, including Email; no Object cast is needed. A compiler checks constraints without running the draft's constructor. Ordinary execution can still fail later in entry validation, construction or a reached body. See CS-GENERIC-002/003.
A fresh awaitable call has no explicit intent
The following action fragment has a supplied String path.
Invalid: the call returns an operation, but the declaration says neither to await it nor to retain it:
let text = Global.System.IO.File.ReadAllTextAsync(path)
For the completed String value, write:
let text = await Global.System.IO.File.ReadAllTextAsync(path)
Global.System.Console.WriteLine(text)
To retain the operation and await it later, use this alternative:
let pending = task_of(Global.System.IO.File.ReadAllTextAsync(path))
let text = await pending
Global.System.Console.WriteLine(text)
An ordinary action can infer its async implementation from the explicit await. A Task-typed assignment target would not repair the original unmarked call. Existing task variables can be copied without another task_of. Capturing an operation adds no automatic join or resource ownership; a locally detectable abandoned capture receives a warning. For owned overlapping workflows, use concurrent scopes.
Required rules: CS-ASYNC-002/006 and the observation warning.
Async inference does not create a cancellation context
Invalid: an ordinary inferred action uses Cancellation without a contract supplying it:
public action ReadReport(path as String)
returns String
return await Global.System.IO.File.ReadAllTextAsync(path, Cancellation.Token)
end action
Await permits async inference, but inference does not add a cancellation parameter or the contextual Cancellation name. For this authored action, declare the explicit asynchronous/cancellation contract:
public async action ReadReport(path as String)
returns String
return await Global.System.IO.File.ReadAllTextAsync(path, Cancellation.Token)
end action
Alternatively, an application can take an ordinary CancellationToken parameter and explicitly pass that variable to imported APIs, without assuming that it creates the contextual name. Imported interface/override signatures remain fixed; do not add a token to an incompatible contract. Owned producer bodies have their own defined context. See CS-ASYNC-003/008 and cancellation binding.
An iteration variable is a readonly binding
Invalid: changing the binding supplied by the loop:
for_each position in range 0 to 2
position += 1
end for_each
The range supplies position for each iteration; reassignment does not control its next value. Compute a separate local instead:
for_each position in range 0 to 2
let nextPosition = position + 1
Global.System.Console.WriteLine(nextPosition)
end for_each
For a different range progression, use its step clause. A readonly reference binding still permits valid mutation of the referenced object's writable members; it does not freeze the object. Writable byref/swap targets must also satisfy binding mutability. See CS-DECL-002–005.
An atomic block cannot suspend
These action fragments have an HttpClient client and Uri address. The repair also borrows a caller-owned Gate called requestGate; all callers coordinating the same work must share that Gate.
Invalid: awaiting inside a synchronous atomic transition:
atomic
let text = await client.GetStringAsync(address)
Global.System.Console.WriteLine(text)
end atomic
Use an async-capable Gate when the operation genuinely needs bounded protected access across the wait:
enter requestGate
let text = await client.GetStringAsync(address)
Global.System.Console.WriteLine(text)
leave
If only a short state update needs protection, perform the I/O outside atomic and protect that update separately. Merely removing the word await is not a repair: the fresh call would be unmarked, and generated asynchronous cleanup cannot suspend inside atomic either. Gate admission releases its permit on exit and borrows the Gate; its owner disposes it after users settle. See CS-ATOMIC-002 and Gate lifetime.
A concurrent iteration cannot return from its action
These fragments belong to an ordinary action with an Integer sequence identifiers and an application action ProcessAsync returning an awaitable.
Invalid: a worker tries to return from the enclosing action:
concurrent for_each identifier in identifiers with limit: 4
if identifier = 0 then
return
end if
await ProcessAsync(identifier)
end for_each
To skip just this item's work, use continue:
concurrent for_each identifier in identifiers with limit: 4
if identifier = 0 then
continue
end if
await ProcessAsync(identifier)
end for_each
Break or bare exit instead stops new scheduling and joins active iterations without automatically cancelling them. A return inside an inline run body is different: it completes that producer's callable boundary after cleanup; it still does not return from the enclosing action. Choose the intended boundary rather than replacing every return mechanically. See CS-CONCURRENT-011 and inline run blocks.
A raw database-reader row is unsafe for concurrent consumption
These action fragments borrow reader, statically an IDataReader positioned before rows in its current result set. Column zero is expected to contain String data.
Invalid: concurrently consume raw row views:
concurrent consume row from reader with limit: 4
Global.System.Console.WriteLine(row.GetString(0))
end consume
The IDataRecord row is the reader's current view. Advancing the reader can change it while a worker still uses it. Serializing advancement alone does not make those views stable. For processing directly against that view, consume sequentially:
consume row from reader
let customerName = row.GetString(0)
Global.System.Console.WriteLine(customerName)
end consume
For parallel work, first copy the needed column values into independent application records or scalar values, then hand those snapshots to workers through a suitable bounded source. Copying a row reference into a list is insufficient. An arbitrary IConsumable implementation does not establish concurrent capability either; IConcurrentConsumable is a promise of safe overlapping acquisition and stable acquired values. The reader remains caller-owned, and completion does not automatically advance to another result set. See CS-CONSUME-003/004, CS-CONSUME-009 and the reader/worker example.
A lazy binding cannot be reassigned
Invalid: assigning a replacement to an already declared lazy binding:
let factor = 2
let total = lazy factor * 10
total = 50
The binding is assign-once even before the initializer has run. Assignment cannot replace or reset the computation. If the application needs an editable result, force the lazy value into an ordinary mutable local:
let factor = 2
let total = lazy factor * 10
let current = total
current = 50
Reading total initializes it once to 20. Changing current then updates that separate Integer local; total retains its cached value. Writable by-reference uses and writable lazy setters are also invalid. This restriction does not make a returned object deeply immutable. See CS-LAZY-003 and the complete lazy reference.
A declarative event source must be trackable
Invalid: using a transient call as a declarative source, where GetProgress is an application action returning ImportProgress:
private action ShowProgress(completed as Integer, total as Integer)
handles_event GetProgress().ProgressChanged
Global.System.Console.WriteLine("{{completed}} of {{total}}")
end action
The compiler has no stored source relationship to track or rewire. For one observation session, obtain the source once and subscribe imperatively:
public action ObserveProgress()
let progress = GetProgress() ?? throw
action ShowProgress(completed as Integer, total as Integer)
Global.System.Console.WriteLine("{{completed}} of {{total}}")
end action
event progress.ProgressChanged add_handler ShowProgress
try
progress.Report(1, 1)
finally
event progress.ProgressChanged remove_handler ShowProgress
end try
end action
This repair assumes the same application GetProgress action and the ImportProgress class from the event reference. The source call runs once; absence fails before subscription. Finally removes the same handler when the session ends. If a lasting, replaceable relationship is intended instead, store the source in a trackable field, property or need and use handles_event there. See CS-EVENT-004/005.
Distinguish a diagnostic from a runtime failure
| Case | Required treatment |
|---|---|
| A fresh awaitable call is unmarked | Compile-time error; choose await, task_of or an applicable owned producer |
| An unobserved captured operation is locally detectable | Compiler warning; analysis cannot prove every opaque transfer of responsibility is safe |
| Strict Take receives a constant negative count | Ordinary library argument validation at the call; no new mandatory compiler diagnostic is inferred |
| Strict Take requests more items than the source supplies | Failure during deferred traversal, possibly after a yielded prefix |
| A Gate is constructed with a computed nonpositive permit count | ArgumentOutOfRangeException from constructor validation |
| A statically known borrowed need is disposed by its consumer | Ownership diagnostic; leave cleanup to the provider/scope |
| A registered service's constructor throws | Propagate activation failure; do not relabel it as missing registration |
Static type/access/ownership checks cannot predict every source exception, network failure or native container behavior. Conversely, wrapping a compile-time contract violation in try does not make it valid. Use the boundary index to find the execution and failure timing of a valid program.