Skip to content
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

Fix for #270, and framework for testing cross-targeting scenarios #275

Closed
wants to merge 5 commits into from
Closed
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
20 changes: 1 addition & 19 deletions src/absil/ilreflect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -324,25 +324,7 @@ type cenv =
// [ns] ,name -> ns+name
// [ns;typeA;typeB],name -> ns+typeA+typeB+name
let convTypeRefAux (cenv:cenv) (tref:ILTypeRef) =

// If an inner nested type's name contains a space, the proper encoding is "\+" on both sides - otherwise,
// we use "+"
let rec collectPrefixParts (l : string list) (acc : string list) =
match l with
| h1 :: (h2 :: _ as tl) ->
collectPrefixParts tl
(List.append
acc
[ yield h1
if h1.Contains(" ") || h2.Contains(" ") then
yield "\\+"
else
yield "+"])
| h :: [] -> List.append acc [h]
| _ -> acc

let prefix = collectPrefixParts tref.Enclosing [] |> List.fold (fun (s1 : string) (s2 : string) -> s1 + s2) ""
let qualifiedName = prefix + (if prefix <> "" then (if tref.Name.Contains(" ") then "\\+" else "+") else "") + tref.Name // e.g. Name.Space.Class+NestedClass
let qualifiedName = (String.concat "+" (tref.Enclosing @ [ tref.Name ])).Replace(",", @"\,")
match tref.Scope with
| ILScopeRef.Assembly asmref ->
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I can tell from my testing, this comment is just wrong -- I can't find any scenarios where the + is escaped. Indeed, the incorrect escaping needed to be removed in order to get my tests passing. All current tests continue to work fine.

This is now simplified and exactly matches https://github.com/Microsoft/visualfsharp/blob/fsharp4/src/absil/il.fs#L682

Copy link
Contributor

Choose a reason for hiding this comment

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

That code had always been very, very suspect to me. Thanks for digging into this so thoroughly

let assembly =
Expand Down
5 changes: 4 additions & 1 deletion src/fsharp/FSharp.Core/quotations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,8 +1298,11 @@ module Patterns =
if System.Int32.TryParse(a, &idx) && b = "" then
st.referencedTypeDefs.[idx]
else
// escape commas found in type name, which are not already escaped
// '\' is not valid in a type name except as an escape character, so logic can be pretty simple
let escapedTcName = System.Text.RegularExpressions.Regex.Replace(a, @"(?<!\\),", @"\,")
let assref = decodeAssemblyRef st b
mkNamedTycon (a,assref)
mkNamedTycon (escapedTcName, assref)

let u_tyconstSpec st =
let tag = u_byte_as_int st
Expand Down
5 changes: 5 additions & 0 deletions tests/RunTests.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ IF NOT DEFINED GACUTILEXE64 IF EXIST "%WINSDKNETFXTOOLS%x64\gacutil.exe" set GAC
set FSC=%FSCBINPATH%\fsc.exe
set PATH=%FSCBINPATH%;%PATH%

set FSCVPREVBINPATH=%X86_PROGRAMFILES%\Microsoft SDKs\F#\3.1\Framework\v4.0
set FSCVPREV=%FSCVPREVBINPATH%\fsc.exe

REM == VS-installed paths to FSharp.Core.dll
set FSCOREDLLPATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.4.0.0
set FSCOREDLL20PATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETFramework\v2.0\2.3.0.0
Expand All @@ -134,6 +137,7 @@ set FSCOREDLLNETCOREPATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FShar
set FSCOREDLLNETCORE78PATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETCore\3.78.4.0
set FSCOREDLLNETCORE259PATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETCore\3.259.4.0
set FSDATATPPATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.3.0.0\Type Providers
set FSCOREDLLVPREVPATH=%X86_PROGRAMFILES%\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.3.1.0

REM == open source logic
if exist "%FSCBinPath%\FSharp.Core.dll" set FSCOREDLLPATH=%FSCBinPath%
Expand All @@ -151,6 +155,7 @@ set FSCOREDLLNETCOREPATH=%FSCOREDLLNETCOREPATH%\FSharp.Core.dll
set FSCOREDLLNETCORE78PATH=%FSCOREDLLNETCORE78PATH%\FSharp.Core.dll
set FSCOREDLLNETCORE259PATH=%FSCOREDLLNETCORE259PATH%\FSharp.Core.dll
set FSDATATPPATH=%FSDATATPPATH%\FSharp.Data.TypeProviders.dll
set FSCOREDLLVPREVPATH=%FSCOREDLLVPREVPATH%\FSharp.Core.dll

