diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md
index 5bda852e8f4..2687dc328e9 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md
@@ -27,6 +27,7 @@
* Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231))
* Expose inner exception information of TypeProviders to help diagnostics in IDE ([PR #17251](https://github.com/dotnet/fsharp/pull/17251))
* Parser: recover on empty match clause ([PR #17233](https://github.com/dotnet/fsharp/pull/17233))
+* Support empty-bodied computation expressions. ([Language suggestion #1232](https://github.com/fsharp/fslang-suggestions/issues/1232), [RFC FS-1144 (PR #774)](https://github.com/fsharp/fslang-design/pull/774), [PR #17352](https://github.com/dotnet/fsharp/pull/17352))
### Changed
* Enforce `AttributeTargets.Interface` ([PR #17173](https://github.com/dotnet/fsharp/pull/17173))
diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md
index 49f990c8c3d..7b25b17a179 100644
--- a/docs/release-notes/.Language/preview.md
+++ b/docs/release-notes/.Language/preview.md
@@ -8,6 +8,7 @@
* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473))
* Allow #nowarn to support the FS prefix on error codes to disable warnings ([Issue #17206](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209))
* Allow ParsedHashDirectives to have argument types other than strings ([Issue #17240](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209))
+* Support empty-bodied computation expressions. ([Language suggestion #1232](https://github.com/fsharp/fslang-suggestions/issues/1232), [PR #17352](https://github.com/dotnet/fsharp/pull/17352))
### Fixed
diff --git a/src/Compiler/Checking/CheckComputationExpressions.fs b/src/Compiler/Checking/CheckComputationExpressions.fs
index fa56149ac7f..4fc0c8844ef 100644
--- a/src/Compiler/Checking/CheckComputationExpressions.fs
+++ b/src/Compiler/Checking/CheckComputationExpressions.fs
@@ -1086,6 +1086,8 @@ let TcComputationExpression (cenv: cenv) env (overallTy: OverallTy) tpenv (mWhol
&& hasMethInfo "Delay"
&& YieldFree cenv comp)
+ let origComp = comp
+
///
/// Try translate the syntax sugar
///
@@ -1633,7 +1635,19 @@ let TcComputationExpression (cenv: cenv) env (overallTy: OverallTy) tpenv (mWhol
(not enableImplicitYield)
&& isNil (TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env m ad "Zero" builderTy)
then
- error (Error(FSComp.SR.tcRequireBuilderMethod ("Zero"), m))
+ match origComp with
+ // builder { }
+ //
+ // The compiler inserts a dummy () in CheckExpressions.fs for
+ // empty-bodied computation expressions. In this case, the user
+ // has not actually written any "control construct" in the body,
+ // and so we use a more specific error message for clarity.
+ | SynExpr.Const(SynConst.Unit, mUnit) when
+ g.langVersion.SupportsFeature LanguageFeature.EmptyBodiedComputationExpressions
+ && Range.equals mUnit range0
+ ->
+ error (Error(FSComp.SR.tcEmptyBodyRequiresBuilderZeroMethod (), mWhole))
+ | _ -> error (Error(FSComp.SR.tcRequireBuilderMethod ("Zero"), m))
Some(translatedCtxt (mkSynCall "Zero" m []))
diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs
index 89253bd6783..6bdf94ed9ff 100644
--- a/src/Compiler/Checking/CheckExpressions.fs
+++ b/src/Compiler/Checking/CheckExpressions.fs
@@ -8189,6 +8189,10 @@ and Propagate (cenv: cenv) (overallTy: OverallTy) (env: TcEnv) tpenv (expr: Appl
// seq { ... }
| SynExpr.ComputationExpr _ -> ()
+ // async { }
+ // seq { }
+ | SynExpr.Record (None, None, [], _) when g.langVersion.SupportsFeature LanguageFeature.EmptyBodiedComputationExpressions -> ()
+
// expr[idx]
// expr[idx1, idx2]
// expr[idx1..]
@@ -8453,6 +8457,16 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
let mArg = synArg.Range
let mLeftExpr = leftExpr.Range
+ /// Treat an application of a value to an empty record expression
+ /// as a computation expression with a single unit expression.
+ /// Insert a (), i.e., such that builder { } ≡ builder { () }.
+ /// This transformation is only valid for language
+ /// versions that support this feature.
+ let (|EmptyFieldListAsUnit|_|) recordFields =
+ match recordFields with
+ | [] when g.langVersion.SupportsFeature LanguageFeature.EmptyBodiedComputationExpressions -> Some (EmptyFieldListAsUnit (SynExpr.Const (SynConst.Unit, range0)))
+ | _ -> None
+
// 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 or indexer or delegate invoke
match UnifyFunctionTypeUndoIfFailed cenv denv mLeftExpr exprTy with
@@ -8474,11 +8488,15 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
// though users don't realise that.
let synArg =
match synArg with
- | SynExpr.ComputationExpr (false, comp, m) when
+ // seq { comp }
+ // seq { }
+ | SynExpr.ComputationExpr (false, comp, m)
+ | SynExpr.Record (None, None, EmptyFieldListAsUnit comp, m) when
(match leftExpr with
| ApplicableExpr(expr=Expr.Op(TOp.Coerce, _, [SeqExpr g], _)) -> true
| _ -> false) ->
SynExpr.ComputationExpr (true, comp, m)
+
| _ -> synArg
let arg, tpenv =
@@ -8519,8 +8537,11 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
| _ -> None, delayed
TcIndexingThen cenv env overallTy mExprAndArg m tpenv setInfo synLeftExprOpt leftExpr.Expr exprTy expandedIndexArgs indexArgs delayed
- // Perhaps 'leftExpr' is a computation expression builder, and 'arg' is '{ ... }'
- | SynExpr.ComputationExpr (false, comp, _m) ->
+ // Perhaps 'leftExpr' is a computation expression builder, and 'arg' is '{ ... }' or '{ }':
+ // leftExpr { comp }
+ // leftExpr { }
+ | SynExpr.ComputationExpr (false, comp, _m)
+ | SynExpr.Record (None, None, EmptyFieldListAsUnit comp, _m) ->
let bodyOfCompExpr, tpenv = cenv.TcComputationExpression cenv env overallTy tpenv (mLeftExpr, leftExpr.Expr, exprTy, comp)
TcDelayed cenv overallTy env tpenv mExprAndArg (MakeApplicableExprNoFlex cenv bodyOfCompExpr) (tyOfExpr g bodyOfCompExpr) ExprAtomicFlag.NonAtomic delayed
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index f74fc3da3e3..55484591abc 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -561,6 +561,7 @@ tcCouldNotFindIDisposable,"Couldn't find Dispose on IDisposable, or it was overl
706,tcInvalidUnitsOfMeasurePrefix,"Units-of-measure cannot be used as prefix arguments to a type. Rewrite as postfix arguments in angle brackets."
707,tcUnitsOfMeasureInvalidInTypeConstructor,"Unit-of-measure cannot be used in type constructor application"
708,tcRequireBuilderMethod,"This control construct may only be used if the computation expression builder defines a '%s' method"
+708,tcEmptyBodyRequiresBuilderZeroMethod,"An empty body may only be used if the computation expression builder defines a 'Zero' method."
709,tcTypeHasNoNestedTypes,"This type has no nested types"
711,tcUnexpectedSymbolInTypeExpression,"Unexpected %s in type expression"
712,tcTypeParameterInvalidAsTypeConstructor,"Type parameter cannot be used as type constructor"
@@ -1755,3 +1756,4 @@ featureReuseSameFieldsInStructUnions,"Share underlying fields in a [] di
featureParsedHashDirectiveArgumentNonString,"# directives with non-quoted string arguments"
3869,featureParsedHashDirectiveUnexpectedInteger,"Unexpected integer literal '%d'."
3869,featureParsedHashDirectiveUnexpectedIdentifier,"Unexpected identifier '%s'."
+featureEmptyBodiedComputationExpressions,"Support for computation expressions with empty bodies: builder {{ }}"
diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs
index c8378c1042f..f6baf0dcf3b 100644
--- a/src/Compiler/Facilities/LanguageFeatures.fs
+++ b/src/Compiler/Facilities/LanguageFeatures.fs
@@ -90,6 +90,7 @@ type LanguageFeature =
| LowerIntegralRangesToFastLoops
| LowerSimpleMappingsInComprehensionsToDirectCallsToMap
| ParsedHashDirectiveArgumentNonQuotes
+ | EmptyBodiedComputationExpressions
/// LanguageVersion management
type LanguageVersion(versionText) =
@@ -207,6 +208,7 @@ type LanguageVersion(versionText) =
LanguageFeature.LowerIntegralRangesToFastLoops, previewVersion
LanguageFeature.LowerSimpleMappingsInComprehensionsToDirectCallsToMap, previewVersion
LanguageFeature.ParsedHashDirectiveArgumentNonQuotes, previewVersion
+ LanguageFeature.EmptyBodiedComputationExpressions, previewVersion
]
static let defaultLanguageVersion = LanguageVersion("default")
@@ -356,6 +358,7 @@ type LanguageVersion(versionText) =
| LanguageFeature.LowerSimpleMappingsInComprehensionsToDirectCallsToMap ->
FSComp.SR.featureLowerSimpleMappingsInComprehensionsToDirectCallsToMap ()
| LanguageFeature.ParsedHashDirectiveArgumentNonQuotes -> FSComp.SR.featureParsedHashDirectiveArgumentNonString ()
+ | LanguageFeature.EmptyBodiedComputationExpressions -> FSComp.SR.featureEmptyBodiedComputationExpressions ()
/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi
index 354d657bc40..ff60b7442f7 100644
--- a/src/Compiler/Facilities/LanguageFeatures.fsi
+++ b/src/Compiler/Facilities/LanguageFeatures.fsi
@@ -81,6 +81,7 @@ type LanguageFeature =
| LowerIntegralRangesToFastLoops
| LowerSimpleMappingsInComprehensionsToDirectCallsToMap
| ParsedHashDirectiveArgumentNonQuotes
+ | EmptyBodiedComputationExpressions
/// LanguageVersion management
type LanguageVersion =
diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf
index e8b29f9dd54..199d58ba6f8 100644
--- a/src/Compiler/xlf/FSComp.txt.cs.xlf
+++ b/src/Compiler/xlf/FSComp.txt.cs.xlf
@@ -302,6 +302,11 @@
literál float32 bez tečky
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Vynutit AttributeTargets
@@ -1247,6 +1252,11 @@
Zkrácená syntaxe lambda je podporována pouze pro atomické výrazy, jako je metoda, vlastnost, pole nebo indexer v implicitní argumentu _. Příklad: let f = _. Length'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ Syntaxe expr1[expr2] se používá pro indexování. Pokud chcete povolit indexování, zvažte možnost přidat anotaci typu, nebo pokud voláte funkci, přidejte mezeru, třeba expr1 [expr2].
diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf
index 26932a4dc81..34ee51909be 100644
--- a/src/Compiler/xlf/FSComp.txt.de.xlf
+++ b/src/Compiler/xlf/FSComp.txt.de.xlf
@@ -302,6 +302,11 @@
punktloses float32-Literal
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ AttributeTargets erzwingen
@@ -1247,6 +1252,11 @@
Die Lambdasyntax der Kurzform wird nur für atomische Ausdrücke wie Methode, Eigenschaft, Feld oder Indexer für das implizite Argument "_" unterstützt. Beispiel: "let f = _. Länge".
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ Die Syntax "expr1[expr2]" wird für die Indizierung verwendet. Fügen Sie ggf. eine Typanmerkung hinzu, um die Indizierung zu aktivieren, oder fügen Sie beim Aufrufen einer Funktion ein Leerzeichen hinzu, z. B. "expr1 [expr2]".
diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf
index 5ae9f079d23..97cedff5dc4 100644
--- a/src/Compiler/xlf/FSComp.txt.es.xlf
+++ b/src/Compiler/xlf/FSComp.txt.es.xlf
@@ -302,6 +302,11 @@
literal float32 sin punto
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Aplicar AttributeTargets
@@ -1247,6 +1252,11 @@
La sintaxis lambda abreviada solo se admite para expresiones atómicas, como el método, la propiedad, el campo o el indexador en el argumento '_' implícito. Por ejemplo: 'let f = _.Length'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ La sintaxis "expr1[expr2]" se usa para la indexación. Considere la posibilidad de agregar una anotación de tipo para habilitar la indexación, si se llama a una función, agregue un espacio, por ejemplo, "expr1 [expr2]".
diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf
index a8ae73b3c65..8336143887c 100644
--- a/src/Compiler/xlf/FSComp.txt.fr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.fr.xlf
@@ -302,6 +302,11 @@
littéral float32 sans point
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Appliquer AttributeTargets
@@ -1247,6 +1252,11 @@
La syntaxe lambda raccourcie est prise en charge uniquement pour les expressions atomiques, telles que la méthode, la propriété, le champ ou l’indexeur sur l’argument ’_’ implicite. Par exemple : « let f = _. Longueur ».
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ La syntaxe « expr1[expr2] » est utilisée pour l’indexation. Envisagez d’ajouter une annotation de type pour activer l’indexation, ou si vous appelez une fonction, ajoutez un espace, par exemple « expr1 [expr2] ».
diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf
index 8bfde9a01b8..eb2aaec5305 100644
--- a/src/Compiler/xlf/FSComp.txt.it.xlf
+++ b/src/Compiler/xlf/FSComp.txt.it.xlf
@@ -302,6 +302,11 @@
valore letterale float32 senza punti
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Imponi destinazioni attributo
@@ -1247,6 +1252,11 @@
La sintassi lambda a sintassi abbreviata è supportata solo per le espressioni atomiche, ad esempio metodo, proprietà, campo o indicizzatore nell'argomento '_' implicito. Ad esempio: 'let f = _. Lunghezza'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ La sintassi 'expr1[expr2]' viene usata per l'indicizzazione. Provare ad aggiungere un'annotazione di tipo per abilitare l'indicizzazione oppure se la chiamata a una funzione aggiunge uno spazio, ad esempio 'expr1 [expr2]'.
diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf
index 3c1f9932d0c..fef3e7db33d 100644
--- a/src/Compiler/xlf/FSComp.txt.ja.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ja.xlf
@@ -302,6 +302,11 @@
ドットなしの float32 リテラル
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ AttributeTargets を適用する
@@ -1247,6 +1252,11 @@
短縮ラムダ構文は、暗黙的な '_' 引数のメソッド、プロパティ、フィールド、インデクサーなどのアトミック式でのみサポートされています。例: 'let f = _.Length'。
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ 構文 'expr1[expr2]' はインデックス作成に使用されます。インデックスを有効にするために型の注釈を追加するか、関数を呼び出す場合には、'expr1 [expr2]' のようにスペースを入れます。
diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf
index a960ad4399f..dc1d8334e95 100644
--- a/src/Compiler/xlf/FSComp.txt.ko.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ko.xlf
@@ -302,6 +302,11 @@
점이 없는 float32 리터럴
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ AttributeTargets 적용
@@ -1247,6 +1252,11 @@
줄임 람다 구문은 암시적 '_' 인수의 메서드, 속성, 필드 또는 인덱서와 같은 원자성 식에 대해서만 지원됩니다. 예: 'let f = _.Length'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ 인덱싱에는 'expr1[expr2]' 구문이 사용됩니다. 인덱싱을 사용하도록 설정하기 위해 형식 주석을 추가하는 것을 고려하거나 함수를 호출하는 경우 공백을 추가하세요(예: 'expr1 [expr2]').
diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf
index bd2facc3cba..d7a6d1ba814 100644
--- a/src/Compiler/xlf/FSComp.txt.pl.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pl.xlf
@@ -302,6 +302,11 @@
bezkropkowy literał float32
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Wymuszaj elementy AttributeTargets
@@ -1247,6 +1252,11 @@
Składnia lambda skrótu jest obsługiwana tylko w przypadku wyrażeń niepodzielnych, takich jak metoda, właściwość, pole lub indeksator w dorozumianym argumencie „_”. Na przykład: „let f = _. Length”.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ Do indeksowania używana jest składnia „expr1[expr2]”. Rozważ dodanie adnotacji typu, aby umożliwić indeksowanie, lub jeśli wywołujesz funkcję dodaj spację, np. „expr1 [expr2]”.
diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
index 59138d83890..57c562c050a 100644
--- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
@@ -302,6 +302,11 @@
literal float32 sem ponto
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Impor AttributeTargets
@@ -1247,6 +1252,11 @@
A sintaxe lambda abreviada só tem suporte para expressões atômicas, como método, propriedade, campo ou indexador no argumento '_' implícito. Por exemplo: 'let f = _. Comprimento'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ A sintaxe 'expr1[expr2]' é usada para indexação. Considere adicionar uma anotação de tipo para habilitar a indexação ou, se chamar uma função, adicione um espaço, por exemplo, 'expr1 [expr2]'.
diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf
index 58c625f0746..2dcc335b628 100644
--- a/src/Compiler/xlf/FSComp.txt.ru.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ru.xlf
@@ -302,6 +302,11 @@
литерал float32 без точки
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ Принудительно применить AttributeTargets
@@ -1247,6 +1252,11 @@
Сокращенный синтаксис лямбда-выражений поддерживается только для атомарных выражений, таких как метод, свойство, поле или индексатор подразумеваемого аргумента «_». Например: 'let f = _.Length'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ Для индексирования используется синтаксис "expr1[expr2]". Рассмотрите возможность добавления аннотации типа для включения индексации или при вызове функции добавьте пробел, например "expr1 [expr2]".
diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf
index cfe703430c0..1ba13ab3ede 100644
--- a/src/Compiler/xlf/FSComp.txt.tr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.tr.xlf
@@ -302,6 +302,11 @@
noktasız float32 sabit değeri
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ AttributeTargets'ı zorla
@@ -1247,6 +1252,11 @@
Toplu lambda söz dizimi yalnızca örtülü '_' bağımsız değişkeninde yöntem, özellik, alan veya dizin oluşturucu gibi atomik ifadeler için destekleniyor. Örnek: 'let f = _.Length'.
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ Söz dizimi “expr1[expr2]” dizin oluşturma için kullanılıyor. Dizin oluşturmayı etkinleştirmek için bir tür ek açıklama eklemeyi düşünün veya bir işlev çağırıyorsanız bir boşluk ekleyin, örn. “expr1 [expr2]”.
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
index d707003f18f..b3a3fa421c5 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
@@ -302,6 +302,11 @@
无点 float32 文本
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ 强制使用 AttributeTargets
@@ -1247,6 +1252,11 @@
仅原子表达式支持速记 lambda 语法,例如隐含的“_”参数上的方法、属性、字段或索引器。例如:“let f = _.Length”。
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ 语法“expr1[expr2]”用于索引。考虑添加类型批注来启用索引,或者在调用函数添加空格,例如“expr1 [expr2]”。
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
index 5cc67b951b6..1beb02fecd5 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
@@ -302,6 +302,11 @@
無點號的 float32 常值
+
+
+ Support for computation expressions with empty bodies: builder {{ }}
+
+ 強制使用 AttributeTargets
@@ -1247,6 +1252,11 @@
只有不可部分完成運算式才支援速記 Lambda 語法,例如隱含 '_' 引數上的方法、屬性、欄位或索引子。例如: 'let f = _.Length'。
+
+
+ An empty body may only be used if the computation expression builder defines a 'Zero' method.
+
+ 語法 'expr1[expr2]' 已用於編製索引。請考慮新增類型註釋來啟用編製索引,或是呼叫函式並新增空格,例如 'expr1 [expr2]'。
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs
new file mode 100644
index 00000000000..2738c701134
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs
@@ -0,0 +1,264 @@
+module Conformance.Expressions.ComputationExpressions
+
+open Xunit
+open FSharp.Test.Compiler
+
+[]
+let ``[] without explicit name is allowed, uses method name as operation name`` () =
+ FSharp """
+ module CustomOperationTest
+ type CBuilder() =
+ []
+ member this.Foo _ = "Foo"
+ []
+ member this.foo _ = "foo"
+ []
+ member this.bar _ = "bar"
+ member this.Yield _ = ()
+ member this.Zero _ = ()
+
+
+ []
+ let main _ =
+ let cb = CBuilder()
+
+ let x = cb { Foo }
+ let y = cb { foo }
+ let z = cb { bar }
+ printfn $"{x}"
+ printfn $"{y}"
+
+ if x <> "Foo" then
+ failwith "not Foo"
+ if y <> "foo" then
+ failwith "not foo"
+ if z <> "bar" then
+ failwith "not bar"
+ 0
+ """
+ |> asExe
+ |> compileAndRun
+ |> shouldSucceed
+
+/// Tests for empty-bodied computation expressions: builder { }
+module EmptyBodied =
+ /// F# 8.0 and below do not support empty-bodied computation expressions.
+ module Unsupported =
+ []
+ let ``seq { } does not compile`` () =
+ Fsx """
+ let xs : int seq = seq { }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 789
+ |> withErrorMessage "'{ }' is not a valid expression. Records must include at least one field. Empty sequences are specified by using Seq.empty or an empty list '[]'."
+
+ []
+ let ``async { } does not compile`` () =
+ Fsx """
+ let a : Async = async { }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3
+ |> withErrorMessage "This value is not a function and cannot be applied."
+
+ []
+ let ``task { } does not compile`` () =
+ Fsx """
+ open System.Threading.Tasks
+
+ let t : Task = task { }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3
+ |> withErrorMessage "This value is not a function and cannot be applied."
+
+ []
+ let ``builder { } does not compile`` () =
+ FSharp """
+ type Builder () =
+ member _.Zero () = Seq.empty
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ let xs : int seq = builder { }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3
+ |> withErrorMessage "This value is not a function and cannot be applied."
+
+ []
+ let ``builder { () } and no Zero: FS0708`` () =
+ FSharp """
+ type Builder () =
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ let xs : int seq = builder { () }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 708
+ |> withErrorMessage "This control construct may only be used if the computation expression builder defines a 'Zero' method"
+
+ []
+ let ``builder { () } ≡ seq { () } when Zero () = Seq.empty`` () =
+ Fsx """
+ type Builder () =
+ member _.Zero () = Seq.empty
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ if List.ofSeq (builder { () }) <> List.ofSeq (seq { () }) then
+ failwith "builder { () } ≢ seq { () }"
+ """
+ |> withLangVersion80
+ |> runFsi
+ |> shouldSucceed
+
+ []
+ let ``Unchecked﹒defaultof<'a> { }`` () =
+ FSharp """
+ Unchecked.defaultof<'a> { }
+ """
+ |> withLangVersion80
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 789
+ |> withErrorMessage "'{ }' is not a valid expression. Records must include at least one field. Empty sequences are specified by using Seq.empty or an empty list '[]'."
+
+ /// F# 9.0 and above support empty-bodied computation expressions.
+ module Supported =
+ /// The language version that supports empty-bodied computation expressions.
+ /// TODO: Update this to the appropriate version when the feature comes out of preview.
+ let [] SupportedLanguageVersion = "preview"
+
+ []
+ let ``seq { } ≡ seq { () }`` () =
+ Fsx """
+ if List.ofSeq (seq { }) <> List.ofSeq (seq { () }) then
+ failwith "seq { } ≢ seq { () }"
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> runFsi
+ |> shouldSucceed
+
+ []
+ let ``async { } ≡ async { () }`` () =
+ Fsx """
+ if
+ [|(); ()|] <> (
+ [|async { }; async { () }|]
+ |> Async.Parallel
+ |> Async.RunSynchronously
+ )
+ then
+ failwith "async { } ≢ async { () }"
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> runFsi
+ |> shouldSucceed
+
+ []
+ let ``task { } ≡ task { () }`` () =
+ Fsx """
+ open System.Threading.Tasks
+
+ // We wrap this in a function to avoid https://github.com/dotnet/fsharp/issues/12038
+ let f () =
+ if
+ [|(); ()|] <> Task.WhenAll(task { }, task { () }).GetAwaiter().GetResult()
+ then
+ failwith "task { } ≢ task { () }"
+
+ f ()
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> runFsi
+ |> shouldSucceed
+
+ []
+ let ``builder { () } and no Zero: FS0708`` () =
+ FSharp """
+ type Builder () =
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ let xs : int seq = builder { () }
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 708
+ |> withErrorMessage "This control construct may only be used if the computation expression builder defines a 'Zero' method"
+
+ []
+ let ``builder { } and no Zero: FS0708 and new message`` () =
+ FSharp """
+ type Builder () =
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ let xs : int seq = builder { }
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 708
+ |> withErrorMessage "An empty body may only be used if the computation expression builder defines a 'Zero' method."
+
+ []
+ let ``builder { } ≡ seq { } when Zero () = Seq.empty`` () =
+ Fsx """
+ type Builder () =
+ member _.Zero () = Seq.empty
+ member _.Delay f = f
+ member _.Run f = f ()
+
+ let builder = Builder ()
+
+ if List.ofSeq (builder { }) <> List.ofSeq (seq { }) then
+ failwith "builder { } ≢ seq { }"
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> runFsi
+ |> shouldSucceed
+
+ []
+ let ``Unchecked﹒defaultof<'a> { }`` () =
+ FSharp """
+ Unchecked.defaultof<'a> { }
+ """
+ |> withLangVersion SupportedLanguageVersion
+ |> asExe
+ |> compile
+ |> shouldFail
+ |> withErrorCode 789
+ |> withErrorMessage "'{ }' is not a valid expression. Records must include at least one field. Empty sequences are specified by using Seq.empty or an empty list '[]'."
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CustomOperations.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CustomOperations.fs
deleted file mode 100644
index 28e9ff22c7a..00000000000
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/CustomOperations.fs
+++ /dev/null
@@ -1,43 +0,0 @@
-namespace Conformance.Expressions.ComputationExpressions
-
-open Xunit
-open FSharp.Test.Compiler
-
-module CustomOperations =
-
- []
- let ``[] without explicit name is allowed, uses method name as operation name`` () =
- FSharp """
- module CustomOperationTest
- type CBuilder() =
- []
- member this.Foo _ = "Foo"
- []
- member this.foo _ = "foo"
- []
- member this.bar _ = "bar"
- member this.Yield _ = ()
- member this.Zero _ = ()
-
-
- []
- let main _ =
- let cb = CBuilder()
-
- let x = cb { Foo }
- let y = cb { foo }
- let z = cb { bar }
- printfn $"{x}"
- printfn $"{y}"
-
- if x <> "Foo" then
- failwith "not Foo"
- if y <> "foo" then
- failwith "not foo"
- if z <> "bar" then
- failwith "not bar"
- 0
- """
- |> asExe
- |> compileAndRun
- |> shouldSucceed
\ No newline at end of file
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
index 419776657f5..aa343573791 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
+++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
@@ -79,7 +79,7 @@
-
+