Skip to content

Support CallerArgumentExpression #17519

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

Draft
wants to merge 93 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
9d3917a
Support CallerArgumentExpression
ijklam Jun 11, 2024
b698a3b
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Aug 11, 2024
dec3d3e
fix for fsi
ijklam Aug 11, 2024
e5d7763
revert unnecessary changes
ijklam Aug 11, 2024
5119bb6
Read and store file content before compilation
ijklam Aug 11, 2024
c914672
Change test; Add release note
ijklam Aug 11, 2024
5fe3495
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Aug 11, 2024
536cf34
Add tests
ijklam Aug 11, 2024
b270e16
Merge branch 'SupportCallerArgumentExpression' of https://github.com/…
ijklam Aug 11, 2024
3fc4642
fix build; format
ijklam Aug 11, 2024
550daa4
Support `#line`
ijklam Aug 15, 2024
5cecb20
format
ijklam Aug 15, 2024
1b6cd56
support callee side optional arg
ijklam Aug 16, 2024
08d958b
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Aug 16, 2024
68f2f61
format code
ijklam Aug 16, 2024
ec3b97f
Merge branch 'SupportCallerArgumentExpression' of https://github.com/…
ijklam Aug 16, 2024
7e93270
fix build
ijklam Aug 16, 2024
5790db9
fix test
ijklam Aug 16, 2024
46c4d46
try fix tests
ijklam Aug 25, 2024
8d792fa
fix build
ijklam Aug 25, 2024
65a4acc
test
ijklam Aug 25, 2024
1bdae47
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Sep 26, 2024
9e46116
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Feb 3, 2025
f379b7f
simplify code
ijklam Feb 3, 2025
843c71a
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Feb 3, 2025
6d1a30d
simplify code; fix test
ijklam Feb 4, 2025
5f499a5
fix range
ijklam Feb 4, 2025
470b993
fix tests
ijklam Feb 4, 2025
477ed6e
fix tests
ijklam Feb 4, 2025
6fb6548
fix
ijklam Feb 4, 2025
c8ca45b
test
ijklam Feb 4, 2025
b5f22fb
test
ijklam Feb 4, 2025
271985b
test
ijklam Feb 4, 2025
31e7bb2
fix tests
ijklam Feb 26, 2025
d47ee7e
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Feb 26, 2025
424b83c
update baselines
ijklam Feb 26, 2025
77697cd
fix tests
ijklam Feb 26, 2025
1e43eff
test
ijklam Feb 26, 2025
2d38b2a
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Mar 11, 2025
52a0fc0
fix named arguments didn't trigger the feature
ijklam Mar 11, 2025
24a9494
baseline
ijklam Mar 11, 2025
935af15
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Mar 11, 2025
31125f5
add new tests; improve err msg
ijklam Mar 13, 2025
49c46b1
adjust code; add and update tests
ijklam Mar 13, 2025
983f180
baseline
ijklam Mar 13, 2025
cf2085b
fix test
ijklam Mar 14, 2025
52a4d53
fmt
ijklam Mar 14, 2025
6e6dd39
Support for user defined CallerArgumentExpressionAttribute
ijklam Mar 15, 2025
9c5d917
replace the way get substring text
ijklam Mar 15, 2025
795c64a
test
ijklam Mar 15, 2025
98d8708
fix build
ijklam Mar 15, 2025
d68a6f7
fix test
ijklam Mar 15, 2025
9ac37e6
test
ijklam Mar 15, 2025
2cfba8d
Merge branch 'main' into SupportCallerArgumentExpression
psfinaki Mar 17, 2025
262ed24
revert the modify to `range`
ijklam Mar 17, 2025
9c24123
fix test and add new test
ijklam Mar 17, 2025
081a36b
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Mar 17, 2025
1999506
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Mar 20, 2025
2e7121d
add a new test
ijklam Mar 20, 2025
fb8a3d0
add test
ijklam Mar 20, 2025
70f5630
change the position to get code file content
ijklam Mar 21, 2025
1cc4395
refractor
ijklam Mar 21, 2025
3c1deb4
new test
ijklam Mar 21, 2025
a5a4b8b
refactor
ijklam Mar 25, 2025
5cba8ca
fmt
ijklam Mar 25, 2025
0b02f01
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Mar 25, 2025
1258816
baseline; fix test
ijklam Mar 25, 2025
95a4930
fix test
ijklam Mar 25, 2025
c3baed5
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Mar 25, 2025
5e1e27c
try fix cannot determine in C# method with non BCL attr
ijklam Mar 25, 2025
199d4b5
refactor
ijklam Mar 26, 2025
5e3e596
fix test
ijklam Mar 26, 2025
607f2d1
ilverify
ijklam Mar 26, 2025
1194359
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Mar 26, 2025
f792bfa
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Apr 14, 2025
5aff4b2
fix build
ijklam Apr 14, 2025
173d748
Change the approach to implement the feature
ijklam Apr 19, 2025
499bb33
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Apr 19, 2025
a215efa
change the way to pass the source text in
ijklam Apr 19, 2025
10b37d1
ilverify
ijklam Apr 19, 2025
f0da587
fmt
ijklam Apr 19, 2025
6d76a8e
make `cenv.Create(..., sourceText)` param not optional
ijklam Apr 22, 2025
be64605
Merge branch 'main' into SupportCallerArgumentExpression
ijklam Apr 28, 2025
874755a
fix no clear action; add OptionalArgument + Struct test
ijklam Apr 28, 2025
0ccbdf9
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam Apr 28, 2025
5005a74
revert clear action
ijklam Apr 28, 2025
ec351c5
add a big file test
ijklam Apr 29, 2025
5b51706
Merge branch 'main' into SupportCallerArgumentExpression
ijklam May 5, 2025
09d200e
Merge remote-tracking branch 'upstream/main' into SupportCallerArgume…
ijklam May 15, 2025
ca912b5
fix build; fantomas
ijklam May 15, 2025
bf3841e
make the feature compatible with #line
ijklam May 15, 2025
462fd40
ilverify
ijklam May 15, 2025
d703b20
disable fGetOriginalRange
ijklam May 15, 2025
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
Prev Previous commit
Next Next commit
refactor
  • Loading branch information
