Skip to content

Commit

Permalink
Allow _.Property / _.MethodCall() / _.IndexerAccess[idx] shorthand fo…
Browse files Browse the repository at this point in the history
…r accessor functions (#13907)

Co-authored-by: Tomas Grosup <[email protected]>
  • Loading branch information
tboby and T-Gro authored Jul 20, 2023
1 parent 5c2dc44 commit b645ace
Show file tree
Hide file tree
Showing 52 changed files with 702 additions and 20 deletions.
11 changes: 10 additions & 1 deletion src/Compiler/Checking/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5525,7 +5525,15 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE
TcNonControlFlowExpr env <| fun env ->
CallExprHasTypeSink cenv.tcSink (m, env.NameEnv, overallTy.Commit, env.AccessRights)
TcConstExpr cenv overallTy env m tpenv synConst

| SynExpr.DotLambda (synExpr, m, trivia) ->
if env.NameEnv.eUnqualifiedItems |> Map.containsKey "_arg1"
then
warning(Error(FSComp.SR.tcAmbiguousDiscardDotLambda(), trivia.UnderscoreRange))
let unaryArg = mkSynId trivia.UnderscoreRange (cenv.synArgNameGenerator.New())
let svar = mkSynCompGenSimplePatVar unaryArg
let pushedExpr = pushUnaryArg synExpr unaryArg
let lambda = SynExpr.Lambda(false, false, SynSimplePats.SimplePats([ svar ],[], svar.Range), pushedExpr, None, m, SynExprLambdaTrivia.Zero)
TcIteratedLambdas cenv true env overallTy Set.empty tpenv lambda
| SynExpr.Lambda _ ->
TcIteratedLambdas cenv true env overallTy Set.empty tpenv synExpr

Expand Down Expand Up @@ -8722,6 +8730,7 @@ and TcImplicitOpItemThen (cenv: cenv) overallTy env id sln tpenv mItem delayed =
| SynExpr.Const _
| SynExpr.Typar _
| SynExpr.LongIdent _
| SynExpr.DotLambda _
| SynExpr.Dynamic _ -> true

| SynExpr.Tuple (_, synExprs, _, _)
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Checking/MethodCalls.fs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ let InferLambdaArgsForLambdaPropagation origRhsExpr =
match e with
| SynExpr.Lambda (body = rest) -> 1 + loop rest
| SynExpr.MatchLambda _ -> 1
| SynExpr.DotLambda _ -> 1
| _ -> 0
loop origRhsExpr

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Driver/GraphChecking/FileContentMapping.fs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
| SynExpr.IndexFromEnd (expr, _) -> visit expr continuation
| SynExpr.ComputationExpr (expr = expr) -> visit expr continuation
| SynExpr.Lambda (args = args; body = body) -> visit body (fun bodyNodes -> visitSynSimplePats args @ bodyNodes |> continuation)
| SynExpr.DotLambda (expr = expr) -> visit expr continuation
| SynExpr.MatchLambda (matchClauses = clauses) -> List.collect visitSynMatchClause clauses |> continuation
| SynExpr.Match (expr = expr; clauses = clauses) ->
visit expr (fun exprNodes ->
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,9 @@ featureStaticLetInRecordsDusEmptyTypes,"Allow static let bindings in union, reco
3566,tcMultipleRecdTypeChoice,"Multiple type matches were found:\n%s\nThe type '%s' was used. Due to the overlapping field names\n%s\nconsider using type annotations or change the order of open statements."
3567,parsMissingMemberBody,"Expecting member body"
3568,parsMissingKeyword,"Missing keyword '%s'"
3570,tcAmbiguousDiscardDotLambda,"The meaning of _ is ambiguous here. It cannot be used for a discarded variable and a function shorthand in the same scope."
3571,parsUnderScoreDotLambdaNonAtomic," _. shorthand syntax for lambda functions can only be used with atomic expressions. That means expressions with no whitespace unless enclosed in parentheses."
featureAccessorFunctionShorthand,"underscore dot shorthand for accessor only function"
3569,chkNotTailRecursive,"The member or function '%s' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way."
3570,tcStaticBindingInExtrinsicAugmentation,"Static bindings cannot be added to extrinsic augmentations. Consider using a 'static member' instead."
3571,pickleFsharpCoreBackwardsCompatible,"Newly added pickle state cannot be used in FSharp.Core, since it must be working in older compilers+tooling as well. The time window is at least 3 years after feature introduction. Violation: %s . Context: \n %s "
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type LanguageFeature =
| LowercaseDUWhenRequireQualifiedAccess
| InterfacesWithAbstractStaticMembers
| SelfTypeConstraints
| AccessorFunctionShorthand
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired
| ErrorForNonVirtualMembersOverrides
Expand Down Expand Up @@ -150,6 +151,7 @@ type LanguageVersion(versionText) =

// F# preview
LanguageFeature.FromEndSlicing, previewVersion
LanguageFeature.AccessorFunctionShorthand, previewVersion
LanguageFeature.MatchNotAllowedForUnionCaseWithNoData, previewVersion
LanguageFeature.CSharpExtensionAttributeNotRequired, previewVersion
LanguageFeature.ErrorForNonVirtualMembersOverrides, previewVersion
Expand Down Expand Up @@ -275,6 +277,7 @@ type LanguageVersion(versionText) =
| LanguageFeature.LowercaseDUWhenRequireQualifiedAccess -> FSComp.SR.featureLowercaseDUWhenRequireQualifiedAccess ()
| LanguageFeature.InterfacesWithAbstractStaticMembers -> FSComp.SR.featureInterfacesWithAbstractStaticMembers ()
| LanguageFeature.SelfTypeConstraints -> FSComp.SR.featureSelfTypeConstraints ()
| LanguageFeature.AccessorFunctionShorthand -> FSComp.SR.featureAccessorFunctionShorthand ()
| LanguageFeature.MatchNotAllowedForUnionCaseWithNoData -> FSComp.SR.featureMatchNotAllowedForUnionCaseWithNoData ()
| LanguageFeature.CSharpExtensionAttributeNotRequired -> FSComp.SR.featureCSharpExtensionAttributeNotRequired ()
| LanguageFeature.ErrorForNonVirtualMembersOverrides -> FSComp.SR.featureErrorForNonVirtualMembersOverrides ()
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type LanguageFeature =
| LowercaseDUWhenRequireQualifiedAccess
| InterfacesWithAbstractStaticMembers
| SelfTypeConstraints
| AccessorFunctionShorthand
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired
| ErrorForNonVirtualMembersOverrides
Expand Down
7 changes: 7 additions & 0 deletions src/Compiler/Service/FSharpParseFileResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
// Capture the body of a lambda, often nested in a call to a collection function
| SynExpr.Lambda (body = body) when rangeContainsPos body.Range pos -> getIdentRangeForFuncExprInApp traverseSynExpr body pos

| SynExpr.DotLambda (expr = body) when rangeContainsPos body.Range pos -> getIdentRangeForFuncExprInApp traverseSynExpr body pos

| SynExpr.Do (expr, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos

| SynExpr.Assert (expr, range) when rangeContainsPos range pos -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos
Expand Down Expand Up @@ -425,6 +427,7 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
override _.VisitBinding(_path, defaultTraverse, binding) =
match binding with
| SynBinding(expr = SynExpr.Lambda _) when skipLambdas -> defaultTraverse binding
| SynBinding(expr = SynExpr.DotLambda _) when skipLambdas -> defaultTraverse binding

// Skip manually type-annotated bindings
| SynBinding(returnInfo = Some (SynBindingReturnInfo _)) -> defaultTraverse binding
Expand Down Expand Up @@ -533,6 +536,7 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
| SynBinding.SynBinding (expr = expr; range = range) when Position.posEq range.Start pos ->
match expr with
| SynExpr.Lambda _ -> Some range
| SynExpr.DotLambda _ -> Some range
| _ -> None
| _ -> defaultTraverse binding
}
Expand Down Expand Up @@ -667,6 +671,7 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
match expr with
| SynExpr.ArbitraryAfterError _
| SynExpr.LongIdent _
| SynExpr.DotLambda _
| SynExpr.LibraryOnlyILAssembly _
| SynExpr.LibraryOnlyStaticOptimization _
| SynExpr.Null _
Expand Down Expand Up @@ -828,6 +833,8 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,

| SynExpr.Lambda (body = bodyExpr) -> yield! walkExpr true bodyExpr

| SynExpr.DotLambda (expr = bodyExpr) -> yield! walkExpr true bodyExpr

| SynExpr.Match (matchDebugPoint = spBind; expr = inpExpr; clauses = cl) ->
yield! walkBindSeqPt spBind
yield! walkExpr false inpExpr
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Service/ServiceInterfaceStubGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,8 @@ module InterfaceStubGenerator =

| SynExpr.Lambda (body = synExpr) -> walkExpr synExpr

| SynExpr.DotLambda (expr = synExpr) -> walkExpr synExpr

| SynExpr.MatchLambda (_isExnMatch, _argm, synMatchClauseList, _spBind, _wholem) ->
synMatchClauseList
|> List.tryPick (fun (SynMatchClause (resultExpr = e)) -> walkExpr e)
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Service/ServiceParseTreeWalk.fs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,8 @@ module SyntaxTraversal =
| None -> traverseSynExpr synExpr
| x -> x

| SynExpr.DotLambda (expr = e) -> traverseSynExpr e

| SynExpr.MatchLambda (_isExnMatch, _argm, synMatchClauseList, _spBind, _wholem) ->
synMatchClauseList
|> List.map (fun x -> dive x x.Range (traverseSynMatchClause path))
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Service/ServiceParsedInputOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ module ParsedInput =

| SynExpr.Lambda (body = e) -> walkExprWithKind parentKind e

| SynExpr.DotLambda (expr = e) -> walkExprWithKind parentKind e

| SynExpr.MatchLambda (_, _, synMatchClauseList, _, _) -> List.tryPick walkClause synMatchClauseList

| SynExpr.Match (expr = e; clauses = synMatchClauseList) ->
Expand Down Expand Up @@ -1739,6 +1741,7 @@ module ParsedInput =
| SynExpr.Lambda (args = pats; body = e) ->
walkSimplePats pats
walkExpr e
| SynExpr.DotLambda (expr = e) -> walkExpr e
| SynExpr.New (_, t, e, _)
| SynExpr.TypeTest (e, t, _)
| SynExpr.Upcast (e, t, _)
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTree.fs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ type SynExpr =

| DotGet of expr: SynExpr * rangeOfDot: range * longDotId: SynLongIdent * range: range

| DotLambda of expr: SynExpr * range: range * trivia: SynExprDotLambdaTrivia

| DotSet of targetExpr: SynExpr * longDotId: SynLongIdent * rhsExpr: SynExpr * range: range

| Set of targetExpr: SynExpr * rhsExpr: SynExpr * range: range
Expand Down Expand Up @@ -755,6 +757,7 @@ type SynExpr =
| SynExpr.DotIndexedGet (range = m)
| SynExpr.DotIndexedSet (range = m)
| SynExpr.DotGet (range = m)
| SynExpr.DotLambda (range = m)
| SynExpr.DotSet (range = m)
| SynExpr.Set (range = m)
| SynExpr.DotNamedIndexedPropertySet (range = m)
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTree.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,9 @@ type SynExpr =
/// F# syntax: expr.ident.ident
| DotGet of expr: SynExpr * rangeOfDot: range * longDotId: SynLongIdent * range: range

/// F# syntax: _.ident.ident
| DotLambda of expr: SynExpr * range: range * trivia: SynExprDotLambdaTrivia

/// F# syntax: expr.ident...ident <- expr
| DotSet of targetExpr: SynExpr * longDotId: SynLongIdent * rhsExpr: SynExpr * range: range

Expand Down
37 changes: 37 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,42 @@ let mkSynSimplePatVar isOpt id =
let mkSynCompGenSimplePatVar id =
SynSimplePat.Id(id, None, true, false, false, id.idRange)

let rec pushUnaryArg expr arg =
match expr with
| SynExpr.App (ExprAtomicFlag.Atomic, infix, SynExpr.Ident ident, x1, m1) ->
SynExpr.App(
ExprAtomicFlag.Atomic,
infix,
SynExpr.LongIdent(false, SynLongIdent(arg :: ident :: [], [ ident.idRange ], [ None ]), None, ident.idRange),
x1,
m1
)
| SynExpr.App (ExprAtomicFlag.Atomic,
infix,
SynExpr.LongIdent (isOptional, SynLongIdent (id, dotRanges, trivia), altNameRefCell, range),
x1,
m1) ->
SynExpr.App(
ExprAtomicFlag.Atomic,
infix,
SynExpr.LongIdent(isOptional, SynLongIdent(arg :: id, dotRanges, trivia), altNameRefCell, range),
x1,
m1
)
| SynExpr.App (ExprAtomicFlag.Atomic, infix, (SynExpr.App (_) as innerApp), x1, m1) ->
SynExpr.App(ExprAtomicFlag.Atomic, infix, (pushUnaryArg innerApp arg), x1, m1)
| SynExpr.App (ExprAtomicFlag.Atomic, infix, SynExpr.DotGet (synExpr, rangeOfDot, synLongIdent, range), x1, m1) ->
SynExpr.App(ExprAtomicFlag.Atomic, infix, SynExpr.DotGet((pushUnaryArg synExpr arg), rangeOfDot, synLongIdent, range), x1, m1)
| SynExpr.App (ExprAtomicFlag.Atomic, infix, innerExpr, x1, m1) ->
SynExpr.App(ExprAtomicFlag.Atomic, infix, pushUnaryArg innerExpr arg, x1, m1)
| SynExpr.Ident ident -> SynExpr.LongIdent(false, SynLongIdent(arg :: ident :: [], [ ident.idRange ], [ None ]), None, ident.idRange)
| SynExpr.LongIdent (isOptional, SynLongIdent (id, dotRanges, trivia), altNameRefCell, range) ->
SynExpr.LongIdent(isOptional, SynLongIdent(arg :: id, dotRanges, trivia), altNameRefCell, range)
| SynExpr.DotGet (synExpr, rangeOfDot, synLongIdent, range) -> SynExpr.DotGet(pushUnaryArg synExpr arg, rangeOfDot, synLongIdent, range)
| SynExpr.DotIndexedGet (objectExpr, indexArgs, dotRange, range) ->
SynExpr.DotIndexedGet(pushUnaryArg objectExpr arg, indexArgs, dotRange, range)
| _ -> expr

let (|SynSingleIdent|_|) x =
match x with
| SynLongIdent ([ id ], _, _) -> Some id
Expand Down Expand Up @@ -792,6 +828,7 @@ let rec synExprContainsError inpExpr =
| SynExpr.ArbitraryAfterError _ -> true

| SynExpr.LongIdent _
| SynExpr.DotLambda _
| SynExpr.Quote _
| SynExpr.LibraryOnlyILAssembly _
| SynExpr.LibraryOnlyStaticOptimization _
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ val mkSynSimplePatVar: isOpt: bool -> id: Ident -> SynSimplePat

val mkSynCompGenSimplePatVar: id: Ident -> SynSimplePat

val pushUnaryArg: expr: SynExpr -> arg: Ident -> SynExpr

/// Match a long identifier, including the case for single identifiers which gets a more optimized node in the syntax tree.
val (|LongOrSingleIdent|_|):
inp: SynExpr -> (bool * SynLongIdent * SynSimplePatAlternativeIdInfo ref option * range) option
Expand Down
7 changes: 7 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTrivia.fs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ type SynExprLambdaTrivia =

static member Zero: SynExprLambdaTrivia = { ArrowRange = None }

[<NoEquality; NoComparison>]
type SynExprDotLambdaTrivia =
{
UnderscoreRange: range
DotRange: range
}

[<NoEquality; NoComparison>]
type SynExprLetOrUseTrivia = { InKeyword: range option }

Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTrivia.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ type SynExprLambdaTrivia =

static member Zero: SynExprLambdaTrivia

/// Represents additional information for SynExpr.DotLambda
[<NoEquality; NoComparison>]
type SynExprDotLambdaTrivia =
{ UnderscoreRange: range
DotRange: range }

/// Represents additional information for SynExpr.LetOrUse
[<NoEquality; NoComparison>]
type SynExprLetOrUseTrivia =
Expand Down
18 changes: 18 additions & 0 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) ->
%left pat_app
%left pat_args
%left PREFIX_OP
%right dot_lambda
%left DOT QMARK
%left HIGH_PRECEDENCE_BRACK_APP
%left HIGH_PRECEDENCE_PAREN_APP
Expand Down Expand Up @@ -4791,6 +4792,23 @@ argExpr:
arg }

atomicExpr:
| UNDERSCORE DOT atomicExpr %prec dot_lambda
{ let mUnderscore = rhs parseState 1
let mDot = rhs parseState 2
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AccessorFunctionShorthand (unionRanges mUnderscore mDot )
let expr, hpa = $3
let trivia: SynExprDotLambdaTrivia = { UnderscoreRange = mUnderscore ; DotRange = mDot }
SynExpr.DotLambda(expr, unionRanges mUnderscore expr.Range, trivia), false }

| UNDERSCORE DOT appExpr recover %prec dot_lambda
{ let mUnderscore = rhs parseState 1
let mDot = rhs parseState 2
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AccessorFunctionShorthand (unionRanges mUnderscore mDot )
reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnderScoreDotLambdaNonAtomic())
let expr = $3
let trivia: SynExprDotLambdaTrivia = { UnderscoreRange = mUnderscore ; DotRange = mDot }
SynExpr.DotLambda(expr, unionRanges mUnderscore expr.Range, trivia), false }

| atomicExpr HIGH_PRECEDENCE_BRACK_APP atomicExpr
{ let arg1, _ = $1
let arg2, hpa = $3
Expand Down
15 changes: 15 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b645ace

Please sign in to comment.