Skip to content

Commit 99bd001

Browse files
abonievzarytovskii
andauthored
Do not throw from IsUnionCaseTester and handle a case where symbol is not considered a method/property (#17634)
* WIP some unit tests * Return false instead of throwing * Fix IsUnionCaseTester for generated code Seems like methods/properties that are generated in IL, like `get_Is*` in this case, have `IsMethod` (or `IsProperty`) false for some reason, even when `IsPropertyGetterMethod` is true. This would result in `IsUnionCaseTester` giving incorrect answers. This fixes that at `IsUnionCaseTester`, though it might be worth it to see if it can be fixed at the root of the issue * Add a release note * Move helpers around in unit tests for more reuse * Add negative unit test --------- Co-authored-by: Adam Boniecki <abonie@users.noreply.github.com> Co-authored-by: Vlad Zarytovskii <vzaritovsky@hotmail.com>
1 parent 11561af commit 99bd001

File tree

7 files changed

+235
-79
lines changed

7 files changed

+235
-79
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* Fixed checking failure when `global` namespace is involved with enabled GraphBasedChecking ([PR #17553](https://github.com/dotnet/fsharp/pull/17553))
1515
* Add missing byte chars notations, enforce limits in decimal notation in byte char & string (Issues [#15867](https://github.com/dotnet/fsharp/issues/15867), [#15868](https://github.com/dotnet/fsharp/issues/15868), [#15869](https://github.com/dotnet/fsharp/issues/15869), [PR #15898](https://github.com/dotnet/fsharp/pull/15898))
1616
* Parentheses analysis: keep extra parentheses around unit & tuples in method definitions. ([PR #17618](https://github.com/dotnet/fsharp/pull/17618))
17+
* Fix IsUnionCaseTester throwing for non-methods/properties [#17301](https://github.com/dotnet/fsharp/pull/17634)
1718
* Consider `open type` used when the type is an enum and any of the enum cases is used unqualified. ([PR #17628](https://github.com/dotnet/fsharp/pull/17628))
1819

1920
### Added

src/Compiler/Symbols/Symbols.fs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,11 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) =
17881788
match d with
17891789
| P p -> p.IsUnionCaseTester
17901790
| M m -> m.IsUnionCaseTester
1791-
| E _ | C _ | V _ -> invalidOp "the value or member is not a property"
1791+
| V v ->
1792+
v.IsPropertyGetterMethod &&
1793+
v.LogicalName.StartsWith("get_Is") &&
1794+
v.IsImplied && v.MemberApparentEntity.IsUnionTycon
1795+
| E _ | C _ -> false
17921796

17931797
member _.EventAddMethod =
17941798
checkIsResolved()

tests/FSharp.Compiler.ComponentTests/Signatures/TypeTests.fs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,50 @@ type Foo =
263263
member Bar: a: int -> int with get
264264
265265
member Bar: a: int -> int with set"""
266+
267+
[<Fact>]
268+
let ``get_Is* method has IsUnionCaseTester = true`` () =
269+
FSharp """
270+
module Lib
271+
272+
type Foo =
273+
| Bar of int
274+
| Baz of string
275+
member this.IsP
276+
with get () = 42
277+
278+
let bar = Bar 5
279+
280+
let f = bar.get_IsBar
281+
"""
282+
|> withLangVersionPreview
283+
|> typecheckResults
284+
|> fun results ->
285+
let isBarSymbolUse = results.GetSymbolUseAtLocation(12, 21, "let f = bar.get_IsBar", [ "get_IsBar" ]).Value
286+
match isBarSymbolUse.Symbol with
287+
| :? FSharpMemberOrFunctionOrValue as mfv ->
288+
Assert.True(mfv.IsUnionCaseTester, "IsUnionCaseTester returned true")
289+
Assert.True(mfv.IsMethod, "IsMethod returned true")
290+
Assert.False(mfv.IsProperty, "IsProptery returned true")
291+
Assert.True(mfv.IsPropertyGetterMethod, "IsPropertyGetterMethod returned false")
292+
| _ -> failwith "Expected FSharpMemberOrFunctionOrValue"
293+
294+
[<Fact>]
295+
let ``IsUnionCaseTester does not throw for non-method non-property`` () =
296+
FSharp """
297+
module Lib
298+
299+
type Foo() =
300+
member _.Bar x = x
301+
302+
let foo = Foo()
303+
"""
304+
|> withLangVersionPreview
305+
|> typecheckResults
306+
|> fun results ->
307+
let isBarSymbolUse = results.GetSymbolUseAtLocation(7, 13, "let foo = Foo()", [ "Foo" ]).Value
308+
match isBarSymbolUse.Symbol with
309+
| :? FSharpMemberOrFunctionOrValue as mfv ->
310+
Assert.False(mfv.IsUnionCaseTester, "IsUnionCaseTester returned true")
311+
Assert.True(mfv.IsConstructor)
312+
| _ -> failwith "Expected FSharpMemberOrFunctionOrValue"

tests/FSharp.Compiler.Service.Tests/Common.fs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ open System
55
open System.Diagnostics
66
open System.IO
77
open System.Collections.Generic
8+
open System.Threading
89
open System.Threading.Tasks
910
open FSharp.Compiler.CodeAnalysis
1011
open FSharp.Compiler.IO
11-
open FSharp.Compiler.Diagnostics
1212
open FSharp.Compiler.Symbols
1313
open FSharp.Compiler.Syntax
1414
open FSharp.Compiler.Text
@@ -473,3 +473,63 @@ let assertRange
473473
Assert.Equal(Position.mkPos expectedStartLine expectedStartColumn, actualRange.Start)
474474
Assert.Equal(Position.mkPos expectedEndLine expectedEndColumn, actualRange.End)
475475

476+
[<AutoOpen>]
477+
module TempDirUtils =
478+
let getTempPath dir =
479+
Path.Combine(Path.GetTempPath(), dir)
480+
481+
/// Returns the file name part of a temp file name created with tryCreateTemporaryFileName ()
482+
/// and an added process id and thread id to ensure uniqueness between threads.
483+
let getTempFileName() =
484+
let tempFileName = tryCreateTemporaryFileName ()
485+
try
486+
let tempFile, tempExt = Path.GetFileNameWithoutExtension tempFileName, Path.GetExtension tempFileName
487+
let procId, threadId = Process.GetCurrentProcess().Id, Thread.CurrentThread.ManagedThreadId
488+
String.concat "" [tempFile; "_"; string procId; "_"; string threadId; tempExt] // ext includes dot
489+
finally
490+
try
491+
FileSystem.FileDeleteShim tempFileName
492+
with _ -> ()
493+
494+
/// Given just a file name, returns it with changed extension located in %TEMP%\ExprTests
495+
let getTempFilePathChangeExt dir tmp ext =
496+
Path.Combine(getTempPath dir, Path.ChangeExtension(tmp, ext))
497+
498+
/// If it doesn't exists, create a folder 'ExprTests' in local user's %TEMP% folder
499+
let createTempDir dirName =
500+
let tempPath = getTempPath dirName
501+
do
502+
if Directory.Exists tempPath then ()
503+
else Directory.CreateDirectory tempPath |> ignore
504+
505+
/// Clean up after a test is run. If you need to inspect the create *.fs files, change this function to do nothing, or just break here.
506+
let cleanupTempFiles dirName files =
507+
{ new IDisposable with
508+
member _.Dispose() =
509+
for fileName in files do
510+
try
511+
// cleanup: only the source file is written to the temp dir.
512+
FileSystem.FileDeleteShim fileName
513+
with _ -> ()
514+
515+
try
516+
// remove the dir when empty
517+
let tempPath = getTempPath dirName
518+
if Directory.GetFiles tempPath |> Array.isEmpty then
519+
Directory.Delete tempPath
520+
with _ -> () }
521+
522+
let createProjectOptions dirName fileSources extraArgs =
523+
let fileNames = fileSources |> List.map (fun _ -> getTempFileName())
524+
let temp2 = getTempFileName()
525+
let fileNames = fileNames |> List.map (fun temp1 -> getTempFilePathChangeExt dirName temp1 ".fs")
526+
let dllName = getTempFilePathChangeExt dirName temp2 ".dll"
527+
let projFileName = getTempFilePathChangeExt dirName temp2 ".fsproj"
528+
529+
createTempDir dirName
530+
for fileSource: string, fileName in List.zip fileSources fileNames do
531+
FileSystem.OpenFileForWriteShim(fileName).Write(fileSource)
532+
let args = [| yield! extraArgs; yield! mkProjectCommandLineArgs (dllName, []) |]
533+
let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames |> List.toArray }
534+
535+
cleanupTempFiles dirName (fileNames @ [dllName; projFileName]), options

tests/FSharp.Compiler.Service.Tests/ExprTests.fs

Lines changed: 16 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@
33
open Xunit
44
open FsUnit
55
open System
6-
open System.IO
76
open System.Text
87
open System.Collections.Generic
9-
open System.Diagnostics
10-
open System.Threading
118
open FSharp.Compiler.CodeAnalysis
129
open FSharp.Compiler.Diagnostics
1310
open FSharp.Compiler.IO
1411
open FSharp.Compiler.Service.Tests.Common
1512
open FSharp.Compiler.Symbols
1613
open FSharp.Compiler.Symbols.FSharpExprPatterns
17-
open TestFramework
1814

1915
type FSharpCore =
2016
| FC45
@@ -29,52 +25,11 @@ type FSharpCore =
2925
| FC47 -> "FSharp.Core 4.7"
3026
| FC50 -> "FSharp.Core 5.0"
3127

28+
[<Literal>]
29+
let dirName = "ExprTests"
3230

3331
[<AutoOpen>]
3432
module internal Utils =
35-
let getTempPath() =
36-
Path.Combine(Path.GetTempPath(), "ExprTests")
37-
38-
/// If it doesn't exists, create a folder 'ExprTests' in local user's %TEMP% folder
39-
let createTempDir() =
40-
let tempPath = getTempPath()
41-
do
42-
if Directory.Exists tempPath then ()
43-
else Directory.CreateDirectory tempPath |> ignore
44-
45-
/// Returns the file name part of a temp file name created with tryCreateTemporaryFileName ()
46-
/// and an added process id and thread id to ensure uniqueness between threads.
47-
let getTempFileName() =
48-
let tempFileName = tryCreateTemporaryFileName ()
49-
try
50-
let tempFile, tempExt = Path.GetFileNameWithoutExtension tempFileName, Path.GetExtension tempFileName
51-
let procId, threadId = Process.GetCurrentProcess().Id, Thread.CurrentThread.ManagedThreadId
52-
String.concat "" [tempFile; "_"; string procId; "_"; string threadId; tempExt] // ext includes dot
53-
finally
54-
try
55-
FileSystem.FileDeleteShim tempFileName
56-
with _ -> ()
57-
58-
/// Clean up after a test is run. If you need to inspect the create *.fs files, change this function to do nothing, or just break here.
59-
let cleanupTempFiles files =
60-
{ new IDisposable with
61-
member _.Dispose() =
62-
for fileName in files do
63-
try
64-
// cleanup: only the source file is written to the temp dir.
65-
FileSystem.FileDeleteShim fileName
66-
with _ -> ()
67-
68-
try
69-
// remove the dir when empty
70-
let tempPath = getTempPath()
71-
if Directory.GetFiles tempPath |> Array.isEmpty then
72-
Directory.Delete tempPath
73-
with _ -> () }
74-
75-
/// Given just a file name, returns it with changed extension located in %TEMP%\ExprTests
76-
let getTempFilePathChangeExt tmp ext =
77-
Path.Combine(getTempPath(), Path.ChangeExtension(tmp, ext))
7833

7934
// This behaves slightly differently on Mono versions, 'null' is printed sometimes, 'None' other times
8035
// Presumably this is very small differences in Mono reflection causing F# printing to change behaviour
@@ -345,22 +300,6 @@ module internal Utils =
345300
yield! collectMembers e |> Seq.map printMemberSignature
346301
}
347302

348-
349-
let createOptionsAux fileSources extraArgs =
350-
let fileNames = fileSources |> List.map (fun _ -> Utils.getTempFileName())
351-
let temp2 = Utils.getTempFileName()
352-
let fileNames = fileNames |> List.map (fun temp1 -> Utils.getTempFilePathChangeExt temp1 ".fs")
353-
let dllName = Utils.getTempFilePathChangeExt temp2 ".dll"
354-
let projFileName = Utils.getTempFilePathChangeExt temp2 ".fsproj"
355-
356-
Utils.createTempDir()
357-
for fileSource: string, fileName in List.zip fileSources fileNames do
358-
FileSystem.OpenFileForWriteShim(fileName).Write(fileSource)
359-
let args = [| yield! extraArgs; yield! mkProjectCommandLineArgs (dllName, []) |]
360-
let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames |> List.toArray }
361-
362-
Utils.cleanupTempFiles (fileNames @ [dllName; projFileName]), options
363-
364303
//---------------------------------------------------------------------------------------------------------
365304
// This project is a smoke test for a whole range of standard and obscure expressions
366305

@@ -653,7 +592,7 @@ let testMutableVar = mutableVar 1
653592
let testMutableConst = mutableConst ()
654593
"""
655594

656-
let createOptionsWithArgs args = createOptionsAux [ fileSource1; fileSource2 ] args
595+
let createOptionsWithArgs args = createProjectOptions dirName [ fileSource1; fileSource2 ] args
657596

658597
let createOptions() = createOptionsWithArgs []
659598

@@ -1002,15 +941,15 @@ let ``Test Optimized Declarations Project1`` useTransparentCompiler =
1002941

1003942
let testOperators dnName fsName excludedTests expectedUnoptimized expectedOptimized =
1004943

1005-
let tempFileName = Utils.getTempFileName()
1006-
let filePath = Utils.getTempFilePathChangeExt tempFileName ".fs"
1007-
let dllPath =Utils.getTempFilePathChangeExt tempFileName ".dll"
1008-
let projFilePath = Utils.getTempFilePathChangeExt tempFileName ".fsproj"
944+
let tempFileName = getTempFileName()
945+
let filePath = getTempFilePathChangeExt dirName tempFileName ".fs"
946+
let dllPath =getTempFilePathChangeExt dirName tempFileName ".dll"
947+
let projFilePath = getTempFilePathChangeExt dirName tempFileName ".fsproj"
1009948
let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=true)
1010949

1011950
begin
1012-
use _cleanup = Utils.cleanupTempFiles [filePath; dllPath; projFilePath]
1013-
createTempDir()
951+
use _cleanup = cleanupTempFiles dirName [filePath; dllPath; projFilePath]
952+
createTempDir dirName
1014953
let source = String.Format(Project1.operatorTests, dnName, fsName)
1015954
let replace (s:string) r = s.Replace("let " + r, "// let " + r)
1016955
let fileSource = excludedTests |> List.fold replace source
@@ -3192,7 +3131,7 @@ let BigSequenceExpression(outFileOpt,docFileOpt,baseAddressOpt) =
31923131
"""
31933132

31943133

3195-
let createOptions() = createOptionsAux [fileSource1] []
3134+
let createOptions() = createProjectOptions dirName [fileSource1] []
31963135

31973136
#if !NETFRAMEWORK && DEBUG
31983137
[<Fact(Skip = "Test is known to fail in DEBUG when not using NetFramework. Use RELEASE configuration or NetFramework to run it.")>]
@@ -3279,7 +3218,7 @@ let f7() = callXY (C()) (D())
32793218
let f8() = callXY (D()) (C())
32803219
"""
32813220

3282-
let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"]
3221+
let createOptions() = createProjectOptions dirName [fileSource1] ["--langversion:7.0"]
32833222

32843223
[<Theory>]
32853224
[<InlineData(false)>]
@@ -3407,7 +3346,7 @@ type MyNumberWrapper =
34073346
{ MyNumber: MyNumber }
34083347
"""
34093348

3410-
let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"]
3349+
let createOptions() = createProjectOptions dirName [fileSource1] ["--langversion:7.0"]
34113350

34123351
[<Theory>]
34133352
[<InlineData(false)>]
@@ -3465,13 +3404,13 @@ let s2 = sign p1
34653404
34663405
"""
34673406

3468-
let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"]
3407+
let createOptions() = createProjectOptions dirName [fileSource1] ["--langversion:7.0"]
34693408

34703409
[<Theory>]
34713410
[<InlineData(false)>]
34723411
[<InlineData(true)>]
34733412
let ``Test ProjectForWitnesses3`` useTransparentCompiler =
3474-
let cleanup, options = createOptionsAux [ ProjectForWitnesses3.fileSource1 ] ["--langversion:7.0"]
3413+
let cleanup, options = createProjectOptions dirName [ ProjectForWitnesses3.fileSource1 ] ["--langversion:7.0"]
34753414
use _holder = cleanup
34763415
let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler)
34773416
let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate
@@ -3563,7 +3502,7 @@ let isNullQuoted (ts : 't[]) =
35633502
35643503
"""
35653504

3566-
let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"]
3505+
let createOptions() = createProjectOptions dirName [fileSource1] ["--langversion:7.0"]
35673506

35683507
[<Theory>]
35693508
[<InlineData(false)>]
@@ -3603,7 +3542,7 @@ module N.M
36033542
let rec f = new System.EventHandler(fun _ _ -> f.Invoke(null,null))
36043543
"""
36053544

3606-
let createOptions() = createOptionsAux [fileSource1] []
3545+
let createOptions() = createProjectOptions dirName [fileSource1] []
36073546

36083547
[<Theory>]
36093548
[<InlineData(false)>]

tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Link>FsUnit.fs</Link>
2626
</Compile>
2727
<Compile Include="Common.fs" />
28+
<Compile Include="GeneratedCodeSymbolsTests.fs" />
2829
<Compile Include="AssemblyReaderShim.fs" />
2930
<Compile Include="ModuleReaderCancellationTests.fs" />
3031
<Compile Include="EditorTests.fs" />

0 commit comments

Comments
 (0)