ijklam committed Mar 26, 2025
commit 199d4b5b76d625b052cf8e545284de30c45ea26f
18 changes: 9 additions & 9 deletions src/Compiler/Driver/CompilerConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1540,13 +1540,13 @@ type TcConfigProvider =

let GetFSharpCoreLibraryName () = getFSharpCoreLibraryName

/// Read and store the source file content for the `CallerArgumentExpression` feature
/// Read and store the source file content for the `CallerArgumentExpression` feature.
/// This should only be called in `fsc` or `fsi` processing.
let readAndStoreFileContents (tcConfig: TcConfig) (sourceFiles: #seq<string>) =
for fileName in sourceFiles do
if FSharpImplFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName) then
try
use fileStream = FileSystem.OpenFileForReadShim fileName
use reader = fileStream.GetReader(tcConfig.inputCodePage)
FileContent.update fileName (reader.ReadToEnd())
with _ ->
()
if
tcConfig.langVersion.SupportsFeature LanguageFeature.SupportCallerArgumentExpression
&& (tcConfig.compilationMode.IsOneOff || tcConfig.isInteractive)
then
for fileName in sourceFiles do
if FSharpImplFileSuffixes |> List.exists (FileSystemUtils.checkSuffix fileName) then
FileContent.update fileName (FileContent.FileCacheType.NotYetRead tcConfig.inputCodePage)
9 changes: 6 additions & 3 deletions src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3520,7 +3520,8 @@ type FsiStdinLexerProvider
| NonNull t ->
fsiStdinSyphon.Add(t + "\n")
// Update the stdin file content for the `CallerArgumentExpression` feature
FileContent.update stdinMockFileName fsiStdinSyphon.SyphonText)
FileContent.clear ()
FileContent.update stdinMockFileName (FileContent.FileCacheType.FromString fsiStdinSyphon.SyphonText))

match inputOption with
| Some Null
Expand Down Expand Up @@ -4299,7 +4300,8 @@ type FsiInteractionProcessor
let expr = ParseInteraction tokenizer

