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 #3274 and other features #3279

Merged
merged 3 commits into from
Nov 23, 2022
Merged
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
40 changes: 28 additions & 12 deletions src/Fable.Cli/ProjectCracker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Fable.Cli.ProjectCracker

open System
open System.Xml.Linq
open System.Text.RegularExpressions
open System.Collections.Generic
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Text
Expand Down Expand Up @@ -248,11 +249,6 @@ let private getDllName (dllFullPath: string) =
let i = dllFullPath.LastIndexOf('/')
dllFullPath[(i + 1) .. (dllFullPath.Length - 5)] // -5 removes the .dll extension

let (|Regex|_|) (pattern: string) (input: string) =
let m = Text.RegularExpressions.Regex.Match(input, pattern)
if m.Success then Some [for x in m.Groups -> x.Value]
else None

let getBasicCompilerArgs () =
[|
// "--debug"
Expand All @@ -271,12 +267,32 @@ let getBasicCompilerArgs () =
// yield "--target:library"
|]

let MSBUILD_CONDITION = Regex(@"^\s*'\$\((\w+)\)'\s*([!=]=)\s*'(true|false)'\s*$", RegexOptions.IgnoreCase)

/// Simplistic XML-parsing of .fsproj to get source files, as we cannot
/// run `dotnet restore` on .fsproj files embedded in Nuget packages.
let getSourcesFromFablePkg (projFile: string) =
let getSourcesFromFablePkg (opts: CrackerOptions) (projFile: string) =
let withName s (xs: XElement seq) =
xs |> Seq.filter (fun x -> x.Name.LocalName = s)

let checkCondition (el: XElement) =
match el.Attribute(XName.Get "Condition") with
| null -> true
| attr ->
match attr.Value with
| Naming.Regex MSBUILD_CONDITION [_; prop; op; bval] ->
let bval = Boolean.Parse bval
let isTrue = (op = "==") = bval // (op = "==" && bval) || (op = "!=" && not bval)
let isDefined =
opts.FableOptions.Define
|> List.exists (fun d -> String.Equals(d, prop, StringComparison.InvariantCultureIgnoreCase))
// printfn $"CONDITION: {prop} ({isDefined}) {op} {bval} ({isTrue})"
isTrue = isDefined
| _ -> false

let withNameAndCondition s (xs: XElement seq) =
xs |> Seq.filter (fun el -> el.Name.LocalName = s && checkCondition el)

let xmlDoc = XDocument.Load(projFile)
let projDir = Path.GetDirectoryName(projFile)

Expand All @@ -290,11 +306,11 @@ let getSourcesFromFablePkg (projFile: string) =
|> not))

