weir
On this page

Divergences from F#

The complete border: what is deliberately different, what is rejected by design, what is merely pending. One entry per divergence or absence, machine-verified — the fidelity oracle runs both compilers and fails the build on any pin whose F#/weir verdicts disagree without citing an id here. The source of truth is tests/fidelity/divergences.md, machine-read and dense by design; this page is its readable face.

double-equals different

equality is == ; = is binding-only

weir
== accepts / = rejects
F#
== rejects / = accepts

SEMANTICS: why == and not =

statement-rule different

discarded non-unit statement

weir
reject
F#
accept (warning)

SEMANTICS: the statement rule

pipe-precedence-error different

piping into an operator expression

weir
reject (targeted)
F#
accept

SEMANTICS: precedence error

interp-scalar-only different

non-scalar interpolation holes

weir
reject (show is the renderer)
F#
accept

SEMANTICS: interpolation, show

no-printf-family rejected

printfn / sprintf / %d

weir
reject (interpolation is the mechanism)
F#
accept

SEMANTICS: interpolation

no-mutation rejected

mutable / <- assignment (reserved word)

weir
reject
F#
accept

SEMANTICS: evaluation

no-operator-defaulting rejected

+ on two unresolved params

weir
reject (int-or-string guess refused)
F#
accept (defaults int)

SEMANTICS: var-var operators

no-oo rejected

classes, interfaces, members, inheritance

weir
reject
F#
accept

a typed shell, not an object language

no-srtp rejected

SRTP — inline + ^a member-constraint machinery