// Update the file content for the `CallerArgumentExpression` feature
FileContent.update scriptFileName sourceText
FileContent.clear ()
FileContent.update scriptFileName (FileContent.FileCacheType.FromString sourceText)

ExecuteParsedInteractionOnMainThread(ctok, diagnosticsLogger, expr, istate, cancellationToken))
|> commitResult
Expand Down Expand Up @@ -4336,7 +4338,8 @@ type FsiInteractionProcessor
)

// Update the file content for the `CallerArgumentExpression` feature
FileContent.update scriptFileName sourceText
FileContent.clear ()
FileContent.update scriptFileName (FileContent.FileCacheType.FromString sourceText)

ExecuteParsedExpressionOnMainThread(ctok, diagnosticsLogger, exprWithSeq, istate))
|> commitResult
Expand Down
130 changes: 58 additions & 72 deletions src/Compiler/Utilities/range.fs
Original file line number Diff line number Diff line change
Expand Up @@ -583,95 +583,81 @@ module Range =
mkRange file (mkPos 1 0) (mkPos 1 80)

module internal FileContent =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel uneasy with this module.

  • It adds a global resource for a rather local feature.
  • It creates another dependency on the file system.
  • It re-reads sources from the file system that have been read before.

Getting the source text out of the checking environment as proposed by @brianrourkeboll would be much cleaner.
A (probably too ambitious) alternative would be to make this a central place for source access, to give ISourceText the role that it was meant to have and to remove file system access elsewhere.

let private fileContentDict = ConcurrentDictionary<string, string>()
[<RequireQualifiedAccess>]
type FileCacheType =
| AlreadyRead of lines: string array
| NotYetRead of codePage: int option

let update (fileName: string) (fileContent: string) =
fileContentDict.AddOrUpdate(fileName, (fun _ -> fileContent), (fun _ _ -> fileContent))
|> ignore
static member FromString(s: string) = AlreadyRead(s.Split '\n')

let private seperators = [| '\r'; '\n' |]
let private fileContentDict = ConcurrentDictionary<string, FileCacheType>()

/// Find the index of the nearest line separator (\r or \r\n or \n) in the given string and offset.
let findLineEnd (input: string) lineStart =
if lineStart >= input.Length then
input.Length - 1
else
let idx = input.IndexOfAny(seperators, lineStart)
let update (fileName: string) (fileContent: FileCacheType) =
fileContentDict.AddOrUpdate(fileName, (fun _ -> fileContent), (fun _ _ -> fileContent))
|> ignore

if idx = -1 then
input.Length - 1
elif input.[idx] = '\r' && idx + 1 < input.Length && input.[idx + 1] = '\n' then
idx + 1
else
idx
let clear () = fileContentDict.Clear()

/// Get the substring of the given range.
/// This can retain the line seperators in the source string.
let substring (input: string) (range: range) =
let private substring (input: string array) (range: range) =
let startLine, startCol = range.StartLine, range.StartColumn
let endLine, endCol = range.EndLine, range.EndColumn

if
startLine < 1
|| (startLine = endLine && startCol > endCol)
(startCol < 0 || endCol < 0)
|| startLine < 1
|| startLine > endLine
|| startCol < 0
|| endCol < 0
|| startLine > input.Length
|| (startLine = endLine
&& (startCol > endCol || startCol >= input.[startLine - 1].Length))
then
System.String.Empty
elif startLine = endLine then
let line = input.[startLine - 1]
let endCol = min (endCol - 1) (line.Length - 1)
let result = line.Substring(startCol, endCol - startCol + 1)

if endCol = line.Length - 1 && line[endCol] = '\r' then
result + "\n"
else
result
else
// Here the loop was splited into two functions to avoid the non necessary allocation of the StringBuilder

/// Take text until reach the end line of the range.
let rec loopEnd (result: System.Text.StringBuilder) lineCount (startIndex, endIndex) =
if lineCount < endLine then
result.Append(input.Substring(startIndex, endIndex - startIndex + 1)) |> ignore

let nextLineEnd = findLineEnd input (endIndex + 1)

