-
Notifications
You must be signed in to change notification settings - Fork 851
Feature: #elif preprocessor directive
#19323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
T-Gro
wants to merge
27
commits into
main
Choose a base branch
from
feature-directive-elif
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
8f87f7a
Add feature design document for #elif preprocessor directive
T-Gro b8a0984
Implement #elif preprocessor directive (F# 11.0)
T-Gro 4edb766
Fix missing language version check for #elif in nested-inactive ifdef…
T-Gro 75ca0e9
Add test for nested #elif langversion check (n > 0 path)
T-Gro 92f183f
Add missing #elif compiler directive tests
T-Gro 22e64ec
Add missing #elif preprocessor directive tests (T1-T3)
T-Gro 0528a4b
Add missing compiler directive tests (T1-T3)
T-Gro 0cfc903
Fix elifMustBeFirstWarning test to actually verify FS3882 for #elif
T-Gro 90a32a1
Add IDE/tooling tests for #elif directive (T4-T7)
T-Gro e1bb328
Refactor: reduce duplication in #elif preprocessor handling
T-Gro 14fec3d
Merge branch 'main' into feature-directive-elif
T-Gro b101973
Add tests for IfDefElif stack state transitions
T-Gro 75f9225
Assert error codes FS3882/FS3883 in #elif diagnostic tests
T-Gro 833b476
Add #elif signature file syntax tree baseline
T-Gro 1708c02
Add FSharpLexer.Tokenize test for HashElif token kind
T-Gro e9aea76
Refactor lex.fsl: remove redundant bindings, improve comments
T-Gro 6a345f5
Refactor LexerStore: extract saveSimpleHash for Else/EndIf
T-Gro 7f18485
Fix #elif docs: forward compat caveat, update trivia comment
T-Gro 6604fc0
Add release notes for #elif preprocessor directive
T-Gro 14f97fa
Add VS Editor colorization tests for #elif keyword and grayout
T-Gro 1a2f64f
Merge branch 'main' into feature-directive-elif
T-Gro d139b1f
Fix FSComp.txt error code sorting and update ILVerify baselines
T-Gro 0395fc6
Merge branch 'main' into feature-directive-elif
T-Gro 34eeaa3
Fix ILVerify baselines: add entries to netstandard2.0, revert net10.0
T-Gro 5b00343
Fix ILVerify baselines: correct ServiceLexing entries for all 4 configs
T-Gro fade9f3
Fix ILVerify net10.0 baselines: remove shifted original offsets
T-Gro 20a16b5
Merge branch 'main' into feature-directive-elif
T-Gro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Claims Coverage Report for PR #19297 | ||
|
|
||
| ## Findings | ||
|
|
||
| ### Implemented Claims | ||
| 1. **Fixes #19296 - State machines: low-level resumable code not always expanded correctly** | ||
| - **Location**: `src/Compiler/Optimize/LowerStateMachines.fs:178` (`BindResumableCodeDefinitions`), `tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs:57` | ||
| - **Description**: Implemented logic to eliminate `IfUseResumableStateMachinesExpr` by taking the static branch and recursively binding resumable code definitions, including looking through debug points. Verified by `Nested __useResumableCode is expanded correctly` test case. | ||
| - **Severity**: **None (Verified)** | ||
|
|
||
| 2. **Fixes #12839 - Unexpected FS3511 warning for big records in tasks** | ||
| - **Location**: `src/Compiler/Optimize/LowerStateMachines.fs:242` (`TryReduceApp`), `tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs:274` | ||
| - **Description**: Enhanced `TryReduceApp` to better track and resolve resumable code variables and push applications through, enabling static compilation for complex constructs (like big records and nested matches) that previously fell back to dynamic code (triggering FS3511). Verified by `Big record` test case. | ||
| - **Severity**: **None (Verified)** | ||
|
|
||
| 3. **Includes test cases from #14930** | ||
| - **Location**: `tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs:225` | ||
| - **Description**: Added `Task with for loop over tuples compiles statically` test case, which was the repro for #14930. The code changes in `LowerStateMachines.fs` support this scenario. | ||
| - **Severity**: **None (Verified)** | ||
|
|
||
| 4. **Issue #13404 - Referenced in NestedTaskFailures.fs** | ||
| - **Location**: `tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/NestedTaskFailures.fs` | ||
| - **Description**: Removed `#nowarn "3511"` from the file, indicating that the nested task failures are now statically compiled correctly and don't trigger the warning/error. | ||
| - **Severity**: **None (Verified)** | ||
|
|
||
| ### Orphan Claims | ||
| - None found. All claims in the PR description and linked issues have corresponding code changes and tests. | ||
|
|
||
| ### Partial Implementations | ||
| - None found. The implementations appear to fully address the described issues based on the provided repro cases. | ||
|
|
||
| ### Orphan Changes | ||
| - None found. All code changes in `LowerStateMachines.fs` are directly related to improving the static compilation of state machines as described in the PR. | ||
|
|
||
| ## Summary | ||
| The PR covers all its claims with appropriate code changes and regression tests. The changes in `LowerStateMachines.fs` logically address the issues of incorrect expansion and fallback to dynamic code. The added tests cover the reported reproduction scenarios. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # `#elif` Preprocessor Directive | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is more of a reviewer pack, will delete it before merging. |
||
|
|
||
| - Language suggestion: [fslang-suggestions#1370](https://github.com/fsharp/fslang-suggestions/issues/1370) (approved-in-principle) | ||
|
|
||
| F# adds the `#elif` preprocessor directive for conditional compilation, aligning with C# and reducing nesting depth when checking multiple conditions. | ||
|
|
||
| ## Motivation | ||
|
|
||
| Currently F# only has `#if`, `#else`, and `#endif` for conditional compilation. When you need to check multiple conditions, you must nest `#if` inside `#else` blocks, leading to deeply indented code: | ||
|
|
||
| ```fsharp | ||
| let myPath = | ||
| #if WIN64 | ||
| "/library/x64/runtime.dll" | ||
| #else | ||
| #if WIN86 | ||
| "/library/x86/runtime.dll" | ||
| #else | ||
| #if MAC | ||
| "/library/iOS/runtime-osx.dll" | ||
| #else | ||
| "/library/unix/runtime.dll" | ||
| #endif | ||
| #endif | ||
| #endif | ||
| ``` | ||
|
|
||
| An alternative workaround uses repeated `#if` blocks with complex negated conditions, which is error-prone and verbose: | ||
|
|
||
| ```fsharp | ||
| let myPath = | ||
| #if WIN64 | ||
| "/library/x64/runtime.dll" | ||
| #endif | ||
| #if WIN86 | ||
| "/library/x86/runtime.dll" | ||
| #endif | ||
| #if MAC | ||
| "/library/iOS/runtime-osx.dll" | ||
| #endif | ||
| #if !WIN64 && !WIN86 && !MAC | ||
| "/library/unix/runtime.dll" | ||
| #endif | ||
| ``` | ||
|
|
||
| Both approaches are harder to read, maintain, and extend than they need to be. | ||
|
|
||
| ## Feature Description | ||
|
|
||
| With `#elif`, the same logic is flat and clear: | ||
|
|
||
| ```fsharp | ||
| let myPath = | ||
| #if WIN64 | ||
| "/library/x64/runtime.dll" | ||
| #elif WIN86 | ||
| "/library/x86/runtime.dll" | ||
| #elif MAC | ||
| "/library/iOS/runtime-osx.dll" | ||
| #else | ||
| "/library/unix/runtime.dll" | ||
| #endif | ||
| ``` | ||
|
|
||
| ### Semantics | ||
|
|
||
| - `#elif` is short for "else if" in the preprocessor. | ||
| - It evaluates its condition only if no previous `#if` or `#elif` branch in the same chain was active. | ||
| - Only one branch in a `#if`/`#elif`/`#else`/`#endif` chain is ever active. | ||
| - `#elif` supports the same boolean expressions as `#if`: identifiers, `&&`, `||`, `!`, and parentheses. | ||
| - `#elif` can appear zero or more times between `#if` and `#else`/`#endif`. | ||
| - `#elif` after `#else` is an error. | ||
| - `#elif` without a matching `#if` is an error. | ||
| - `#elif` blocks can be nested inside other `#if`/`#elif`/`#else` blocks. | ||
| - Each `#elif` must appear at the start of a line (same rule as `#if`/`#else`/`#endif`). | ||
|
|
||
| ## Detailed Semantics | ||
|
|
||
| The following table shows which branch is active for a `#if A` / `#elif B` / `#else` chain under all combinations of A and B: | ||
|
|
||
| | Source | A=true, B=true | A=true, B=false | A=false, B=true | A=false, B=false | | ||
| |---|---|---|---|---| | ||
| | `#if A` block | **active** | **active** | skip | skip | | ||
| | `#elif B` block | skip | skip | **active** | skip | | ||
| | `#else` block | skip | skip | skip | **active** | | ||
|
|
||
| Only the **first** matching branch is active. When both A and B are true, the `#if A` block is active and the `#elif B` block is skipped — the `#elif` condition is never evaluated. | ||
|
|
||
| ## Language Version | ||
|
|
||
| - This feature requires F# 11.0 (language version `11.0` or `preview`). | ||
| - Using `#elif` with an older language version produces a compiler error directing the user to upgrade. | ||
|
|
||
| ## F# Language Specification Changes | ||
|
|
||
| This feature requires changes to [section 3.3 Conditional Compilation](https://fsharp.github.io/fslang-spec/lexical-analysis/#33-conditional-compilation) of the F# Language Specification. | ||
|
|
||
| ### Grammar | ||
|
|
||
| **Current spec grammar:** | ||
|
|
||
| ``` | ||
| token if-directive = "#if" whitespace if-expression-text | ||
| token else-directive = "#else" | ||
| token endif-directive = "#endif" | ||
| ``` | ||
|
|
||
| **Proposed spec grammar (add `elif-directive`):** | ||
|
|
||
| ``` | ||
| token if-directive = "#if" whitespace if-expression-text | ||
| token elif-directive = "#elif" whitespace if-expression-text | ||
| token else-directive = "#else" | ||
| token endif-directive = "#endif" | ||
| ``` | ||
|
|
||
| ### Description Updates | ||
|
|
||
| The current spec says: | ||
|
|
||
| > If an `if-directive` token is matched during tokenization, text is recursively tokenized until a corresponding `else-directive` or `endif-directive`. | ||
|
|
||
| This should be updated to: | ||
|
|
||
| > If an `if-directive` token is matched during tokenization, text is recursively tokenized until a corresponding `elif-directive`, `else-directive`, or `endif-directive`. An `elif-directive` evaluates its condition only if no preceding `if-directive` or `elif-directive` in the same chain evaluated to true. At most one branch in an `if`/`elif`/`else` chain is active. | ||
|
|
||
| Additionally: | ||
|
|
||
| > An `elif-directive` must appear after an `if-directive` or another `elif-directive`, and before any `else-directive`. An `elif-directive` after an `else-directive` is an error. | ||
|
|
||
| ## Tooling Impact | ||
|
|
||
| This change affects: | ||
|
|
||
| - **Fantomas** (F# code formatter) — will need to recognize `#elif` for formatting. | ||
| - **FSharp.Compiler.Service consumers** — new `ConditionalDirectiveTrivia.Elif` case, new `FSharpTokenKind.HashElif`. | ||
| - **Any tool** that processes F# source code with preprocessor directives. | ||
|
|
||
| These are non-breaking changes since `#elif` was previously a syntax error, so no existing valid F# code uses it. | ||
|
|
||
| ## Compatibility | ||
|
|
||
| - **Backward compatible**: Old code without `#elif` continues to work unchanged. | ||
| - **Forward compatible**: Code using `#elif` will produce a clear error on older compilers when `#elif` appears in active code. However, if `#elif` appears inside an inactive `#if` branch (e.g., `#if FALSE` / `#elif X` / `#endif`), older compilers silently skip the `#elif` line as inactive text without error, potentially producing wrong branch selection. The language version gate (`LanguageFeature.PreprocessorElif` at F# 11.0) prevents this scenario in practice by requiring a compiler that understands `#elif`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should delete this one