Skip to content

Commit

Permalink
Merge pull request #6809 from dotnet/feature/nameof
Browse files Browse the repository at this point in the history
[RFC FS-1003] Nameof operator redux
  • Loading branch information
KevinRansom authored Jul 3, 2019
2 parents 8b66074 + 77121fd commit 92e4a52
Show file tree
Hide file tree
Showing 42 changed files with 655 additions and 48 deletions.
1 change: 1 addition & 0 deletions eng/Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function BuildSolution() {
/p:QuietRestore=$quietRestore `
/p:QuietRestoreBinaryLog=$binaryLog `
/p:TestTargetFrameworks=$testTargetFrameworks `
/v:$verbosity `
$suppressExtensionDeployment `
@properties
}
Expand Down
2 changes: 2 additions & 0 deletions eng/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ function BuildSolution {
# do real build
MSBuild $toolset_build_proj \
$bl \
/v:$verbosity \
/p:Configuration=$configuration \
/p:Projects="$projects" \
/p:RepoRoot="$repo_root" \
Expand Down Expand Up @@ -273,3 +274,4 @@ if [[ "$test_core_clr" == true ]]; then
fi

ExitWithExitCode 0

Empty file modified eng/common/dotnet-install.sh
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions src/fsharp/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,8 @@ notAFunctionButMaybeDeclaration,"This value is not a function and cannot be appl
3243,parsInvalidAnonRecdExpr,"Invalid anonymous record expression"
3244,parsInvalidAnonRecdType,"Invalid anonymous record type"
3245,tcCopyAndUpdateNeedsRecordType,"The input to a copy-and-update expression that creates an anonymous record must be either an anonymous record or a record"
3250,expressionHasNoName,"Expression does not have a name."
3251,chkNoFirstClassNameOf,"Using the 'nameof' operator as a first-class function value is not permitted."
3300,chkInvalidFunctionParameterType,"The parameter '%s' has an invalid type '%s'. This is not permitted by the rules of Common IL."
3301,chkInvalidFunctionReturnType,"The function or method has an invalid return type '%s'. This is not permitted by the rules of Common IL."
useSdkRefs,"Use reference assemblies for .NET framework references when available (Enabled by default)."
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/FSharp.Core/prim-types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4142,6 +4142,9 @@ namespace Microsoft.FSharp.Core
[<CompiledName("TypeOf")>]
let inline typeof<'T> = BasicInlinedOperations.typeof<'T>

[<CompiledName("NameOf")>]
let inline nameof (_: 'T) : string = raise (Exception "may not call directly, should always be optimized away")

[<CompiledName("MethodHandleOf")>]
let methodhandleof (_call: ('T -> 'TResult)) : System.RuntimeMethodHandle = raise (Exception "may not call directly, should always be optimized away")

Expand Down
4 changes: 4 additions & 0 deletions src/fsharp/FSharp.Core/prim-types.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2401,6 +2401,10 @@ namespace Microsoft.FSharp.Core
[<CompiledName("TypeOf")>]
val inline typeof<'T> : System.Type

/// <summary>Returns the name of the given symbol.</summary>
[<CompiledName("NameOf")>]
val inline nameof : 'T -> string

/// <summary>An internal, library-only compiler intrinsic for compile-time
/// generation of a RuntimeMethodHandle.</summary>
[<CompiledName("MethodHandleOf")>]
Expand Down
2 changes: 1 addition & 1 deletion src/fsharp/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,7 @@ type FormatStringCheckContext =
type ITypecheckResultsSink =
abstract NotifyEnvWithScope: range * NameResolutionEnv * AccessorDomain -> unit
abstract NotifyExprHasType: pos * TType * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range -> unit
abstract NotifyNameResolution: pos * Item * Item * TyparInst * ItemOccurence * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range * bool -> unit
abstract NotifyNameResolution: pos * item: Item * itemMethodGroup: Item * TyparInst * ItemOccurence * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range * replace: bool -> unit
abstract NotifyFormatSpecifierLocation: range * int -> unit
abstract NotifyOpenDeclaration: OpenDeclaration -> unit
abstract CurrentSourceText: ISourceText option
Expand Down
1 change: 1 addition & 0 deletions src/fsharp/PostInferenceChecks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ and CheckValRef (cenv: cenv) (env: env) v m (context: PermitByRefExpr) =
if isSpliceOperator cenv.g v then errorR(Error(FSComp.SR.chkNoFirstClassSplicing(), m))
if valRefEq cenv.g v cenv.g.addrof_vref then errorR(Error(FSComp.SR.chkNoFirstClassAddressOf(), m))
if valRefEq cenv.g v cenv.g.reraise_vref then errorR(Error(FSComp.SR.chkNoFirstClassRethrow(), m))
if valRefEq cenv.g v cenv.g.nameof_vref then errorR(Error(FSComp.SR.chkNoFirstClassNameOf(), m))

// ByRefLike-typed values can only occur in permitting contexts
if context.Disallow && isByrefLikeTy cenv.g m v.Type then
Expand Down
18 changes: 17 additions & 1 deletion src/fsharp/TastOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3206,6 +3206,11 @@ let isSizeOfValRef g vref =
// There is an internal version of typeof defined in prim-types.fs that needs to be detected
|| (g.compilingFslib && vref.LogicalName = "sizeof")

let isNameOfValRef g vref =
valRefEq g vref g.nameof_vref
// There is an internal version of nameof defined in prim-types.fs that needs to be detected
|| (g.compilingFslib && vref.LogicalName = "nameof")

let isTypeDefOfValRef g vref =
valRefEq g vref g.typedefof_vref
// There is an internal version of typedefof defined in prim-types.fs that needs to be detected
Expand All @@ -3231,6 +3236,16 @@ let (|TypeDefOfExpr|_|) g expr =
| Expr.App (Expr.Val (vref, _, _), _, [ty], [], _) when isTypeDefOfValRef g vref -> Some ty
| _ -> None

let (|NameOfExpr|_|) g expr =
match expr with
| Expr.App(Expr.Val(vref,_,_),_,[ty],[],_) when isNameOfValRef g vref -> Some ty
| _ -> None

let (|SeqExpr|_|) g expr =
match expr with
| Expr.App(Expr.Val(vref,_,_),_,_,_,_) when valRefEq g vref g.seq_vref -> Some()
| _ -> None

//--------------------------------------------------------------------------
// DEBUG layout
//---------------------------------------------------------------------------
Expand Down Expand Up @@ -8553,7 +8568,8 @@ let IsSimpleSyntacticConstantExpr g inputExpr =
| Expr.Op (TOp.UnionCase _, _, [], _) // Nullary union cases
| UncheckedDefaultOfExpr g _
| SizeOfExpr g _
| TypeOfExpr g _ -> true
| TypeOfExpr g _
| NameOfExpr g _ -> true
// All others are not simple constant expressions
| _ -> false

Expand Down
2 changes: 2 additions & 0 deletions src/fsharp/TastOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,8 @@ val (|EnumExpr|_|) : TcGlobals -> Expr -> Expr option
val (|TypeOfExpr|_|) : TcGlobals -> Expr -> TType option

val (|TypeDefOfExpr|_|) : TcGlobals -> Expr -> TType option
val (|NameOfExpr|_|) : TcGlobals -> Expr -> TType option
val (|SeqExpr|_|) : TcGlobals -> Expr -> unit option

val EvalLiteralExprOrAttribArg: TcGlobals -> Expr -> Expr

Expand Down
21 changes: 12 additions & 9 deletions src/fsharp/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
let v_typeof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "typeof" , None , Some "TypeOf" , [vara], ([], v_system_Type_ty))
let v_methodhandleof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "methodhandleof" , None , Some "MethodHandleOf", [vara;varb], ([[varaTy --> varbTy]], v_system_RuntimeMethodHandle_ty))
let v_sizeof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "sizeof" , None , Some "SizeOf" , [vara], ([], v_int_ty))
let v_nameof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "nameof" , None , Some "NameOf" , [vara], ([[varaTy]], v_string_ty))

let v_unchecked_defaultof_info = makeIntrinsicValRef(fslib_MFOperatorsUnchecked_nleref, "defaultof" , None , Some "DefaultOf", [vara], ([], varaTy))
let v_typedefof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "typedefof" , None , Some "TypeDefOf", [vara], ([], v_system_Type_ty))
let v_range_op_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "op_Range" , None , None , [vara], ([[varaTy];[varaTy]], mkSeqTy varaTy))
Expand Down Expand Up @@ -1331,6 +1333,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
member val methodhandleof_vref = ValRefForIntrinsic v_methodhandleof_info
member val typeof_vref = ValRefForIntrinsic v_typeof_info
member val sizeof_vref = ValRefForIntrinsic v_sizeof_info
member val nameof_vref = ValRefForIntrinsic v_nameof_info
member val typedefof_vref = ValRefForIntrinsic v_typedefof_info
member val enum_vref = ValRefForIntrinsic v_enum_operator_info
member val enumOfValue_vref = ValRefForIntrinsic v_enumOfValue_info
Expand Down Expand Up @@ -1358,15 +1361,15 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
member val unbox_fast_vref = ValRefForIntrinsic v_unbox_fast_info
member val istype_vref = ValRefForIntrinsic v_istype_info
member val istype_fast_vref = ValRefForIntrinsic v_istype_fast_info
member val query_source_vref = ValRefForIntrinsic v_query_source_info
member val query_value_vref = ValRefForIntrinsic v_query_value_info
member val query_run_value_vref = ValRefForIntrinsic v_query_run_value_info
member val query_run_enumerable_vref = ValRefForIntrinsic v_query_run_enumerable_info
member val query_for_vref = ValRefForIntrinsic v_query_for_value_info
member val query_yield_vref = ValRefForIntrinsic v_query_yield_value_info
member val query_yield_from_vref = ValRefForIntrinsic v_query_yield_from_value_info
member val query_select_vref = ValRefForIntrinsic v_query_select_value_info
member val query_where_vref = ValRefForIntrinsic v_query_where_value_info
member val query_source_vref = ValRefForIntrinsic v_query_source_info
member val query_value_vref = ValRefForIntrinsic v_query_value_info
member val query_run_value_vref = ValRefForIntrinsic v_query_run_value_info
member val query_run_enumerable_vref = ValRefForIntrinsic v_query_run_enumerable_info
member val query_for_vref = ValRefForIntrinsic v_query_for_value_info
member val query_yield_vref = ValRefForIntrinsic v_query_yield_value_info
member val query_yield_from_vref = ValRefForIntrinsic v_query_yield_from_value_info
member val query_select_vref = ValRefForIntrinsic v_query_select_value_info
member val query_where_vref = ValRefForIntrinsic v_query_where_value_info
member val query_zero_vref = ValRefForIntrinsic v_query_zero_value_info

member __.seq_collect_info = v_seq_collect_info
Expand Down
117 changes: 84 additions & 33 deletions src/fsharp/TypeChecker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8711,37 +8711,87 @@ and delayRest rest mPrior delayed =
let mPriorAndLongId = unionRanges mPrior (rangeOfLid longId)
DelayedDotLookup (rest, mPriorAndLongId) :: delayed

/// Typecheck "nameof" expressions
and TcNameOfExpr cenv env tpenv (synArg: SynExpr) =

let rec stripParens expr =
match expr with
| SynExpr.Paren(expr, _, _, _) -> stripParens expr
| _ -> expr

let cleanSynArg = stripParens synArg
let m = cleanSynArg.Range
let rec check overallTyOpt expr (delayed: DelayedItem list) =
match expr with
| LongOrSingleIdent (false, (LongIdentWithDots(longId, _) as lidd), _, _) when longId.Length > 0 ->
let ad = env.eAccessRights
let id, rest = List.headAndTail longId
match ResolveLongIndentAsModuleOrNamespace cenv.tcSink ResultCollectionSettings.AllResults cenv.amap m true OpenQualified env.eNameResEnv ad id rest true with
| Result modref when delayed.IsEmpty && modref |> List.exists (p23 >> IsEntityAccessible cenv.amap m ad) ->
() // resolved to a module or namespace, done with checks
| _ ->
let (TypeNameResolutionInfo(_, staticArgsInfo)) = GetLongIdentTypeNameInfo delayed
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurence.UseInAttribute OpenQualified env.eNameResEnv ad longId staticArgsInfo PermitDirectReferenceToGeneratedType.No with
| Result tcref when IsEntityAccessible cenv.amap m ad tcref ->
() // resolved to a type name, done with checks
| _ ->
let overallTy = match overallTyOpt with None -> NewInferenceType() | Some t -> t
let _, _ = TcLongIdentThen cenv overallTy env tpenv lidd delayed
() // checked as an expression, done with checks
List.last longId

| SynExpr.TypeApp (hd, _, types, _, _, _, m) ->
check overallTyOpt hd (DelayedTypeApp(types, m, m) :: delayed)

| SynExpr.Paren(expr, _, _, _) when overallTyOpt.IsNone && delayed.IsEmpty ->
check overallTyOpt expr []

| SynExpr.Typed (synBodyExpr, synType, _m) when delayed.IsEmpty && overallTyOpt.IsNone ->
let tgtTy, _tpenv = TcTypeAndRecover cenv NewTyparsOK CheckCxs ItemOccurence.UseInType env tpenv synType
check (Some tgtTy) synBodyExpr []

| _ ->
error (Error(FSComp.SR.expressionHasNoName(), m))

let lastIdent = check None cleanSynArg []
let constRange = mkRange m.FileName m.Start (mkPos m.StartLine (m.StartColumn + lastIdent.idText.Length + 2)) // `2` are for quotes
Expr.Const(Const.String(lastIdent.idText), constRange, cenv.g.string_ty)

//-------------------------------------------------------------------------
// TcFunctionApplicationThen: Typecheck "expr x" + projections
//-------------------------------------------------------------------------

and TcFunctionApplicationThen cenv overallTy env tpenv mExprAndArg expr exprty (synArg: SynExpr) atomicFlag delayed =

let denv = env.DisplayEnv
let mArg = synArg.Range
let mFunExpr = expr.Range

// If the type of 'synArg' unifies as a function type, then this is a function application, otherwise
// it is an error or a computation expression
match UnifyFunctionTypeUndoIfFailed cenv denv mFunExpr exprty with
| ValueSome (domainTy, resultTy) ->

// Notice the special case 'seq { ... }'. In this case 'seq' is actually a function in the F# library.
// Set a flag in the syntax tree to say we noticed a leading 'seq'
match synArg with
| SynExpr.CompExpr (false, isNotNakedRefCell, _comp, _m) ->
isNotNakedRefCell :=
!isNotNakedRefCell
||
(match expr with
| ApplicableExpr(_, Expr.Op (TOp.Coerce, _, [Expr.App (Expr.Val (vf, _, _), _, _, _, _)], _), _) when valRefEq cenv.g vf cenv.g.seq_vref -> true
| _ -> false)
| _ -> ()

let arg, tpenv = TcExpr cenv domainTy env tpenv synArg
let exprAndArg, resultTy = buildApp cenv expr resultTy arg mExprAndArg
TcDelayed cenv overallTy env tpenv mExprAndArg exprAndArg resultTy atomicFlag delayed
| _ ->
match expr with
| ApplicableExpr(_, NameOfExpr cenv.g _, _) ->
let replacementExpr = TcNameOfExpr cenv env tpenv synArg
TcDelayed cenv overallTy env tpenv mExprAndArg (ApplicableExpr(cenv, replacementExpr, true)) cenv.g.string_ty ExprAtomicFlag.Atomic delayed
| _ ->
// Notice the special case 'seq { ... }'. In this case 'seq' is actually a function in the F# library.
// Set a flag in the syntax tree to say we noticed a leading 'seq'
match synArg with
| SynExpr.CompExpr (false, isNotNakedRefCell, _comp, _m) ->
isNotNakedRefCell :=
!isNotNakedRefCell
||
(match expr with
| ApplicableExpr(_, Expr.Op(TOp.Coerce, _, [SeqExpr cenv.g], _), _) -> true
| _ -> false)
| _ -> ()

let arg, tpenv = TcExpr cenv domainTy env tpenv synArg
let exprAndArg, resultTy = buildApp cenv expr resultTy arg mExprAndArg
TcDelayed cenv overallTy env tpenv mExprAndArg exprAndArg resultTy atomicFlag delayed

| ValueNone ->
// OK, 'expr' doesn't have function type, but perhaps 'expr' is a computation expression builder, and 'arg' is '{ ... }'
match synArg with
| SynExpr.CompExpr (false, _isNotNakedRefCell, comp, _m) ->
Expand All @@ -8754,25 +8804,26 @@ and TcFunctionApplicationThen cenv overallTy env tpenv mExprAndArg expr exprty (
// TcLongIdentThen: Typecheck "A.B.C<D>.E.F ... " constructs
//-------------------------------------------------------------------------

and TcLongIdentThen cenv overallTy env tpenv (LongIdentWithDots(longId, _)) delayed =
and GetLongIdentTypeNameInfo delayed =
// Given 'MyOverloadedType<int>.MySubType...' use the number of given type arguments to help
// resolve type name lookup of 'MyOverloadedType'
// Also determine if type names should resolve to Item.Types or Item.CtorGroup
match delayed with
| DelayedTypeApp (tyargs, _, _) :: (DelayedDot | DelayedDotLookup _) :: _ ->
// cases like 'MyType<int>.Sth'
TypeNameResolutionInfo(ResolveTypeNamesToTypeRefs, TypeNameResolutionStaticArgsInfo.FromTyArgs tyargs.Length)

let ad = env.eAccessRights
let typeNameResInfo =
// Given 'MyOverloadedType<int>.MySubType...' use arity of #given type arguments to help
// resolve type name lookup of 'MyOverloadedType'
// Also determine if type names should resolve to Item.Types or Item.CtorGroup
match delayed with
| DelayedTypeApp (tyargs, _, _) :: (DelayedDot | DelayedDotLookup _) :: _ ->
// cases like 'MyType<int>.Sth'
TypeNameResolutionInfo(ResolveTypeNamesToTypeRefs, TypeNameResolutionStaticArgsInfo.FromTyArgs tyargs.Length)
| DelayedTypeApp (tyargs, _, _) :: _ ->
// Note, this also covers the case 'MyType<int>.' (without LValue_get), which is needed for VS (when typing)
TypeNameResolutionInfo(ResolveTypeNamesToCtors, TypeNameResolutionStaticArgsInfo.FromTyArgs tyargs.Length)

| DelayedTypeApp (tyargs, _, _) :: _ ->
// Note, this also covers the case 'MyType<int>.' (without LValue_get), which is needed for VS (when typing)
TypeNameResolutionInfo(ResolveTypeNamesToCtors, TypeNameResolutionStaticArgsInfo.FromTyArgs tyargs.Length)
| _ ->
TypeNameResolutionInfo.Default

| _ ->
TypeNameResolutionInfo.Default
and TcLongIdentThen cenv overallTy env tpenv (LongIdentWithDots(longId, _)) delayed =

let ad = env.eAccessRights
let typeNameResInfo = GetLongIdentTypeNameInfo delayed
let nameResolutionResult = ResolveLongIdentAsExprAndComputeRange cenv.tcSink cenv.nameResolver (rangeOfLid longId) ad env.eNameResEnv typeNameResInfo longId
TcItemThen cenv overallTy env tpenv nameResolutionResult delayed

Expand Down
10 changes: 10 additions & 0 deletions src/fsharp/xlf/FSComp.txt.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -7182,6 +7182,16 @@
<target state="translated">Následující členy nemají žádnou implementaci (některé výsledky jsou vynechané): {0} Všechny členy rozhraní je potřeba implementovat a uvést pod příslušnou deklarací interface, třeba takto: interface ... with member ...</target>
<note />
</trans-unit>
<trans-unit id="expressionHasNoName">
<source>Expression does not have a name.</source>
<target state="new">Expression does not have a name.</target>
<note />
</trans-unit>
<trans-unit id="chkNoFirstClassNameOf">
<source>Using the 'nameof' operator as a first-class function value is not permitted.</source>
<target state="new">Using the 'nameof' operator as a first-class function value is not permitted.</target>
<note />
</trans-unit>
<trans-unit id="optsPathMap">
<source>Maps physical paths to source path names output by the compiler</source>
<target state="translated">Mapuje fyzické cesty na názvy zdrojových cest z výstupu kompilátoru.</target>
Expand Down
Loading

0 comments on commit 92e4a52

Please sign in to comment.