Skip to content

Allow _ in use! bindings values #18487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* Fixed [#18433](https://github.com/dotnet/fsharp/issues/18433), a rare case of an internal error in xml comment processing. ([PR #18436](https://github.com/dotnet/fsharp/pull/18436))
* Fix confusing type inference error in task expression ([Issue #13789](https://github.com/dotnet/fsharp/issues/13789), [PR #18450](https://github.com/dotnet/fsharp/pull/18450))
* Fix missing `null` highlighting in tooltips ([PR #18457](https://github.com/dotnet/fsharp/pull/18457))
* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487))
* Make `[<CallerMemberName; Struct>]` combination work([PR #18444](https://github.com/dotnet/fsharp/pull/18444/))

### Added
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772))
* Added type conversions cache, only enabled for compiler runs ([PR#17668](https://github.com/dotnet/fsharp/pull/17668))
* Support ValueOption + Struct attribute as optional parameter for methods ([Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098))
* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487))
* Warn when `unit` is passed to an `obj`-typed argument ([PR #18330](https://github.com/dotnet/fsharp/pull/18330))

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// with generalization at appropriate points.
module internal FSharp.Compiler.CheckComputationExpressions

open FSharp.Compiler.TcGlobals
open Internal.Utilities.Library
open FSharp.Compiler.AccessibilityLogic
open FSharp.Compiler.AttributeChecking
Expand Down Expand Up @@ -849,6 +850,23 @@ let (|OptionalSequential|) e =
| SynExpr.Sequential(debugPoint = _sp; isTrueSeq = true; expr1 = dataComp1; expr2 = dataComp2) -> (dataComp1, Some dataComp2)
| _ -> (e, None)

// use! x = ...
// use! (x) = ...
// use! (__) = ...
// use! _ = ...
// use! (_) = ...
[<return: Struct>]
let rec (|UnwrapUseBang|_|) supportsUseBangBindingValueDiscard pat =
match pat with
| SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> ValueSome(id, pat)
| SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> ValueSome(id, pat)
| SynPat.Wild(m) when supportsUseBangBindingValueDiscard ->
// To properly call the Using(disposable) CE member, we need to convert the wildcard to a SynPat.Named
let tmpIdent = mkSynId m "_"
ValueSome(tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m))
| SynPat.Paren(pat = UnwrapUseBang supportsUseBangBindingValueDiscard (id, pat)) -> ValueSome(id, pat)
| _ -> ValueNone

[<return: Struct>]
let (|ExprAsUseBang|_|) expr =
match expr with
Expand Down Expand Up @@ -1809,17 +1827,27 @@ let rec TryTranslateComputationExpression
translatedCtxt
)

// 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2))))
// 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2))))
// or
// 'use! _ = e1 in e2' --> build.Bind(e1, (fun _argN -> e2))
| ExprAsUseBang(spBind, isFromSource, pat, rhsExpr, andBangs, innerComp, mBind) ->
if ceenv.isQuery then
error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind))

match pat, andBangs with
| (SynPat.Named(ident = SynIdent(id, _); isThisVal = false) | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ]))), [] ->
match andBangs with
| [] ->
// Valid pattern case - handle with Using + Bind
requireBuilderMethod "Using" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind
requireBuilderMethod "Bind" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind

let supportsUseBangBindingValueDiscard =
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard

let ident, pat =
match pat with
| UnwrapUseBang supportsUseBangBindingValueDiscard (ident, pat) -> ident, pat
| _ -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range))

let bindExpr =
let consumeExpr =
SynExpr.MatchLambda(
Expand All @@ -1840,14 +1868,14 @@ let rec TryTranslateComputationExpression
)

let consumeExpr =
mkSynCall "Using" mBind [ SynExpr.Ident id; consumeExpr ] ceenv.builderValName
mkSynCall "Using" mBind [ SynExpr.Ident ident; consumeExpr ] ceenv.builderValName