for /d %%i in (%WINDIR%\Microsoft.NET\Framework\v4.0.?????) do set CORDIR=%%i
set PATH=%PATH%;%CORDIR%
Expand Down
114 changes: 114 additions & 0 deletions tests/fsharpqa/Source/MultiTargeting/MultiTargetMatrix.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
open System
open System.IO
open System.Diagnostics
open System.Reflection

module Helpers =

// runs a program, and exits the script if nonzero exit code is encountered
let private run exePath args =
let args = String.concat " " args
let psi = ProcessStartInfo(FileName = exePath, Arguments = args, CreateNoWindow = true, UseShellExecute = false, RedirectStandardError = true)
let p = Process.Start(psi)
match p.WaitForExit(10 * 60 * 1000) with
| false -> eprintfn "Process timed out"; exit 1
| true ->
if p.ExitCode <> 0 then
eprintfn "%s %s" exePath args
eprintfn "%s" (p.StandardError.ReadToEnd())
exit p.ExitCode

let private authorCompile compilerPath runtime source =
run compilerPath ["-a"; "-o:author.dll"; "--noframework"; sprintf "\"-r:%s\"" runtime; source]

let private consumerCompile compilerPath runtime source =
run compilerPath ["-o:consumer.exe"; "--noframework"; sprintf "\"-r:%s\"" runtime; "-r:author.dll"; source]

let private consumerRunFsi fsiPath source =
run fsiPath ["--exec"; source]

// runs the consumer EXE, handling binding redirects automatically
let private consumerRunExe redirectVer =
if File.Exists("consumer.exe.config") then
File.Delete("consumer.exe.config")

let content = File.ReadAllText("consumer.exe.config.txt").Replace("{ver}", redirectVer)
File.WriteAllText("consumer.exe.config", content)

run "consumer.exe" []

/// gets the version of the assembly at the specified path
let getVer dllPath =
let asm = Assembly.ReflectionOnlyLoadFrom(dllPath)
asm.GetName().Version.ToString()

/// runs through the end-to-end scenario of
/// - Author uses [authorComiler] to build DLL targeting [authorRuntime] with source [authorSource]
/// - Consumer uses [consumerCompiler] to build EXE ref'ing above DLL, building EXE targeting [consumerRuntime] with source [consumerSource]
/// - Run the resulting EXE
let testExe authorCompiler authorRuntime consumerCompiler consumerRuntime authorSource consumerSource =
authorCompile authorCompiler authorRuntime authorSource
consumerCompile consumerCompiler consumerRuntime consumerSource
consumerRunExe (getVer consumerRuntime)

/// runs through the end-to-end scenario of
/// - Author uses [authorComiler] to build DLL targeting [authorRuntime] with source [authorSource]
/// - Consumer uses [consumerFsi] to #r above DLL and run script [consumerSource]
let testFsi authorCompiler authorRuntime consumerFsi authorSource consumerSource =
authorCompile authorCompiler authorRuntime authorSource
consumerRunFsi consumerFsi consumerSource

module Test =
let private env s =
match Environment.GetEnvironmentVariable(s) with
| var when not (String.IsNullOrWhiteSpace(var)) -> var
| _ -> failwithf "Required env var %s not defined" s

// paths to vPrevious of fsc.exe, fsi.exe, FSharp.Core.dll
let vPrevCompiler = env "FSCVPREV"
let vPrevFsi = Path.Combine(env "FSCVPREVBINPATH", "fsi.exe")
let vPrevRuntime = env "FSCOREDLLVPREVPATH"

// paths to vCurrent of fsc.exe, fsi.exe, FSharp.Core.dll
let vCurrentCompiler = env "FSC"
let vCurrentFsi = Path.Combine(env "FSCBINPATH", "fsi.exe")
let vCurrentRuntime = env "FSCOREDLLPATH"

