Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
* Add diagnostic FS3889 when a namespace and a type have the same fully-qualified name in the same assembly, replacing the misleading FS0247 "namespace and a module" error. ([Issue #17827](https://github.com/dotnet/fsharp/issues/17827), [PR #19802](https://github.com/dotnet/fsharp/pull/19802))
* Debug: rework for expressions stepping ([PR #19894](https://github.com/dotnet/fsharp/pull/19894))
* Debug: rework conditional erasure, fix stepping over literals ([PR #19897](https://github.com/dotnet/fsharp/pull/19897))
* Spread operator for records ([RFC FS-1151](https://github.com/fsharp/fslang-design/pull/805), [PR #18927](https://github.com/dotnet/fsharp/pull/18927))
* Debug: fix if and match condition sequence points ([PR #19932](https://github.com/dotnet/fsharp/pull/19932))
* Checker: recover on checking language version ([PR ##19970](https://github.com/dotnet/fsharp/pull/19970))
* Implied argument names for function-to-delegate coercions now fall back to the delegate's `Invoke` parameter names when the function has no recoverable names (e.g. a partial application like `System.Func<int, int>((+) 1)`), instead of synthetic `delegateArg0`, `delegateArg1`, … names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))
Expand Down
3 changes: 2 additions & 1 deletion docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072))
* Added `ErrorOnMissingSignatureAttribute` preview language feature: makes FS3888 (compiler-semantic attribute on the `.fs` but not on the `.fsi`) an error instead of a warning. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
* Spread operator for records ([RFC FS-1151](https://github.com/fsharp/fslang-design/pull/805), [PR #18927](https://github.com/dotnet/fsharp/pull/18927))
* Added `AccessProtectedBaseFieldFromClosure` preview language feature: a derived member can now read a `protected` base-class field from an ordinary closure (lambda, delegate, `async`/`seq`/`lazy`, `function`, or list/array literal), which previously failed with FS1097 even though direct access compiles. Object expressions remain unsupported — bind the field to a local function or expose it through a member. ([Issue #5302](https://github.com/dotnet/fsharp/issues/5302))
* Added `ImprovedImpliedArgumentNamesPartTwo` language feature: when a function with no recoverable parameter names is coerced to a delegate (e.g. a partial application like `System.Func<int, int>((+) 1)`), the synthesized `Invoke` parameters take their names from the delegate's own `Invoke` signature instead of synthetic `delegateArg0`, `delegateArg1`, … names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))

### Fixed

### Changed
### Changed
287 changes: 231 additions & 56 deletions src/Compiler/Checking/CheckDeclarations.fs

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions src/Compiler/Checking/CheckPatterns.fs
Original file line number Diff line number Diff line change
Expand Up @@ -498,13 +498,18 @@ and TcPatArrayOrList warnOnUpper cenv env vFlags patEnv ty isArray args m =
phase2, acc

and TcRecordPat warnOnUpper (cenv: cenv) env vFlags patEnv ty fieldPats m =
let fieldPats =
let idents =
let (|Last|) = List.last
fieldPats
|> List.map (fun (NamePatPairField (fieldName = SynLongIdent (id = Last fieldId))) -> fieldId)

let fieldPats =
fieldPats
|> List.map (fun (NamePatPairField(fieldName = fieldLid; pat = pat)) ->
match fieldLid.LongIdent with
| [id] -> ([], id), pat
| lid -> List.frontAndBack lid, pat)
|> List.map (fun (NamePatPairField(fieldName = fieldLid; pat = pat)) ->
let path, fieldId = List.frontAndBack fieldLid.LongIdent
fieldId, ExplicitOrSpread.Explicit (path, pat))

CheckRecdExprDuplicateFields idents
match BuildFieldMap cenv env false ty fieldPats m with
| None -> (fun _ -> TPat_error m), patEnv
| Some(tinst, tcref, fldsmap, _fldsList) ->
Expand All @@ -520,13 +525,14 @@ and TcRecordPat warnOnUpper (cenv: cenv) env vFlags patEnv ty fieldPats m =
let fieldPats, patEnvR =
(patEnv, ftys) ||> List.mapFold (fun s (ty, fsp) ->
match fldsmap.TryGetValue fsp.rfield_id.idText with
| true, v ->
| true, ExplicitOrSpread.Explicit v ->
let warnOnUpper =
if cenv.g.langVersion.SupportsFeature(LanguageFeature.DontWarnOnUppercaseIdentifiersInBindingPatterns) then
AllIdsOK
else
warnOnUpper
TcPat warnOnUpper cenv env None vFlags s ty v
| true, ExplicitOrSpread.Spread _ -> (* Unreachable. *) error (InternalError ("Spreads in patterns are not supported.", m))
| _ -> (fun _ -> TPat_wild m), s)

let phase2 values =
Expand Down
125 changes: 66 additions & 59 deletions src/Compiler/Checking/CheckRecordSyntaxHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module internal FSharp.Compiler.CheckRecordSyntaxHelpers

open System
open FSharp.Compiler.CheckBasics
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Features
Expand All @@ -14,47 +15,6 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.Xml
open FSharp.Compiler.SyntaxTrivia

/// Merges updates to nested record fields on the same level in record copy-and-update.
///
/// `TransformAstForNestedUpdates` expands `{ x with A.B = 10; A.C = "" }`
///
/// into
///
/// { x with
/// A = { x.A with B = 10 };
/// A = { x.A with C = "" }
/// }
///
/// which we here convert to
///
/// { x with A = { x.A with B = 10; C = "" } }
let GroupUpdatesToNestedFields (fields: ((Ident list * Ident) * SynExpr option) list) =
let rec groupIfNested res xs =
match xs with
| [] -> res
| [ x ] -> x :: res
| x :: y :: ys ->
match x, y with
| (lidwid, Some(SynExpr.Record(baseInfo, copyInfo, fields1, m))), (_, Some(SynExpr.Record(recordFields = fields2))) ->
let reducedRecd =
(lidwid, Some(SynExpr.Record(baseInfo, copyInfo, fields1 @ fields2, m)))

groupIfNested res (reducedRecd :: ys)
| (lidwid, Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1, m, trivia))), (_, Some(SynExpr.AnonRecd(recordFields = fields2))) ->
let reducedRecd =
(lidwid, Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1 @ fields2, m, trivia)))

groupIfNested res (reducedRecd :: ys)
| _ -> groupIfNested (x :: res) (y :: ys)

fields
|> List.groupBy (fun ((_, field), _) -> field.idText)
|> List.collect (fun (_, fields) ->
if fields.Length < 2 then
fields
else
groupIfNested [] fields)

/// Expands a long identifier into nested copy-and-update expressions.
///
/// `{ x with A.B = 0; A.C = "" }` becomes `{ x with A = { x.A with B = 0 }; A = { x.A with C = "" } }`
Expand Down Expand Up @@ -122,17 +82,27 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
| Item.AnonRecdField(
anonInfo = {
AnonRecdTypeInfo.TupInfo = TupInfo.Const isStruct
}) ->
let fields = [ LongIdentWithDots([ fieldId ], []), None, nestedField ]
}
range = m) ->
let fields =
[
SynExprAnonRecordFieldOrSpread.Field(
SynExprAnonRecordField(LongIdentWithDots([ fieldId ], []), None, nestedField, m),
None
)
]

SynExpr.AnonRecd(isStruct, copyInfo outerFieldId, fields, outerFieldId.idRange, { OpeningBraceRange = range0 })
| _ ->
let fields =
[
SynExprRecordField(
(LongIdentWithDots([ fieldId ], []), true),
None,
Some nestedField,
unionRanges fieldId.idRange nestedField.Range,
SynExprRecordFieldOrSpread.Field(
SynExprRecordField(
(LongIdentWithDots([ fieldId ], []), true),
None,
Some nestedField,
unionRanges fieldId.idRange nestedField.Range
),
None
)
]
Expand All @@ -149,30 +119,28 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid

match access, fields with
| _, [] -> failwith "unreachable"
| accessIds, [ (fieldId, _) ] -> (accessIds, fieldId), Some exprBeingAssigned
| accessIds, [ (fieldId, _) ] -> (accessIds, fieldId), exprBeingAssigned
| accessIds, (outerFieldId, item) :: rest ->
checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.NestedCopyAndUpdate (rangeOfLid lid)

CallNameResolutionSink cenv.tcSink (outerFieldId.idRange, env.NameEnv, item, [], ItemOccurrence.Use, env.AccessRights)

let outerFieldId = ident (outerFieldId.idText, outerFieldId.idRange.MakeSynthetic())

(accessIds, outerFieldId),
Some(synExprRecd (recdExprCopyInfo (fields |> List.map fst) withExpr) outerFieldId rest exprBeingAssigned)
(accessIds, outerFieldId), synExprRecd (recdExprCopyInfo (fields |> List.map fst) withExpr) outerFieldId rest exprBeingAssigned

/// This name is used when a complex expression is bound for use as a binding in a copy-and-update expression.
/// For example, in `{ f () with ... }`, `f ()` is replaced by `let bind@ = f ()`
let BindIdText = "bind@"

/// Finding the 'bind@' identifier is the only way to detect that an expression has already been bound.
let inline (|IsSimpleOrBoundExpr|_|) (withExprOpt: (SynExpr * BlockSeparator) option) =
match withExprOpt with
| None -> true
| Some(expr, _) ->
match expr with
| SynExpr.LongIdent(_, lIds, _, _) -> lIds.LongIdent |> List.exists (fun id -> id.idText = BindIdText)
| SynExpr.Ident _ -> true
| _ -> false
let inline (|IsSimpleOrBoundExpr|_|) (withExpr: SynExpr) =
match withExpr with
| SynExpr.LongIdent(_, lIds, _, _) ->
lIds.LongIdent
|> List.exists _.idText.StartsWith(BindIdText, StringComparison.Ordinal)
| SynExpr.Ident _ -> true
| _ -> false

/// When the original expression in copy-and-update is more complex than `{ x with ... }`, like `{ f () with ... }`,
/// we bind it first, so that it's not evaluated multiple times during a nested update
Expand Down Expand Up @@ -209,3 +177,42 @@ let BindOriginalRecdExpr (withExpr: SynExpr * BlockSeparator) mkRecdExpr =
Range = mOrigExprSynth
Trivia = SynLetOrUseTrivia.Zero
}

let mutable private bindId = 0

let private newBindId () =
System.Threading.Interlocked.Increment &bindId

let bindSrcIn (spreadSrcExpr: SynExpr) =
let mOrigExprSynth = spreadSrcExpr.Range.MakeSynthetic()
let id = mkSynId mOrigExprSynth $"%s{BindIdText}-%d{newBindId ()}"
let newSpreadSrcExpr = SynExpr.Ident id

let binding =
mkSynBinding
(PreXmlDoc.Empty, mkSynPatVar None id)
(None,
false,
false,
mOrigExprSynth,
DebugPointAtBinding.NoneAtSticky,
None,
spreadSrcExpr,
mOrigExprSynth,
[],
[],
None,
SynBindingTrivia.Zero)

fun mkBody ->
SynExpr.LetOrUse
{
IsRecursive = false
//isUse = false,
IsFromSource = false // compiler generated during desugaring
// isBang = false,
Bindings = [ binding ]
Body = mkBody newSpreadSrcExpr
Range = mOrigExprSynth
Trivia = SynLetOrUseTrivia.Zero
}
9 changes: 4 additions & 5 deletions src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@ open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree

val GroupUpdatesToNestedFields:
fields: ((Ident list * Ident) * SynExpr option) list -> ((Ident list * Ident) * SynExpr option) list

val TransformAstForNestedUpdates<'a> :
cenv: TcFileState ->
env: TcEnv ->
overallTy: TType ->
lid: LongIdent ->
exprBeingAssigned: SynExpr ->
withExpr: SynExpr * (range * 'a) ->
(Ident list * Ident) * SynExpr option
(Ident list * Ident) * SynExpr

val BindIdText: string

val inline (|IsSimpleOrBoundExpr|_|): withExprOpt: (SynExpr * BlockSeparator) option -> bool
val inline (|IsSimpleOrBoundExpr|_|): withExpr: SynExpr -> bool

val BindOriginalRecdExpr:
withExpr: SynExpr * BlockSeparator -> mkRecdExpr: ((SynExpr * BlockSeparator) option -> SynExpr) -> SynExpr

val bindSrcIn: spreadSrcExpr: SynExpr -> ((SynExpr -> SynExpr) -> SynExpr)
9 changes: 7 additions & 2 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ and SolveTyparEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalT
}

// Like SolveTyparEqualsType but asserts all typar equalities simultaneously instead of one by one
and SolveTyparsEqualTypes (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) tpTys tys =
and SolveTyparsEqualTypesAux (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) tpTys tys =
trackErrors {
do! Iterate2D (
fun tpTy ty ->
Expand Down Expand Up @@ -4340,7 +4340,7 @@ let CodegenWitnessesForTyparInst tcVal g amap m typars tyargs =
let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m (DisplayEnv.Empty g)
let ftps, _renaming, tinst = FreshenTypeInst g m typars
let traitInfos = GetTraitConstraintInfosOfTypars g ftps
let! _res = SolveTyparsEqualTypes csenv 0 m NoTrace tinst tyargs
let! _res = SolveTyparsEqualTypesAux csenv 0 m NoTrace tinst tyargs
return GenWitnessArgs amap g m traitInfos
}

Expand Down Expand Up @@ -4418,3 +4418,8 @@ let IsApplicableMethApprox g amap m (minfo: MethInfo) availObjTy =
| _ -> true
else
true

let SolveTyparsEqualTypes g (css: ConstraintSolverState) m (typars: TypeInst) (tys: TypeInst) =
let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m (DisplayEnv.Empty g)
SolveTyparsEqualTypesAux csenv 0 m NoTrace typars tys
|> CommitOperationResult
3 changes: 3 additions & 0 deletions src/Compiler/Checking/ConstraintSolver.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,6 @@ val ChooseTyparSolutionAndSolve: ConstraintSolverState -> DisplayEnv -> Typar ->
val IsApplicableMethApprox: TcGlobals -> ImportMap -> range -> MethInfo -> TType -> bool

val CanonicalizePartialInferenceProblem: ConstraintSolverState -> DisplayEnv -> range -> Typars -> unit

val SolveTyparsEqualTypes:
g: TcGlobals -> css: ConstraintSolverState -> m: range -> typars: TypeInst -> tys: TypeInst -> unit
Loading
Loading