diff --git a/build.fsx b/build.fsx index 55ecc4db3c..67650d58a0 100644 --- a/build.fsx +++ b/build.fsx @@ -534,9 +534,8 @@ let testPython() = "--lang Python" ] - runInDir buildDir "poetry run pytest -x" - // Testing in Windows - // runInDir buildDir "python -m pytest -x" + if isWindows then runInDir buildDir "python3 -m pytest -x" + else runInDir buildDir "poetry run pytest -x" type RustTestMode = | SingleThreaded @@ -764,7 +763,7 @@ match BUILD_ARGS_LOWER with | "test-integration"::_ -> testIntegration() | "test-repos"::_ -> testRepos() | ("test-ts"|"test-typescript")::_ -> testTypeScript() -| "test-py"::_ -> testPython() +| ("test-py"|"test-python")::_ -> testPython() | "test-rust"::_ -> testRust SingleThreaded | "test-rust-default"::_ -> testRust SingleThreaded | "test-rust-threaded"::_ -> testRust MultiThreaded diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index a9aa8753a1..d822c4862e 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -1156,20 +1156,15 @@ module TypeHelpers = | Choice1Of2 t -> t | Choice2Of2 fullName -> makeRuntimeTypeWithMeasure genArgs fullName | _ -> - let mkDeclType () = - Fable.DeclaredType(FsEnt.Ref tdef, makeTypeGenArgsWithConstraints withConstraints ctxTypeArgs genArgs) - // Emit attribute - if tdef.Attributes |> hasAttribute Atts.emitAttr then - mkDeclType () - else - // other special attributes - tdef.Attributes |> tryPickAttribute [ - Atts.stringEnum, Fable.String - Atts.erase, Fable.Any - Atts.tsTaggedUnion, Fable.Any - ] - // Rest of declared types - |> Option.defaultWith mkDeclType + let transformAttrs = + match Compiler.Language with + | Language.JavaScript | Language.TypeScript -> [ Atts.stringEnum, Fable.String ] + // Other languages can type erased unions too after fixing tests + | _ -> [ Atts.stringEnum, Fable.String; Atts.erase, Fable.Any ] + tdef.Attributes + |> tryPickAttribute transformAttrs + |> Option.defaultWith (fun () -> + Fable.DeclaredType(FsEnt.Ref tdef, makeTypeGenArgsWithConstraints withConstraints ctxTypeArgs genArgs)) let rec makeTypeWithConstraints withConstraints (ctxTypeArgs: Map) (NonAbbreviatedType t) = // Generic parameter (try to resolve for inline functions) @@ -1262,22 +1257,10 @@ module TypeHelpers = /// Enums in F# are uint32 /// -> Allow into all int & uint | EnumIntoInt = 0b0001 - /// Erased Unions are reduced to `Any` - /// -> Cannot distinguish between 'normal' Any (like `obj`) and Erased Union (like Erased Union with string field) - /// - /// For interface members the FSharp Type is available - /// -> `Ux<...>` receive special treatment and its types are extracted - /// -> `abstract Value: U2` -> extract `int` & `string` - /// BUT: for Expressions in Anon Records that's not possible, and `U2` is only recognized as `Any` - /// -> `{| Value = v |}`: `v: int` and `v: string` are recognized as matching, - /// but `v: U2` isn't: only `Any`/`obj` as Type available - /// To recognize as matching, we must allow all `Any` expressions for `U2` in interface place. - /// - /// Note: Only `Ux<...>` are currently handled (on interface side), not other Erased Unions! - | AnyIntoErased = 0b0010 - /// Unlike `AnyIntoErased`, this allows all expressions of type `Any` in all interface properties. - /// (The other way is always allow: Expression of all Types fits into `Any`) - | AlwaysAny = 0b0100 + + // We could try to identify all erased unions (without tag) instead of only handling Fable.Core.Ux ones + // but it's more complex because we cannot simply extra the alternative types from the generics + let ERASED_UNION = Regex(@"^Fable\.Core\.U\d+`\d+$") let fitsAnonRecordInInterface (_com: IFableCompiler) @@ -1293,60 +1276,20 @@ module TypeHelpers = getAllInterfaceMembers interface_ |> Seq.toList - let makeType = makeType Map.empty /// Returns for: /// * `Ux<...>`: extracted types from `<....>`: `U2` -> `[String; Int]` /// * `Option>`: extracted types from `<...>`, then made Optional: `Option>` -> `[Option String; Option Int]` /// * 'normal' type: `makeType`ed type: `string` -> `[String]` - /// Note: Erased Unions (except handled `Ux<...>`) are reduced to `Any` - /// - /// Extracting necessary: Erased Unions are reduced to `Any` -> special handling for `Ux<...>` /// /// Note: nested types aren't handled: `U2>` -> `[Int; Any]` let rec collectTypes (ty: FSharpType) : Fable.Type list = // Special treatment for Ux<...> and Option>: extract types in Ux - // This is necessary because: `makeType` reduces Erased Unions (including Ux) to `Any` -> no type info any more - // // Note: no handling of nested types: `U2>` -> `int` & `float` don't get extract - match ty with - | UType tys -> - tys - |> List.map makeType - |> List.distinct - | OptionType (UType tys, isStruct) -> - tys - |> List.map (fun t -> Fable.Option(makeType t, isStruct)) - |> List.distinct - | _ -> - makeType ty - |> List.singleton - and (|OptionType|_|) (ty: FSharpType) = - match ty with - | TypeDefinition tdef -> - match FsEnt.FullName tdef with - | Types.valueOption -> Some(ty.GenericArguments[0], true) - | Types.option -> Some(ty.GenericArguments[0], false) - | _ -> None - | _ -> None - and (|UType|_|) (ty: FSharpType) = - let (|UName|_|) (tdef: FSharpEntity) = - if - tdef.Namespace = Some "Fable.Core" - && - ( - let name = tdef.DisplayName - name.Length = 2 && name[0] = 'U' && Char.IsDigit name[1] - ) - then - Some () - else - None - match ty with - | TypeDefinition UName -> - ty.GenericArguments - |> Seq.toList - |> Some - | _ -> None + match makeType Map.empty ty with + | Fable.DeclaredType({ FullName = Naming.Regex ERASED_UNION _ }, genArgs) -> genArgs + | Fable.Option(Fable.DeclaredType({ FullName = Naming.Regex ERASED_UNION _ }, genArgs), isStruct) -> + genArgs |> List.map (fun t -> Fable.Option(t, isStruct)) + | t -> [t] /// Special Rules mostly for Indexers: /// For direct interface member implementation we want to be precise (-> exact_ish match) @@ -1358,14 +1301,10 @@ module TypeHelpers = function | Fable.Number((Int8 | UInt8 | Int16 | UInt16 | Int32 | UInt32), _) -> Some () | _ -> None + let fitsIntoSingle (rules: Allow) (expected: Fable.Type) (actual: Fable.Type) = match expected, actual with | Fable.Any, _ -> true - | _, Fable.Any when rules.HasFlag Allow.AlwaysAny -> - // Erased Unions are reduced to `Any` - // -> cannot distinguish between 'normal' Any (like 'obj') - // and Erased Union (like Erased Union with string field) - true | IntNumber, Fable.Number(_, Fable.NumberInfo.IsEnum _) when rules.HasFlag Allow.EnumIntoInt -> // the underlying type of enum in F# is uint32 // For practicality: allow in all uint & int fields @@ -1374,22 +1313,14 @@ module TypeHelpers = | Fable.Option(t1,_), t2 | t1, t2 -> typeEquals false t1 t2 + let fitsIntoMulti (rules: Allow) (expected: Fable.Type list) (actual: Fable.Type) = expected |> List.contains Fable.Any - || - ( - // special treatment for actual=Any & multiple expected: - // multiple expected -> `Ux<...>` -> extracted types - // BUT: in actual that's not possible -> in actual `Ux<...>` = `Any` - // -> no way to distinguish Ux (or other Erased Unions) from 'normal` Any (like obj) - rules.HasFlag Allow.AnyIntoErased - && - expected |> List.isMultiple - && - actual = Fable.Any - ) - || - expected |> List.exists (fun expected -> fitsIntoSingle rules expected actual) + || (match actual with + | Fable.DeclaredType({ FullName = Naming.Regex ERASED_UNION _ }, actual) when List.sameLength expected actual -> + List.zip expected actual |> List.forall (fun (expected, actual) -> fitsIntoSingle rules expected actual) + | _ -> false) + || expected |> List.exists (fun expected -> fitsIntoSingle rules expected actual) fitsIntoMulti rules expected actual @@ -1460,10 +1391,10 @@ module TypeHelpers = | [] -> unreachable () | [expectedType] -> let expectedType = expectedType |> formatType - $"Expected type '{expectedType}' for field '{fieldName}' because of Indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" + $"Expected type '{expectedType}' for field '{fieldName}' because of indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" | _ -> let expectedTypes = expectedTypes |> formatTypes - $"Expected any type of [{expectedTypes}] for field '{fieldName}' because of Indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" + $"Expected any type of [{expectedTypes}] for field '{fieldName}' because of indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" | _ -> let indexerNames = indexers @@ -1473,10 +1404,10 @@ module TypeHelpers = | [] -> unreachable () | [expectedType] -> let expectedType = expectedType |> formatType - $"Expected type '{expectedType}' for field '{fieldName}' because of Indexers [{indexerNames}] in interface '{interfaceName}', but is '{actualType}'" + $"Expected type '{expectedType}' for field '{fieldName}' because of indexers [{indexerNames}] in interface '{interfaceName}', but is '{actualType}'" | _ -> let expectedTypes = expectedTypes |> formatTypes - $"Expected any type of [{expectedTypes}] for field '{fieldName}' because of Indexers [{indexerNames}] in interface '{interfaceName}', but is '{actualType}'" + $"Expected any type of [{expectedTypes}] for field '{fieldName}' because of indexers [{indexerNames}] in interface '{interfaceName}', but is '{actualType}'" let r = r |> Option.orElse range // fall back to anon record range @@ -1504,7 +1435,7 @@ module TypeHelpers = | Some i -> let expr = List.item i argExprs let ty = expr.Type - if ty |> fitsInto (Allow.TheUsual ||| Allow.AnyIntoErased) expectedTypes then + if ty |> fitsInto Allow.TheUsual expectedTypes then None else formatUnexpectedTypeError None m.DisplayName expectedTypes ty expr.Range @@ -1537,7 +1468,7 @@ module TypeHelpers = |> List.filter (fun (fieldName, _) -> fieldsToIgnore |> Set.contains fieldName |> not ) |> List.choose (fun (name, expr) -> let ty = expr.Type - if fitsInto (Allow.TheUsual ||| Allow.EnumIntoInt ||| Allow.AnyIntoErased) validTypes ty then + if fitsInto (Allow.TheUsual ||| Allow.EnumIntoInt) validTypes ty then None else formatUnexpectedTypeError (Some indexers) name validTypes ty expr.Range @@ -1804,13 +1735,13 @@ module Util = let isErasedOrStringEnumEntity (ent: Fable.Entity) = ent.Attributes |> Seq.exists (fun att -> match att.Entity.FullName with - | Atts.erase | Atts.stringEnum | Atts.tsTaggedUnion -> true + | Atts.erase | Atts.stringEnum | Atts.tsTaggedUnion | Atts.emit -> true | _ -> false) let isErasedOrStringEnumFSharpEntity (ent: FSharpEntity) = ent.Attributes |> Seq.exists (fun att -> match (nonAbbreviatedDefinition att.AttributeType).TryFullName with - | Some(Atts.erase | Atts.stringEnum | Atts.tsTaggedUnion) -> true + | Some(Atts.erase | Atts.stringEnum | Atts.tsTaggedUnion | Atts.emit) -> true | _ -> false) let isGlobalOrImportedEntity (ent: Fable.Entity) = diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index 4d70bfa08b..c89ea43835 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -46,19 +46,25 @@ let private transformNewUnion com ctx r fsType (unionCase: FSharpUnionCase) (arg match getUnionPattern fsType unionCase with | ErasedUnionCase -> makeTuple r false argExprs - // TODO: Wrap erased unions in type cast so type info is not lost - | ErasedUnion(tdef, _genArgs, rule, tag) -> - if tag then - (transformStringEnum rule unionCase)::argExprs |> makeTuple r false - else - match argExprs with - | [] -> transformStringEnum rule unionCase - | [argExpr] -> argExpr - | _ when tdef.UnionCases.Count > 1 -> - $"Erased unions with multiple fields must have one single case: {getFsTypeFullName fsType}. " + - "To allow multiple cases pass tag argument, e.g.: []" - |> addErrorAndReturnNull com ctx.InlinePath r - | argExprs -> makeTuple r false argExprs + | ErasedUnion(tdef, genArgs, rule, tag) -> + let unionExpr = + if tag then + (transformStringEnum rule unionCase)::argExprs |> makeTuple r false + else + match argExprs with + | [] -> transformStringEnum rule unionCase + | [argExpr] -> argExpr + | _ when tdef.UnionCases.Count > 1 -> + $"Erased unions with multiple fields must have one single case: {getFsTypeFullName fsType}. " + + "To allow multiple cases pass tag argument, e.g.: []" + |> addErrorAndReturnNull com ctx.InlinePath r + | argExprs -> makeTuple r false argExprs + match com.Options.Language with + // Tests are failing for Rust if we wrap erased unions + | Language.Rust -> unionExpr + | _ -> + let genArgs = makeTypeGenArgs ctx.GenericArgs genArgs + Fable.TypeCast(unionExpr, Fable.DeclaredType(FsEnt.Ref tdef, genArgs)) | TypeScriptTaggedUnion _ -> match argExprs with | [argExpr] -> argExpr diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index c4d8a61dd3..79e0800afe 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -87,7 +87,10 @@ module Lib = module Reflection = open Lib - let private libReflectionCall (com: IBabelCompiler) ctx r memberName args = + let private primitiveTypeInfo com ctx name = + libValue com ctx "Reflection" (name + "_type") + + let private declaredTypeInfo (com: IBabelCompiler) ctx r memberName args = libCall com ctx r "Reflection" (memberName + "_type") [] args let private transformRecordReflectionInfo com ctx r (ent: Fable.Entity) generics = @@ -104,7 +107,7 @@ module Reflection = |> Seq.toArray let fields = Expression.arrowFunctionExpression([||], Expression.arrayExpression(fields)) [fullnameExpr; Expression.arrayExpression(generics); jsConstructor com ctx ent; fields] - |> libReflectionCall com ctx None "record" + |> declaredTypeInfo com ctx None "record" let private transformUnionReflectionInfo com ctx r (ent: Fable.Entity) generics = let fullname = ent.FullName @@ -123,30 +126,28 @@ module Reflection = ) |> Seq.toArray let cases = Expression.arrowFunctionExpression([||], Expression.arrayExpression(cases)) [fullnameExpr; Expression.arrayExpression(generics); jsConstructor com ctx ent; cases] - |> libReflectionCall com ctx None "union" + |> declaredTypeInfo com ctx None "union" let transformTypeInfo (com: IBabelCompiler) ctx r (genMap: Map option) t: Expression = - let primitiveTypeInfo name = - libValue com ctx "Reflection" (name + "_type") let numberInfo kind = getNumberKindName kind - |> primitiveTypeInfo + |> primitiveTypeInfo com ctx let nonGenericTypeInfo fullname = [Expression.stringLiteral(fullname)] - |> libReflectionCall com ctx None "class" + |> declaredTypeInfo com ctx None "class" let resolveGenerics generics: Expression list = generics |> List.map (transformTypeInfo com ctx r genMap) let genericTypeInfo name genArgs = let resolved = resolveGenerics genArgs - libReflectionCall com ctx None name resolved + declaredTypeInfo com ctx None name resolved let genericEntity (fullname: string) generics = - libReflectionCall com ctx None "class" [ + declaredTypeInfo com ctx None "class" [ Expression.stringLiteral(fullname) if not(Array.isEmpty generics) then Expression.arrayExpression(generics) ] let genericGlobalOrImportedEntity generics (ent: Fable.Entity) = - libReflectionCall com ctx None "class" [ + declaredTypeInfo com ctx None "class" [ yield Expression.stringLiteral(ent.FullName) match generics with | [||] -> yield Util.undefined None @@ -157,20 +158,20 @@ module Reflection = ] match t with | Fable.Measure _ - | Fable.Any -> primitiveTypeInfo "obj" + | Fable.Any -> primitiveTypeInfo com ctx "obj" | Fable.GenericParam(name=name) -> match genMap with - | None -> [Expression.stringLiteral(name)] |> libReflectionCall com ctx None "generic" + | None -> [Expression.stringLiteral(name)] |> declaredTypeInfo com ctx None "generic" | Some genMap -> match Map.tryFind name genMap with | Some t -> t | None -> Replacements.Util.genericTypeInfoError name |> addError com [] r Expression.nullLiteral() - | Fable.Unit -> primitiveTypeInfo "unit" - | Fable.Boolean -> primitiveTypeInfo "bool" - | Fable.Char -> primitiveTypeInfo "char" - | Fable.String -> primitiveTypeInfo "string" + | Fable.Unit -> primitiveTypeInfo com ctx "unit" + | Fable.Boolean -> primitiveTypeInfo com ctx "bool" + | Fable.Char -> primitiveTypeInfo com ctx "char" + | Fable.String -> primitiveTypeInfo com ctx "string" | Fable.Number(kind, info) -> match info with | Fable.NumberInfo.IsEnum entRef -> @@ -185,7 +186,7 @@ module Reflection = |> Seq.toArray |> Expression.arrayExpression [Expression.stringLiteral(entRef.FullName); numberInfo kind; cases ] - |> libReflectionCall com ctx None "enum" + |> declaredTypeInfo com ctx None "enum" | _ -> numberInfo kind | Fable.LambdaType(argType, returnType) -> @@ -202,7 +203,7 @@ module Reflection = let genArgs = resolveGenerics genArgs List.zip (fieldNames |> Array.toList) genArgs |> List.map (fun (k, t) -> Expression.arrayExpression[|Expression.stringLiteral(k); t|]) - |> libReflectionCall com ctx None "anonRecord" + |> declaredTypeInfo com ctx None "anonRecord" | Fable.DeclaredType(entRef, genArgs) -> let fullName = entRef.FullName match fullName, genArgs with @@ -243,9 +244,9 @@ module Reflection = let ent = com.GetEntity(entRef) let generics = genArgs |> List.map (transformTypeInfo com ctx r genMap) |> List.toArray // Check if the entity is actually declared in JS code - // TODO: Interfaces should be declared when generating Typescript if FSharp2Fable.Util.isGlobalOrImportedEntity ent then genericGlobalOrImportedEntity generics ent + // TODO: Interfaces (and probably erased unions too) should be declared when generating Typescript elif ent.IsInterface || FSharp2Fable.Util.isErasedOrStringEnumEntity ent || FSharp2Fable.Util.isReplacementCandidate entRef then @@ -254,7 +255,7 @@ module Reflection = // See Fable.Transforms.FSharp2Fable.TypeHelpers.makeTypeGenArgs elif ent.IsMeasure then [Expression.stringLiteral(ent.FullName)] - |> libReflectionCall com ctx None "measure" + |> declaredTypeInfo com ctx None "measure" else let reflectionMethodExpr = FSharp2Fable.Util.entityIdentWithSuffix com entRef Naming.reflectionSuffix let callee = com.TransformAsExpr(ctx, reflectionMethodExpr) @@ -286,7 +287,7 @@ module Reflection = |> transformTypeInfo com ctx r genMap | None -> () ] - |> libReflectionCall com ctx r "class" + |> declaredTypeInfo com ctx r "class" let private ofString s = Expression.stringLiteral(s) let private ofArray babelExprs = Expression.arrayExpression(List.toArray babelExprs) @@ -1003,6 +1004,8 @@ module Util = com.GetImportExpr(ctx, selector, path, r) |> getParts parts + // If we decide to make casts explicit in Typescript we need to check the target type is not a base type + // already of the expression type (as we do in Dart) and also whether the target type is erased let transformCast (com: IBabelCompiler) (ctx: Context) t e: Expression = match t with // Optimization for (numeric) array or list literals casted to seq diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index 9d5fcc047f..72d634bf31 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -78,3 +78,103 @@ let measureTime (f: unit -> unit): unit = emitJsStatement () """ // to Fable.Tests project. For example: // testCase "Addition works" <| fun () -> // 2 + 2 |> equal 4 + +[] +type MyRecord = + { Foo: int } + +let testMyRecord (r: MyRecord) = + r.Foo + +testMyRecord { Foo = 5 } |> printfn "%i" + +[] +type Foo = + | Foo of foo: string * bar: int + | Zas of ja: float + +let test = function + | Foo(foo, bar) -> String.replicate bar foo + | Zas f -> $"It is a float: %.2f{f}" + +Zas 5.67890 |> test |> printfn "%s" +Foo("oh", 3) |> test |> printfn "%s" + +(* +module TaggedUnion = + type Base<'Kind> = + abstract kind: 'Kind + + type Foo<'Kind> = + inherit Base<'Kind> + abstract foo: string + + type Bar<'Kind> = + inherit Base<'Kind> + abstract bar: int + + type Baz<'Kind> = + inherit Base<'Kind> + abstract baz: bool + + [] + type StringTagged = + | Foo of Foo + | Bar of Bar + | [] Baz of Baz + + [] + type NumberTagged = + | [] Foo of Foo + | [] Bar of Bar + | [] Baz of Baz + + [] + type BoolTagged = + | [] Foo of Foo + | [] Bar of Bar + +module Tests = + testCase "Case testing with TS tagged unions of string tags works" <| fun () -> + let describe = function + | TaggedUnion.StringTagged.Foo x -> sprintf "foo: %s" x.foo + | TaggedUnion.StringTagged.Bar x -> sprintf "bar: %d" x.bar + | TaggedUnion.StringTagged.Baz x -> sprintf "baz: %b" x.baz + TaggedUnion.StringTagged.Foo !!{| kind = "foo"; foo = "hello" |} |> describe |> equal "foo: hello" + TaggedUnion.StringTagged.Bar !!{| kind = "bar"; bar = 42 |} |> describe |> equal "bar: 42" + TaggedUnion.StringTagged.Baz !!{| kind = "_baz"; baz = false |} |> describe |> equal "baz: false" + + testCase "Case testing with TS tagged unions of number tags works" <| fun () -> + let describe = function + | TaggedUnion.NumberTagged.Foo x -> sprintf "foo: %s" x.foo + | TaggedUnion.NumberTagged.Bar x -> sprintf "bar: %d" x.bar + | TaggedUnion.NumberTagged.Baz x -> sprintf "baz: %b" x.baz + TaggedUnion.NumberTagged.Foo !!{| kind = 0; foo = "hello" |} |> describe |> equal "foo: hello" + TaggedUnion.NumberTagged.Bar !!{| kind = 1.0; bar = 42 |} |> describe |> equal "bar: 42" + TaggedUnion.NumberTagged.Baz !!{| kind = 2; baz = false |} |> describe |> equal "baz: false" + + testCase "Case testing with TS tagged unions of boolean tags works" <| fun () -> + let describe = function + | TaggedUnion.BoolTagged.Foo x -> sprintf "foo: %s" x.foo + | TaggedUnion.BoolTagged.Bar x -> sprintf "bar: %d" x.bar + TaggedUnion.BoolTagged.Foo !!{| kind = true; foo = "hello" |} |> describe |> equal "foo: hello" + TaggedUnion.BoolTagged.Bar !!{| kind = false; bar = 42 |} |> describe |> equal "bar: 42" + + // testCase "Case testing with TS tagged unions of mixed type tags works" <| fun () -> + // let describe = function + // | TaggedUnion.MixedTagged.Foo x -> sprintf "foo: %s" x.foo + // | TaggedUnion.MixedTagged.Bar x -> sprintf "bar: %d" x.bar + // | TaggedUnion.MixedTagged.Baz x -> sprintf "baz: %b" x.baz + // TaggedUnion.MixedTagged.Foo !!{| kind = 0; foo = "hello" |} |> describe |> equal "foo: hello" + // TaggedUnion.MixedTagged.Bar !!{| kind = "bar"; bar = 42 |} |> describe |> equal "bar: 42" + // TaggedUnion.MixedTagged.Baz !!{| kind = false; baz = false |} |> describe |> equal "baz: false" + + // testCase "Case testing with TS tagged unions of enum tags works" <| fun () -> + // let describe = function + // | TaggedUnion.EnumTagged.Foo x -> sprintf "foo: %s" x.foo + // | TaggedUnion.EnumTagged.Bar x -> sprintf "bar: %d" x.bar + // | TaggedUnion.EnumTagged.Baz x -> sprintf "baz: %b" x.baz + // TaggedUnion.EnumTagged.Foo !!{| kind = TaggedUnion.Kind.Foo; foo = "hello" |} |> describe |> equal "foo: hello" + // TaggedUnion.EnumTagged.Bar !!{| kind = TaggedUnion.Kind.Bar; bar = 42 |} |> describe |> equal "bar: 42" + // TaggedUnion.EnumTagged.Baz !!{| kind = TaggedUnion.Kind.Baz; baz = false |} |> describe |> equal "baz: false" +*) \ No newline at end of file diff --git a/tests/Integration/Compiler/AnonRecordInInterfaceTests.fs b/tests/Integration/Compiler/AnonRecordInInterfaceTests.fs index a6dd0e323c..e955145fee 100644 --- a/tests/Integration/Compiler/AnonRecordInInterfaceTests.fs +++ b/tests/Integration/Compiler/AnonRecordInInterfaceTests.fs @@ -171,7 +171,7 @@ module Error = let fieldName = fieldName |> orNameRegex let expectedType = expectedType |> orNameRegex let actualType = actualType |> orNameRegex - $"Expected type '{expectedType}' for field '{fieldName}' because of Indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" + $"Expected type '{expectedType}' for field '{fieldName}' because of indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" |> Rx static member UnexpectedIndexerTypeMulti (?interfaceName: string, ?indexerName: string, ?fieldName: string, ?expectedTypes: string list, ?actualType: string) = let interfaceName = interfaceName |> orNameRegex @@ -179,7 +179,7 @@ module Error = let fieldName = fieldName |> orNameRegex let expectedTypes = expectedTypes |> orNamesRegex let actualType = actualType |> orNameRegex - $"Expected any type of {expectedTypes} for field '{fieldName}' because of Indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" + $"Expected any type of {expectedTypes} for field '{fieldName}' because of indexer '{indexerName}' in interface '{interfaceName}', but is '{actualType}'" |> Rx module private Ty = @@ -352,18 +352,19 @@ let tests = ] |> compile |> Assert.Is.strictSuccess - testCase "Ideally: error because of missmatching type U3" <| fun _ -> - // Erased Union is reduced to type `Any` -> cannot distinguish between correct U2 and other Erased Unions (or even just `obj`) + testCase "error because of mismatching type U3" <| fun _ -> interfaceAndAssignments i [ assign "a" "U3" "U3.Case3 3.14" assignAnonRecord i [("Value", "a")] ] |> compile - |> Assert.Is.strictSuccess - testCase "Ideally: error because of missmatching type obj" <| fun _ -> + |> Assert.Is.Single.errorOrWarning + |> Assert.Exists.errorOrWarningMatching (Error.Regex.UnexpectedTypeMulti (interfaceName = i.Name, fieldName = "Value")) + testCase "error because of mismatching type obj" <| fun _ -> interfaceAndAnonRecordAssignment i [("Value", "obj()")] |> compile - |> Assert.Is.strictSuccess + |> Assert.Is.Single.errorOrWarning + |> Assert.Exists.errorOrWarningMatching (Error.Regex.UnexpectedTypeMulti (interfaceName = i.Name, fieldName = "Value")) testCase "Probably: no error because of double-bangs" <| fun _ -> interfaceAndAnonRecordAssignment i [("Value", "!!3.14")] |> compile @@ -433,8 +434,7 @@ let tests = |> compile |> Assert.Is.strictSuccess - testCase "Ideally: error for existing field with wrong int type" <| fun _ -> - // Erased Union gets reduced to `Any` -> `Any` accepts everything + testCase "error for existing field with wrong int type" <| fun _ -> [ yield! du |> DiscriminatedUnion.format yield! i |> Interface.format @@ -443,11 +443,10 @@ let tests = ] |> concat |> compile - // |> Assert.Is.Single.errorOrWarning - // |> Assert.Exists.errorOrWarningWith (Error.incorrectType "Tmp.ErasedUnion1" "Value") - |> Assert.Is.strictSuccess + |> Assert.Is.Single.errorOrWarning + |> Assert.Exists.errorOrWarningMatching (Error.Regex.UnexpectedType (interfaceName = i.Name, fieldName = "Value")) - testCase "Ideally: error for existing field with wrong obj type" <| fun _ -> + testCase "error for existing field with wrong obj type" <| fun _ -> [ yield! du |> DiscriminatedUnion.format yield! i |> Interface.format @@ -456,7 +455,8 @@ let tests = ] |> concat |> compile - |> Assert.Is.strictSuccess + |> Assert.Is.Single.errorOrWarning + |> Assert.Exists.errorOrWarningMatching (Error.Regex.UnexpectedType (interfaceName = i.Name, fieldName = "Value")) ] testList "interface with field with option type" [ @@ -592,7 +592,7 @@ let tests = |> concat |> compile |> Assert.Is.strictSuccess - testCase "Ideally: error for missmatching type U3" <| fun _ -> + testCase "error for mismatching type U3" <| fun _ -> [ yield! i |> Interface.format yield assign "a" "U3" "U3.Case3 3.14" @@ -601,7 +601,8 @@ let tests = ] |> concat |> compile - |> Assert.Is.strictSuccess + |> Assert.Is.Single.errorOrWarning + |> Assert.Exists.errorOrWarningMatching (Error.Regex.UnexpectedIndexerTypeMulti (interfaceName = i.Name, indexerName = "Item", fieldName = "Value")) ] testList "interface with two indexers U and int" [ // in TS: number indexer must be subset of string indexer diff --git a/tests/Rust/tests/src/InteropTests.fs b/tests/Rust/tests/src/InteropTests.fs index fde7e130c2..9ae5c0ccc5 100644 --- a/tests/Rust/tests/src/InteropTests.fs +++ b/tests/Rust/tests/src/InteropTests.fs @@ -44,12 +44,12 @@ module Subs = let sin (x: float): float = nativeOnly module Performance = - [] + [] type Duration = abstract as_millis: unit -> uint64 // actually u128 abstract as_secs_f64: unit -> float - [] + [] type Instant = abstract duration_since: Instant -> Duration abstract elapsed: unit -> Duration