Skip to content

Commit 14c4ced

Browse files
authored
Warn FS3888 when consumer-visible attributes are missing from .fsi (#19880)
1 parent b2319f7 commit 14c4ced

68 files changed

Lines changed: 2309 additions & 216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
applyTo:
3+
- "src/Compiler/**/*.{fs,fsi}"
4+
- "vsintegration/src/**/*.{fs,fsi}"
5+
- "tests/FSharp.Compiler.ComponentTests/**/*.fs"
6+
- "tests/FSharp.Compiler.Service.Tests/**/*.fs"
7+
- "vsintegration/tests/**/*.fs"
8+
---
9+
10+
# Code Style: No Bloat
11+
12+
Reviewers read code, not prose. Add bytes only when they pay for themselves.
13+
14+
## Comments
15+
16+
Good names **always** beat comments. Before writing a comment, ask: *can I rename a value, extract a function, or use an active pattern so the comment becomes unnecessary?* If yes, do that instead.
17+
18+
- **Do not** restate what the code says (variable name, type name, attribute name, function signature).
19+
- **Do not** narrate the algorithm step-by-step. The diff is the algorithm.
20+
- **Do not** justify design decisions inline ("we chose X over Y because…"). Put rationale in the commit message or PR body.
21+
- **Do not** leave war-story comments ("previously we did Z, but…", "counter-example: …"). The history is in `git log`.
22+
- **Do not** write multi-line `///` doc comments for internal helpers whose body is one expression.
23+
24+
Acceptable comments answer **why**, not **what**, and only when the *why* is non-obvious and cannot be expressed by renaming:
25+
- Workarounds for compiler/runtime bugs (link the bug).
26+
- Performance constraints invisible from the code shape ("inner loop runs 50M times per typecheck").
27+
- Cross-file invariants the code itself can't enforce.
28+
29+
If you are tempted to write `// This is intentional`, change the code so the intent is structural, not decorative.
30+
31+
## Code shape
32+
33+
- Compact, idiomatic F#: pattern matching over `if`/`elif` ladders; active patterns where they remove duplication.
34+
- Low cyclomatic complexity per function. Extract helpers — even one-line ones — when a name clarifies a step.
35+
- Prefer module-level `let` over big bodies with nested locals.
36+
- New file > bloating an existing 5000-line file when adding a self-contained concept.
37+
38+
## Test code
39+
40+
Tests get a touch more leeway for explanation, but the same rules largely apply:
41+
42+
- One parametrized test (`[<Theory>]`, `[<InlineData>]`, or a `for/yield` over inputs) > five copy-pasted tests.
43+
- Module-level constants for shared paths (`Path.Combine` for OS neutrality), shared source strings, and shared expected outputs.
44+
- Helpers like `parseAndCheck code` over reinventing the setup per test.
45+
- Don't reinvent an entire `.fs` file inside each test when one shared module-level binding will do.
46+
47+
## PR scope
48+
49+
**Not paid by LOC.** Large PRs are typically shitty PRs. If the diff has 1000+ lines, split it.
50+
- Cleanup commits separate from feature commits.
51+
- No "phase tag" / "transitional measure" / "follow-up" comments left behind — either do it now in a follow-up commit, or file an issue. Don't leave breadcrumbs in the code.
52+
53+
## When in doubt
54+
55+
Delete the comment, rename the value, and re-read. If the code is still unclear, *that* is what needs fixing — not the comment.

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `<ProduceReferenceAssembly>true</ProduceReferenceAssembly>` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801))
7878
* Parser: recover on unfinished if and binary expressions
7979
([PR #19724](https://github.com/dotnet/fsharp/pull/19724))
80+
* Warn FS3888 when a compiler-semantic attribute on a value/member or type/module is present in the `.fs` but missing from the `.fsi`. Such attributes were previously ignored at the consumer side. Under the `ErrorOnMissingSignatureAttribute` preview language feature, FS3888 is an error. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
8081
* Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877))
8182
* Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884))
8283
* Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Fixed
22