if nextLineEnd = endIndex then
result.ToString()
else
loopEnd result (lineCount + 1) (endIndex + 1, nextLineEnd)
elif lineCount = endLine then
let len = min endCol (endIndex - startIndex + 1)
result.Append(input.Substring(startIndex, len)).ToString()
else
result.ToString()

/// Go to the start line of the range.
let rec loopStart lineCount (startIndex, endIndex) =
if lineCount < startLine then
let nextLineEnd = findLineEnd input (endIndex + 1)

if nextLineEnd = endIndex then
System.String.Empty
else
loopStart (lineCount + 1) (endIndex + 1, nextLineEnd)

// reach the start line
elif lineCount = startLine && startLine = endLine then
if startIndex + startCol <= endIndex then
let endCol = min (endCol - 1) (endIndex - startIndex)
input.Substring(startIndex + startCol, endCol - startCol + 1)
else
System.String.Empty
else
let result = System.Text.StringBuilder()
let appendNewLineMark sb =
(sb: System.Text.StringBuilder).Append '\n' |> ignore

let startCol = min startCol (input.[startLine - 1].Length - 1)
let result = System.Text.StringBuilder()
result.Append(input.[startLine - 1].Substring(startCol)) |> ignore
appendNewLineMark result

if lineCount = startLine then
if startCol < endIndex - startIndex + 1 then
result.Append(input.Substring(startIndex + startCol, endIndex - startIndex + 1 - startCol))
|> ignore
let upperBound = min (endLine - 2) (input.Length - 1)

loopEnd result (lineCount + 1) (endIndex + 1, findLineEnd input (endIndex + 1))
else
// Should not go into here
loopEnd result lineCount (startIndex, endIndex)
for i in startLine..upperBound do
result.Append(input.[i]) |> ignore
appendNewLineMark result

loopStart 1 (0, findLineEnd input 0)
if endLine < input.Length then
let line = input.[endLine - 1]
let endCol = min endCol line.Length
result.Append(line.Substring(0, endCol)) |> ignore

if endCol = line.Length - 1 && line[endCol] = '\r' then
appendNewLineMark result

result.ToString()

let getCodeText (m: range) =
match fileContentDict.TryGetValue m.FileName with
| true, text -> substring text m
let fileName = m.FileName

match fileContentDict.TryGetValue fileName with
| true, FileCacheType.AlreadyRead lines -> substring lines m
| true, FileCacheType.NotYetRead codePage ->
try
use fileStream = FileSystem.OpenFileForReadShim fileName
use reader = fileStream.GetReader(codePage)
let lines = reader.ReadToEnd().Split('\n')
update fileName (FileCacheType.AlreadyRead lines)
substring lines m
with _ ->
String.Empty
| _ -> String.Empty
16 changes: 11 additions & 5 deletions src/Compiler/Utilities/range.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,19 @@ module Line =

/// Store code file content. Used to implement `CallerArgumentExpression`
module internal FileContent =
[<RequireQualifiedAccess>]
type FileCacheType =
| AlreadyRead of lines: string array
| NotYetRead of codePage: int option

static member FromString: s: string -> FileCacheType

/// Update the file content. Should be called before the code file/expression is typechecked.
val update: fileName: string -> fileContent: string -> unit
val update: fileName: string -> fileContent: FileCacheType -> unit

/// Clean up all the file contents.
/// May be called in `fsi` processing, to avoid holding the source files which were compiled.
val clear: unit -> unit

/// Get the substring of the given range.
/// This can retain the line seperators in the source string.
val substring: input: string -> range: range -> string

/// Get the code text of the specific `range`
val getCodeText: range -> string
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,16 @@ open FSharp.Test.Compiler
open FSharp.Test

module CustomAttributes_CallerArgumentExpression =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider a de-generate scenario of a huge file being a single expression (e.g. a massive collection builder, or a massive async block) passed into a single CallerArgumentExpressionAttribute member?


let scriptPath = __SOURCE_DIRECTORY__ ++ "test script.fsx"

[<FactForNETCOREAPP>]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason for all added tests being FactForNETCOREAPP and not Fact that would also test Desktop framework?

