Skip to content

Commit

Permalink
Further enhancements to nameof (#8754)
Browse files Browse the repository at this point in the history
* Implement `nameof x` as a constant pattern

* Resolve ident to find `nameof` in patterns

* fix build

* fix build

* re-enable tests

* fix test

* fix operators

* align code

* test and fix pattern matching

* fix 8661, 7416

* fix tests and error messages

* 'fix test'

* 'fix test'

* add message for C# and old compilers

* fix build

* suppress error 3501 when a compiler supports nameof

Co-authored-by: Don Syme <dsyme@microsoft.com>
  • Loading branch information
Tarmil and dsyme authored Aug 4, 2020
1 parent 129f9ec commit ae5c1db
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 123 deletions.
6 changes: 5 additions & 1 deletion src/fsharp/AttributeChecking.fs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,11 @@ let CheckFSharpAttributes (g:TcGlobals) attribs m =
match namedArgs with
| ExtractAttribNamedArg "IsError" (AttribBoolArg v) -> v
| _ -> false
if isError && (not g.compilingFslib || n <> 1204) then ErrorD msg else WarnD msg
// If we are using a compiler that supports nameof then error 3501 is always suppressed.
// See attribute on FSharp.Core 'nameof'
if n = 3501 then CompleteD
elif isError && (not g.compilingFslib || n <> 1204) then ErrorD msg
else WarnD msg
| _ ->
CompleteD
) ++ (fun () ->
Expand Down
3 changes: 2 additions & 1 deletion src/fsharp/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1515,4 +1515,5 @@ featureWitnessPassing,"witness passing for trait constraints in F# quotations"
3361,typrelInterfaceWithConcreteAndVariableObjectExpression,"You cannot implement the interface '%s' with the two instantiations '%s' and '%s' because they may unify."
featureInterfacesWithMultipleGenericInstantiation,"interfaces with multiple generic instantiation"
3362,tcLiteralFieldAssignmentWithArg,"Cannot assign '%s' to a value marked literal"
3363,tcLiteralFieldAssignmentNoArg,"Cannot assign a value to another value marked literal"
3363,tcLiteralFieldAssignmentNoArg,"Cannot assign a value to another value marked literal"
#3501 "This construct is not supported by your version of the F# compiler" CompilerMessage(ExperimentalAttributeMessages.NotSupportedYet, 3501, IsError=true)
5 changes: 4 additions & 1 deletion src/fsharp/FSharp.Core/prim-types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ namespace Microsoft.FSharp.Core
[<Literal>]
let RequiresPreview : string = "Experimental library feature, requires '--langversion:preview'"

[<Literal>]
let NotSupportedYet : string = "This construct is not supported by your version of the F# compiler"

[<AttributeUsage(AttributeTargets.All, AllowMultiple=false)>]
[<Sealed>]
type ExperimentalAttribute(message:string) =
Expand Down Expand Up @@ -4718,7 +4721,7 @@ namespace Microsoft.FSharp.Core
[<CompiledName("TypeOf")>]
let inline typeof<'T> = BasicInlinedOperations.typeof<'T>

[<CompiledName("NameOf")>]
[<CompiledName("NameOf"); CompilerMessage(ExperimentalAttributeMessages.NotSupportedYet, 3501, IsError=true)>]
let inline nameof (_: 'T) : string = raise (Exception "may not call directly, should always be optimized away")

[<CompiledName("MethodHandleOf")>]
Expand Down
7 changes: 4 additions & 3 deletions src/fsharp/FSharp.Core/prim-types.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -653,10 +653,11 @@ namespace Microsoft.FSharp.Core
/// <summary>Indicates one or more adjustments to the compiled representation of an F# type or member</summary>
member Flags : CompilationRepresentationFlags

module internal ExperimentalAttributeMessages = begin
module internal ExperimentalAttributeMessages =
[<Literal>]
val RequiresPreview : string = "Experimental library feature, requires '--langversion:preview'"
end
[<Literal>]
val NotSupportedYet : string = "This construct is not supported by your version of the F# compiler"

/// <summary>This attribute is used to tag values that are part of an experimental library
/// feature.</summary>
Expand Down Expand Up @@ -2846,7 +2847,7 @@ namespace Microsoft.FSharp.Core
val inline typeof<'T> : System.Type

/// <summary>Returns the name of the given symbol.</summary>
[<CompiledName("NameOf")>]
[<CompiledName("NameOf"); CompilerMessage(ExperimentalAttributeMessages.NotSupportedYet, 3501, IsError=true)>]
val inline nameof : 'T -> string

/// <summary>An internal, library-only compiler intrinsic for compile-time
Expand Down
122 changes: 63 additions & 59 deletions src/fsharp/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2579,7 +2579,7 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
if first && id.idText = MangledGlobalName then
match rest with
| [] ->
error (Error(FSComp.SR.nrGlobalUsedOnlyAsFirstName(), id.idRange))
raze (Error(FSComp.SR.nrGlobalUsedOnlyAsFirstName(), id.idRange))
| [next] ->
ResolveExprLongIdentPrim sink ncenv false fullyQualified m ad nenv typeNameResInfo next [] isOpenDecl
| id2 :: rest2 ->
Expand All @@ -2603,11 +2603,12 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified

let search = ChooseTyconRefInExpr (ncenv, m, ad, nenv, id, typeNameResInfo, resInfo, tcrefs)
match AtMostOneResult m search with
| Result _ as res ->
let resInfo, item, rest = ForceRaise res
| Result (resInfo, item, rest) ->
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.Use, ad, resInfo, ResultTyparChecker(fun () -> CheckAllTyparsInferrable ncenv.amap m item))
Some(item, rest)
| Exception e -> typeError <- Some e; None
| Exception e ->
typeError <- Some e
None

| true, res ->
let fresh = FreshenUnqualifiedItem ncenv m res
Expand All @@ -2624,7 +2625,7 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
None

match envSearch with
| Some res -> res
| Some res -> success res
| None ->
let innerSearch =
// Check if it's a type name, e.g. a constructor call or a type instantiation
Expand All @@ -2640,9 +2641,9 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified

ctorSearch +++ implicitOpSearch

let resInfo, item, rest =
let res =
match AtMostOneResult m innerSearch with
| Result _ as res -> ForceRaise res
| Result _ as res -> res
| _ ->
let failingCase =
match typeError with
Expand Down Expand Up @@ -2671,11 +2672,12 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
addToBuffer (e.Value.DisplayName + "." + id.idText)

raze (UndefinedName(0, FSComp.SR.undefinedNameValueOfConstructor, id, suggestNamesAndTypes))
ForceRaise failingCase

failingCase
match res with
| Exception e -> raze e
| Result (resInfo, item, rest) ->
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.Use, ad, resInfo, ResultTyparChecker(fun () -> CheckAllTyparsInferrable ncenv.amap m item))
item, rest

