diff --git a/src/Fable.Transforms/Babel/Fable2Babel.fs b/src/Fable.Transforms/Babel/Fable2Babel.fs index 5582a9ae22..dea2983193 100644 --- a/src/Fable.Transforms/Babel/Fable2Babel.fs +++ b/src/Fable.Transforms/Babel/Fable2Babel.fs @@ -4535,10 +4535,21 @@ but thanks to the optimisation done below we get ) let args = - Array.append baseFieldIds fieldIds - |> Array.map (fun fi -> - Parameter.parameter (fi.Name, ?typeAnnotation = makeFieldAnnotationIfTypeScript com ctx fi.Type) - ) + Array.append + (baseFieldIds + |> Array.map (fun fi -> + let typeAnnotation = + if com.IsTypeScript then + makeTypeAnnotation com ctx fi.Type |> Some + else + None + + Parameter.parameter (fi.Name, ?typeAnnotation = typeAnnotation) + )) + (fieldIds + |> Array.map (fun fi -> + Parameter.parameter (fi.Name, ?typeAnnotation = makeFieldAnnotationIfTypeScript com ctx fi.Type) + )) declareType com ctx ent decl.Name decl.XmlDoc args body baseExpr classMembers @@ -4822,7 +4833,17 @@ but thanks to the optimisation done below we get let classMembers = Array.append abstractClassMembers classMembers let exposedCons = - let argExprs = consArgs |> Array.map (fun p -> Expression.identifier (p.Name)) + let argExprs = + consArgs + |> Array.mapi (fun i p -> + let arg = Expression.identifier (p.Name) + + if consInfo.HasSpread && i = consArgs.Length - 1 then + Expression.spreadElement arg + else + arg + ) + let exposedConsBody = Expression.newExpression (classIdent, argExprs) makeFunctionExpression None (consArgs, exposedConsBody, returnType, typeParamDecl) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index b9ff3043a9..6a27775ad0 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -2497,6 +2497,86 @@ module Util = collect ent genArgs [] + let uncurryLikeDeclaredType (declaredType: Fable.Type) (actualType: Fable.Type) = + let rec arity = + function + | Fable.LambdaType(_, returnType) -> 1 + arity returnType + | _ -> 0 + + let rec uncurry n argTypes = + function + | Fable.LambdaType(argType, returnType) when n > 0 -> uncurry (n - 1) (argType :: argTypes) returnType + | returnType -> Fable.DelegateType(List.rev argTypes, returnType) + + match declaredType, actualType with + | Fable.Option(declaredType, _), Fable.Option(actualType, isStruct) when arity declaredType > 1 -> + Fable.Option(uncurry (arity declaredType) [] actualType, isStruct) + | _ when arity declaredType > 1 -> uncurry (arity declaredType) [] actualType + | _ -> actualType + + let tryGetPrimaryConstructorBase (ent: FSharpEntity) = + match tryGetBaseEntity ent with + | Some(baseEnt, baseGenArgs) when + baseEnt.IsClass + && not (isGlobalOrImportedFSharpEntity baseEnt) + && (FsEnt.Ref baseEnt).SourcePath.IsSome + -> + baseEnt.MembersFunctionsAndValues + |> Seq.tryFind _.IsImplicitConstructor + |> Option.map (fun cons -> baseEnt, baseGenArgs, cons) + | _ -> None + + let getPrimaryConstructorBaseArgs + (com: Compiler) + (ent: FSharpEntity) + (baseEnt: FSharpEntity) + (baseGenArgs: IList) + (cons: FSharpMemberOrFunctionOrValue) + = + let genArgs = + Seq.zip baseEnt.GenericParameters baseGenArgs + |> Seq.map (fun (param, typ) -> genParamName param, makeType Map.empty typ) + |> Map.ofSeq + + let sanitize conflicts name = + match com.Options.Language with + | Python -> + (Fable.Py.Naming.toPythonNaming name, Naming.NoMemberPart) + ||> Fable.Py.Naming.sanitizeIdent conflicts + | _ -> (name, Naming.NoMemberPart) ||> Naming.sanitizeJsIdent conflicts + + let usedNames = + ent.FSharpFields + |> Seq.filter (fun field -> not field.IsStatic) + |> Seq.map (FsField.FSharpFieldName >> sanitize (fun _ -> false)) + |> HashSet + + let isUsed name = + usedNames.Contains name + || com.Options.Language = Python && Fable.Py.Naming.pyBuiltins.Contains name + + match List.ofSeq (Seq.concat cons.CurriedParameterGroups) with + | [ param ] when isUnit param.Type && param.Name.IsNone -> [] + | parameters -> + parameters + |> List.mapi (fun i param -> + let name = param.Name |> Option.defaultValue $"arg%d{i}" |> sanitize isUsed + + usedNames.Add(name) |> ignore + + let typ = + uncurryLikeDeclaredType (makeType Map.empty param.Type) (makeType genArgs param.Type) + + makeTypedIdentExpr typ name + ) + + let tryGetPrimaryConstructorBaseArgTypes (ent: Fable.Entity) = + match ent with + | :? FsEnt as ent -> + tryGetPrimaryConstructorBase ent.FSharpEntity + |> Option.map (fun (_, _, cons) -> getArgTypes () cons) + | _ -> None + /// We can add a suffix to the entity name for special methods, like reflection declaration let entityIdentWithSuffix (com: Compiler) (ent: Fable.EntityRef) suffix = let error msg = @@ -2515,27 +2595,6 @@ module Util = let entityIdent (com: Compiler) (ent: Fable.EntityRef) = entityIdentWithSuffix com ent "" - /// Compiler-generated constructors of derived classes receive the inherited fields and pass them to the base - let tryMakeCompilerGeneratedBaseCall (com: Compiler) (ent: FSharpEntity) = - if supportsCompilerGeneratedBaseCall com && hasCompilerGeneratedConstructor ent then - match tryGetCompilerGeneratedBases ent with - | Some(_ :: _ as bases) -> - let baseEnt = List.last bases - - let args = - getInheritedValFields ent Map.empty - |> List.map (fun (field, typ) -> makeTypedIdentExpr typ field.Name) - - let baseExpr = - match tryGlobalOrImportedFSharpEntity com baseEnt with - | Some baseExpr -> baseExpr - | None -> FsEnt.Ref baseEnt |> entityIdent com - - Some(Fable.Call(baseExpr, Fable.CallInfo.Create(args = args), Fable.Unit, None)) - | _ -> None - else - None - /// In Python a union is emitted as a public type alias (`type Demo = Demo_A | Demo_B`) /// plus a private base class (`_Demo`) that holds the runtime members: attached static /// members and the `cases()` method used by reflection. The type alias is typing-only, @@ -2709,6 +2768,48 @@ module Util = ?entRef = entRef ) + /// Compiler-generated constructors of derived classes receive the inherited fields and pass them to the base + let tryMakeCompilerGeneratedBaseCall (com: Compiler) (ent: FSharpEntity) = + if supportsCompilerGeneratedBaseCall com && hasCompilerGeneratedConstructor ent then + match tryGetCompilerGeneratedBases ent with + | Some(_ :: _ as bases) -> + let baseEnt = List.last bases + + let args = + getInheritedValFields ent Map.empty + |> List.map (fun (field, typ) -> + let typ = uncurryLikeDeclaredType (makeType Map.empty field.FieldType) typ + makeTypedIdentExpr typ field.Name + ) + + let baseExpr = + match tryGlobalOrImportedFSharpEntity com baseEnt with + | Some baseExpr -> baseExpr + | None -> FsEnt.Ref baseEnt |> entityIdent com + + Some(Fable.Call(baseExpr, Fable.CallInfo.Create(args = args), Fable.Unit, None)) + | Some _ -> None + | None -> + match tryGetPrimaryConstructorBase ent, tryGetBaseEntity ent with + | Some(baseEnt, baseGenArgs, cons), _ -> + let args = getPrimaryConstructorBaseArgs com ent baseEnt baseGenArgs cons + let baseExpr = FsEnt.Ref baseEnt |> entityIdent com + + let info = Fable.CallInfo.Create(args = args, memberRef = getFunctionMemberRef cons) + + Some(Fable.Call(baseExpr, info, Fable.Unit, None)) + | None, Some(baseEnt, _) when + hasCompilerGeneratedConstructor baseEnt + && ent.MembersFunctionsAndValues |> Seq.exists _.IsConstructor + -> + $"%s{ent.FullName} has no primary constructor and inherits %s{baseEnt.FullName}, which has no primary constructor either but inherits a class that has one. Fable cannot compile this inheritance chain." + |> addError com [] (makeRange ent.DeclarationLocation |> Some) + + None + | _ -> None + else + None + let rec tryFindInTypeHierarchy (ent: FSharpEntity) filter = if filter ent then Some ent diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index ee731140ff..134ca17bd5 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -54,6 +54,36 @@ let private transformBaseConsCall | Some baseExpr -> baseExpr | None -> FsEnt.Ref baseEnt |> entityIdent com + let info = + match tryGetPrimaryConstructorBase baseEnt with + | Some(primaryBaseEnt, primaryBaseGenArgs, primaryCons) when + not baseCons.IsImplicitConstructor + && supportsCompilerGeneratedBaseCall com + && hasCompilerGeneratedConstructor baseEnt + -> + let primaryBaseParams = + getPrimaryConstructorBaseArgs com baseEnt primaryBaseEnt primaryBaseGenArgs primaryCons + + let primaryBaseArgs = + primaryBaseParams + |> List.map (fun param -> + match param.Type with + | Fable.Option(genArg, isStruct) -> + Fable.Value(Fable.NewOption(None, genArg, isStruct), None) + | t -> Replacements.Api.defaultof com ctx None t + ) + + let sigArgTypes = + match info.SignatureArgTypes with + | [] -> [] + | argTypes -> (primaryBaseParams |> List.map _.Type) @ argTypes + + { info with + Args = primaryBaseArgs @ info.Args + SignatureArgTypes = sigArgTypes + } + | _ -> info + Fable.Call(baseExpr, info, t, r) // Other cases, like Emit will call directly the base expression | e -> e @@ -742,6 +772,23 @@ let private transformUnionCaseTest return Fable.Test(unionExpr, Fable.UnionCaseTest(tag), r) } +let rec private (|BoundNewRecord|_|) (e: FSharpExpr) = + match e with + | FSharpExprPatterns.NewRecord(fsType, args) -> Some([], fsType, args) + | FSharpExprPatterns.Let((var, value, _), BoundNewRecord(bindings, fsType, args)) -> + Some((var, value) :: bindings, fsType, args) + | _ -> None + +// With a base, F# puts the `then` writes on self, such as `init@`, after the record +let rec private (|BoundNewRecordThen|_|) (e: FSharpExpr) = + match e with + | BoundNewRecord(bindings, fsType, args) -> Some(bindings, fsType, args, []) + | FSharpExprPatterns.Sequential(BoundNewRecordThen(bindings, fsType, args, rest), next) -> + Some(bindings, fsType, args, rest @ [ next ]) + | FSharpExprPatterns.Let((var, value, _), BoundNewRecordThen(bindings, fsType, args, rest)) -> + Some((var, value) :: bindings, fsType, args, rest) + | _ -> None + let private withDefaultValueFields (com: IFableCompiler) (ctx: Context) (fsType: FSharpType) (args: Fable.Expr list) = let ent = fsType.TypeDefinition @@ -894,7 +941,11 @@ let private (|ExplicitConstructorThen|_|) com (ctx: Context) (fsExpr: FSharpExpr && not m.IsImplicitConstructor && supportsCompilerGeneratedBaseCall com && m.DeclaringEntity - |> Option.exists (fun ent -> (tryGetBaseEntity ent).IsNone && not (isAttachMembersEntity com ent)) + |> Option.exists (fun ent -> + not (isAttachMembersEntity com ent) + && ((tryGetBaseEntity ent).IsNone + || hasCompilerGeneratedConstructor ent && (tryGetPrimaryConstructorBase ent).IsSome) + ) -> let isCreated (e: FSharpExpr) = e.Type.HasTypeDefinition @@ -1114,7 +1165,7 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs match ctx.BoundConstructorThis with | Some i -> identWithRange r i |> Fable.IdentExpr | None -> - "This explicit constructor uses a self identifier (`new() as x`), which Fable only supports on JavaScript, TypeScript and Python, in a class with no base class and without []." + "This explicit constructor uses a self identifier (`new() as x`), which Fable only supports on JavaScript, TypeScript and Python, in a class without [] whose base class, if any, has a primary constructor." |> addErrorAndReturnNull com ctx.InlinePath r | FSharpExprPatterns.Value var -> @@ -1737,6 +1788,138 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs return makeCallFrom com ctx (makeRangeFrom fsExpr) typ genArgs None args memb + | FSharpExprPatterns.Sequential(ConstructorCall(baseCons, baseGenArgs, baseArgs), + BoundNewRecordThen(bindings, fsType, ownArgs, thenExprs)) when + ctx.CaptureBaseConsCall.IsNone + && supportsCompilerGeneratedBaseCall com + && fsType.HasTypeDefinition + && hasCompilerGeneratedConstructor fsType.TypeDefinition + && (thenExprs.IsEmpty || not (isAttachMembersEntity com fsType.TypeDefinition)) + && ( + match baseCons.DeclaringEntity, tryGetPrimaryConstructorBase fsType.TypeDefinition with + | Some callee, Some(baseEnt, _, _) -> callee = baseEnt + | _ -> false + ) + -> + let r = makeRangeFrom fsExpr + + let baseArgs = + if not baseCons.IsImplicitConstructor then + $"%s{fsType.TypeDefinition.FullName} calls a secondary constructor of %s{baseCons.DeclaringEntity.Value.FullName}. Fable only supports calling the primary constructor of the base class here." + |> addError com ctx.InlinePath r + + [] + else + match + transformBaseConsCall com ctx r baseCons.DeclaringEntity.Value baseCons baseGenArgs baseArgs + with + | Fable.Call(_, info, _, _) -> dropUnitCallArg com info.Args info.SignatureArgTypes info.MemberRef + | _ -> [] + + let omittedBaseArgs = + match tryGetPrimaryConstructorBase fsType.TypeDefinition with + | Some(baseEnt, baseGenArgs, cons) -> + let baseParams = + getPrimaryConstructorBaseArgs com fsType.TypeDefinition baseEnt baseGenArgs cons + + baseParams + |> List.skip (min baseArgs.Length baseParams.Length) + |> List.map (fun param -> + match param.Type with + | Fable.Option(genArg, isStruct) -> Fable.Value(Fable.NewOption(None, genArg, isStruct), None) + | t -> Fable.Value(Fable.Null t, None) + ) + | None -> [] + + let baseArgs = baseArgs @ omittedBaseArgs + + let ctx, bindings = + ((ctx, []), bindings) + ||> List.fold (fun (ctx, bindings) (var, value) -> + let value = transformExpr com ctx [] value |> run + let ctx, ident = putIdentInScope com ctx var (Some value) + ctx, (ident, value) :: bindings + ) + + let bindings = List.rev bindings + + let withBindings body = + List.foldBack (fun (ident, value) body -> Fable.Let(ident, value, body)) bindings body + + let! ownArgs = transformExprList com ctx ownArgs + + let ownArgs = + withDefaultValueFields com ctx fsType ownArgs + |> withOmittedFieldDefaults com ctx fsType + + let genArgs = makeTypeGenArgs ctx.GenericArgs (getGenericArguments fsType) + let entRef = FsEnt.Ref fsType.TypeDefinition + entRef.SourcePath |> Option.iter com.AddSignatureDependency + + let thisIdent = + getIdentUniqueName com ctx "this" + |> makeTypedIdent (makeType ctx.GenericArgs fsType) + + let! thenExprs = + transformExprList com { ctx with BoundConstructorThis = Some thisIdent } thenExprs + + let withThen created = + match thenExprs with + | [] -> created + | _ -> Fable.Let(thisIdent, created, Fable.Sequential(thenExprs @ [ Fable.IdentExpr thisIdent ])) + + if + ownArgs |> List.exists (canHaveSideEffects com) + || bindings |> List.exists (snd >> canHaveSideEffects com) + then + let fields = + fsType.TypeDefinition.FSharpFields + |> Seq.filter (fun field -> not field.IsStatic) + |> List.ofSeq + + let placeholders = + ownArgs + |> List.map (fun arg -> + match arg.Type with + | Fable.Option(genArg, isStruct) -> Fable.Value(Fable.NewOption(None, genArg, isStruct), None) + | NestedLambdaType(argTypes, returnType) when argTypes.Length > 1 -> + Replacements.Api.defaultof com ctx None (Fable.DelegateType(argTypes, returnType)) + | t -> Replacements.Api.defaultof com ctx None t + ) + + let instance = makeTypedIdent Fable.Any (getIdentUniqueName com ctx "instance") + + let fieldSets = + List.zip fields ownArgs + |> List.map (fun (field, arg) -> + Fable.Set( + Fable.IdentExpr instance, + Fable.FieldSet(FsField.FSharpFieldName field), + makeType Map.empty field.FieldType, + arg, + None + ) + ) + + let record = + Fable.NewRecord(baseArgs @ placeholders, entRef, genArgs) |> makeValue r + + let typ = makeType ctx.GenericArgs fsType + + return + Fable.Let( + instance, + record, + Fable.Sequential(fieldSets @ [ Fable.TypeCast(Fable.IdentExpr instance, typ) |> withThen ]) + |> withBindings + ) + else + return + Fable.NewRecord(baseArgs @ ownArgs, entRef, genArgs) + |> makeValue r + |> withThen + |> withBindings + | FSharpExprPatterns.Sequential(ConstructorCall(baseCons, _, _) as first, FSharpExprPatterns.NewRecord(fsType, ownArgs)) when ctx.CaptureBaseConsCall.IsNone @@ -1759,7 +1942,12 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs let inheritedArgs = getInheritedValFields fsType.TypeDefinition ctx.GenericArgs - |> List.map (fun (field, typ) -> getFieldWith None typ (Fable.IdentExpr baseIdent) field.Name) + |> List.map (fun (field, typ) -> + let info = + Fable.FieldInfo.Create(field.Name, fieldType = typ, maybeCalculated = true) + + Fable.Get(Fable.IdentExpr baseIdent, info, typ, None) + ) let genArgs = makeTypeGenArgs ctx.GenericArgs (getGenericArguments fsType) let entRef = FsEnt.Ref fsType.TypeDefinition diff --git a/src/Fable.Transforms/FableTransforms.fs b/src/Fable.Transforms/FableTransforms.fs index 70730b2074..425e8b97cb 100644 --- a/src/Fable.Transforms/FableTransforms.fs +++ b/src/Fable.Transforms/FableTransforms.fs @@ -662,6 +662,9 @@ module private Transforms = Get(innerExpr, OptionValue, t, r) | Value(NewOption(Some(Extended(Curry(innerExpr, arity2), _)), t, isStruct), r), _ when matches arity arity2 -> Value(NewOption(Some(innerExpr), t, isStruct), r) + | Value(NewOption(None, (LambdaType _ as t), isStruct), r), Some arity -> + let argTypes, returnType = uncurryLambdaType arity [] t + Value(NewOption(None, DelegateType(argTypes, returnType), isStruct), r) // User imports are uncurried even if they're typed as lambdas, see test "ofImport should inline properly" | Import({ Kind = UserImport _ }, _, _), _ -> expr | _, Some arity -> Replacements.Api.uncurryExprAtRuntime com arity expr @@ -877,10 +880,22 @@ module private Transforms = Emit({ emitInfo with CallInfo = { callInfo with Args = args } }, t, r) // Uncurry also values in setters or new record/union/tuple | Value(NewRecord(args, ent, genArgs), r) -> - let args = - com.GetEntity(ent).FSharpFields - |> Seq.filter (fun f -> not f.IsStatic) - |> uncurryConsArgs args + let entity = com.GetEntity(ent) + + let fieldTypes = + entity.FSharpFields + |> List.filter (fun f -> not f.IsStatic) + |> List.map (fun f -> f.FieldType) + + let baseArgTypes = + match args.Length - fieldTypes.Length with + | baseArgCount when baseArgCount > 0 -> + match FSharp2Fable.Util.tryGetPrimaryConstructorBaseArgTypes entity with + | Some argTypes -> argTypes @ List.replicate baseArgCount Any |> List.take baseArgCount + | None -> args |> List.take baseArgCount |> List.map (fun a -> a.Type) + | _ -> [] + + let args = uncurryArgs com false (baseArgTypes @ fieldTypes) args Value(NewRecord(args, ent, genArgs), r) | Value(NewAnonymousRecord(args, fieldNames, genArgs, isStruct), r) -> diff --git a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs index c77d94d28c..83ef5f0828 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs @@ -4579,7 +4579,12 @@ let transformClassWithCompilerGeneratedConstructor ] let args = - baseFieldIds @ Array.toList fieldIds + let fieldIds = + fieldIds + |> Array.toList + |> List.map (fun id -> { id with Type = FableTransforms.uncurryType id.Type }) + + baseFieldIds @ fieldIds |> List.map (fun id -> let ta, _ = Annotation.typeAnnotation com ctx None id.Type Arg.arg (ident com ctx id, annotation = ta) @@ -4637,7 +4642,7 @@ let transformClassWithPrimaryConstructor else [], [] - let consArgs, consBody, _returnType = + let consArgs, consBody, _returnType, varArgInit = if classAttributes.Style = ClassStyle.Attributes then if classAttributes.Init then // For attributes style with init=true, generate new constructor with property names @@ -4673,13 +4678,24 @@ let transformClassWithPrimaryConstructor assign None left right |> exprAsStatement ctx ) - propertyArgs, propertyBody, Expression.none + propertyArgs, propertyBody, Expression.none, [] else // For attributes style with init=false, no constructor arguments or body - Arguments.empty, [], Expression.none + Arguments.empty, [], Expression.none, [] else let info = com.GetMember(cons.MemberRef) - getMemberArgsAndBody com ctx ClassConstructor info.HasSpread cons.Args cons.Body + + let usedIn = cons.Body :: Option.toList classDecl.BaseCall + + let args, varArgInit = + match tryMakeConstructorVarArgTuple ctx info.HasSpread cons.Args usedIn with + | Some(args, (varArg, array)) -> args, transformBindingAsStatements com ctx varArg array + | None -> cons.Args, [] + + let consArgs, consBody, returnType = + getMemberArgsAndBody com ctx ClassConstructor info.HasSpread args cons.Body + + consArgs, consBody, returnType, varArgInit let isOptional = Helpers.isOptional (cons.Args |> Array.ofList) @@ -4701,7 +4717,13 @@ let transformClassWithPrimaryConstructor // Don't generate exposed constructor for abstract classes since they can't be instantiated [] else - let argExprs = consArgs.Args |> List.map (fun p -> Expression.identifier p.Arg) + let argExprs = + let args = consArgs.Args |> List.map (fun p -> Expression.identifier p.Arg) + + match consArgs.VarArg with + | Some varArg -> args @ [ Expression.starred (Expression.identifier varArg.Arg) ] + | None -> args + let exposedConsBody = Expression.call (classIdent, argExprs) let name = com.GetIdentifier(ctx, cons.Name) @@ -4746,6 +4768,8 @@ let transformClassWithPrimaryConstructor ) |> Option.defaultValue (None, consBody) + let consBody = varArgInit @ consBody + [ yield! stmts yield! diff --git a/src/Fable.Transforms/Python/Fable2Python.Util.fs b/src/Fable.Transforms/Python/Fable2Python.Util.fs index 23bfd37303..09f0cf44cb 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Util.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Util.fs @@ -114,6 +114,40 @@ module Util = | Fable.Array(elemType, _) -> elemType | _ -> Fable.Any // Fallback if not array type + let tryMakeConstructorVarArgTuple + (ctx: Context) + (hasSpread: bool) + (args: Fable.Ident list) + (usedIn: Fable.Expr list) + = + match List.tryLast args with + | Some varArg when hasSpread -> + let argNames = args |> List.map _.Name |> set + + let tupleName = + ("_" + varArg.Name, Naming.NoMemberPart) + ||> Naming.sanitizeIdent (fun name -> + argNames.Contains name + || ctx.UsedNames.RootScope.Contains name + || ctx.UsedNames.CurrentDeclarationScope.Contains name + || usedIn |> List.exists (isIdentUsed name) + ) + + let varArgTuple = { varArg with Name = tupleName } + + let array = + Fable.Value( + Fable.NewArray( + Fable.ArrayFrom(Fable.IdentExpr varArgTuple), + getVarArgElementType varArg.Type, + Fable.MutableArray + ), + None + ) + + Some(List.take (args.Length - 1) args @ [ varArgTuple ], (varArg, array)) + | _ -> None + /// Splits a list of items into regular items and an optional vararg item. /// When hasSpread is true and items is non-empty, the last item becomes the vararg. let splitVarArg<'T> (hasSpread: bool) (items: 'T list) : 'T list * 'T option = diff --git a/tests/Integration/Compiler/CompilerMessagesTests.fs b/tests/Integration/Compiler/CompilerMessagesTests.fs index 7fe26be375..be0816b933 100644 --- a/tests/Integration/Compiler/CompilerMessagesTests.fs +++ b/tests/Integration/Compiler/CompilerMessagesTests.fs @@ -107,7 +107,7 @@ type Derived = |> Assert.Exists.errorWith "uses a self identifier" |> ignore - testCase "Self identifier with a primary constructor base results in error" <| fun _ -> + testCase "Self identifier with a primary constructor base compiles" <| fun _ -> let source = """ type Base(a: int) = @@ -119,7 +119,7 @@ type Derived = new (a: int) as self = { inherit Base(a); Y = 0 } then self.Y <- self.A + 1 """ compile source - |> Assert.Exists.errorWith "uses a self identifier" + |> Assert.Is.strictSuccess |> ignore testCase "Self identifier with a BCL base results in error" <| fun _ -> @@ -1114,4 +1114,36 @@ let one = AuthState.One "a" compile source |> Assert.Is.strictSuccess |> ignore + + testCase "Explicit constructor calling a secondary constructor of a base with a primary constructor results in error" <| fun _ -> + let source = + """ +type Base(x: int) = + new (s: string) = Base(s.Length) + member _.X = x + +type Derived = + inherit Base + val Y: int + new (s: string) = { inherit Base(s); Y = 1 } +""" + compile source + |> Assert.Exists.errorWith "Derived calls a secondary constructor of" + |> ignore + + testCase "Explicit constructor calling a parameterless secondary constructor of a base results in error" <| fun _ -> + let source = + """ +type Base(x: int) = + new () = Base(42) + member _.X = x + +type Derived = + inherit Base + val Y: int + new () = { inherit Base(); Y = 1 } +""" + compile source + |> Assert.Exists.errorWith "calls a secondary constructor" + |> ignore ] diff --git a/tests/Js/Main/TypeTests.fs b/tests/Js/Main/TypeTests.fs index f646f7f8d5..2ee949a34c 100644 --- a/tests/Js/Main/TypeTests.fs +++ b/tests/Js/Main/TypeTests.fs @@ -1019,6 +1019,276 @@ type SelfPrimaryBase(x: int) as this = type SelfPrimaryChild() = inherit SelfPrimaryBase(2) +type PrimaryCtorBase(x: int) = + member _.X = x + member val Tag = "base" with get, set + member this.Describe() = this.Tag + ":" + string x + +type PrimaryCtorValChild = + inherit PrimaryCtorBase + val Label: string + new (x: int, label: string) = { inherit PrimaryCtorBase(x); Label = label } + new (label: string) = { inherit PrimaryCtorBase(label.Length); Label = label } + +type PrimaryCtorUnitBase() = + member val Greeting = "hi" + +type PrimaryCtorUnitChild = + inherit PrimaryCtorUnitBase + val Count: int + new (count: int) = { inherit PrimaryCtorUnitBase(); Count = count } + +type PrimaryCtorGenericBase<'T>(value: 'T, size: int) = + member _.Value = value + member _.Size = size + +type PrimaryCtorGenericChild<'T> = + inherit PrimaryCtorGenericBase<'T list> + val Item: 'T + new (item: 'T) = { inherit PrimaryCtorGenericBase<'T list>([ item; item ], 2); Item = item } + +type PrimaryCtorNamedBase(label: string) = + member _.BaseLabel = label + +type PrimaryCtorSameNameChild = + inherit PrimaryCtorNamedBase + val label: string + new (a: string, b: string) = { inherit PrimaryCtorNamedBase(a); label = b } + +type PrimaryCtorOptionalBase(a: int, ?b: string, ?c: int) = + member _.Describe = $"""{a}-{defaultArg b "B"}-{defaultArg c 7}""" + +type PrimaryCtorOptionalChild = + inherit PrimaryCtorOptionalBase + val Z: int + new () = { inherit PrimaryCtorOptionalBase(1); Z = 9 } + new (b: string) = { inherit PrimaryCtorOptionalBase(1, b); Z = 8 } + new (c: int) = { inherit PrimaryCtorOptionalBase(1, c = c); Z = 7 } + +type PrimaryCtorParamArrayBase(a: int, [] xs: int[]) = + member _.Total = a + Array.sum xs + member _.Count = xs.Length + +type PrimaryCtorParamArrayChild = + inherit PrimaryCtorParamArrayBase + val Z: int + new () = { inherit PrimaryCtorParamArrayBase(1, 2, 3); Z = 5 } + new (xs: int[]) = { inherit PrimaryCtorParamArrayBase(1, xs); Z = 6 } + new (_: bool) = { inherit PrimaryCtorParamArrayBase(1); Z = 7 } + +type PrimaryCtorSuffixBase(x: int, x_: int) = + member _.Sum = x * 10 + x_ + +type PrimaryCtorSuffixChild = + inherit PrimaryCtorSuffixBase + val x: int + new (a: int, b: int, c: int) = { inherit PrimaryCtorSuffixBase(a, b); x = c } + +type PrimaryCtorKeywordBase(``switch``: int, pass: int) = + member _.Value = ``switch`` * 10 + pass + +type PrimaryCtorKeywordChild = + inherit PrimaryCtorKeywordBase + val Z: int + new (a: int) = { inherit PrimaryCtorKeywordBase(a, 2); Z = 1 } + +type PrimaryCtorFunctionBase(f: int -> int -> int) = + member _.Apply a b = f a b + +type PrimaryCtorFunctionChild = + inherit PrimaryCtorFunctionBase + val G: int -> int -> int + new (n: int, g: int -> int -> int) = { inherit PrimaryCtorFunctionBase(fun a b -> a + b + n); G = g } + +type PrimaryCtorDefaultValueChild = + inherit PrimaryCtorFunctionBase + val G: int -> int -> int + [] val mutable D: int + new (g: int -> int -> int) = { inherit PrimaryCtorFunctionBase(fun a b -> a + b); G = g } + +type PrimaryCtorVirtualBase(name: string) = + abstract Calc: int -> int + default _.Calc a = a + member _.Name = name + +type PrimaryCtorVirtualChild = + inherit PrimaryCtorVirtualBase + val K: int + val F: int -> int -> int + new (k, f) = { inherit PrimaryCtorVirtualBase("base"); K = k; F = f } + override this.Calc a = a * this.K + +let multiplyInts (a: int) (b: int) = a * b + +type ValClassFunctionBase = + val F: int -> int -> int + new (f) = { F = f } + +type ValClassFunctionChild = + inherit ValClassFunctionBase + val G: int -> int -> int + new (f, g) = { inherit ValClassFunctionBase(f); G = g } + +type PrimaryCtorArrayBase(xs: int[]) = + member _.Items = xs + +type PrimaryCtorParamArrayForwardingChild([] xs: int[]) = + inherit PrimaryCtorArrayBase(xs) + member _.First = xs.[0] + +type PrimaryCtorParamArrayPrefixed(_xs: int, [] xs: int[]) = + member _.Value = _xs + xs.Length + +type PrimaryCtorSelfBase(self: int, this: int) = + member _.Sum = self * 10 + this + +type PrimaryCtorSelfChild = + inherit PrimaryCtorSelfBase + val Z: int + new (a: int) = { inherit PrimaryCtorSelfBase(a, 2); Z = 4 } + +type PrimaryCtorTypedFunctionBase(f: int -> int -> int, g: int * int -> int, h: (int -> int -> int) option) = + member _.F a b = f a b + member _.G a b = g (a, b) + member _.H a b = match h with Some h -> h a b | None -> -1 + +type PrimaryCtorTypedFunctionChild = + inherit PrimaryCtorTypedFunctionBase + val Z: int + new (f: int -> int -> int) = { inherit PrimaryCtorTypedFunctionBase(f, (fun (a, b) -> a * b), Some(fun a b -> a + b)); Z = 5 } + +type PrimaryCtorGenericFunctionBase<'T>(v: 'T, f: int -> int -> 'T) = + member _.V = v + member _.F a b = f a b + +type PrimaryCtorGenericFunctionChild = + inherit PrimaryCtorGenericFunctionBase int -> int> + val Z: int + new () = { inherit PrimaryCtorGenericFunctionBase int -> int>((fun a b -> a * 10 + b), (fun a b c d -> a + b + c * d)); Z = 1 } + +type PrimaryCtorNamedUnitBase(u: unit) = + member _.Unit = u + member val Value = 42 + +type PrimaryCtorNamedUnitChild = + inherit PrimaryCtorNamedUnitBase + val Count: int + new (count: int) = { inherit PrimaryCtorNamedUnitBase(()); Count = count } + +let primaryCtorOrderLog = ResizeArray() +let mutable primaryCtorOrderCounter = 0 + +let logPrimaryCtorOrder step value = + primaryCtorOrderLog.Add step + value + +let primaryCtorThrowingLog = ResizeArray() + +type PrimaryCtorOrderBase(n: int) = + do primaryCtorOrderLog.Add $"base body %d{n}" + do primaryCtorOrderCounter <- primaryCtorOrderCounter + 1 + +type PrimaryCtorOrderChild = + inherit PrimaryCtorOrderBase + val Snapshot: int + val Label: string + val Combine: int -> int -> int + new () = + { inherit PrimaryCtorOrderBase(logPrimaryCtorOrder "base arg" 7) + Snapshot = logPrimaryCtorOrder "Snapshot" primaryCtorOrderCounter + Label = logPrimaryCtorOrder "Label" "l" + Combine = logPrimaryCtorOrder "Combine" (fun a b -> a * 10 + b) } + +type PrimaryCtorThrowingBase(n: int) = + do failwith $"base failed %d{n}" + +type PrimaryCtorThrowingChild = + inherit PrimaryCtorThrowingBase + val X: int + new () = { inherit PrimaryCtorThrowingBase(1); X = (primaryCtorThrowingLog.Add "X"; 1) } + +let primaryCtorOutOfOrderLog = ResizeArray() + +let logPrimaryCtorOutOfOrder step value = + primaryCtorOutOfOrderLog.Add step + value + +type PrimaryCtorOutOfOrderChild = + inherit PrimaryCtorBase + val P: int + val Q: int + new (a: int, b: int) = { inherit PrimaryCtorBase(a); Q = b + 1; P = a } + +type PrimaryCtorOutOfOrderLoggedBase(n: int) = + do primaryCtorOutOfOrderLog.Add $"base body %d{n}" + +type PrimaryCtorOutOfOrderLoggedChild = + inherit PrimaryCtorOutOfOrderLoggedBase + val X: int + val Y: int + new (a: int) = + { inherit PrimaryCtorOutOfOrderLoggedBase(logPrimaryCtorOutOfOrder "base arg" a) + Y = logPrimaryCtorOutOfOrder "Y" (a + 1) + X = logPrimaryCtorOutOfOrder "X" a } + +let passPrimaryCtorValue value = value + +type PrimaryCtorOptionFunctionChild = + inherit PrimaryCtorBase + val H: (int -> int -> int) option + new () = { inherit PrimaryCtorBase(1); H = passPrimaryCtorValue (Some(fun a b -> a * 10 + b)) } + +type PrimaryCtorExplicitMid = + inherit PrimaryCtorBase + val B: int + new (b) = { inherit PrimaryCtorBase(1); B = b } + +type PrimaryCtorOverExplicitLeaf(c: int) = + inherit PrimaryCtorExplicitMid(c) + member _.C = c + +let primaryCtorThenLog = ResizeArray() +let primaryCtorThenSaved = ResizeArray() + +type PrimaryCtorThenBase(a: int) = + do primaryCtorThenLog.Add "base" + member _.A = a + +type PrimaryCtorThenChild = + inherit PrimaryCtorThenBase + val X: int + val Y: string + new (n: int) = + let v = n + 1 + { inherit PrimaryCtorThenBase((primaryCtorThenLog.Add "arg"; v)); Y = (primaryCtorThenLog.Add "y"; "y"); X = (primaryCtorThenLog.Add "x"; v * 2) } + then primaryCtorThenLog.Add "then" + +type PrimaryCtorThenSelfChild = + inherit PrimaryCtorThenBase + val mutable X: int + new (x: int) as self = { inherit PrimaryCtorThenBase(x); X = x } then primaryCtorThenSaved.Add(box self); self.X <- self.X + self.A + new () as self = PrimaryCtorThenSelfChild(7) then self.X <- self.X * 2; primaryCtorThenSaved.Add(box self) + +type PrimaryCtorThenGenericBase<'T>(a: 'T) = + member _.A = a + +type PrimaryCtorThenGenericChild<'T> = + inherit PrimaryCtorThenGenericBase<'T> + val mutable Items: 'T list + new (a: 'T) as self = { inherit PrimaryCtorThenGenericBase<'T>(a); Items = [] } then self.Items <- [ self.A; a ] + +type PrimaryCtorThenSelfBase(a: int) as this = + let mutable touched = 0 + do this.Touch() + member _.A = a + member _.Touched = touched + member _.Touch() = touched <- touched + 1 + +type PrimaryCtorThenSelfBaseChild = + inherit PrimaryCtorThenSelfBase + val X: int + new () = { inherit PrimaryCtorThenSelfBase(1); X = 2 } + let tests = testList "Types" [ @@ -2071,4 +2341,185 @@ let tests = valClassSelfSaved.Count |> equal 3 obj.ReferenceEquals(valClassSelfSaved.[1], b) |> equal true obj.ReferenceEquals(valClassSelfSaved.[2], b) |> equal true + + testCase "Explicit constructor calls a base primary constructor" <| fun () -> + let child = PrimaryCtorValChild(3, "origin") + child.X |> equal 3 + child.Label |> equal "origin" + child.Tag |> equal "base" + child.Describe() |> equal "base:3" + (box child :? PrimaryCtorBase) |> equal true + let other = PrimaryCtorValChild("abcd") + other.X |> equal 4 + other.Label |> equal "abcd" + + testCase "Explicit constructor calls a parameterless base primary constructor" <| fun () -> + let child = PrimaryCtorUnitChild(4) + child.Greeting |> equal "hi" + child.Count |> equal 4 + (box child :? PrimaryCtorUnitBase) |> equal true + + testCase "Explicit constructor resolves the generic arguments of a base primary constructor" <| fun () -> + let child = PrimaryCtorGenericChild(7) + child.Value |> equal [ 7; 7 ] + child.Size |> equal 2 + child.Item |> equal 7 + + testCase "Explicit constructor keeps base primary constructor arguments apart from fields of the same name" <| fun () -> + let child = PrimaryCtorSameNameChild("a", "b") + child.BaseLabel |> equal "a" + child.label |> equal "b" + + testCase "Explicit constructor passes omitted optional arguments to a base primary constructor" <| fun () -> + let child = PrimaryCtorOptionalChild() + child.Describe |> equal "1-B-7" + child.Z |> equal 9 + let child = PrimaryCtorOptionalChild("x") + child.Describe |> equal "1-x-7" + child.Z |> equal 8 + let child = PrimaryCtorOptionalChild(3) + child.Describe |> equal "1-B-3" + child.Z |> equal 7 + + testCase "Explicit constructor passes ParamArray arguments to a base primary constructor" <| fun () -> + let child = PrimaryCtorParamArrayChild() + child.Total |> equal 6 + child.Count |> equal 2 + child.Z |> equal 5 + let child = PrimaryCtorParamArrayChild([| 4; 5 |]) + child.Total |> equal 10 + child.Count |> equal 2 + child.Z |> equal 6 + let child = PrimaryCtorParamArrayChild(true) + child.Total |> equal 1 + child.Count |> equal 0 + child.Z |> equal 7 + + testCase "Explicit constructor gives distinct names to base primary constructor arguments" <| fun () -> + let child = PrimaryCtorSuffixChild(1, 2, 3) + child.Sum |> equal 12 + child.x |> equal 3 + + testCase "Explicit constructor accepts base primary constructor arguments named after keywords" <| fun () -> + let child = PrimaryCtorKeywordChild(4) + child.Value |> equal 42 + child.Z |> equal 1 + + testCase "Explicit constructor passes functions to a base primary constructor and its own fields" <| fun () -> + let child = PrimaryCtorFunctionChild(1, (fun a b -> a * b)) + child.Apply 2 3 |> equal 6 + child.G 2 3 |> equal 6 + + testCase "Explicit constructor passes base arguments next to a DefaultValue field" <| fun () -> + let child = PrimaryCtorDefaultValueChild(fun a b -> a * b) + child.D <- 7 + child.Apply 2 3 |> equal 5 + child.G 5 2 |> equal 10 + child.D |> equal 7 + + testCase "Object expression over an explicit constructor passes its own fields" <| fun () -> + let o = { new PrimaryCtorVirtualChild(3, multiplyInts) with override this.Calc a = a + this.K * 100 } + o.Calc 1 |> equal 301 + o.F 2 3 |> equal 6 + + testCase "Explicit constructor copies inherited function fields" <| fun () -> + let child = ValClassFunctionChild((fun a b -> a - b), (fun a b -> a * b)) + child.F 5 2 |> equal 3 + child.G 5 2 |> equal 10 + + testCase "Primary constructor forwards its ParamArray to the base constructor" <| fun () -> + let child = PrimaryCtorParamArrayForwardingChild(9, 8) + child.Items |> equal [| 9; 8 |] + child.First |> equal 9 + + testCase "Primary constructor ParamArray does not clash with other arguments" <| fun () -> + PrimaryCtorParamArrayPrefixed(1, 2, 3).Value |> equal 3 + + testCase "Explicit constructor accepts base primary constructor arguments named self or this" <| fun () -> + let child = PrimaryCtorSelfChild(3) + child.Sum |> equal 32 + child.Z |> equal 4 + + testCase "Explicit constructor passes curried, tupled and optional functions to a base primary constructor" <| fun () -> + let child = PrimaryCtorTypedFunctionChild(fun a b -> a - b) + child.F 5 2 |> equal 3 + child.G 3 4 |> equal 12 + child.H 3 4 |> equal 7 + child.Z |> equal 5 + + testCase "Explicit constructor keeps functions passed to a generic base primary constructor parameter curried" <| fun () -> + let child = PrimaryCtorGenericFunctionChild() + child.V 1 2 |> equal 12 + child.F 1 2 3 4 |> equal 15 + child.Z |> equal 1 + + testCase "Explicit constructor passes a named unit argument to a base primary constructor" <| fun () -> + let child = PrimaryCtorNamedUnitChild(3) + child.Count |> equal 3 + child.Value |> equal 42 + child.Unit |> equal () + + testCase "Explicit constructor evaluates its field values after the base primary constructor" <| fun () -> + primaryCtorOrderLog.Clear() + let counter = primaryCtorOrderCounter + let child = PrimaryCtorOrderChild() + List.ofSeq primaryCtorOrderLog |> equal [ "base arg"; "base body 7"; "Snapshot"; "Label"; "Combine" ] + child.Snapshot |> equal (counter + 1) + child.Label |> equal "l" + child.Combine 1 2 |> equal 12 + + testCase "Explicit constructor skips its field values when the base primary constructor throws" <| fun () -> + primaryCtorThrowingLog.Clear() + let failed = + try + PrimaryCtorThrowingChild() |> ignore + false + with _ -> + true + failed |> equal true + List.ofSeq primaryCtorThrowingLog |> equal [] + + testCase "Explicit constructor sets fields listed out of declaration order" <| fun () -> + let child = PrimaryCtorOutOfOrderChild(1, 2) + child.P |> equal 1 + child.Q |> equal 3 + child.Describe() |> equal "base:1" + + testCase "Explicit constructor evaluates fields listed out of declaration order after the base primary constructor" <| fun () -> + primaryCtorOutOfOrderLog.Clear() + let child = PrimaryCtorOutOfOrderLoggedChild(5) + List.ofSeq primaryCtorOutOfOrderLog |> equal [ "base arg"; "base body 5"; "Y"; "X" ] + child.X |> equal 5 + child.Y |> equal 6 + + testCase "Explicit constructor sets an option of a curried function computed after the base primary constructor" <| fun () -> + let child = PrimaryCtorOptionFunctionChild() + child.H.Value 1 2 |> equal 12 + + testCase "Primary constructor passes its arguments to an explicit base constructor over a primary constructor" <| fun () -> + let leaf = PrimaryCtorOverExplicitLeaf(4) + leaf.B |> equal 4 + leaf.C |> equal 4 + + testCase "Explicit constructor runs its then block after a base primary constructor" <| fun () -> + primaryCtorThenLog.Clear() + let c = PrimaryCtorThenChild(1) + (c.A, c.X, c.Y) |> equal (2, 4, "y") + List.ofSeq primaryCtorThenLog |> equal [ "arg"; "base"; "y"; "x"; "then" ] + + testCase "Explicit constructor with a self identifier works with a base primary constructor" <| fun () -> + primaryCtorThenSaved.Clear() + let a = PrimaryCtorThenSelfChild(3) + (a.A, a.X) |> equal (3, 6) + obj.ReferenceEquals(primaryCtorThenSaved.[0], a) |> equal true + let b = PrimaryCtorThenSelfChild() + (b.A, b.X) |> equal (7, 28) + obj.ReferenceEquals(primaryCtorThenSaved.[1], b) |> equal true + obj.ReferenceEquals(primaryCtorThenSaved.[2], b) |> equal true + let g = PrimaryCtorThenGenericChild("q") + g.Items |> equal [ "q"; "q" ] + + testCase "Explicit constructor works with a base primary constructor that has a self identifier" <| fun () -> + let c = PrimaryCtorThenSelfBaseChild() + (c.A, c.Touched, c.X) |> equal (1, 1, 2) ] diff --git a/tests/Python/TestType.fs b/tests/Python/TestType.fs index ab9fc9476c..60f9a40225 100644 --- a/tests/Python/TestType.fs +++ b/tests/Python/TestType.fs @@ -2117,3 +2117,482 @@ let ``test explicit constructor self identifier is the created object`` () = valClassSelfSaved.Count |> equal 3 obj.ReferenceEquals(valClassSelfSaved.[1], b) |> equal true obj.ReferenceEquals(valClassSelfSaved.[2], b) |> equal true + +type PrimaryCtorBase(x: int) = + member _.X = x + member val Tag = "base" with get, set + member this.Describe() = this.Tag + ":" + string x + +type PrimaryCtorValChild = + inherit PrimaryCtorBase + val Label: string + new (x: int, label: string) = { inherit PrimaryCtorBase(x); Label = label } + new (label: string) = { inherit PrimaryCtorBase(label.Length); Label = label } + +type PrimaryCtorUnitBase() = + member val Greeting = "hi" + +type PrimaryCtorUnitChild = + inherit PrimaryCtorUnitBase + val Count: int + new (count: int) = { inherit PrimaryCtorUnitBase(); Count = count } + +type PrimaryCtorGenericBase<'T>(value: 'T, size: int) = + member _.Value = value + member _.Size = size + +type PrimaryCtorGenericChild<'T> = + inherit PrimaryCtorGenericBase<'T list> + val Item: 'T + new (item: 'T) = { inherit PrimaryCtorGenericBase<'T list>([ item; item ], 2); Item = item } + +type PrimaryCtorNamedBase(label: string) = + member _.BaseLabel = label + +type PrimaryCtorSameNameChild = + inherit PrimaryCtorNamedBase + val label: string + new (a: string, b: string) = { inherit PrimaryCtorNamedBase(a); label = b } + +type PrimaryCtorOptionalBase(a: int, ?b: string, ?c: int) = + member _.Describe = $"""{a}-{defaultArg b "B"}-{defaultArg c 7}""" + +type PrimaryCtorOptionalChild = + inherit PrimaryCtorOptionalBase + val Z: int + new () = { inherit PrimaryCtorOptionalBase(1); Z = 9 } + new (b: string) = { inherit PrimaryCtorOptionalBase(1, b); Z = 8 } + new (c: int) = { inherit PrimaryCtorOptionalBase(1, c = c); Z = 7 } + +type PrimaryCtorParamArrayBase(a: int, [] xs: int[]) = + member _.Total = a + Array.sum xs + member _.Count = xs.Length + +type PrimaryCtorParamArrayChild = + inherit PrimaryCtorParamArrayBase + val Z: int + new () = { inherit PrimaryCtorParamArrayBase(1, 2, 3); Z = 5 } + new (xs: int[]) = { inherit PrimaryCtorParamArrayBase(1, xs); Z = 6 } + new (_: bool) = { inherit PrimaryCtorParamArrayBase(1); Z = 7 } + +type PrimaryCtorSuffixBase(x: int, x_: int) = + member _.Sum = x * 10 + x_ + +type PrimaryCtorSuffixChild = + inherit PrimaryCtorSuffixBase + val x: int + new (a: int, b: int, c: int) = { inherit PrimaryCtorSuffixBase(a, b); x = c } + +type PrimaryCtorKeywordBase(``switch``: int, pass: int) = + member _.Value = ``switch`` * 10 + pass + +type PrimaryCtorKeywordChild = + inherit PrimaryCtorKeywordBase + val Z: int + new (a: int) = { inherit PrimaryCtorKeywordBase(a, 2); Z = 1 } + +type PrimaryCtorFunctionBase(f: int -> int -> int) = + member _.Apply a b = f a b + +type PrimaryCtorFunctionChild = + inherit PrimaryCtorFunctionBase + val G: int -> int -> int + new (n: int, g: int -> int -> int) = { inherit PrimaryCtorFunctionBase(fun a b -> a + b + n); G = g } + +type PrimaryCtorDefaultValueChild = + inherit PrimaryCtorFunctionBase + val G: int -> int -> int + [] val mutable D: int + new (g: int -> int -> int) = { inherit PrimaryCtorFunctionBase(fun a b -> a + b); G = g } + +type ValClassFunctionBase = + val F: int -> int -> int + new (f) = { F = f } + +type ValClassFunctionChild = + inherit ValClassFunctionBase + val G: int -> int -> int + new (f, g) = { inherit ValClassFunctionBase(f); G = g } + +type PrimaryCtorArrayBase(xs: int[]) = + member _.Items = xs + +type PrimaryCtorParamArrayForwardingChild([] xs: int[]) = + inherit PrimaryCtorArrayBase(xs) + member _.First = xs.[0] + +type PrimaryCtorParamArrayPrefixed(_xs: int, [] xs: int[]) = + member _.Value = _xs + xs.Length + +type PrimaryCtorSelfBase(self: int, this: int) = + member _.Sum = self * 10 + this + +type PrimaryCtorSelfChild = + inherit PrimaryCtorSelfBase + val Z: int + new (a: int) = { inherit PrimaryCtorSelfBase(a, 2); Z = 4 } + +type PrimaryCtorTypedFunctionBase(f: int -> int -> int, g: int * int -> int, h: (int -> int -> int) option) = + member _.F a b = f a b + member _.G a b = g (a, b) + member _.H a b = match h with Some h -> h a b | None -> -1 + +type PrimaryCtorTypedFunctionChild = + inherit PrimaryCtorTypedFunctionBase + val Z: int + new (f: int -> int -> int) = { inherit PrimaryCtorTypedFunctionBase(f, (fun (a, b) -> a * b), Some(fun a b -> a + b)); Z = 5 } + +type PrimaryCtorGenericFunctionBase<'T>(v: 'T, f: int -> int -> 'T) = + member _.V = v + member _.F a b = f a b + +type PrimaryCtorGenericFunctionChild = + inherit PrimaryCtorGenericFunctionBase int -> int> + val Z: int + new () = { inherit PrimaryCtorGenericFunctionBase int -> int>((fun a b -> a * 10 + b), (fun a b c d -> a + b + c * d)); Z = 1 } + +type PrimaryCtorNamedUnitBase(u: unit) = + member _.Unit = u + member val Value = 42 + +type PrimaryCtorNamedUnitChild = + inherit PrimaryCtorNamedUnitBase + val Count: int + new (count: int) = { inherit PrimaryCtorNamedUnitBase(()); Count = count } + +let primaryCtorOrderLog = ResizeArray() +let mutable primaryCtorOrderCounter = 0 + +let logPrimaryCtorOrder step value = + primaryCtorOrderLog.Add step + value + +let primaryCtorThrowingLog = ResizeArray() + +type PrimaryCtorOrderBase(n: int) = + do primaryCtorOrderLog.Add $"base body %d{n}" + do primaryCtorOrderCounter <- primaryCtorOrderCounter + 1 + +type PrimaryCtorOrderChild = + inherit PrimaryCtorOrderBase + val Snapshot: int + val Label: string + val Combine: int -> int -> int + new () = + { inherit PrimaryCtorOrderBase(logPrimaryCtorOrder "base arg" 7) + Snapshot = logPrimaryCtorOrder "Snapshot" primaryCtorOrderCounter + Label = logPrimaryCtorOrder "Label" "l" + Combine = logPrimaryCtorOrder "Combine" (fun a b -> a * 10 + b) } + +type PrimaryCtorThrowingBase(n: int) = + do failwith $"base failed %d{n}" + +type PrimaryCtorThrowingChild = + inherit PrimaryCtorThrowingBase + val X: int + new () = { inherit PrimaryCtorThrowingBase(1); X = (primaryCtorThrowingLog.Add "X"; 1) } + +type PrimaryCtorVirtualBase(name: string) = + abstract Calc: int -> int + default _.Calc a = a + member _.Name = name + +type PrimaryCtorVirtualChild = + inherit PrimaryCtorVirtualBase + val K: int + val F: int -> int -> int + new (k, f) = { inherit PrimaryCtorVirtualBase("base"); K = k; F = f } + override this.Calc a = a * this.K + +let multiplyInts (a: int) (b: int) = a * b + +let primaryCtorOutOfOrderLog = ResizeArray() + +let logPrimaryCtorOutOfOrder step value = + primaryCtorOutOfOrderLog.Add step + value + +type PrimaryCtorOutOfOrderChild = + inherit PrimaryCtorBase + val P: int + val Q: int + new (a: int, b: int) = { inherit PrimaryCtorBase(a); Q = b + 1; P = a } + +type PrimaryCtorOutOfOrderLoggedBase(n: int) = + do primaryCtorOutOfOrderLog.Add $"base body %d{n}" + +type PrimaryCtorOutOfOrderLoggedChild = + inherit PrimaryCtorOutOfOrderLoggedBase + val X: int + val Y: int + new (a: int) = + { inherit PrimaryCtorOutOfOrderLoggedBase(logPrimaryCtorOutOfOrder "base arg" a) + Y = logPrimaryCtorOutOfOrder "Y" (a + 1) + X = logPrimaryCtorOutOfOrder "X" a } + +let passPrimaryCtorValue value = value + +type PrimaryCtorOptionFunctionChild = + inherit PrimaryCtorBase + val H: (int -> int -> int) option + new () = { inherit PrimaryCtorBase(1); H = passPrimaryCtorValue (Some(fun a b -> a * 10 + b)) } + +type PrimaryCtorExplicitMid = + inherit PrimaryCtorBase + val B: int + new (b) = { inherit PrimaryCtorBase(1); B = b } + +type PrimaryCtorOverExplicitLeaf(c: int) = + inherit PrimaryCtorExplicitMid(c) + member _.C = c + +[] +let ``test explicit constructor calls a base primary constructor`` () = + let child = PrimaryCtorValChild(3, "origin") + child.X |> equal 3 + child.Label |> equal "origin" + child.Tag |> equal "base" + child.Describe() |> equal "base:3" + (box child :? PrimaryCtorBase) |> equal true + let other = PrimaryCtorValChild("abcd") + other.X |> equal 4 + other.Label |> equal "abcd" + +[] +let ``test explicit constructor calls a parameterless base primary constructor`` () = + let child = PrimaryCtorUnitChild(4) + child.Greeting |> equal "hi" + child.Count |> equal 4 + (box child :? PrimaryCtorUnitBase) |> equal true + +[] +let ``test explicit constructor resolves the generic arguments of a base primary constructor`` () = + let child = PrimaryCtorGenericChild(7) + child.Value |> equal [ 7; 7 ] + child.Size |> equal 2 + child.Item |> equal 7 + +[] +let ``test explicit constructor keeps base primary constructor arguments apart from fields of the same name`` () = + let child = PrimaryCtorSameNameChild("a", "b") + child.BaseLabel |> equal "a" + child.label |> equal "b" + +[] +let ``test explicit constructor passes omitted optional arguments to a base primary constructor`` () = + let child = PrimaryCtorOptionalChild() + child.Describe |> equal "1-B-7" + child.Z |> equal 9 + let child = PrimaryCtorOptionalChild("x") + child.Describe |> equal "1-x-7" + child.Z |> equal 8 + let child = PrimaryCtorOptionalChild(3) + child.Describe |> equal "1-B-3" + child.Z |> equal 7 + +[] +let ``test explicit constructor passes ParamArray arguments to a base primary constructor`` () = + let child = PrimaryCtorParamArrayChild() + child.Total |> equal 6 + child.Count |> equal 2 + child.Z |> equal 5 + let child = PrimaryCtorParamArrayChild([| 4; 5 |]) + child.Total |> equal 10 + child.Count |> equal 2 + child.Z |> equal 6 + let child = PrimaryCtorParamArrayChild(true) + child.Total |> equal 1 + child.Count |> equal 0 + child.Z |> equal 7 + +[] +let ``test explicit constructor gives distinct names to base primary constructor arguments`` () = + let child = PrimaryCtorSuffixChild(1, 2, 3) + child.Sum |> equal 12 + child.x |> equal 3 + +[] +let ``test explicit constructor accepts base primary constructor arguments named after keywords`` () = + let child = PrimaryCtorKeywordChild(4) + child.Value |> equal 42 + child.Z |> equal 1 + +[] +let ``test explicit constructor passes functions to a base primary constructor and its own fields`` () = + let child = PrimaryCtorFunctionChild(1, (fun a b -> a * b)) + child.Apply 2 3 |> equal 6 + child.G 2 3 |> equal 6 + +[] +let ``test explicit constructor passes base arguments next to a DefaultValue field`` () = + let child = PrimaryCtorDefaultValueChild(fun a b -> a * b) + child.D <- 7 + child.Apply 2 3 |> equal 5 + child.G 5 2 |> equal 10 + child.D |> equal 7 + +[] +let ``test explicit constructor copies inherited function fields`` () = + let child = ValClassFunctionChild((fun a b -> a - b), (fun a b -> a * b)) + child.F 5 2 |> equal 3 + child.G 5 2 |> equal 10 + +[] +let ``test primary constructor forwards its ParamArray to the base constructor`` () = + let child = PrimaryCtorParamArrayForwardingChild(9, 8) + child.Items |> equal [| 9; 8 |] + child.First |> equal 9 + +[] +let ``test primary constructor ParamArray does not clash with other arguments`` () = + PrimaryCtorParamArrayPrefixed(1, 2, 3).Value |> equal 3 + +[] +let ``test explicit constructor accepts base primary constructor arguments named self or this`` () = + let child = PrimaryCtorSelfChild(3) + child.Sum |> equal 32 + child.Z |> equal 4 + +[] +let ``test explicit constructor passes curried, tupled and optional functions to a base primary constructor`` () = + let child = PrimaryCtorTypedFunctionChild(fun a b -> a - b) + child.F 5 2 |> equal 3 + child.G 3 4 |> equal 12 + child.H 3 4 |> equal 7 + child.Z |> equal 5 + +[] +let ``test explicit constructor keeps functions passed to a generic base primary constructor parameter curried`` () = + let child = PrimaryCtorGenericFunctionChild() + child.V 1 2 |> equal 12 + child.F 1 2 3 4 |> equal 15 + child.Z |> equal 1 + +[] +let ``test explicit constructor passes a named unit argument to a base primary constructor`` () = + let child = PrimaryCtorNamedUnitChild(3) + child.Count |> equal 3 + child.Value |> equal 42 + child.Unit |> equal () + +[] +let ``test explicit constructor evaluates its field values after the base primary constructor`` () = + primaryCtorOrderLog.Clear() + let counter = primaryCtorOrderCounter + let child = PrimaryCtorOrderChild() + List.ofSeq primaryCtorOrderLog |> equal [ "base arg"; "base body 7"; "Snapshot"; "Label"; "Combine" ] + child.Snapshot |> equal (counter + 1) + child.Label |> equal "l" + child.Combine 1 2 |> equal 12 + +[] +let ``test explicit constructor skips its field values when the base primary constructor throws`` () = + primaryCtorThrowingLog.Clear() + let failed = + try + PrimaryCtorThrowingChild() |> ignore + false + with _ -> + true + failed |> equal true + List.ofSeq primaryCtorThrowingLog |> equal [] + +[] +let ``test explicit constructor sets fields listed out of declaration order`` () = + let child = PrimaryCtorOutOfOrderChild(1, 2) + child.P |> equal 1 + child.Q |> equal 3 + child.Describe() |> equal "base:1" + +[] +let ``test explicit constructor evaluates fields listed out of declaration order after the base primary constructor`` () = + primaryCtorOutOfOrderLog.Clear() + let child = PrimaryCtorOutOfOrderLoggedChild(5) + List.ofSeq primaryCtorOutOfOrderLog |> equal [ "base arg"; "base body 5"; "Y"; "X" ] + child.X |> equal 5 + child.Y |> equal 6 + +[] +let ``test explicit constructor sets an option of a curried function computed after the base primary constructor`` () = + let child = PrimaryCtorOptionFunctionChild() + child.H.Value 1 2 |> equal 12 + +[] +let ``test primary constructor passes its arguments to an explicit base constructor over a primary constructor`` () = + let leaf = PrimaryCtorOverExplicitLeaf(4) + leaf.B |> equal 4 + leaf.C |> equal 4 + +[] +let ``test object expression over an explicit constructor passes its own fields`` () = + let o = { new PrimaryCtorVirtualChild(3, multiplyInts) with override _.ToString() = "o" } + o.Calc 1 |> equal 3 + o.F 2 3 |> equal 6 + string o |> equal "o" + +let primaryCtorThenLog = ResizeArray() +let primaryCtorThenSaved = ResizeArray() + +type PrimaryCtorThenBase(a: int) = + do primaryCtorThenLog.Add "base" + member _.A = a + +type PrimaryCtorThenChild = + inherit PrimaryCtorThenBase + val X: int + val Y: string + new (n: int) = + let v = n + 1 + { inherit PrimaryCtorThenBase((primaryCtorThenLog.Add "arg"; v)); Y = (primaryCtorThenLog.Add "y"; "y"); X = (primaryCtorThenLog.Add "x"; v * 2) } + then primaryCtorThenLog.Add "then" + +type PrimaryCtorThenSelfChild = + inherit PrimaryCtorThenBase + val mutable X: int + new (x: int) as self = { inherit PrimaryCtorThenBase(x); X = x } then primaryCtorThenSaved.Add(box self); self.X <- self.X + self.A + new () as self = PrimaryCtorThenSelfChild(7) then self.X <- self.X * 2; primaryCtorThenSaved.Add(box self) + +type PrimaryCtorThenGenericBase<'T>(a: 'T) = + member _.A = a + +type PrimaryCtorThenGenericChild<'T> = + inherit PrimaryCtorThenGenericBase<'T> + val mutable Items: 'T list + new (a: 'T) as self = { inherit PrimaryCtorThenGenericBase<'T>(a); Items = [] } then self.Items <- [ self.A; a ] + +type PrimaryCtorThenSelfBase(a: int) as this = + let mutable touched = 0 + do this.Touch() + member _.A = a + member _.Touched = touched + member _.Touch() = touched <- touched + 1 + +type PrimaryCtorThenSelfBaseChild = + inherit PrimaryCtorThenSelfBase + val X: int + new () = { inherit PrimaryCtorThenSelfBase(1); X = 2 } + +[] +let ``test explicit constructor runs its then block after a base primary constructor`` () = + primaryCtorThenLog.Clear() + let c = PrimaryCtorThenChild(1) + (c.A, c.X, c.Y) |> equal (2, 4, "y") + List.ofSeq primaryCtorThenLog |> equal [ "arg"; "base"; "y"; "x"; "then" ] + +[] +let ``test explicit constructor with a self identifier works with a base primary constructor`` () = + primaryCtorThenSaved.Clear() + let a = PrimaryCtorThenSelfChild(3) + (a.A, a.X) |> equal (3, 6) + obj.ReferenceEquals(primaryCtorThenSaved.[0], a) |> equal true + let b = PrimaryCtorThenSelfChild() + (b.A, b.X) |> equal (7, 28) + obj.ReferenceEquals(primaryCtorThenSaved.[1], b) |> equal true + obj.ReferenceEquals(primaryCtorThenSaved.[2], b) |> equal true + let g = PrimaryCtorThenGenericChild("q") + g.Items |> equal [ "q"; "q" ] + +[] +let ``test explicit constructor works with a base primary constructor that has a self identifier`` () = + let c = PrimaryCtorThenSelfBaseChild() + (c.A, c.Touched, c.X) |> equal (1, 1, 2)