let ``Can consume CallerArgumentExpression in BCL methods`` () =
FSharp """let assertEqual a b = if a <> b then failwithf "not equal: %A and %A" a b
try System.ArgumentException.ThrowIfNullOrWhiteSpace(Seq.init 50 (fun _ -> " ")
(* comment *)
|> String.concat " ")
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter 'Seq.init 50 (fun _ -> \" \")
(* comment *)
|> String.concat \" \""))


try System.ArgumentException.ThrowIfNullOrWhiteSpace(argument = (Seq.init 11 (fun _ -> " ")
(* comment *)
|> String.concat " "))
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter '(Seq.init 11 (fun _ -> \" \")
(* comment *)
|> String.concat \" \")"))
"""
|> withLangVersionPreview
|> asExe
|> compileAndRun
|> shouldSucceed
|> ignore
FsFromPath scriptPath
|> withLangVersionPreview
|> asExe
|> compileAndRun
|> shouldSucceed
|> ignore

[<FactForNETCOREAPP>]
let ``Can define methods using CallerArgumentExpression with C#-style optional arguments`` () =
Expand Down Expand Up @@ -377,28 +361,22 @@ AInCs.B (123 - 7) |> assertEqual "123 - 7"
|> compileAndRun
|> shouldSucceed
|> ignore


(* ------------ FSI tests ------------- *)

[<FactForNETCOREAPP>]
let ``Check in fsi`` () =
Fsx """let assertEqual a b = if a <> b then failwithf "not equal: %A and %A" a b
try System.ArgumentException.ThrowIfNullOrWhiteSpace(Seq.init 50 (fun _ -> " ")
(* comment *)
|> String.concat " ")
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter 'Seq.init 50 (fun _ -> \" \")
(* comment *)
|> String.concat \" \""))

FsxFromPath scriptPath
|> withLangVersionPreview
|> runFsi
|> shouldSucceed
|> ignore

try System.ArgumentException.ThrowIfNullOrWhiteSpace(argument = (Seq.init 11 (fun _ -> " ")
(* comment *)
|> String.concat " "))
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter '(Seq.init 11 (fun _ -> \" \")
(* comment *)
|> String.concat \" \")"))
"""
|> withLangVersionPreview
|> runFsi
|> shouldSucceed
|> ignore

[<FactForNETCOREAPP>]
let ``Check fsi #load`` () =
Fsx $"""#load @"{scriptPath}" """
|> withLangVersionPreview
|> runFsi
|> shouldSucceed
|> ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
let assertEqual a b = if a <> b then failwithf "not equal: %A and %A" a b
try System.ArgumentException.ThrowIfNullOrWhiteSpace(Seq.init 50 (fun _ -> " ")
(* comment *)
|> String.concat " ")
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter 'Seq.init 50 (fun _ -> \" \")
(* comment *)
|> String.concat \" \""))


try System.ArgumentException.ThrowIfNullOrWhiteSpace(argument = (Seq.init 11 (fun _ -> " ")
(* comment *)
|> String.concat " "))
with :? System.ArgumentException as ex ->
assertEqual true (ex.Message.Contains("(Parameter '(Seq.init 11 (fun _ -> \" \")
(* comment *)
|> String.concat \" \")"))
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<Compile Include="Conformance\BasicGrammarElements\AccessibilityAnnotations\PermittedLocations\PermittedLocations.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeInheritance\AttributeInheritance.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeUsage\AttributeUsage.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\CallerArgumentExpression.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\CallerArgumentExpression\CallerArgumentExpression.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\Basic\Basic.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ImportedAttributes\ImportedAttributes.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ArgumentsOfAllTypes\ArgumentsOfAllTypes.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
<Compile Include="XmlDocTests.fs" />
<Compile Include="XmlDocTests - Units of Measure.fs" />
<Compile Include="RangeTests.fs" />
<Compile Include="FileContentTests.fs" />
<Compile Include="TooltipTests.fs" />
<Compile Include="TokenizerTests.fs" />
<Compile Include="CompilerTestHelpers.fs" />
Expand Down
30 changes: 0 additions & 30 deletions tests/FSharp.Compiler.Service.Tests/FileContentTests.fs

This file was deleted.

Loading