success (item, rest)

// A compound identifier.
// It still might be a value in the environment, or something in an F# module, namespace, type, or nested type
Expand All @@ -2693,13 +2695,13 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
| _ -> false

if ValIsInEnv id.idText then
nenv.eUnqualifiedItems.[id.idText], rest
success (nenv.eUnqualifiedItems.[id.idText], rest)
else
// Otherwise modules are searched first. REVIEW: modules and types should be searched together.
// For each module referenced by 'id', search the module as if it were an F# module and/or a .NET namespace.
let moduleSearch ad () =
ResolveLongIndentAsModuleOrNamespaceThen sink ResultCollectionSettings.AtMostOneResult ncenv.amap m fullyQualified nenv ad id rest isOpenDecl
(ResolveExprLongIdentInModuleOrNamespace ncenv nenv typeNameResInfo ad)
ResolveLongIndentAsModuleOrNamespaceThen sink ResultCollectionSettings.AtMostOneResult ncenv.amap m fullyQualified nenv ad id rest isOpenDecl
(ResolveExprLongIdentInModuleOrNamespace ncenv nenv typeNameResInfo ad)

// REVIEW: somewhat surprisingly, this shows up on performance traces, with tcrefs non-nil.
// This seems strange since we would expect in the vast majority of cases tcrefs is empty here.
Expand All @@ -2717,59 +2719,59 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified
NoResultsOrUsefulErrors

let search =
let envSearch () =
match fullyQualified with
| FullyQualified ->
NoResultsOrUsefulErrors
| OpenQualified ->
match nenv.eUnqualifiedItems.TryGetValue id.idText with
| true, Item.UnqualifiedType _
| false, _ -> NoResultsOrUsefulErrors
| true, res -> OneSuccess (resInfo, FreshenUnqualifiedItem ncenv m res, rest)

moduleSearch ad () +++ tyconSearch ad +++ envSearch

let resInfo, item, rest =
let envSearch () =
match fullyQualified with
| FullyQualified ->
NoResultsOrUsefulErrors
| OpenQualified ->
match nenv.eUnqualifiedItems.TryGetValue id.idText with
| true, Item.UnqualifiedType _
| false, _ -> NoResultsOrUsefulErrors
| true, res -> OneSuccess (resInfo, FreshenUnqualifiedItem ncenv m res, rest)

