Skip to content

Commit e513690

Browse files
authored
Error on invalid declarations in type definitions. (#18813)
1 parent f5e27c5 commit e513690

File tree

107 files changed

+4174
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+4174
-37
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* Completion: fix previous namespace considered opened [PR #18609](https://github.com/dotnet/fsharp/pull/18609)
1818
* Fix active pattern typechecking regression. ([Issue #18638](https://github.com/dotnet/fsharp/issues/18638), [PR #18642](https://github.com/dotnet/fsharp/pull/18642))
1919
* Fix nullness warnings when casting non-nullable values to `IEquatable<T>` to match C# behavior. ([Issue #18759](https://github.com/dotnet/fsharp/issues/18759))
20+
* Error on invalid declarations in type definitions.([Issue #10066](https://github.com/dotnet/fsharp/issues/10066), [PR #18813](https://github.com/dotnet/fsharp/pull/18813))
2021
* Fix IsByRefLikeAttribute types being incorrectly suppressed in completion lists. Types like `Span<T>` and `ReadOnlySpan<T>` now appear correctly in IntelliSense.
2122
* Fix SRTP nullness constraint resolution for types imported from older assemblies. AmbivalentToNull types now use legacy F# nullness rules instead of always satisfying `'T : null` constraints. ([Issue #18390](https://github.com/dotnet/fsharp/issues/18390), [Issue #18344](https://github.com/dotnet/fsharp/issues/18344))
2223
* Fix Show XML doc for enum fields in external metadata ([Issue #17939](https://github.com/dotnet/fsharp/issues/17939#issuecomment-3137410105), [PR #18800](https://github.com/dotnet/fsharp/pull/18800))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
### Fixed
1616

1717
* Warn on uppercase identifiers in patterns. ([PR #15816](https://github.com/dotnet/fsharp/pull/15816))
18+
* Error on invalid declarations in type definitions.([Issue #10066](https://github.com/dotnet/fsharp/issues/10066), [PR #18813](https://github.com/dotnet/fsharp/pull/18813))
1819

1920
### Changed

src/Compiler/FSComp.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,11 @@ lexhlpIdentifierReserved,"The identifier '%s' is reserved for future use by F#"
10051005
lexfltIncorrentIndentationOfIn,"The indentation of this 'in' token is incorrect with respect to the corresponding 'let'"
10061006
lexfltTokenIsOffsideOfContextStartedEarlier,"Unexpected syntax or possible incorrect indentation: this token is offside of context started at position %s. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7."
10071007
lexfltSeparatorTokensOfPatternMatchMisaligned,"The '|' tokens separating rules of this pattern match are misaligned by one column. Consider realigning your code or using further indentation."
1008+
lexfltInvalidNestedTypeDefinition,"Nested type definitions are not allowed. Types must be defined at module or namespace level."
1009+
lexfltInvalidNestedModule,"Modules cannot be nested inside types. Define modules at module or namespace level."
1010+
lexfltInvalidNestedExceptionDefinition,"Exceptions must be defined at module level, not inside types."
1011+
lexfltInvalidNestedOpenDeclaration,"'open' declarations must appear at module level, not inside types."
1012+
lexfltInvalidNestedConstruct,"'%s' must be defined at module level, not inside a type."
10081013
1123,nrInvalidModuleExprType,"Invalid module/expression/type"
10091014
1124,nrTypeInstantiationNeededToDisambiguateTypesWithSameName,"Multiple types exist called '%s', taking different numbers of generic parameters. Provide a type instantiation to disambiguate the type resolution, e.g. '%s'."
10101015
1125,nrTypeInstantiationIsMissingAndCouldNotBeInferred,"The instantiation of the generic type '%s' is missing and can't be inferred from the arguments or return type of this member. Consider providing a type instantiation when accessing this type, e.g. '%s'."
@@ -1802,6 +1807,7 @@ featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member a
18021807
featureUseBangBindingValueDiscard,"Allows use! _ = ... in computation expressions"
18031808
featureBetterAnonymousRecordParsing,"Support for better anonymous record parsing"
18041809
featureScopedNowarn,"Support for scoped enabling / disabling of warnings by #warn and #nowarn directives, also inside modules"
1810+
featureErrorOnInvalidDeclsInTypeDefinitions,"Error when invalid declarations are used in type definitions."
18051811
featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type annotations without requiring parentheses"
18061812
3874,lexWarnDirectiveMustBeFirst,"#nowarn/#warnon directives must appear as the first non-whitespace characters on a line"
18071813
3875,lexWarnDirectiveMustHaveArgs,"Warn directives must have warning number(s) as argument(s)"

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ type LanguageFeature =
102102
| UseBangBindingValueDiscard
103103
| BetterAnonymousRecordParsing
104104
| ScopedNowarn
105+
| ErrorOnInvalidDeclsInTypeDefinitions
105106
| AllowTypedLetUseAndBang
106107
| ReturnFromFinal
107108

@@ -240,6 +241,7 @@ type LanguageVersion(versionText) =
240241
LanguageFeature.UnmanagedConstraintCsharpInterop, languageVersion100
241242
LanguageFeature.AllowAccessModifiersToAutoPropertiesGettersAndSetters, languageVersion100
242243
LanguageFeature.ReturnFromFinal, languageVersion100
244+
LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions, languageVersion100
243245

244246
// F# preview (still preview in 10.0)
245247
LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work
@@ -410,6 +412,7 @@ type LanguageVersion(versionText) =
410412
| LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard ()
411413
| LanguageFeature.BetterAnonymousRecordParsing -> FSComp.SR.featureBetterAnonymousRecordParsing ()
412414
| LanguageFeature.ScopedNowarn -> FSComp.SR.featureScopedNowarn ()
415+
| LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions -> FSComp.SR.featureErrorOnInvalidDeclsInTypeDefinitions ()
413416
| LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()
414417
| LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal ()
415418

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type LanguageFeature =
9393
| UseBangBindingValueDiscard
9494
| BetterAnonymousRecordParsing
9595
| ScopedNowarn
96+
| ErrorOnInvalidDeclsInTypeDefinitions
9697
| AllowTypedLetUseAndBang
9798
| ReturnFromFinal
9899

src/Compiler/SyntaxTree/LexFilter.fs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,98 @@ type LexFilterImpl (
13681368
if debug then dprintf "inserting %+A\n" tok
13691369
returnToken (lexbufStateForInsertedDummyTokens (startPosOfTokenTup tokenTup, tokenTup.LexbufState.EndPos)) tok
13701370

1371+
// Check if we're inappropriately inside a type definition for constructs that shouldn't be there
1372+
// This validates that TYPE, MODULE, EXCEPTION, and OPEN declarations are not nested within type definitions
1373+
// The check works as follows:
1374+
// 1. Only check if the language feature WarnOnUnexpectedModuleDefinitionsInsideTypes is enabled
1375+
// 2. Skip validation inside parentheses (to avoid false positives with inline IL)
1376+
// 3. Traverse the context stack looking for a CtxtTypeDefns
1377+
// 4. If found, check if the current token is indented INSIDE it (greater column, not equal)
1378+
// 5. Verify we're not in a legitimate nested context (members, augmentations, or escaped to module/namespace)
1379+
// 6. If all conditions match, issue an error message
1380+
//
1381+
// Note: We don't check 'let' bindings as they can be valid in classes with constructors
1382+
// Note: Constructs at the same column level are NOT nested (e.g., type A = A type B = B on same line)
1383+
let checkForInvalidDeclsInTypeDefn keyword =
1384+
if lexbuf.SupportsFeature LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions then
1385+
// Skip validation if we're inside a parenthesis context
1386+
// This avoids false positives with inline IL: (# "unbox.any !0" type ('T) x : 'T #)
1387+
let rec hasParenContext stack =
1388+
match stack with
1389+
| [] -> false
1390+
| CtxtParen _ :: _ -> true
1391+
| CtxtSeqBlock _ :: rest
1392+
| CtxtVanilla _ :: rest -> hasParenContext rest
1393+
| _ -> false
1394+
1395+
// Don't validate if we're in a paren context (could be inline IL or other valid syntax)
1396+
if not (hasParenContext offsideStack) then
1397+
// Find the nearest type definition context and check if we're inappropriately nested
1398+
let rec checkNesting stack typeDefnsSeen =
1399+
match stack with
1400+
| [] ->
1401+
// We've traversed the whole stack without finding issues
1402+
false
1403+
1404+
| CtxtModuleBody _ :: _
1405+
| CtxtNamespaceBody _ :: _ ->
1406+
// We've escaped to module/namespace level - constructs here are OK
1407+
false
1408+
1409+
| CtxtTypeDefns(typePos, _) :: rest ->
1410+
// Found a type definition - check if we're inappropriately inside it
1411+
// IMPORTANT: Same-line declarations are sequential, not nested
1412+
// Example: type A = A type B = B (all on same line, B is not nested in A)
1413+
// Only warn if on a DIFFERENT line with GREATER indentation
1414+
if tokenStartPos.Line > typePos.Line && tokenStartCol > typePos.Column then
1415+
// We're indented inside the type - this might be invalid
1416+
// But first check if we're in a valid member/augmentation context
1417+
let rec isInMemberContext s =
1418+
match s with
1419+
| [] -> false
1420+
| CtxtMemberHead _ :: _
1421+
| CtxtMemberBody _ :: _ -> true
1422+
| CtxtWithAsAugment _ :: _ -> true // Type augmentation with 'with'
1423+
| CtxtSeqBlock _ :: tail
1424+
| CtxtVanilla _ :: tail -> isInMemberContext tail
1425+
| _ -> false
1426+
1427+
not (isInMemberContext stack)
1428+
else
1429+
// Not indented inside this type (same column or less), check deeper in the stack
1430+
checkNesting rest true
1431+
1432+
| CtxtSeqBlock _ :: rest
1433+
| CtxtVanilla _ :: rest
1434+
| CtxtParen _ :: rest ->
1435+
// Transparent contexts - continue checking
1436+
checkNesting rest typeDefnsSeen
1437+
1438+
| CtxtMemberHead _ :: _
1439+
| CtxtMemberBody _ :: _ when typeDefnsSeen ->
1440+
// We're in a member context after seeing a type - this is OK
1441+
false
1442+
1443+
| _ :: rest ->
1444+
// Other contexts - continue checking
1445+
checkNesting rest typeDefnsSeen
1446+
1447+
if checkNesting offsideStack false then
1448+
let errorMessage =
1449+
match keyword with
1450+
| "TYPE" ->
1451+
FSComp.SR.lexfltInvalidNestedTypeDefinition()
1452+
| "MODULE" ->
1453+
FSComp.SR.lexfltInvalidNestedModule()
1454+
| "EXCEPTION" ->
1455+
FSComp.SR.lexfltInvalidNestedExceptionDefinition()
1456+
| "OPEN" ->
1457+
FSComp.SR.lexfltInvalidNestedOpenDeclaration()
1458+
| _ ->
1459+
FSComp.SR.lexfltInvalidNestedConstruct(keyword)
1460+
1461+
error tokenTup errorMessage
1462+
13711463
let isSemiSemi = match token with SEMICOLON_SEMICOLON -> true | _ -> false
13721464
let relaxWhitespace2OffsideRule =
13731465
// Offside rule for CtxtLetDecl (in types or modules) / CtxtMemberHead / CtxtTypeDefns... (given RelaxWhitespace2)
@@ -2019,7 +2111,11 @@ type LexFilterImpl (
20192111
returnToken tokenLexbufState token
20202112

20212113
// module ... ~~~> CtxtModuleHead
2114+
// Check for inappropriate nesting within type definitions
20222115
| MODULE, _ :: _ ->
2116+
// Check if this module definition is inappropriately nested in a type
2117+
checkForInvalidDeclsInTypeDefn "MODULE"
2118+
20232119
insertComingSoonTokens("MODULE", MODULE_COMING_SOON, MODULE_IS_HERE)
20242120
if debug then dprintf "MODULE: entering CtxtModuleHead, awaiting EQUALS to go to CtxtSeqBlock (%a)\n" outputPos tokenStartPos
20252121
let isNested = match offsideStack with | [ CtxtSeqBlock _ ] -> false | _ -> true
@@ -2029,6 +2125,8 @@ type LexFilterImpl (
20292125

20302126
// exception ... ~~~> CtxtException
20312127
| EXCEPTION, _ :: _ ->
2128+
// Check if this exception definition is inappropriately nested in a type
2129+
checkForInvalidDeclsInTypeDefn "EXCEPTION"
20322130
if debug then dprintf "EXCEPTION: entering CtxtException(%a)\n" outputPos tokenStartPos
20332131
pushCtxt tokenTup (CtxtException tokenStartPos)
20342132
returnToken tokenLexbufState token
@@ -2470,6 +2568,9 @@ type LexFilterImpl (
24702568
returnToken tokenLexbufState token
24712569

24722570
| TYPE, _ ->
2571+
// Check if this type definition is inappropriately nested in another type
2572+
checkForInvalidDeclsInTypeDefn "TYPE"
2573+
24732574
insertComingSoonTokens("TYPE", TYPE_COMING_SOON, TYPE_IS_HERE)
24742575
if debug then dprintf "TYPE, pushing CtxtTypeDefns(%a)\n" outputPos tokenStartPos
24752576
pushCtxt tokenTup (CtxtTypeDefns(tokenStartPos, None))
@@ -2490,6 +2591,11 @@ type LexFilterImpl (
24902591
| OBLOCKBEGIN, _ ->
24912592
returnToken tokenLexbufState token
24922593

2594+
| OPEN, _ :: _ ->
2595+
// Check if this open declaration is inappropriately nested in a type
2596+
checkForInvalidDeclsInTypeDefn "OPEN"
2597+
returnToken tokenLexbufState token
2598+
24932599
| ODUMMY _, _ ->
24942600
if debug then dprintf "skipping dummy token as no offside rules apply\n"
24952601
pool.Return tokenTup

src/Compiler/SyntaxTree/ParseHelpers.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ open FSharp.Compiler.Syntax
77
open FSharp.Compiler.SyntaxTrivia
88
open FSharp.Compiler.Features
99
open FSharp.Compiler.Text
10+
open FSharp.Compiler.UnicodeLexing
1011
open FSharp.Compiler.Xml
1112
open Internal.Utilities.Text.Lexing
1213
open Internal.Utilities.Text.Parsing

src/Compiler/pars.fsy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,9 @@ moduleDefnsOrExpr:
12711271

12721272
/* A sequence of definitions in a namespace or module */
12731273
moduleDefns:
1274+
| moduleDefnOrDirective moduleDefnOrDirective
1275+
{ $1 @ $2 }
1276+
12741277
| moduleDefnOrDirective moduleDefns
12751278
{ $1 @ $2 }
12761279

src/Compiler/xlf/FSComp.txt.cs.xlf

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)