@@ -7560,17 +7560,17 @@ and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: strin
75607560 mkString g m fmtString, tpenv
75617561 )
75627562
7563- /// Lower a string-typed interpolated string to a reflection-free System.String.Concat of its parts.
7564- /// 'holeIsString' flags, in order, the fill expressions that are already of type string.
7565- and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv, m: range, tpenv: UnscopedTyparEnv, parts: SynInterpolatedStringPart list, holeIsString: bool list) =
7563+ /// Lower a string-typed interpolated string to a reflection-free System.String.Concat of its parts,
7564+ /// type-checking each part in place. A literal becomes its text; a plain '{x}' hole is built straight in
7565+ /// the typed tree (passed through if already a string, else converted with the 'string' operator); an
7566+ /// aligned/formatted or printf hole is checked from a small synthesized 'String.Format'/'sprintf' so name
7567+ /// resolution applies.
7568+ and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv, m: range, tpenv: UnscopedTyparEnv, parts: SynInterpolatedStringPart list) =
7569+ let g = cenv.g
75667570 let mSynth = m.MakeSynthetic()
75677571 let strLit (s: string) = SynExpr.Const(SynConst.String(s, SynStringKind.Regular, mSynth), mSynth)
75687572 let paren (e: SynExpr) = SynExpr.Paren(e, range0, None, mSynth)
75697573
7570- // '(string e)': convert any value to a string using invariant culture.
7571- let stringOp (e: SynExpr) =
7572- mkSynApp1 (mkSynLidGet mSynth [ "Microsoft"; "FSharp"; "Core"; "Operators" ] "string") (paren e) mSynth
7573-
75747574 // '(sprintf spec e : string)': format a printf-specifier hole (still reflection-based).
75757575 let sprintfOp (spec: string, e: SynExpr) =
75767576 let f = mkSynApp1 (mkSynLidGet mSynth [ "Microsoft"; "FSharp"; "Core"; "ExtraTopLevelOperators" ] "sprintf") (strLit spec) mSynth
@@ -7586,38 +7586,47 @@ and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv,
75867586 let args = paren (SynExpr.Tuple(false, [ invariant; strLit netFormat; e ], [ range0; range0 ], mSynth))
75877587 mkSynApp1 (mkSynLidGet mSynth [ "System"; "String" ] "Format") args mSynth
75887588
7589- // Build one string expression per part, consuming one 'holeIsString' flag per fill expression.
7590- let rec build (acc: SynExpr list, parts: SynInterpolatedStringPart list, holeIsString: bool list) =
7589+ // Type-check one hole and convert it to a typed string expression. A '%spec' hole is constrained by
7590+ // its specifier (a function there is a type error), so it goes straight to 'sprintf'. A plain or
7591+ // aligned/formatted hole is checked here first, so its value can be warned about if it is a function.
7592+ let convertHole (synFill: SynExpr, formatting: SynInterpolationFormatting, tpenv: UnscopedTyparEnv) =
7593+ match formatting with
7594+ | SynInterpolationFormatting.Printf (spec, _) -> TcExpr cenv (MustEqual g.string_ty) env tpenv (sprintfOp (spec, synFill))
7595+ | SynInterpolationFormatting.DotNet (alignment, format) ->
7596+ let fill, tpenv = TcExprFlex2 cenv (NewInferenceType g) env false tpenv synFill
7597+ let fillTy = tyOfExpr g fill
7598+ if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg && (isFunTy g fillTy || isDelegateTy g fillTy) then
7599+ warning (Error(FSComp.SR.tcFunctionValueUsedAsInterpolatedStringArg (), synFill.Range))
7600+ match alignment, format with
7601+ // A plain hole reuses the checked expression; an aligned or formatted hole goes via String.Format.
7602+ | None, None -> (if isStringTy g fillTy then fill else mkCallStringOperator g m fillTy fill), tpenv
7603+ | _ -> TcExpr cenv (MustEqual g.string_ty) env tpenv (stringFormatOp (alignment, format, synFill))
7604+
7605+ // Build one string expression per part, type-checking each hole in place.
7606+ let rec build (parts: SynInterpolatedStringPart list, tpenv: UnscopedTyparEnv) =
75917607 match parts with
7592- | [] -> List.rev acc
7593- | SynInterpolatedStringPart.String ("", _) :: rest -> build (acc, rest, holeIsString)
7594- | SynInterpolatedStringPart.String (s, _) :: rest -> build (strLit (s.Replace("%%", "%")) :: acc, rest, holeIsString)
7595- | SynInterpolatedStringPart.FillExpr (e, formatting) :: rest ->
7596- let isStr, rest' = match holeIsString with b :: bs -> b, bs | [] -> false, []
7597- let argExpr =
7598- match formatting with
7599- // A string hole is already a string (Concat maps null to ""); convert anything else.
7600- | SynInterpolationFormatting.DotNet (None, None) -> if isStr then e else stringOp e
7601- | SynInterpolationFormatting.DotNet (alignment, format) -> stringFormatOp (alignment, format, e)
7602- | SynInterpolationFormatting.Printf (spec, _) -> sprintfOp (spec, e)
7603- build (argExpr :: acc, rest, rest')
7604-
7605- let argExprs = build ([], parts, holeIsString)
7606-
7607- let concatLid = mkSynLidGet mSynth [ "System"; "String" ] "Concat"
7608+ | [] -> [], tpenv
7609+ | SynInterpolatedStringPart.String ("", _) :: rest -> build (rest, tpenv)
7610+ | SynInterpolatedStringPart.String (s, _) :: rest ->
7611+ let args, tpenv = build (rest, tpenv)
7612+ mkString g m (s.Replace("%%", "%")) :: args, tpenv
7613+ | SynInterpolatedStringPart.FillExpr (synFill, formatting) :: rest ->
7614+ let argExpr, tpenv = convertHole (synFill, formatting, tpenv)
7615+ let args, tpenv = build (rest, tpenv)
7616+ argExpr :: args, tpenv
7617+
7618+ let argExprs, tpenv = build (parts, tpenv)
76087619
76097620 let resultExpr =
76107621 match argExprs with
7611- | [] -> strLit ""
7622+ | [] -> mkString g m ""
76127623 | [ single ] -> single
7613- | _ when List.length argExprs <= 4 ->
7614- let commas = List.replicate (List.length argExprs - 1) range0
7615- mkSynApp1 concatLid (paren (SynExpr.Tuple(false, argExprs, commas, mSynth))) mSynth
7616- | _ ->
7617- mkSynApp1 concatLid (paren (SynExpr.ArrayOrList(true, argExprs, mSynth))) mSynth
7624+ | [ a; b ] -> mkStaticCall_String_Concat2 g m a b
7625+ | [ a; b; c ] -> mkStaticCall_String_Concat3 g m a b c
7626+ | [ a; b; c; d ] -> mkStaticCall_String_Concat4 g m a b c d
7627+ | args -> mkStaticCall_String_Concat_Array g m (mkArray (g.string_ty, args, m))
76187628
7619- TcPropagatingExprLeafThenConvert cenv overallTy cenv.g.string_ty env m (fun () ->
7620- TcExpr cenv (MustEqual cenv.g.string_ty) env tpenv resultExpr)
7629+ TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> resultExpr, tpenv)
76217630
76227631/// Check an interpolated string expression
76237632and [<TailCall>] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs =
@@ -7772,30 +7781,28 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
77727781 else
77737782 let str = mkString g m printfFormatString
77747783 mkCallNewFormat g m printerTy printerArgTy printerResidueTy printerResultTy printerTupleTy str, tpenv
7784+ elif isString then
7785+ // String-typed interpolation: lower to a reflection-free System.String.Concat of the parts,
7786+ // type-checking each hole in place (no separate batch, no flat fill-expression list).
7787+ TcInterpolatedStringViaConcat (cenv, overallTy, env, m, tpenv, parts)
77757788 else
7789+ // $"...{x}..." used as a PrintfFormat value: build a PrintfFormat that captures the args.
77767790 let fillExprs, tpenv = TcExprsNoFlexes cenv env m tpenv argTys synFillExprs
77777791
77787792 if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg then
77797793 warnForFunctionValuesInFillExprs g argTys synFillExprs
77807794
7781- if isString then
7782- // String-typed interpolation: lower to a reflection-free System.String.Concat of the parts.
7783- // A hole whose value is already a string is passed straight through.
7784- let holeIsString = fillExprs |> List.map (fun fillExpr -> isStringTy g (tyOfExpr g fillExpr))
7785- TcInterpolatedStringViaConcat (cenv, overallTy, env, m, tpenv, parts, holeIsString)
7786- else
7787- // $"...{x}..." used as a PrintfFormat value: build a PrintfFormat that captures the args.
7788- let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m)
7795+ let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m)
77897796
7790- let argsExpr = mkArray (g.obj_ty_withNulls, fillExprsBoxed, m)
7791- let percentATysExpr =
7792- if percentATys.Length = 0 then
7793- mkNull m (mkArrayType g g.system_Type_ty)
7794- else
7795- let tyExprs = percentATys |> Array.map (mkCallTypeOf g m) |> Array.toList
7796- mkArray (g.system_Type_ty, tyExprs, m)
7797+ let argsExpr = mkArray (g.obj_ty_withNulls, fillExprsBoxed, m)
7798+ let percentATysExpr =
7799+ if percentATys.Length = 0 then
7800+ mkNull m (mkArrayType g g.system_Type_ty)
7801+ else
7802+ let tyExprs = percentATys |> Array.map (mkCallTypeOf g m) |> Array.toList
7803+ mkArray (g.system_Type_ty, tyExprs, m)
77977804
7798- MakeMethInfoCall cenv.amap m newFormatMethod [] [mkString g m printfFormatString; argsExpr; percentATysExpr] None, tpenv
7805+ MakeMethInfoCall cenv.amap m newFormatMethod [] [mkString g m printfFormatString; argsExpr; percentATysExpr] None, tpenv
77997806
78007807 // The case for $"..." used as type FormattableString or IFormattable
78017808 | Choice2Of2 createFormattableStringMethod ->
0 commit comments