moduleSearch ad () +++ tyconSearch ad +++ envSearch

let res =
match AtMostOneResult m search with
| Result _ as res -> ForceRaise res
| Result _ as res -> res
| _ ->
let innerSearch = search +++ (moduleSearch AccessibleFromSomeFSharpCode) +++ (tyconSearch AccessibleFromSomeFSharpCode)

let suggestEverythingInScope (addToBuffer: string -> unit) =
for kv in nenv.ModulesAndNamespaces fullyQualified do
for modref in kv.Value do
if IsEntityAccessible ncenv.amap m ad modref then
addToBuffer modref.DisplayName
addToBuffer modref.DemangledModuleOrNamespaceName
for kv in nenv.ModulesAndNamespaces fullyQualified do
for modref in kv.Value do
if IsEntityAccessible ncenv.amap m ad modref then
addToBuffer modref.DisplayName
addToBuffer modref.DemangledModuleOrNamespaceName

for e in nenv.TyconsByDemangledNameAndArity fullyQualified do
if IsEntityAccessible ncenv.amap m ad e.Value then
addToBuffer e.Value.DisplayName
for e in nenv.TyconsByDemangledNameAndArity fullyQualified do
if IsEntityAccessible ncenv.amap m ad e.Value then
addToBuffer e.Value.DisplayName

for e in nenv.eUnqualifiedItems do
if canSuggestThisItem e.Value then
addToBuffer e.Value.DisplayName
for e in nenv.eUnqualifiedItems do
if canSuggestThisItem e.Value then
addToBuffer e.Value.DisplayName

match innerSearch with
| Exception (UndefinedName(0, _, id1, suggestionsF)) when Range.equals id.idRange id1.idRange ->
let mergeSuggestions addToBuffer =
suggestionsF addToBuffer
suggestEverythingInScope addToBuffer

let failingCase = raze (UndefinedName(0, FSComp.SR.undefinedNameValueNamespaceTypeOrModule, id, mergeSuggestions))
ForceRaise failingCase
| Exception err -> ForceRaise(Exception err)
| Result (res :: _) -> ForceRaise(Result res)
let mergeSuggestions addToBuffer =
suggestionsF addToBuffer
suggestEverythingInScope addToBuffer
raze (UndefinedName(0, FSComp.SR.undefinedNameValueNamespaceTypeOrModule, id, mergeSuggestions))
| Exception err -> raze err
| Result (res :: _) -> success res
| Result [] ->
let failingCase = raze (UndefinedName(0, FSComp.SR.undefinedNameValueNamespaceTypeOrModule, id, suggestEverythingInScope))
ForceRaise failingCase
raze (UndefinedName(0, FSComp.SR.undefinedNameValueNamespaceTypeOrModule, id, suggestEverythingInScope))

ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.Use, ad, resInfo, ResultTyparChecker(fun () -> CheckAllTyparsInferrable ncenv.amap m item))
item, rest
match res with
| Exception e -> raze e
| Result (resInfo, item, rest) ->
ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurence.Use, ad, resInfo, ResultTyparChecker(fun () -> CheckAllTyparsInferrable ncenv.amap m item))
success (item, rest)

let ResolveExprLongIdent sink (ncenv: NameResolver) m ad nenv typeNameResInfo lid =
match lid with
| [] -> error (Error(FSComp.SR.nrInvalidExpression(textOfLid lid), m))
| [] -> raze (Error(FSComp.SR.nrInvalidExpression(textOfLid lid), m))
| id :: rest -> ResolveExprLongIdentPrim sink ncenv true OpenQualified m ad nenv typeNameResInfo id rest false

//-------------------------------------------------------------------------
Expand Down Expand Up @@ -3414,15 +3416,17 @@ type AfterResolution =
///
/// Called for 'TypeName.Bar' - for VS IntelliSense, we can filter out instance members from method groups
let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameResolver) wholem ad nenv typeNameResInfo lid =
let item1, rest = ResolveExprLongIdent sink ncenv wholem ad nenv typeNameResInfo lid
match ResolveExprLongIdent sink ncenv wholem ad nenv typeNameResInfo lid with
| Exception e -> Exception e
| Result (item1, rest) ->
let itemRange = ComputeItemRange wholem lid rest

let item = FilterMethodGroups ncenv itemRange item1 true