xmlDoc.Root.Elements()
|> withName "ItemGroup"
|> withNameAndCondition "ItemGroup"
|> Seq.map (fun item ->
(item.Elements(), [])
||> Seq.foldBack (fun el src ->
if el.Name.LocalName = "Compile" then
if el.Name.LocalName = "Compile" && checkCondition el then
el.Elements() |> withName "Link"
|> Seq.tryHead |> function
| Some link when Path.isRelativePath link.Value ->
Expand Down Expand Up @@ -445,7 +461,7 @@ let getProjectOptionsFromProjectFile =
tryGetResult isMain opts manager csprojFile
|> Option.map (fun (r: IAnalyzerResult) ->
// Careful, options for .csproj start with / but so do root paths in unix
let reg = System.Text.RegularExpressions.Regex(@"^\/[^\/]+?(:?:|$)")
let reg = Regex(@"^\/[^\/]+?(:?:|$)")
let comArgs =
r.CompilerArguments
|> Array.map (fun line ->
Expand All @@ -469,7 +485,7 @@ let getProjectOptionsFromProjectFile =
tryGetResult isMain opts manager projFile
|> Option.map (fun r ->
// result.CompilerArguments doesn't seem to work well in Linux
let comArgs = Text.RegularExpressions.Regex.Split(r.Command, @"\r?\n")
let comArgs = Regex.Split(r.Command, @"\r?\n")
comArgs, r))
|> function
| Some result -> result
Expand Down Expand Up @@ -631,7 +647,7 @@ let copyFableLibraryAndPackageSourcesPy (opts: CrackerOptions) (pkgs: FablePacka

// See #1455: F# compiler generates *.AssemblyInfo.fs in obj folder, but we don't need it
let removeFilesInObjFolder sourceFiles =
let reg = System.Text.RegularExpressions.Regex(@"[\\\/]obj[\\\/]")
let reg = Regex(@"[\\\/]obj[\\\/]")
sourceFiles |> Array.filter (reg.IsMatch >> not)

let loadPrecompiledInfo (opts: CrackerOptions) otherOptions sourceFiles =
Expand Down Expand Up @@ -775,7 +791,7 @@ let getFullProjectOpts (opts: CrackerOptions) =

let pkgRefs =
pkgRefs |> List.map (fun pkg ->
{ pkg with SourcePaths = getSourcesFromFablePkg pkg.FsprojPath })
{ pkg with SourcePaths = getSourcesFromFablePkg opts pkg.FsprojPath })

let sourcePaths =
let pkgSources = pkgRefs |> List.collect (fun x -> x.SourcePaths)
Expand Down
2 changes: 1 addition & 1 deletion src/Fable.Cli/Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ module Imports =
let outDir = Path.normalizePath outDir
// It may happen the importPath is already in outDir, for example package sources in fable_modules folder.
// (Case insensitive comparison because in some Windows build servers paths can start with C:/ or c:/)
if importPath.StartsWith(outDir, StringComparison.OrdinalIgnoreCase) then importPath
if importPath.StartsWith(outDir + "/", StringComparison.OrdinalIgnoreCase) then importPath
else
let importDir = Path.GetDirectoryName(importPath)
let targetDir = pathResolver.GetOrAddDeduplicateTargetDir(importDir, fun currentTargetDirs ->
Expand Down
18 changes: 10 additions & 8 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,17 +1484,18 @@ let nullables (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg:
| "get_HasValue", Some c -> makeEqOp r c (makeNull()) BinaryUnequal |> Some
| _ -> None

// See fable-library/Option.ts for more info on how options behave in Fable runtime
let options (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
let options isStruct (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, thisArg with
| "get_None", _ -> NewOption(None, t.Generics.Head, isStruct) |> makeValue r |> Some
| "Some", _ -> NewOption(List.tryHead args, t.Generics.Head, isStruct) |> makeValue r |> Some
| "get_Value", Some c -> getOptionValue r t c |> Some
| "get_IsSome", Some c -> Test(c, OptionTest true, r) |> Some
| "get_IsNone", Some c -> Test(c, OptionTest false, r) |> Some
| _ -> None

let optionModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) =
let optionModule isStruct (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) =
match i.CompiledName, args with
| "None", _ -> NewOption(None, t, false) |> makeValue r |> Some
| "None", _ -> NewOption(None, t, isStruct) |> makeValue r |> Some
| "GetValue", [c] -> getOptionValue r t c |> Some
| "IsSome", [c] -> Test(c, OptionTest true, r) |> Some
| "IsNone", [c] -> Test(c, OptionTest false, r) |> Some
Expand Down Expand Up @@ -2708,10 +2709,11 @@ let private replacedModules =
Types.queue, bclType
Types.iset, hashSets
Types.idisposable, disposables
Types.option, options
Types.valueOption, options
Types.option, options false
Types.valueOption, options true
"System.Nullable`1", nullables
"Microsoft.FSharp.Core.OptionModule", optionModule
"Microsoft.FSharp.Core.OptionModule", optionModule false
"Microsoft.FSharp.Core.ValueOption", optionModule true
"Microsoft.FSharp.Core.ResultModule", results
Types.bigint, bigints
"Microsoft.FSharp.Core.NumericLiterals.NumericLiteralI", bigints
Expand Down Expand Up @@ -2892,7 +2894,7 @@ let tryType = function
Some(getNumberFullName false kind info, f, [])
| String -> Some(Types.string, strings, [])
| Tuple(genArgs, _) as t -> Some(getTypeFullName false t, tuples, genArgs)
| Option(genArg, _) -> Some(Types.option, options, [genArg])
| Option(genArg, isStruct) -> Some(Types.option, options isStruct, [genArg])
| Array(genArg,_) -> Some(Types.array, arrays, [genArg])
| List genArg -> Some(Types.list, lists, [genArg])
| Builtin kind ->
Expand Down
7 changes: 4 additions & 3 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1687,11 +1687,11 @@ let options isStruct (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (th
| "get_IsNone", Some c -> Test(c, OptionTest false, r) |> Some
| _ -> None

let optionModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) =
let optionModule isStruct (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) =
let toArray r t arg =
Helper.LibCall(com, "Option", "toArray", Array(t, MutableArray), [arg], ?loc=r)
match i.CompiledName, args with
| "None", _ -> NewOption(None, t, false) |> makeValue r |> Some
| "None", _ -> NewOption(None, t, isStruct) |> makeValue r |> Some
| "GetValue", [c] ->
Helper.LibCall(com, "Option", "value", t, args, ?loc=r) |> Some
| ("OfObj" | "OfNullable"), _ ->
Expand Down Expand Up @@ -2900,7 +2900,8 @@ let private replacedModules =
Types.option, options false
Types.valueOption, options true
"System.Nullable`1", nullables
"Microsoft.FSharp.Core.OptionModule", optionModule
"Microsoft.FSharp.Core.OptionModule", optionModule false
"Microsoft.FSharp.Core.ValueOption", optionModule true
"Microsoft.FSharp.Core.ResultModule", results
Types.bigint, bigints
"Microsoft.FSharp.Core.NumericLiterals.NumericLiteralI", bigints
Expand Down
16 changes: 16 additions & 0 deletions tests/Dart/src/OptionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ let tests() =
Option.isNone o2 |> equal false
Option.isSome o2 |> equal true

testCase "ValueOption.isSome/isNone works" <| fun () ->
let o1: int voption = ValueNone
let o2 = ValueSome 5
ValueOption.isNone o1 |> equal true
ValueOption.isSome o1 |> equal false
ValueOption.isNone o2 |> equal false
ValueOption.isSome o2 |> equal true

testCase "Option.IsSome/IsNone works" <| fun () ->
let o1: int option = None
let o2 = Some 5
Expand All @@ -82,6 +90,14 @@ let tests() =
o2.IsNone |> equal false
o2.IsSome |> equal true

testCase "ValueOption.IsSome/IsNone works" <| fun () ->
let o1: int voption = ValueNone
let o2 = Some 5
o1.IsNone |> equal true
o1.IsSome |> equal false
o2.IsNone |> equal false
o2.IsSome |> equal true

testCase "Option.iter works" <| fun () -> // See #198
let mutable res = false
let getOnlyOnce =
Expand Down
16 changes: 16 additions & 0 deletions tests/Js/Main/OptionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ let tests =
Option.isNone o2 |> equal false
Option.isSome o2 |> equal true

testCase "ValueOption.isSome/isNone works" <| fun () ->
let o1: int voption = ValueNone
let o2 = ValueSome 5
ValueOption.isNone o1 |> equal true
ValueOption.isSome o1 |> equal false
ValueOption.isNone o2 |> equal false
ValueOption.isSome o2 |> equal true

testCase "Option.IsSome/IsNone works" <| fun () ->
let o1 = None
let o2 = Some 5
Expand All @@ -88,6 +96,14 @@ let tests =
o2.IsNone |> equal false
o2.IsSome |> equal true

testCase "ValueOption.IsSome/IsNone works" <| fun () ->
let o1: int voption = ValueNone
let o2 = Some 5
o1.IsNone |> equal true
o1.IsSome |> equal false
o2.IsNone |> equal false
o2.IsSome |> equal true

testCase "Option.iter works" <| fun () -> // See #198
let mutable res = false
let getOnlyOnce =
Expand Down