diff --git a/src/Fable.Cli/ProjectCracker.fs b/src/Fable.Cli/ProjectCracker.fs index 55e1d85170..8ba206ce90 100644 --- a/src/Fable.Cli/ProjectCracker.fs +++ b/src/Fable.Cli/ProjectCracker.fs @@ -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 @@ -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" @@ -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) @@ -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 -> @@ -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 -> @@ -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 @@ -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 = @@ -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) diff --git a/src/Fable.Cli/Util.fs b/src/Fable.Cli/Util.fs index df5ff0bfc9..a5649e3905 100644 --- a/src/Fable.Cli/Util.fs +++ b/src/Fable.Cli/Util.fs @@ -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 -> diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index aae86e3891..3f40a9d96b 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -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 @@ -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 @@ -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 -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index fe638b92d4..095656acac 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -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"), _ -> @@ -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 diff --git a/tests/Dart/src/OptionTests.fs b/tests/Dart/src/OptionTests.fs index 2217ba9271..9d61d8000f 100644 --- a/tests/Dart/src/OptionTests.fs +++ b/tests/Dart/src/OptionTests.fs @@ -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 @@ -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 = diff --git a/tests/Js/Main/OptionTests.fs b/tests/Js/Main/OptionTests.fs index f3e1aaa56f..9ad378602d 100644 --- a/tests/Js/Main/OptionTests.fs +++ b/tests/Js/Main/OptionTests.fs @@ -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 @@ -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 =