On this page
- double-equals
- statement-rule
- pipe-precedence-error
- interp-scalar-only
- no-printf-family
- no-mutation
- no-operator-defaulting
- no-oo
- no-srtp
- no-computation-expressions
- no-async-concurrency
- no-while
- seq-equality
- no-tuple-ord
- tuple-exhaustiveness-bounded
- no-pattern-binders
- lowercase-binds
- uppercase-pattern-is-ctor
- unused-binding
- exhaustiveness-hard-error
- unreachable-arm-hard-error
- attributes-registered
- seq-patterns
- or-patterns
- lambda-body-offside
- match-pipe-offside
- pipe-alignment
- update-path-plain
- ctor-pattern-scrutinee
- column-zero-statements
- record-field-comma-trap
- no-arrays
- no-access-modifiers
- no-auto-members
- no-let-rec
- no-format-specifiers
- block-comments
- floats-finite-only
- no-chars
- no-exceptions
- no-type-ascription
- no-anonymous-records
- no-destructuring-binders
- semicolon-command-argv
- redirect-argv
- raw-single-line
- interpolated-raw
- regex-pattern
- no-heredoc
- semicolon-greedy-bodies
- record-fields-ignore-indent
- bang-sigil
- capture-sigil-aligns
- comment-boundary
- type-name-expression-position
- no-record-patterns
- no-backward-composition
- no-back-pipe
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.
- different — weir has an equivalent, deliberately different
- rejected — absent by design, with a rationale
- pending — absent, undecided — treat as absent
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
^arejects at parse (a sig type is'a-shaped);inlineis 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/pollare 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 bisfalseon 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;_xaccepted 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 (IEEEx % 0.0is 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|> tryFORM, giving Option or Result<'T, string> - F#
- accept try/with/finally; no
|> tryform
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