let cases =
// compiler/runtime of author | compiler/runtime of consumer
[ 0, Helpers.testExe vPrevCompiler vPrevRuntime vCurrentCompiler vPrevRuntime
1, Helpers.testExe vPrevCompiler vPrevRuntime vCurrentCompiler vCurrentRuntime
2, Helpers.testExe vCurrentCompiler vPrevRuntime vPrevCompiler vPrevRuntime
3, Helpers.testExe vCurrentCompiler vPrevRuntime vCurrentCompiler vPrevRuntime
4, Helpers.testExe vCurrentCompiler vPrevRuntime vCurrentCompiler vCurrentRuntime
5, Helpers.testExe vCurrentCompiler vCurrentRuntime vCurrentCompiler vCurrentRuntime

// compiler/runtime of author | fsi of consumer
6, Helpers.testFsi vPrevCompiler vPrevRuntime vCurrentFsi
7, Helpers.testFsi vCurrentCompiler vPrevRuntime vCurrentFsi
8, Helpers.testFsi vCurrentCompiler vPrevRuntime vPrevFsi
9, Helpers.testFsi vCurrentCompiler vCurrentRuntime vCurrentFsi
]

// parse command line args
// final 'exclusions' arg allows for certain scenarios to be skipped if they are not expected to work
let authorSource, consumerSource, exclusions =
match fsi.CommandLineArgs with
| [| _; arg1; arg2 |] -> arg1, arg2, [| |]
| [| _; arg1; arg2; arg3 |] -> arg1, arg2, (arg3.Split(',') |> Array.map int)
| args ->
eprintfn "Expecting args <author source> <consumer source> [excluded cases], got args %A" args
exit 1

// failsafe to make sure that excluded scenarios are revisited on new versions
// i.e. exclusions valid for vN/vN-1 will probably no longer be needed for vN+1/vN
if not ((Helpers.getVer Test.vCurrentRuntime).StartsWith("4.4.0")) then
eprintfn "Runtime version has changed, review exclusions lists for these tests"
exit 1

Test.cases
|> List.filter (fun (id, _) -> not (Array.contains id exclusions))
|> List.iter (fun (id, testCase) ->
printfn "Case %d" id
testCase authorSource consumerSource
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Foo

type ``one, two, three``() = class end

let X = <@ ``one, two, three``() @>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#if INTERACTIVE
#r "author.dll"
#else
module Test
#endif

printfn "%A" Foo.X

#if INTERACTIVE
#q ;;
#endif
16 changes: 16 additions & 0 deletions tests/fsharpqa/Source/MultiTargeting/consumer.exe.config.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity
name="FSharp.Core"
publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect
oldVersion="2.0.0.0-{ver}"
newVersion="{ver}"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
7 changes: 7 additions & 0 deletions tests/fsharpqa/Source/MultiTargeting/dummy.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Every testcase need a sourcefile, so this is a fake one.
// Add some code so that you don't get the 'empty module' warning.

if false then
printfn "Hello, World!"

exit 0
2 changes: 2 additions & 0 deletions tests/fsharpqa/Source/MultiTargeting/env.lst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ NOMONO SOURCE=E_BadPathToFSharpCore.fsx SCFLAGS=" " ISCFLAGS="--nofra

# FSharp.Core is checked in for this test to verify a particular error message related to it. It shouldn't be accidentally picked up by other tests since it isn't in the working directory for them
NOMONO SOURCE=E_UseBinaryIncompatibleLibrary.fs SCFLAGS=" " ISCFLAGS="--noframework -r ..\\Common\\FSharp.Core.dll" # E_UseBinaryIncompatibleLibrary.fs

SOURCE=dummy.fs POSTCMD="\$FSI_PIPE --nologo --quiet --exec .\\MultiTargetMatrix.fsx QuotedCommaTypeName_author.fs QuotedCommaTypeName_consumer.fsx 0,8" # QuotedCommaTypeName
2 changes: 1 addition & 1 deletion tests/fsharpqa/Source/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ Misc01 Libraries\Core\Unchecked

Misc02 Libraries\Portable
Misc02 Misc
Misc02 MultiTargeting
Misc02,Smoke MultiTargeting
Misc02 OCamlCompat
Misc02,CodeGen Optimizations\AssemblyBoundary
Misc02,CodeGen Optimizations\ForLoop
Expand Down