3+
* Mirror compiler-semantic attributes (e.g. `[<NoDynamicInvocation>]`, `[<Experimental>]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
34
* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
45
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))
56
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))

docs/release-notes/.Language/preview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
44
* Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072))
5+
* Added `ErrorOnMissingSignatureAttribute` preview language feature: makes FS3888 (compiler-semantic attribute on the `.fs` but not on the `.fsi`) an error instead of a warning. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
56

67
### Fixed
78

docs/release-notes/.VisualStudio/18.vNext.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### Added
2+
3+
* Code-fixes for FS3888 (compiler-semantic attribute on the `.fs` but not the `.fsi`): copy the attribute into the `.fsi`, or remove it from the `.fs`. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
4+
15
### Fixed
26

37
* Fixed Rename incorrectly renaming `get` and `set` keywords for properties with explicit accessors. ([Issue #18270](https://github.com/dotnet/fsharp/issues/18270), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))

src/Compiler/AbstractIL/il.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ type ILAttribElem =
854854
type ILAttributeNamedArg = string * ILType * bool * ILAttribElem
855855

856856
/// Custom attribute.
857+
[<RequireQualifiedAccess>]
857858
type ILAttribute =
858859
/// Attribute with args encoded to a binary blob according to ECMA-335 II.21 and II.23.3.
859860
/// 'decodeILAttribData' is used to parse the byte[] blob to ILAttribElem's as best as possible.
@@ -973,6 +974,7 @@ type internal ILSecurityAction =
973974
| InheritanceDemandChoice
974975
| DemandChoice
975976

977+
[<RequireQualifiedAccess>]
976978
type internal ILSecurityDecl = ILSecurityDecl of ILSecurityAction * byte[]
977979

978980
/// Abstract type equivalent to ILSecurityDecl list - use helpers
@@ -1041,6 +1043,7 @@ type MethodBody =
10411043
| NotAvailable
10421044

10431045
/// Generic parameters. Formal generic parameter declarations may include the bounds, if any, on the generic parameter.
1046+
[<NoEquality; NoComparison>]
10441047
type ILGenericParameterDef =
10451048
{
10461049
Name: string

src/Compiler/AbstractIL/ilprint.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ and goutput_permission _env os p =
333333
| ILSecurityAction.DemandChoice -> "demandchoice")
334334

335335
match p with
336-
| ILSecurityDecl(sa, b) ->
336+
| ILSecurityDecl.ILSecurityDecl(sa, b) ->
337337
output_string os " .permissionset "
338338
output_security_action os sa
339339
output_string os " = ("

src/Compiler/AbstractIL/ilread.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3358,7 +3358,7 @@ and securityDeclsReader ctxtH tag =
33583358
|> List.toArray)
33593359

33603360
and seekReadSecurityDecl ctxt (act, ty) =
3361-
ILSecurityDecl(
3361+
ILSecurityDecl.ILSecurityDecl(
33623362
(if List.memAssoc (int act) (Lazy.force ILSecurityActionRevMap) then
33633363
List.assoc (int act) (Lazy.force ILSecurityActionRevMap)
33643364
else

src/Compiler/AbstractIL/ilwrite.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1506,7 +1506,7 @@ and GenCustomAttrsPass3Or4 cenv hca (attrs: ILAttributes) =
15061506
// ILSecurityDecl --> DeclSecurity rows
15071507
// -------------------------------------------------------------------- *)
15081508

1509-
let rec GetSecurityDeclRow cenv hds (ILSecurityDecl (action, s)) =
1509+
let rec GetSecurityDeclRow cenv hds (ILSecurityDecl.ILSecurityDecl (action, s)) =
15101510
UnsharedRow
15111511
[| UShort (uint16 (List.assoc action (Lazy.force ILSecurityActionMap)))
15121512
HasDeclSecurity (fst hds, snd hds)

src/Compiler/Checking/NameResolution.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1990,7 +1990,7 @@ let ItemsAreEffectivelyEqual g orig other =
19901990
| TType_var (tp1, _), TType_var (tp2, _) ->
19911991
not tp1.IsCompilerGenerated && not tp1.IsFromError &&
19921992
not tp2.IsCompilerGenerated && not tp2.IsFromError &&
1993-
equals tp1.Range tp2.Range
1993+
Range.equals tp1.Range tp2.Range
19941994
| AbbrevOrAppTy(tcref1, _), AbbrevOrAppTy(tcref2, _) ->
19951995
tyconRefDefnEq g tcref1 tcref2
19961996
| _ -> false)

0 commit comments

Comments
 (0)