Skip to content

Commit b8aaf2d

Browse files
authored
Allow lower-case DU cases when RequireQualifiedAccess is specified (#13432)
* Allow lower-case DU cases when RequireQualifiedAccess is specified * Fix PR suggestions and Add more testing * Protect feature under preview version * Add a NotUpperCaseConstructorWithoutRQA warning to be raised in lang version preview * Fix formatting
1 parent 9df7080 commit b8aaf2d

36 files changed

Lines changed: 289 additions & 10 deletions

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ let ImplicitlyOpenOwnNamespace tcSink g amap scopem enclosingNamespacePath (env:
378378

379379
exception NotUpperCaseConstructor of range: range
380380

381+
exception NotUpperCaseConstructorWithoutRQA of range: range
382+
381383
let CheckNamespaceModuleOrTypeName (g: TcGlobals) (id: Ident) =
382384
// type names '[]' etc. are used in fslib
383385
if not g.compilingFSharpCore && id.idText.IndexOfAny IllegalCharactersInTypeAndNamespaceNames <> -1 then
@@ -453,16 +455,21 @@ module TcRecdUnionAndEnumDeclarations =
453455
// Bind other elements of type definitions (constructors etc.)
454456
//-------------------------------------------------------------------------
455457

456-
let CheckUnionCaseName (cenv: cenv) (id: Ident) =
458+
let CheckUnionCaseName (cenv: cenv) (id: Ident) hasRQAAttribute =
457459
let g = cenv.g
458460
let name = id.idText
459461
if name = "Tags" then
460462
errorR(Error(FSComp.SR.tcUnionCaseNameConflictsWithGeneratedType(name, "Tags"), id.idRange))
461463

462464
CheckNamespaceModuleOrTypeName g id
463465

464-
if not (String.isLeadingIdentifierCharacterUpperCase name) && name <> opNameCons && name <> opNameNil then
465-
errorR(NotUpperCaseConstructor(id.idRange))
466+
if g.langVersion.SupportsFeature(LanguageFeature.LowercaseDUWhenRequireQualifiedAccess) then
467+
468+
if not (String.isLeadingIdentifierCharacterUpperCase name) && not hasRQAAttribute && name <> opNameCons && name <> opNameNil then
469+
errorR(NotUpperCaseConstructorWithoutRQA(id.idRange))
470+
else
471+
if not (String.isLeadingIdentifierCharacterUpperCase name) && name <> opNameCons && name <> opNameNil then
472+
errorR(NotUpperCaseConstructor(id.idRange))
466473

467474
let ValidateFieldNames (synFields: SynField list, tastFields: RecdField list) =
468475
let seen = Dictionary()
@@ -479,13 +486,13 @@ module TcRecdUnionAndEnumDeclarations =
479486
| _ ->
480487
seen.Add(f.LogicalName, sf))
481488

482-
let TcUnionCaseDecl (cenv: cenv) env parent thisTy thisTyInst tpenv (SynUnionCase(Attributes synAttrs, SynIdent(id, _), args, xmldoc, vis, m, _)) =
489+
let TcUnionCaseDecl (cenv: cenv) env parent thisTy thisTyInst tpenv hasRQAAttribute (SynUnionCase(Attributes synAttrs, SynIdent(id, _), args, xmldoc, vis, m, _)) =
483490
let g = cenv.g
484491
let attrs = TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs // the attributes of a union case decl get attached to the generated "static factory" method
485492
let vis, _ = ComputeAccessAndCompPath env None m vis None parent
486493
let vis = CombineReprAccess parent vis
487494

488-
CheckUnionCaseName cenv id
495+
CheckUnionCaseName cenv id hasRQAAttribute
489496

490497
let rfields, recordTy =
491498
match args with
@@ -526,8 +533,8 @@ module TcRecdUnionAndEnumDeclarations =
526533
let xmlDoc = xmldoc.ToXmlDoc(true, Some names)
527534
Construct.NewUnionCase id rfields recordTy attrs xmlDoc vis
528535

529-
let TcUnionCaseDecls cenv env parent (thisTy: TType) thisTyInst tpenv unionCases =
530-
let unionCasesR = unionCases |> List.map (TcUnionCaseDecl cenv env parent thisTy thisTyInst tpenv)
536+
let TcUnionCaseDecls (cenv: cenv) env (parent: ParentRef) (thisTy: TType) (thisTyInst: TypeInst) hasRQAAttribute tpenv unionCases =
537+
let unionCasesR = unionCases |> List.map (TcUnionCaseDecl cenv env parent thisTy thisTyInst tpenv hasRQAAttribute)
531538
unionCasesR |> CheckDuplicates (fun uc -> uc.Id) "union case"
532539

533540
let TcEnumDecl cenv env parent thisTy fieldTy (SynEnumCase(attributes=Attributes synAttrs; ident= SynIdent(id,_); value=v; xmlDoc=xmldoc; range=m)) =
@@ -3188,7 +3195,9 @@ module EstablishTypeDefinitionCores =
31883195

31893196
structLayoutAttributeCheck false
31903197
noAllowNullLiteralAttributeCheck()
3191-
TcRecdUnionAndEnumDeclarations.CheckUnionCaseName cenv unionCaseName
3198+
3199+
let hasRQAAttribute = HasFSharpAttribute cenv.g cenv.g.attrib_RequireQualifiedAccessAttribute tycon.Attribs
3200+
TcRecdUnionAndEnumDeclarations.CheckUnionCaseName cenv unionCaseName hasRQAAttribute
31923201
let unionCase = Construct.NewUnionCase unionCaseName [] thisTy [] XmlDoc.Empty tycon.Accessibility
31933202
writeFakeUnionCtorsToSink [ unionCase ]
31943203
Construct.MakeUnionRepr [ unionCase ], None, NoSafeInitInfo
@@ -3219,8 +3228,9 @@ module EstablishTypeDefinitionCores =
32193228
noAbstractClassAttributeCheck()
32203229
noAllowNullLiteralAttributeCheck()
32213230
structLayoutAttributeCheck false
3222-
let unionCases = TcRecdUnionAndEnumDeclarations.TcUnionCaseDecls cenv envinner innerParent thisTy thisTyInst tpenv unionCases
3223-
3231+
3232+
let hasRQAAttribute = HasFSharpAttribute cenv.g cenv.g.attrib_RequireQualifiedAccessAttribute tycon.Attribs
3233+
let unionCases = TcRecdUnionAndEnumDeclarations.TcUnionCaseDecls cenv envinner innerParent thisTy thisTyInst hasRQAAttribute tpenv unionCases
32243234
if tycon.IsStructRecordOrUnionTycon && unionCases.Length > 1 then
32253235
let fieldNames = [ for uc in unionCases do for ft in uc.FieldTable.TrueInstanceFieldsAsList do yield ft.LogicalName ]
32263236
if fieldNames |> List.distinct |> List.length <> fieldNames.Length then

src/Compiler/Checking/CheckDeclarations.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,5 @@ val CheckOneSigFile:
7676
Cancellable<TcEnv * ModuleOrNamespaceType * bool>
7777

7878
exception NotUpperCaseConstructor of range: range
79+
80+
exception NotUpperCaseConstructorWithoutRQA of range: range

src/Compiler/Driver/CompilerDiagnostics.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ let GetRangeOfDiagnostic (diagnostic: PhasedDiagnostic) =
124124
| LetRecCheckedAtRuntime m
125125
| UpperCaseIdentifierInPattern m
126126
| NotUpperCaseConstructor m
127+
| NotUpperCaseConstructorWithoutRQA m
127128
| RecursiveUseCheckedAtRuntime (_, _, m)
128129
| LetRecEvaluatedOutOfOrder (_, _, _, m)
129130
| DiagnosticWithText (_, _, m)
@@ -270,6 +271,7 @@ let GetDiagnosticNumber (diagnostic: PhasedDiagnostic) =
270271
| UseOfAddressOfOperator _ -> 51
271272
| DefensiveCopyWarning _ -> 52
272273
| NotUpperCaseConstructor _ -> 53
274+
| NotUpperCaseConstructorWithoutRQA _ -> 53
273275
| TypeIsImplicitlyAbstract _ -> 54
274276
// 55 cannot be reused
275277
| DeprecatedThreadStaticBindingWarning _ -> 56
@@ -435,6 +437,7 @@ let ErrorFromApplyingDefault2E () = Message("ErrorFromApplyingDefault2", "")
435437
let ErrorsFromAddingSubsumptionConstraintE () = Message("ErrorsFromAddingSubsumptionConstraint", "%s%s%s")
436438
let UpperCaseIdentifierInPatternE () = Message("UpperCaseIdentifierInPattern", "")
437439
let NotUpperCaseConstructorE () = Message("NotUpperCaseConstructor", "")
440+
let NotUpperCaseConstructorWithoutRQAE () = Message("NotUpperCaseConstructorWithoutRQA", "")
438441
let FunctionExpectedE () = Message("FunctionExpected", "")
439442
let BakedInMemberConstraintNameE () = Message("BakedInMemberConstraintName", "%s")
440443
let BadEventTransformationE () = Message("BadEventTransformation", "")
@@ -771,6 +774,8 @@ let OutputPhasedErrorR (os: StringBuilder) (diagnostic: PhasedDiagnostic) (canSu
771774

772775
| NotUpperCaseConstructor _ -> os.AppendString(NotUpperCaseConstructorE().Format)
773776

777+
| NotUpperCaseConstructorWithoutRQA _ -> os.AppendString(NotUpperCaseConstructorWithoutRQAE().Format)
778+
774779
| ErrorFromAddingConstraint (_, e, _) -> OutputExceptionR os e
775780

776781
#if !NO_TYPEPROVIDERS

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,7 @@ featureStructActivePattern,"struct representation for active patterns"
15431543
featureRelaxWhitespace2,"whitespace relaxation v2"
15441544
featureReallyLongList,"list literals of any size"
15451545
featureErrorOnDeprecatedRequireQualifiedAccess,"give error on deprecated access of construct with RequireQualifiedAccess attribute"
1546+
featureLowercaseDUWhenRequireQualifiedAccess,"Allow lowercase DU when RequireQualifiedAccess attribute"
15461547
3353,fsiInvalidDirective,"Invalid directive '#%s %s'"
15471548
3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation."
15481549
3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation."

src/Compiler/FSStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,4 +1107,7 @@
11071107
<data name="TargetInvocationExceptionWrapper" xml:space="preserve">
11081108
<value>internal error: {0}</value>
11091109
</data>
1110+
<data name="NotUpperCaseConstructorWithoutRQA" xml:space="preserve">
1111+
<value>Lowercase discriminated union cases are only allowed when using RequireQualifiedAccess attribute</value>
1112+
</data>
11101113
</root>

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type LanguageFeature =
4949
| DelegateTypeNameResolutionFix
5050
| ReallyLongLists
5151
| ErrorOnDeprecatedRequireQualifiedAccess
52+
| LowercaseDUWhenRequireQualifiedAccess
5253

5354
/// LanguageVersion management
5455
type LanguageVersion(versionText) =
@@ -111,6 +112,7 @@ type LanguageVersion(versionText) =
111112
LanguageFeature.BetterExceptionPrinting, previewVersion
112113
LanguageFeature.ReallyLongLists, previewVersion
113114
LanguageFeature.ErrorOnDeprecatedRequireQualifiedAccess, previewVersion
115+
LanguageFeature.LowercaseDUWhenRequireQualifiedAccess, previewVersion
114116
]
115117

116118
static let defaultLanguageVersion = LanguageVersion("default")
@@ -210,6 +212,7 @@ type LanguageVersion(versionText) =
210212
| LanguageFeature.DelegateTypeNameResolutionFix -> FSComp.SR.featureDelegateTypeNameResolutionFix ()
211213
| LanguageFeature.ReallyLongLists -> FSComp.SR.featureReallyLongList ()
212214
| LanguageFeature.ErrorOnDeprecatedRequireQualifiedAccess -> FSComp.SR.featureErrorOnDeprecatedRequireQualifiedAccess ()
215+
| LanguageFeature.LowercaseDUWhenRequireQualifiedAccess -> FSComp.SR.featureLowercaseDUWhenRequireQualifiedAccess ()
213216

214217
/// Get a version string associated with the given feature.
215218
member _.GetFeatureVersionString feature =

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type LanguageFeature =
3939
| DelegateTypeNameResolutionFix
4040
| ReallyLongLists
4141
| ErrorOnDeprecatedRequireQualifiedAccess
42+
| LowercaseDUWhenRequireQualifiedAccess
4243

4344
/// LanguageVersion management
4445
type LanguageVersion =

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@
192192
<target state="translated">rozhraní s vícenásobným obecným vytvářením instancí</target>
193193
<note />
194194
</trans-unit>
195+
<trans-unit id="featureLowercaseDUWhenRequireQualifiedAccess">
196+
<source>Allow lowercase DU when RequireQualifiedAccess attribute</source>
197+
<target state="new">Allow lowercase DU when RequireQualifiedAccess attribute</target>
198+
<note />
199+
</trans-unit>
195200
<trans-unit id="featureMLCompatRevisions">
196201
<source>ML compatibility revisions</source>
197202
<target state="translated">Revize kompatibility ML</target>

src/Compiler/xlf/FSComp.txt.de.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@
192192
<target state="translated">Schnittstellen mit mehrfacher generischer Instanziierung</target>
193193
<note />
194194
</trans-unit>
195+
<trans-unit id="featureLowercaseDUWhenRequireQualifiedAccess">
196+
<source>Allow lowercase DU when RequireQualifiedAccess attribute</source>
197+
<target state="new">Allow lowercase DU when RequireQualifiedAccess attribute</target>
198+
<note />
199+
</trans-unit>
195200
<trans-unit id="featureMLCompatRevisions">
196201
<source>ML compatibility revisions</source>
197202
<target state="translated">ML-Kompatibilitätsrevisionen</target>

src/Compiler/xlf/FSComp.txt.es.xlf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@
192192
<target state="translated">interfaces con creación de instancias genéricas múltiples</target>
193193
<note />
194194
</trans-unit>
195+
<trans-unit id="featureLowercaseDUWhenRequireQualifiedAccess">
196+
<source>Allow lowercase DU when RequireQualifiedAccess attribute</source>
197+
<target state="new">Allow lowercase DU when RequireQualifiedAccess attribute</target>
198+
<note />
199+
</trans-unit>
195200
<trans-unit id="featureMLCompatRevisions">
196201
<source>ML compatibility revisions</source>
197202
<target state="translated">Revisiones de compatibilidad de ML</target>

0 commit comments

Comments
 (0)