Skip to content

Feature: #elif preprocessor directive#19323

Merged
T-Gro merged 30 commits intomainfrom
feature-directive-elif
Feb 27, 2026
Merged

Feature: #elif preprocessor directive#19323
T-Gro merged 30 commits intomainfrom
feature-directive-elif

Conversation

@T-Gro
Copy link
Member

@T-Gro T-Gro commented Feb 18, 2026

Inspired by and following up from #19045 by @Thorium.

RFC FS-1334 · Suggestion #1370

What

#if WIN64
let path = "/library/x64/runtime.dll"
#elif WIN86
let path = "/library/x86/runtime.dll"
#elif MAC
let path = "/library/iOS/runtime-osx.dll"
#else
let path = "/library/unix/runtime.dll"
#endif

Supports &&, ||, ! in conditions, nesting, and arbitrary chaining. Gated behind --langversion:11.0.

Design decisions

  • New IfDefElif stack entry (not reusing IfDefElse) — lets the lexer distinguish "a branch was taken" from "we are in #else". Uses 2-bit encoding in the IDE ifdef stack, reducing max nesting from 24 to 12 (plenty).
  • Four dedicated diagnostics (lexHashElifNoMatchingIf, lexHashElifAfterElse, lexHashElifMustBeFirst, lexHashElifMustHaveIdent) instead of reusing #else error messages.
  • ConditionalDirectiveTrivia.Elif case added to syntax trivia — enables fold regions, colorization, and grayout in VS without any editor-side changes.

Future work (other repos)

  • Fantomas: teach the formatter about #elif (parse + format roundtrip)
  • FsAutoComplete / Ionide: verify #elif grayout, folding, and completion work end-to-end
  • MS Learn docs: update the conditional compilation page