match item1, item with
| Item.MethodGroup(name, minfos1, _), Item.MethodGroup(_, [], _) when not (isNil minfos1) ->
error(Error(FSComp.SR.methodIsNotStatic name, wholem))
| _ -> ()
raze(Error(FSComp.SR.methodIsNotStatic name, wholem))
| _ ->

// Fake idents e.g. 'Microsoft.FSharp.Core.None' have identical ranges for each part
let isFakeIdents =
Expand Down Expand Up @@ -3462,16 +3466,14 @@ let ResolveLongIdentAsExprAndComputeRange (sink: TcResultsSink) (ncenv: NameReso
callSink (item, emptyTyparInst)
AfterResolution.DoNothing

item, itemRange, rest, afterResolution
success (item, itemRange, rest, afterResolution)

let (|NonOverridable|_|) namedItem =
match namedItem with
| Item.MethodGroup(_, minfos, _) when minfos |> List.exists(fun minfo -> minfo.IsVirtual || minfo.IsAbstract) -> None
| Item.Property(_, pinfos) when pinfos |> List.exists(fun pinfo -> pinfo.IsVirtualProperty) -> None
| _ -> Some ()



/// Called for 'expression.Bar' - for VS IntelliSense, we can filter out static members from method groups
/// Also called for 'GenericType<Args>.Bar' - for VS IntelliSense, we can filter out non-static members from method groups
let ResolveExprDotLongIdentAndComputeRange (sink: TcResultsSink) (ncenv: NameResolver) wholem ad nenv ty lid findFlag thisIsActuallyATyAppNotAnExpr =
Expand Down Expand Up @@ -3571,7 +3573,9 @@ let IsUnionCaseUnseen ad g amap m (ucref: UnionCaseRef) =

let ItemIsUnseen ad g amap m item =
match item with
| Item.Value x -> IsValUnseen ad g m x
| Item.Value x ->
let isUnseenNameOfOperator = valRefEq g g.nameof_vref x && not (g.langVersion.SupportsFeature LanguageFeature.NameOf)
isUnseenNameOfOperator || IsValUnseen ad g m x
| Item.UnionCase(x, _) -> IsUnionCaseUnseen ad g amap m x.UnionCaseRef
| Item.ExnCase x -> IsTyconUnseen ad g amap m x
| _ -> false
Expand Down
4 changes: 2 additions & 2 deletions src/fsharp/NameResolution.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ val internal ResolveTypeLongIdent : TcResultsSink -> NameResol
val internal ResolveField : TcResultsSink -> NameResolver -> NameResolutionEnv -> AccessorDomain -> TType -> Ident list * Ident -> Ident list -> FieldResolution list

/// Resolve a long identifier occurring in an expression position
val internal ResolveExprLongIdent : TcResultsSink -> NameResolver -> range -> AccessorDomain -> NameResolutionEnv -> TypeNameResolutionInfo -> Ident list -> Item * Ident list
val internal ResolveExprLongIdent : TcResultsSink -> NameResolver -> range -> AccessorDomain -> NameResolutionEnv -> TypeNameResolutionInfo -> Ident list -> ResultOrException<Item * Ident list>

/// Resolve a (possibly incomplete) long identifier to a loist of possible class or record fields
val internal ResolvePartialLongIdentToClassOrRecdFields : NameResolver -> NameResolutionEnv -> range -> AccessorDomain -> string list -> bool -> Item list
Expand All @@ -571,7 +571,7 @@ type AfterResolution =
| RecordResolution of Item option * (TyparInst -> unit) * (MethInfo * PropInfo option * TyparInst -> unit) * (unit -> unit)

/// Resolve a long identifier occurring in an expression position.
val internal ResolveLongIdentAsExprAndComputeRange : TcResultsSink -> NameResolver -> range -> AccessorDomain -> NameResolutionEnv -> TypeNameResolutionInfo -> Ident list -> Item * range * Ident list * AfterResolution
val internal ResolveLongIdentAsExprAndComputeRange : TcResultsSink -> NameResolver -> range -> AccessorDomain -> NameResolutionEnv -> TypeNameResolutionInfo -> Ident list -> ResultOrException<Item * range * Ident list * AfterResolution>

/// Resolve a long identifier occurring in an expression position, qualified by a type.
val internal ResolveExprDotLongIdentAndComputeRange : TcResultsSink -> NameResolver -> range -> AccessorDomain -> NameResolutionEnv -> TType -> Ident list -> FindMemberFlag -> bool -> Item * range * Ident list * AfterResolution
Expand Down
Loading

0 comments on commit ae5c1db

Please sign in to comment.