let consumeExpr =
SynExpr.MatchLambda(
false,
mBind,
[
SynMatchClause(pat, None, consumeExpr, id.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero)
SynMatchClause(pat, None, consumeExpr, ident.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero)
],
DebugPointAtBinding.NoneAtInvisible,
mBind
Expand All @@ -1860,8 +1888,7 @@ let rec TryTranslateComputationExpression
|> addBindDebugPoint spBind

Some(translatedCtxt bindExpr)
| _pat, [] -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), mBind))
| _pat, _ands ->
| _ ->
// Has andBangs
let m =
match andBangs with
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1798,3 +1798,4 @@ featureDontWarnOnUppercaseIdentifiersInBindingPatterns,"Don't warn on uppercase
featureDeprecatePlacesWhereSeqCanBeOmitted,"Deprecate places where 'seq' can be omitted"
featureSupportValueOptionsAsOptionalParameters,"Support ValueOption as valid type for optional member parameters"
featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member accepting `obj` argument, e.g. `Method(o:obj)` will warn if called via `Method()`."
featureUseBangBindingValueDiscard,"Use bang binding with value discard"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type LanguageFeature =
| DeprecatePlacesWhereSeqCanBeOmitted
| SupportValueOptionsAsOptionalParameters
| WarnWhenUnitPassedToObjArg
| UseBangBindingValueDiscard

/// LanguageVersion management
type LanguageVersion(versionText) =
Expand Down Expand Up @@ -229,6 +230,7 @@ type LanguageVersion(versionText) =
LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted, previewVersion
LanguageFeature.SupportValueOptionsAsOptionalParameters, previewVersion
LanguageFeature.WarnWhenUnitPassedToObjArg, previewVersion
LanguageFeature.UseBangBindingValueDiscard, previewVersion
]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -391,6 +393,7 @@ type LanguageVersion(versionText) =
| LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted -> FSComp.SR.featureDeprecatePlacesWhereSeqCanBeOmitted ()
| LanguageFeature.SupportValueOptionsAsOptionalParameters -> FSComp.SR.featureSupportValueOptionsAsOptionalParameters ()
| LanguageFeature.WarnWhenUnitPassedToObjArg -> FSComp.SR.featureSupportWarnWhenUnitPassedToObjArg ()
| LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
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 @@ -90,6 +90,7 @@ type LanguageFeature =
| DeprecatePlacesWhereSeqCanBeOmitted
| SupportValueOptionsAsOptionalParameters
| WarnWhenUnitPassedToObjArg
| UseBangBindingValueDiscard

/// LanguageVersion management
type LanguageVersion =
Expand Down
22 changes: 19 additions & 3 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -3755,13 +3755,23 @@ atomicPattern:
{ SynPat.ArrayOrList(true, $2, lhs parseState) }

| UNDERSCORE
{ SynPat.Wild(lhs parseState) }
{ (* Underscore pattern ('_') is represented as SynPat.Wild
This wild pattern is used in all binding forms:
- let _ = ...
- use _ = ...
- let! _ = ...
- use! _ = ...
This ensures consistent representation of wildcard bindings in the AST *)
SynPat.Wild(lhs parseState) }

| QMARK ident
{ SynPat.OptionalVal($2, lhs parseState) }

| atomicPatternLongIdent %prec prec_atompat_pathop
{ let vis, lidwd = $1
{ (* This rule handles identifiers in patterns like 'use! __' *)
(* For simple identifiers (like '__'), it creates a SynPat.Named AST node *)
(* For complex paths (A.B.C) or uppercase ids, it calls mkSynPatMaybeVar *)
let vis, lidwd = $1
if not (isNilOrSingleton lidwd.LongIdent) || String.isLeadingIdentifierCharacterUpperCase (List.head lidwd.LongIdent).idText then
mkSynPatMaybeVar lidwd vis (lhs parseState)
else
Expand Down Expand Up @@ -4431,7 +4441,13 @@ declExpr:
SynExpr.YieldOrReturnFrom(($1, not $1), arbExpr ("yield!", mYieldAll), mYieldAll, trivia) }

| BINDER headBindingPattern EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
{ let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5)
{ (* This rule handles the 'use!' and 'let!' binding expressions in computation expressions *)
(* The BINDER token represents keywords like 'use!' or 'let!' *)
(* headBindingPattern represents patterns in the binding:
- Underscore ('_') patterns are preserved as SynPat.Wild
- Named patterns ('__') are represented as SynPat.Named
- Identifiers (like 'value') are represented as SynPat.LongIdent *)
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5)
let mEquals = rhs parseState 3
let m = unionRanges (rhs parseState 1) $8.Range
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
Expand Down
5 changes: 5 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.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

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

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

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

Loading