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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets AttributeTargets を適用する @@ -1247,6 +1252,11 @@ 短縮ラムダ構文は、暗黙的な '_' 引数のメソッド、プロパティ、フィールド、インデクサーなどのアトミック式でのみサポートされています。例: 'let f = _.Length'。 + + An empty body may only be used if the computation expression builder defines a 'Zero' method. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 構文 '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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets AttributeTargets 적용 @@ -1247,6 +1252,11 @@ 줄임 람다 구문은 암시적 '_' 인수의 메서드, 속성, 필드 또는 인덱서와 같은 원자성 식에 대해서만 지원됩니다. 예: 'let f = _.Length'. + + An empty body may only be used if the computation expression builder defines a 'Zero' method. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 인덱싱에는 '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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets Принудительно применить AttributeTargets @@ -1247,6 +1252,11 @@ Сокращенный синтаксис лямбда-выражений поддерживается только для атомарных выражений, таких как метод, свойство, поле или индексатор подразумеваемого аргумента «_». Например: 'let f = _.Length'. + + An empty body may only be used if the computation expression builder defines a 'Zero' method. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. Для индексирования используется синтаксис "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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 强制使用 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 语法“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 {{ }} + Support for computation expressions with empty bodies: builder {{ }} + + Enforce AttributeTargets 強制使用 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. + An empty body may only be used if the computation expression builder defines a 'Zero' method. + + The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'. 語法 '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 @@ - +