Skip to content

Commit 4bf094b

Browse files
authored
Merge pull request #243 from nojaf/fix-836
Fix invalid signature file when the fslex header defines a module
2 parents 3bf3dde + 0a16cbf commit 4bf094b

6 files changed

Lines changed: 88 additions & 11 deletions

File tree

RELEASE_NOTES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
#### 11.4.0 - Unreleased
1+
#### 11.4.1 - 28 August, 2026
2+
* Fix fslex generating an invalid signature file when the header defines a module #240
3+
4+
#### 11.4.0 - 6 July, 2026
25
* Add Fable support to FsLexYacc.Runtime.
36
* Make the AssocTable lookup cache initial capacity configurable to avoid pre-allocating 2000 entries per parse #54
47
* Add `--assoc-cache-capacity` option for fsyacc to set the generated parser's AssocTable cache capacity from the command line #54

src/FsLex.Core/fslexdriver.fs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,22 @@ let writeOpens opens (writer: Writer) =
118118
writer.WriteLine ""
119119
writer.WriteLineInterface ""
120120

121+
/// Picks the module declaration and open statements out of the header code, so they can be
122+
/// repeated in the generated signature file. A nested module definition (a `module ... =` line)
123+
/// is not a declaration: its body does not end up in the signature file, so copying it would
124+
/// leave an empty module behind.
125+
let getHeaderDeclarations (code: string) =
126+
code.Split([| '\n'; '\r' |])
127+
|> Array.filter (fun s ->
128+
(s.StartsWith("module ", StringComparison.Ordinal)
129+
&& not (s.TrimEnd().EndsWith("=", StringComparison.Ordinal)))
130+
|| s.StartsWith("open ", StringComparison.Ordinal))
131+
121132
let writeTopCode code (writer: Writer) =
122133
writer.WriteCode code
123134

124-
let moduleAndOpens =
125-
(fst code).Split([| '\n'; '\r' |])
126-
|> Array.filter (fun s ->
127-
s.StartsWith("module ", StringComparison.Ordinal)
128-
|| s.StartsWith("open ", StringComparison.Ordinal))
129-
|> String.concat Environment.NewLine
130-
131-
writer.WriteInterface "%s" moduleAndOpens
135+
for line in getHeaderDeclarations (fst code) do
136+
writer.WriteLineInterface "%s" line
132137

133138
let writeUnicodeTranslationArray dfaNodes domain (writer: Writer) =
134139
let parseContext =
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module FsLex.Core.Tests.DriverTests
2+
3+
open System.IO
4+
open FSharp.Text.Lexing
5+
open FsLexYacc.FsLex.Driver
6+
open Expecto
7+
8+
let private writeTopCodeToInterface (header: string) =
9+
let output = Path.GetTempFileName()
10+
let outputi = String.concat "" [ output; "i" ]
11+
12+
try
13+
using (new Writer(output, outputi)) (writeTopCode (header, Position.Empty))
14+
File.ReadAllText outputi
15+
finally
16+
File.Delete output
17+
File.Delete outputi
18+
19+
[<Tests>]
20+
let tests =
21+
testList "Driver" [
22+
testList "getHeaderDeclarations" [
23+
test "keeps the module declaration and the opens" {
24+
let actual =
25+
getHeaderDeclarations "module Test.Lexer\n\nopen System\nopen System.Text\n\nlet x = 1"
26+
27+
Expect.sequenceEqual
28+
actual
29+
[| "module Test.Lexer"; "open System"; "open System.Text" |]
30+
"Module declaration and opens should be kept"
31+
}
32+
33+
test "skips a nested module definition" {
34+
let actual =
35+
getHeaderDeclarations "open System\nmodule Ranges =\n let isInt8BadMax x = 1 <<< 7 = x"
36+
37+
Expect.sequenceEqual actual [| "open System" |] "A nested module definition should be skipped"
38+
}
39+
40+
test "keeps a module abbreviation" {
41+
let actual = getHeaderDeclarations "module Range = FSharp.Compiler.Text.Range"
42+
43+
Expect.sequenceEqual
44+
actual
45+
[| "module Range = FSharp.Compiler.Text.Range" |]
46+
"A module abbreviation should be kept"
47+
}
48+
]
49+
50+
testList "writeTopCode" [
51+
test "the header in the signature file is newline terminated" {
52+
let actual = writeTopCodeToInterface "module Test.Lexer\nopen System\nlet x = 1"
53+
54+
Expect.equal
55+
actual
56+
(sprintf "module Test.Lexer%sopen System%s" System.Environment.NewLine System.Environment.NewLine)
57+
"Every header line should be written on its own line"
58+
}
59+
60+
test "a header without declarations writes nothing" {
61+
let actual = writeTopCodeToInterface "let x = 1"
62+
63+
Expect.equal actual "" "Nothing should be written when there is no declaration to repeat"
64+
}
65+
]
66+
]

tests/FsLex.Core.Tests/FsLex.Core.Tests.fsproj

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

99
<ItemGroup>
1010
<Compile Include="UnicodeTests.fs" />
11+
<Compile Include="DriverTests.fs" />
1112
<Compile Include="Main.fs" />
1213
</ItemGroup>
1314

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Lexer.fs
2+
Lexer.fsi
23
Parser.fs
3-
Parser.fsi
4+
Parser.fsi
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Lexer.fs
2+
Lexer.fsi
23
Parser.fs
34
Parser.fsi
4-
test.txt
5+
test.txt

0 commit comments

Comments
 (0)