weir
^a rejects at parse (a sig type is 'a-shaped); inline is an ordinary lowercase identifier, no modifier exists
F#
accept

SEMANTICS: inferred classes — the built-in constraint families (Eq, Ord, Show) are closed; ad-hoc polymorphism, if ever, arrives as a family, never user machinery

no-computation-expressions rejected

builder blocks (seq { }, async { }, custom CEs)

weir
reject
F#
accept

pipelines are the composition story; comprehension sugar, if ever, is parser-only — not CE machinery

no-async-concurrency rejected

async/task/await machinery

weir
reject
F#
accept

a scripting shell does not need it: processes and pipelines ARE the concurrency model (data-parallel combinators Seq.pmap/piter exist — parallelism as a library detail, never language machinery); wanting async is the graduation signal — go to full F#

no-while rejected

while / unbounded loops

weir
reject — retry/poll are the bounded condition loops [D:retry-poll]; an unbounded loop is unrepresentable
F#
accept

DECISIONS retry-poll NOT-DOING list; SEMANTICS: retry/poll

seq-equality different

== / = on seq-typed operands

weir
reject at check — Eq excludes seqs: forcing two lazy seqs is expensive (wrong for infinite ones) and reference identity is useless, so refusing is the honest third option; a record with a seq field is poisoned too, which is why the Http constructor-equivalence pin compares in F#, outside the language
F#
accept — and the ANSWER depends on the RUNTIME type (fsy-refereed 2026-08-06): structural for list/array objects even upcast to seq<int> (true), REFERENCE equality for generated enumerators (Seq.map id a = Seq.map id b is false on equal values; seq { 1; 2 } = seq { 1; 2 } false) — same static type, different answers by provenance

SEMANTICS: inferred classes (Eq); oracle pin "seq equality"

no-tuple-ord different

tuples are not orderable (F# compares lexicographically); sortBy keys stay scalar

weir
reject
F#
accept

SEMANTICS: tuples — same narrowness family as Ord itself; with record ordering if ever

tuple-exhaustiveness-bounded different

only an all-irrefutable tuple arm completes a match (F# does per-component product analysis)

weir
catch-all required
F#
accepts e.g. (true,_)/(false,_)

SEMANTICS: tuples — bounded rule; widen on receipts

no-pattern-binders different

REFUTABLE patterns in binding position (F# warns-accepts incomplete binders; weir hard-errors "this pattern can fail; use match"). Irrefutable binders SHIPPED 2026-07-21 — destructuring lets (bare-comma and parenthesized, nested), tuple lambda/sugar params — completing the arc the retired no-tuples row opened with "destructuring is the real scope"

weir
reject refutable
F#
accept + warning

SEMANTICS: binders — the warning-vs-error strictness family (statement rule, exhaustiveness)

lowercase-binds different

binding names must start lowercase (F# accepts uppercase value bindings, style-discouraged)

weir
reject
F#
accept

SEMANTICS: the casing law — fourth member of the warning-vs-error strictness family and honestly its first STYLISTIC member: no silent-wrong-meaning bug is prevented at the binder itself; the payoff is disjoint name classes (pattern-position determinism, unshadowable modules by construction)

uppercase-pattern-is-ctor different

an uppercase identifier in a PATTERN is always a constructor (unknown = hard error + hint); F# resolves by scope and falls back to a VARIABLE binding with warning FS0049

weir
reject unknown
F#
accept + warn (binds a var)

SEMANTICS: branching — the FS0049 trap (typo'd case silently becomes an irrefutable catch-all) made unrepresentable; the strictness family again

unused-binding different

an unread let binder is a HARD ERROR — scripts and module bodies, top-level and block-local, destructured names each judged, an unread binder rebound (shadow) errors at the earlier binder, and a module's unsigned member unread at home is dead private code; _-prefixed names are the deliberate-unused escape and a bare _ let binder refuses (the anonymous swallow); params, match-arm binders, for/until/within and yaml for binders are exempt (v1 scope)

weir
reject
F#
accept SILENTLY by default — FS1182 exists but is opt-in (--warnon:1182); probed via fsy build 2026-09-13: clean accept, zero warnings emitted; _x accepted both sides

PLAN-unused-bindings — the strictness family (statement rule, exhaustiveness, unreachable arms): binding is exactly how weir silences raise-on-nonzero, so an unread let r = cmd | complete is a swallowed failure; Go's compile-error precedent

exhaustiveness-hard-error different

a non-exhaustive match is a HARD ERROR (F# warns and accepts)

weir
reject
F#
accept + warning

SEMANTICS: branching — user decision 2026-07-18; surfaced as an oracle divergence by the literal-pattern pins (2026-07-21); corpus: 5928e91

unreachable-arm-hard-error different

an arm after an unguarded catch-all is a HARD ERROR, located at the catch-all with a constructor hint for variable binders (F# warns FS0026 on the dead arm and accepts)

weir
reject
F#
accept + warning

SEMANTICS: branching — user decision 2026-07-21; coverage's dual, and the casing-law footgun (a typo'd constructor becomes a catch-all binder) caught at its source

attributes-registered different

[<Name arg>] on record fields, union declarations, and union cases (positions widened 2026-09-04 [D:attr-positions], both hosts FCS-probed) is a CLOSED registry (Short/NoShort/Positional/Default/Wire/Tag/Other), position-scoped — unknown names are check errors with didYouMean, registered names in the wrong position teach their home; F# resolves attribute names as types (accepts any in-scope attribute, rejects unknowns)

weir
accept registered in its position, reject the rest
F#
reject Short/Tag/Wire (no such types), accept System.Obsolete

SEMANTICS: attributes — check-time, fully erased; consumers registered; validation at attachment, binding at consumption

seq-patterns different

F#'s LIST-pattern spelling ([], x :: rest, [a; b]) with SEQ semantics — the param-ful-RHS class: existing law reaching a scope F# left empty (F#'s patterns want list; weir has one sequence type). Bounded force at the match site (maxArity+1 probes), memoize-once (Seq.cache — arms probe one buffer, rest = suffix + untouched tail, effects once TOTAL); [] + irrefutable-cons complete (flat v1 — chained-cons completeness wants a wildcard); param scrutinees ride ctor-pattern-scrutinee

weir
accept (seq scrutinee)
F#
reject (patterns want list)

PLAN-seq-force-patterns Part 2; SEMANTICS: patterns

or-patterns different

or-pattern alternatives that BIND names (| (n, 0) | (0, n) -> n); binder-free alternatives (| 0 | 1 ->, | Debug | Info ->) converged

weir
reject (teaches one arm per alternative)
F#
accept (same names, same types)

DECISIONS [D:or-patterns]: v1 is binder-free; binding alternatives wait for a receipt

lambda-body-offside different

a multiline lambda's body lines sit at-or-right of the OPENER line's indent (a named leak error below it); F# floors the body at the enclosing construct's offside context, accepting dedents between the two (col-0 rejects on both sides)

weir
reject (left of the opener)
F#
accept

the strictness family (pipe-alignment's sibling); [D:multiline-lambda]

match-pipe-offside different

a trailing |> reads by its column against the arm: at the arm's | it CLOSES the match and pipes the whole (Same, was an unrecorded divergence burying it in the last arm); at or under the arm BODY column it EXTENDS the arm body (Same); left of the body it REJECTS with a located teaching naming the body column. The floor is the body column, CLEANLY — where F#'s relaxed offside tolerates a leading |> hanging a few columns LEFT of the body before it parse-errors. So weir is STRICTER in that hang zone: a |> one-to-few columns left of the body extends in F# but rejects in weir (pinned). weir has no fudge — line the pipe up at or under the body

weir
body-column floor, no hang
F#
body column minus a token-width hang

[D:match-pipe-offside]; every unambiguous placement is Same

pipe-alignment different

sibling | lines (arms, union cases, chain stages) must align EXACTLY; F# merely warns (FS0058) on off-by-one — the warning-vs-error strictness family (refutable binders, exhaustiveness). The same rule closes two former gaps F#-ward: arms left of their match now reject (F# errors), and a shallower arm offside-CLOSES nested compounds, so the nested-match-return shape F# reads from columns now works (was a weir-reject via flat-join mis-association)

weir
reject off-by-one
F#
warn-accept

[D:pipe-alignment]; SEMANTICS: multi-line rules

update-path-plain different

update paths are FIELD paths only; F# name resolution captures a type named like the path head (type I + { o with I.X = v } rejects in F#, accepts in weir)

weir
accept
F#
reject (type-name capture)

FCS-probed 2026-07-22; the probe's naming collision found it

ctor-pattern-scrutinee narrowed

a constructor pattern now types an unresolved scrutinee from its union when the case names a SINGLE owner (let f x = match x with | A -> ..., nested payloads too) — converged with F# for the common case [D:match-scrutinee-infer]. Residual: a case name owned by TWO unions stays rejected (nothing picks the union; F# resolves by most-recent-in-scope)

weir
accept (single owner) / reject (ambiguous)
F#
accept

corpus: 5928e91; live: git-subrepo (a standalone result-dispatcher fn); the one-way bind, not full HM — no cross-arm unification variables

column-zero-statements different

statements start at column 0 AND end at the next col-0 line — the sole statement boundary (blank-blind since [D:body-blanks]); F# accepts uniformly-indented fragments

weir
reject (continuation without a statement)
F#
accept

corpus: 5928e91 (x5); the assembly law — absorbed the retired blank-line-ends-statement's boundary role

record-field-comma-trap different

{ Name = "x", Age = 21 } — F# silently makes the FIELD a tuple (the classic trap); weir rejects the shape

weir
reject
F#
accept (trap semantics)

corpus: 5928e91; strictness family, weir-safe direction

no-arrays pending

[| ... |] array literals

weir
reject (seqs are the collection)
F#
accept

corpus: 5928e91

no-access-modifiers pending

let internal/private ... (the corpus hit also carried fsi's ;;, an artifact not a feature)

weir
reject
F#
accept

corpus: 5928e91

no-auto-members pending

compiler-generated union testers (.IsA/.IsCaseB) — the no-OO bound's corpus face

weir
reject
F#
accept

corpus: 5928e91 (x2)

no-let-rec pending

let rec (reserved word)

weir
reject
F#
accept

recursion unserved; pipelines cover iteration

no-format-specifiers pending

format specifiers in holes ($"{x:N2}")

weir
reject
F#
accept

decided out of interp v1; no demand logged

block-comments pending

(* ... *)

weir
reject
F#
accept

// decided over #; (* *) never decided, no demand

floats-finite-only different

float semantics

weir
finite-only: non-finite results raise (the checkedInt law); Eq excluded — == on floats is a teaching error naming Float.near; float % refused (IEEE x % 0.0 is NaN) [D:modulo]
F#
IEEE: NaN/Infinity values, == allowed

[D:floats]; plans/PLAN-floats-cost.md — "shells don't have floats" retired (capability peers zsh/pwsh/nushell all float); the decline's other argument died with Duration.toS

no-chars pending

char literals

weir
reject
F#
accept

no demand

no-exceptions different

try/with/finally, raise; and the weir-only e |> try [result] [D:try-form]

weir
reject try/with/finally blocks (fail raises; cleanup is within … always); catching is the |> try FORM, giving Option or Result<'T, string>
F#
accept try/with/finally; no |> try form

error-handling-as-value: the expression twin of | complete

no-type-ascription pending

(e : ty) annotations

weir
reject
F#
accept

checklist 2.3: must re-verify, never relabel, when it lands

no-anonymous-records different

NARROWED 2026-09-03 [D:anon-literals]: literals SHIPPED — {| key = k; n = 3 |} accepts, typed by the canonical shape name, unifying with the same-shape adapter type. The row survives only at its edges: GENERIC literals (fun x -> {| a = x |} — F# accepts; weir's canonical name cannot hold a type variable, so fields must be ground — annotate or declare), EMPTY {||} (F# accepts; no receipt, Map.ofPairs [] writes {}), and copy-update {| r with … |} (F# accepts, widening included; weir refuses toward [D:record-update]'s park). Undeclared PLAIN literals { A = 1 } still resolve by exact declared field set

weir
reject generic/empty/update; accept literals
F#
accept (incl. generic, empty, update)

FCS-probed 2026-09-03 (update probe: accepts, {| r with a = 5 |} evaluates); the write-side receipt (Map.ofPairs ceremony + one-value-type trap); was: one telemetry hit; corpus: 5928e91 (x2)

no-destructuring-binders pending

let (a, b) = ... , fun (x, y) ->

weir
reject
F#
accept

tied to the tuples decision

semicolon-command-argv different

; inside a command line (bash chains; weir passes literal argv + warns)

weir
argv word + warning
F#
n/a (bash prior, not F#)

SEMANTICS: sequencing; the no-injection pin

redirect-argv different

> / >> inside a command line (bash redirects; weir passes literal argv + warns with the File.write/File.append spelling)

weir
argv word + warning
F#
n/a (bash prior, not F#)

SEMANTICS: command mode — the streams stance; the semicolon row's family

raw-single-line different

@"..." and """...""" raw strings are SINGLE-LINE (F# spans physical lines) — the assembler, fmt's refuse-on-mismatch argument, and the highlighter's swallow analysis all rest on strings closing before EOL — RE-PRICED 2026-08-16: Phase 2 (multi-line) deferred by ruling; the four dependencies (assembler, fmt, highlighter, REPL Enter-completeness carve-out) make it its own session, with the yaml-district verbatim machinery as the stated precedent

weir
reject multi-line
F#
accept

blank-line-ends-statement's family; PLAN-raw-strings; [D:interp-raw] Phase-2 deferral

interpolated-raw different

$"""...""" LANDED 2026-08-16 [D:interp-raw] — the park's stated trigger (raw-with-splice receipts) fired on the generating-weir-source workaround (let q = "\""). ONE spelling: $@"..." does not exist and teaches (two spellings for one capability is what this project removes). No brace escape (raw = no escapes; {{ teaches the ordinary form; F#'s multi-$ scheme declined — grammar complexity for a rare need). The Regex position still refuses (computed patterns stay expression-side — the park's original reason, enforced not forgotten). Secrets refuse the hole identically (same EInterp node). Single-line, like every kind.

weir
accept ($"""-only)
F#
accept (both spellings, multi-$)

[D:interp-raw]; the raw-single-line row stands

regex-pattern different

the bespoke Regex "lit" binder match pattern — literal-only, check-time compiled, arity-typed binders (F# has no built-in regex pattern; ParseRegex is userland active patterns, whose group/binder mismatch is a silent runtime non-match)

weir
weir-only match form, accept
F#
reject (unknown pattern)

SEMANTICS: patterns — the FIRST weir-only match form; user active patterns stay parked, stated so the door is closed, not ajar

no-heredoc different

<< is backward composition in expressions and a literal argv word in commands (bash: heredoc); feeding stdin is a seq piped into a command (xs | cmd — the value-headed pipe)

weir
composition / argv word
F#
n/a (bash prior, not F#)

SEMANTICS: pipes — stdin feeding

semicolon-greedy-bodies different

single-line-TYPED if c then a ; b groups INSIDE the body (F# verbose groups outside); multi-line siblings group F#-faithfully since the offside close (2026-07-20)

weir
body-scoped
F#
trailing

SEMANTICS: sequencing — greedy survives only where the sigil-era continuation join needs it; the assembler paren-wraps compounds at same-level siblings

record-fields-ignore-indent different

NARROWED 2026-07-24 [D:field-alignment]: sibling bracket entries (record/type fields, list elements) now align EXACTLY with the first entry — F#-parity (probes: F# errors on off-by-one fields in decls, literals, and lists alike). The row survives only at its edges: update headers and Stroustrup closers may sit where F#'s offside rejects (col-0 closer in update position), and explicit trailing-; continuations stay alignment-free

weir
accept (closers/updates/;)
F#
reject

SEMANTICS: multi-line rules; the field-alignment battery

bang-sigil different

!(cmd chain) runs-and-streams (bash: extglob/history !)

weir
effect sigil, unit
F#
n/a (bash prior; invisible to the F# oracle)

SEMANTICS: sigils

capture-sigil-aligns different

$(cmd chain) captures output — the bash prior HELPS here (recorded per the == archaeology precedent: priors that help get named too)

weir
capture, typed seq<string>
F#
n/a (bash prior)

SEMANTICS: sigils

comment-boundary different

// mid-token is NOT a comment (https://... barewords); comment needs line start or preceding whitespace

weir
url survives
F#
1// c is a comment

SEMANTICS: comments; nuget receipt

type-name-expression-position different

a type NAME as an argument: Args.load Cli, from json T, from yaml T, [<Default …>]-bearing records by name

weir
accept — the boundary forms take the type by name (checker intercepts; [D:typed-argv], [D:modules-v1] qualified literals)
F#
reject (types are not expressions)

flagged unrowed by the coming-from page's harvest 2026-08-05; the F# section documents it

no-record-patterns different

NARROWED TWICE. 2026-08-19a [D:record-patterns]: the irrefutable subset shipped (let/param/lambda/match/for; partial fields; params type by ROW; anonymous shapes destructure with the same brace spelling, no {| |} pattern form). 2026-08-19c [D:record-pattern-rows]: match position emits a ROW like the binder position (the ctor-pattern law was inherited, not justified — a field name needs a row, a CASE name needs the nominal type), so a match on an unresolved param types and generalises; F# requires the nominal type here (its records are nominal, no row inference). 2026-08-19b [D:refutable-record-patterns]: REFUTABLE children admitted too ({ State = "running" }, { t = Some "jh" }, { items = h :: t }, nested and tupled) — the park's "exhaustiveness over field values" pricing was wrong, because a refutable child makes the pattern refutable and a refutable pattern never completes a match, which the literal rule already enforced. ONE divergence remains: PUNNING ({ Names } — F# accepts; dead under the casing law, fields keep declared case and binders must be lowercase), reject-pinned as a weir-error fence

weir
reject punning; accept both irrefutable and refutable patterns
F#
accept (incl. punning)

[D:record-pattern-costing] priced it; two rows shipped it

no-backward-composition rejected

backward composition f << g

weir
reject (teaches g >> f)
F#
accept

DECISIONS [D:left-to-right-ops]: one direction per operation — << was >> reversed

no-back-pipe rejected

back-pipe f <| x

weir
reject (teaches f (x) / x |> f)
F#
accept

DECISIONS [D:left-to-right-ops]: |> reversed, no receipt, F#'s left-associativity gotcha