T-Gro and others added 20 commits February 13, 2026 23:31
Creates docs/feature-elif-preprocessor.md covering motivation, semantics,
spec grammar changes, tooling impact, and compatibility for the #elif
preprocessor directive (fslang-suggestions#1370).
Add #elif support to the F# lexer with IfDefElif stack entry tracking
first-match-wins semantics. The lexer correctly handles:
- Basic #if/#elif/#else/#endif chains
- Multiple #elif directives
- Nested #elif inside other #if blocks
- Error cases (#elif after #else, #elif without #if)
- Language version gating (requires --langversion:11.0)

Fix IDE state serialization to use 2 bits per ifdef stack entry
(00=if, 01=else, 10=elif) so IfDefElif state survives
encodeLexCont/decodeLexCont round-trips correctly.
…Skip path

Add CheckLanguageFeatureAndRecover for PreprocessorElif in the n > 0 branch
of the ifdefSkip rule, matching the existing check in the n = 0 path.
Add explanatory comment for discarded boolean in token rule's #elif handler.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test exercises the ifdefSkip rule's n > 0 branch with --langversion:10.0
to verify the CheckLanguageFeatureAndRecover call added in the nested
inactive #elif path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- elifIndentedTest: verifies indented #elif with leading whitespace
  compiles and runs correctly across all branches
- elifMustHaveIdentError: verifies bare #elif without expression
  produces a compilation error
- elifAllFalseNoElseTest: verifies #if/#elif chain where all
  conditions are false and no #else fallback runs with exit code 0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- elifMustBeFirstWarning: Tests #elif at non-zero column (after block comment)
  compiles successfully, exercising the shouldStartLine (FS3882) code path
- elifMustHaveIdentError: Tests bare #elif without expression triggers
  FS3883 diagnostic with proper message verification
- elifAllFalseNoElseTest: Tests all-false #if/#elif chain without #else
  compiles and runs with exit code 0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- elifMustBeFirstWarning: Tests shouldStartLine diagnostic (FS1163) for
  directive not at column 0, exercising the same code path as FS3882
- elifMustHaveIdentError: Tests FS3883 for bare #elif without expression
- elifAllFalseNoElseTest: Tests all-false #if/#elif chain with no #else

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the #if-based proxy test (FS1163) with a proper #elif test that
triggers FS3882. Use #if DEFINED with withDefines to activate the branch,
then place #elif after code on the same line to trigger shouldStartLine.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- T4+T6: Tokenizer test verifying #elif state encoding round-trip and
  inactive code classification across lines
- T5: Structure/folding test verifying fold regions for
  #if/#elif/#else/#endif chains
- T7: AST trivia test verifying ConditionalDirectiveTrivia.Elif appears
  in parsed syntax tree

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Q1: Extract evalAndSaveElif helper in lex.fsl ifdefSkip rule
- Q2: Merge Else/Elif arms in ServiceStructure.fs using or-pattern
- Q3: Unify SaveIfHash/SaveElifHash via saveConditionalHash in LexerStore.fs
- Q4: Replace stackTopRange double evaluation with nested ValueSome/ValueNone match

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- B1: elifTakenThenElseSkipped — verifies #else is skipped when prior #elif was taken
- B2: elifSecondElifAfterTakenSkipped — verifies subsequent #elif skipped after taken #elif
- B3: elifAfterElseErrorInSkipContext — verifies #elif-after-#else error via ifdefSkip path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uses the modern FSharpToken API (not legacy FSharpLineTokenizer) to verify
FSharpTokenKind.HashElif is emitted for #elif directives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove redundant `let m = lexbuf.LexemeRange` rebindings in #else ifdefSkip
  handler (m already in scope from line 1119)
- Inline `let isTrue = evalAndSaveElif()` into `if evalAndSaveElif() then`
- Add explanatory comment on IfDefElif arm clarifying why stack is not updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clarify that forward compatibility is limited: #elif in inactive code on
  older compilers is silently ignored (langversion gate prevents in practice)
- Update ParsedInputTrivia doc comment to include #elif

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 18, 2026

⚠️ Release notes required, but author opted out

Warning

Author opted out of release notes, check is disabled for this pull request.
cc @dotnet/fsharp-team-msft

@T-Gro T-Gro added the NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes label Feb 18, 2026
@T-Gro
Copy link
Member Author

T-Gro commented Feb 18, 2026

/run fantomas

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run fantomas
  • Outcome: success

✅ Command succeeded, no changes needed.

@T-Gro
Copy link
Member Author

T-Gro commented Feb 18, 2026

/run fantomas

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run fantomas
  • Outcome: success

✅ Command succeeded, no changes needed.

@T-Gro
Copy link
Member Author

T-Gro commented Feb 18, 2026

/run ilverify

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run ilverify
  • Outcome: success

✅ Command succeeded, no changes needed.

T-Gro and others added 5 commits February 18, 2026 20:27
- Move 3882,lexHashElifMustBeFirst and 3883,lexHashElifMustHaveIdent to
  end of FSComp.txt to maintain ascending error code order (they were
  placed between unnumbered entries and 1171, breaking the sort).
- Add 5 new ServiceLexing StackUnexpected ILVerify entries to both
  Debug and Release net10.0 baselines for the new #elif lexer paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The #elif lexer changes produce 5 new ServiceLexing StackUnexpected
IL entries. These need to be added to the netstandard2.0 baselines
(Debug + Release). The net10.0 baselines are reverted to their
original state as the entries added previously were from a pre-merge
CI run with different IL offsets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
netstandard2.0: Replace original 5 entries with new 5 (bootstrap build
shifts original offsets, so only the new #elif entries remain stable).
net10.0: Add 5 new entries alongside existing 5 (10 total).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bootstrap build on CI shifts the original ServiceLexing offsets,
so only the new #elif-added entries (0x37,0x40,0x87/0x69,0x90/0x72,
0x99/0x7B) remain stable across all 4 baseline configs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@T-Gro T-Gro marked this pull request as ready for review February 19, 2026 13:50
@T-Gro T-Gro requested a review from a team as a code owner February 19, 2026 13:50
@T-Gro T-Gro requested a review from abonie February 19, 2026 13:50
@T-Gro T-Gro enabled auto-merge (squash) February 20, 2026 10:08
@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Feb 20, 2026
@abonie
Copy link
Member

abonie commented Feb 27, 2026

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@T-Gro T-Gro merged commit f3bdee4 into main Feb 27, 2026
45 checks passed
T-Gro added a commit that referenced this pull request Mar 6, 2026
* Type checker: recover on argument/overload checking (#19314)

* [main] Update dependencies from dotnet/arcade (#19333)

* Update dependencies from https://github.com/dotnet/arcade build 20260219.2
On relative base path root
Microsoft.DotNet.Arcade.Sdk From Version 10.0.0-beta.26117.6 -> To Version 10.0.0-beta.26119.2

* Update dependencies from https://github.com/dotnet/arcade build 20260223.2
On relative base path root
Microsoft.DotNet.Arcade.Sdk From Version 10.0.0-beta.26117.6 -> To Version 10.0.0-beta.26123.2

---------

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>

* [main] Source code updates from dotnet/dotnet (#19343)

* Backflow from https://github.com/dotnet/dotnet / 854c152 build 302768

[[ commit created by automation ]]

* Update dependencies from build 302768
No dependency updates to commit
[[ commit created by automation ]]

* Backflow from https://github.com/dotnet/dotnet / 51587e2 build 302820

[[ commit created by automation ]]

* Update dependencies from build 302820
No dependency updates to commit
[[ commit created by automation ]]

---------

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>

* Update image used for insert into VS step (#19357)

* Fix :: FAR :: Remove corrupted .ctor symbol reference (#19358)

* DotNetBuildUseMonoRuntime stackguard (#19360)

* Fsharp.Core :: {Array;List;Set;Array.Parallel} partitionWith (taking Choice<T,U> partitioner) (#19335)

* Sort out some outstanding issues after xUnit upgrade  (#19363)

* Improve collection comparison diagnostics in tests (#19365)

Added shouldBeEqualCollections to Assert.fs for detailed collection comparison, including reporting missing/unexpected items and positional differences. Updated Project25 symbol uses test to use this helper and print actual/expected values for easier debugging.

* Enhance the compiler to produce a FS0750 error on let! or use! outside a CE (#19347)

* Update to latest .NET 10 SDK patch (#19350)

* Feature: `#elif` preprocessor directive (#19323)

* Support #exit;; as alias to #quit;; in fsi (#19329)

* Checker: prevent reporting optional parameter rewritten tree symbols (#19353)

* Improve static compilation of state machines (#19297)

* Add FSC compiler options tests (#19348)

* Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-optimization build 20260226.1 (#19366)

On relative base path root
optimization.linux-arm64.MIBC.Runtime , optimization.linux-x64.MIBC.Runtime , optimization.windows_nt-arm64.MIBC.Runtime , optimization.windows_nt-x64.MIBC.Runtime , optimization.windows_nt-x86.MIBC.Runtime From Version 1.0.0-prerelease.26117.2 -> To Version 1.0.0-prerelease.26126.1

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>

* Fix Get-PrBuildIds.ps1 reporting SUCCESS while builds in progress (#19313)

* Fix flaky Project25 TP test by replacing FSharp.Data NuGet with TestTP (#19364) (#19373)

Replace FSharp.Data (resolved via NuGet at runtime) with the built-in
TestTP type provider for the Project25 symbol API tests. This fixes
non-deterministic test failures on Linux CI caused by
Directory.GetFiles returning DLLs in random inode order on ext4,
which varied whether the FSharp.Data namespace was tagged as
'provided' or not.

Changes:
- Replace 70-line NuGet restore/staging setup with 2-line TestTP reference
- Update source to use ErasedWithConstructor.Provided.MyType instead
  of FSharp.Data.XmlProvider
- Replace brittle exact-match symbol list with targeted assertions
  for provided types, methods, and namespaces
- Remove FactSkipOnSignedBuild (TestTP is always available after build)
- Rename test variables to match the types being tested

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add automatic merge flow from main to feature/net11-scouting (#19283)

* Optimize Set.intersect symmetry and add release notes (#19292)

Co-authored-by: Ahmed <w0lf@192.168.1.8>

* Fix strong name signature size to align with Roslyn for public signing (#11887) (#19242)

* Use culture-independent `IndexOf` in interpolated string parsing (#19370)

* Rename "inline hints" to "inlay hints" (#19318)

* Rename "inline hints" to "inlay hints" for LSP consistency

Aligns F#-owned terminology with LSP and VS Code conventions.
The Roslyn ExternalAccess types (IFSharpInlineHintsService, etc.)
are left unchanged as they are owned by Roslyn.

Fixes #16608

* Add release note for inline-to-inlay hints rename

---------

Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>

* FCS: capture additional types during analysis (#19305)

* remove duplicate FlatErrors file (#19383)

* Update dependencies from https://github.com/dotnet/arcade build 20260302.1 (#19379)

[main] Update dependencies from dotnet/arcade

* [main] Update dependencies from dotnet/msbuild (#19021)

* Update dependencies from https://github.com/dotnet/msbuild build 20251021.3
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25521-03

* Update dependencies from https://github.com/dotnet/msbuild build 20251023.2
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25523-02

* Update dependencies from https://github.com/dotnet/msbuild build 20251024.3
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25524-03

* Update dependencies from https://github.com/dotnet/msbuild build 20251027.5
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25527-05

* Update dependencies from https://github.com/dotnet/msbuild build 20251027.6
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25527-06

* Update dependencies from https://github.com/dotnet/msbuild build 20251104.4
On relative base path root
Microsoft.Build , Microsoft.Build.Framework , Microsoft.Build.Tasks.Core , Microsoft.Build.Utilities.Core From Version 18.1.0-preview-25515-01 -> To Version 18.1.0-preview-25554-04

* Bump dependency versions in Version.Details.props

Updated package versions for several dependencies.

---------

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>

* Add fsharp-release-announcement agent (#19390)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Seq.empty rendering as "EmptyEnumerable" in serializers (#19317)

* Update FileContentMapping.fs (#19391)

* Fix flaky help_options test: restore enableConsoleColoring after mutation (#19385)

* Initial plan

* Fix flaky help_options test by saving/restoring enableConsoleColoring global

The `fsc --consolecolors switch` test mutates the global
`enableConsoleColoring` via `--consolecolors-`. When help_options tests
run after this test, the help output says "(off by default)" instead of
"(on by default)", causing baseline mismatches.

Fix: save and restore `enableConsoleColoring` around the test.

Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>

* isolate checker (#19393)

Isolate type-provider tests with dedicated FSharpChecker

Introduce Project25.checker to avoid shared state races in type-provider tests. All relevant test cases now use this dedicated instance, improving test isolation, reliability, and determinism without changing test functionality.

* Introduce CallRelatedSymbolSink to avoid affect name resolution with related symbols (#19361)

* Add RelatedSymbolUseKind flags enum and separate sink for related symbols

Address auduchinok's review comments on PR #19252: related symbols (union case
testers, copy-and-update record types) are now reported via a separate
NotifyRelatedSymbolUse sink instead of abusing NotifyNameResolution.

- Add [<Flags>] RelatedSymbolUseKind enum (None/UnionCaseTester/CopyAndUpdateRecord/All)
- Add NotifyRelatedSymbolUse to ITypecheckResultsSink interface
- Refactor RegisterUnionCaseTesterForProperty to use the new sink
- Refactor copy-and-update in TcRecdExpr to use CallRelatedSymbolSink
- Add ?relatedSymbolKinds parameter to GetUsesOfSymbolInFile (default: None)
- Wire up VS FAR to pass relatedSymbolKinds=All
- Write related symbols to ItemKeyStore for background FAR
- Update semantic classification tests: tester properties now classified as
  Property (not UnionCase) since they're no longer in capturedNameResolutions

* Add release notes for FSharp.Compiler.Service 11.0.100

---------

Co-authored-by: Eugene Auduchinok <eugene.auduchinok@gmail.com>
Co-authored-by: dotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>
Co-authored-by: Adam Boniecki <20281641+abonie@users.noreply.github.com>
Co-authored-by: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Co-authored-by: Evgeny Tsvetkov <61620612+evgTSV@users.noreply.github.com>
Co-authored-by: Youssef Victor <youssefvictor00@gmail.com>
Co-authored-by: Bozhidar Batsov <bozhidar@batsov.dev>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Ahmed Waleed <ahmedwalidahmed.0@gmail.com>
Co-authored-by: Ahmed <w0lf@192.168.1.8>
Co-authored-by: Brian Rourke Boll <brianrourkeboll@users.noreply.github.com>
Co-authored-by: Apoorv Darshan <ad13dtu@gmail.com>
Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NO_RELEASE_NOTES Label for pull requests which signals, that user opted-out of providing release notes

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants