diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index bf4928cc213..f1668b2936b 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -1,5 +1,6 @@ ### Added +* Simplify implementation of interface hierarchies with equally named abstract slots: when a derived interface provides a Default Interface Member (DIM) implementation for a base interface slot, F# no longer requires explicit interface declarations for the DIM-covered slot. (RFC FS-1336, [Language suggestion #1430](https://github.com/fsharp/fslang-suggestions/issues/1430), [PR #19241](https://github.com/dotnet/fsharp/pull/19241)) * Better generic unmanaged structs handling. ([Language suggestion #692](https://github.com/fsharp/fslang-suggestions/issues/692), [PR #12154](https://github.com/dotnet/fsharp/pull/12154)) * Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772)) * Added type conversions cache, only enabled for compiler runs ([PR#17668](https://github.com/dotnet/fsharp/pull/17668)) diff --git a/src/Compiler/Checking/MethodOverrides.fs b/src/Compiler/Checking/MethodOverrides.fs index 60da9ac1ba4..5397fa144d0 100644 --- a/src/Compiler/Checking/MethodOverrides.fs +++ b/src/Compiler/Checking/MethodOverrides.fs @@ -90,6 +90,13 @@ type RequiredSlot = | DefaultInterfaceImplementationSlot(_, _, possiblyNoMostSpecific) -> possiblyNoMostSpecific | _ -> false + /// Checks if this slot has implicit DIM coverage (used when the ImplicitDIMCoverage feature is enabled). + /// A slot has DIM coverage when it has a default interface implementation AND is optional (not re-abstracted). + member this.HasImplicitDIMCoverage(g: TcGlobals) = + g.langVersion.SupportsFeature LanguageFeature.ImplicitDIMCoverage + && this.HasDefaultInterfaceImplementation + && this.IsOptional + /// Gets the method info. member this.MethodInfo = match this with @@ -583,18 +590,19 @@ module DispatchSlotChecking = [ (reqdTy, GetClassDispatchSlots infoReader ad m reqdTy) ] /// Check all implementations implement some dispatch slot. - let CheckOverridesAreAllUsedOnce(denv, g, infoReader: InfoReader, isObjExpr, reqdTy, + let CheckOverridesAreAllUsedOnce(denv, g: TcGlobals.TcGlobals, infoReader: InfoReader, isObjExpr, reqdTy, dispatchSlotsKeyed: NameMultiMap, availPriorOverrides: OverrideInfo list, overrides: OverrideInfo list) = let amap = infoReader.amap let availPriorOverridesKeyed = availPriorOverrides |> NameMultiMap.initBy (fun ov -> ov.LogicalName) + for overrideBy in overrides do if not overrideBy.IsFakeEventProperty then let m = overrideBy.Range - let relevantVirts = NameMultiMap.find overrideBy.LogicalName dispatchSlotsKeyed - let relevantVirts = relevantVirts |> List.map (fun reqdSlot -> reqdSlot.MethodInfo) + let relevantSlots = NameMultiMap.find overrideBy.LogicalName dispatchSlotsKeyed + let relevantVirts = relevantSlots |> List.map (fun reqdSlot -> reqdSlot.MethodInfo) match relevantVirts |> List.filter (fun dispatchSlot -> OverrideImplementsDispatchSlot g amap m dispatchSlot overrideBy) with | [] -> @@ -628,7 +636,22 @@ module DispatchSlotChecking = if dispatchSlot.IsFinal && (isObjExpr || not (typeEquiv g reqdTy dispatchSlot.ApparentEnclosingType)) then errorR(Error(FSComp.SR.typrelMethodIsSealed(NicePrint.stringOfMethInfo infoReader m denv dispatchSlot), m)) | dispatchSlots -> - match dispatchSlots |> List.filter (fun dispatchSlot -> + // Filter out slots that have DIM coverage (when feature is enabled) + // by looking up the original RequiredSlot information + let slotsWithoutDIMCoverage = + dispatchSlots + |> List.filter (fun dispatchSlot -> + // Find the corresponding RequiredSlot to check for DIM coverage + let correspondingSlot = + relevantSlots + |> List.tryFind (fun rs -> + rs.MethodInfo.LogicalName = dispatchSlot.LogicalName && + typeEquiv g rs.MethodInfo.ApparentEnclosingType dispatchSlot.ApparentEnclosingType) + match correspondingSlot with + | Some slot -> not (slot.HasImplicitDIMCoverage g) + | None -> true) + + match slotsWithoutDIMCoverage |> List.filter (fun dispatchSlot -> (dispatchSlot.IsInstance = overrideBy.IsInstance) && isInterfaceTy g dispatchSlot.ApparentEnclosingType || not (DispatchSlotIsAlreadyImplemented g amap m availPriorOverridesKeyed dispatchSlot)) with @@ -860,24 +883,26 @@ module DispatchSlotChecking = let overrideByInfo = GetTypeMemberOverrideInfo g reqdTy overrideBy let overriddenForThisSlotImplSet = [ for reqdSlot in NameMultiMap.find overrideByInfo.LogicalName dispatchSlotsKeyed do - let dispatchSlot = reqdSlot.MethodInfo - if OverrideImplementsDispatchSlot g amap m dispatchSlot overrideByInfo then - if tyconRefEq g overrideByInfo.BoundingTyconRef dispatchSlot.DeclaringTyconRef then - match dispatchSlot.ArbitraryValRef with - | Some virtMember -> - if virtMember.MemberInfo.Value.IsImplemented then errorR(Error(FSComp.SR.tcDefaultImplementationAlreadyExists(), overrideByInfo.Range)) - virtMember.MemberInfo.Value.IsImplemented <- true - | None -> () // not an F# slot - - // Get the slotsig of the overridden method - let slotsig = dispatchSlot.GetSlotSig(amap, m) - - // The slotsig from the overridden method is in terms of the type parameters on the parent type of the overriding method, - // Modify map the slotsig so it is in terms of the type parameters for the overriding method - let slotsig = ReparentSlotSigToUseMethodTypars g m overrideBy slotsig + // Skip slots that have DIM coverage (they don't need MethodImpl) + if not (reqdSlot.HasImplicitDIMCoverage g) then + let dispatchSlot = reqdSlot.MethodInfo + if OverrideImplementsDispatchSlot g amap m dispatchSlot overrideByInfo then + if tyconRefEq g overrideByInfo.BoundingTyconRef dispatchSlot.DeclaringTyconRef then + match dispatchSlot.ArbitraryValRef with + | Some virtMember -> + if virtMember.MemberInfo.Value.IsImplemented then errorR(Error(FSComp.SR.tcDefaultImplementationAlreadyExists(), overrideByInfo.Range)) + virtMember.MemberInfo.Value.IsImplemented <- true + | None -> () // not an F# slot + + // Get the slotsig of the overridden method + let slotsig = dispatchSlot.GetSlotSig(amap, m) + + // The slotsig from the overridden method is in terms of the type parameters on the parent type of the overriding method, + // Modify map the slotsig so it is in terms of the type parameters for the overriding method + let slotsig = ReparentSlotSigToUseMethodTypars g m overrideBy slotsig - // Record the slotsig via mutation - yield slotsig ] + // Record the slotsig via mutation + yield slotsig ] //if mustOverrideSomething reqdTy overrideBy then // assert nonNil overriddenForThisSlotImplSet yield! overriddenForThisSlotImplSet ] diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index b39bf210809..f8f9749f99b 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1803,5 +1803,6 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an 3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields." 3879,xmlDocNotFirstOnLine,"XML documentation comments should be the first non-whitespace text on a line." featureReturnFromFinal,"Support for ReturnFromFinal/YieldFromFinal in computation expressions to enable tailcall optimization when available on the builder." +featureImplicitDIMCoverage,"Implicit dispatch slot coverage for default interface member implementations" 3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s" 3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'." \ No newline at end of file diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 9fb00722263..11a0c9db864 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -104,6 +104,7 @@ type LanguageFeature = | ErrorOnInvalidDeclsInTypeDefinitions | AllowTypedLetUseAndBang | ReturnFromFinal + | ImplicitDIMCoverage /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -251,6 +252,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) // F# preview (still preview in 10.0) LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work + LanguageFeature.ImplicitDIMCoverage, previewVersion ] static let defaultLanguageVersion = LanguageVersion("default") @@ -440,6 +442,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions -> FSComp.SR.featureErrorOnInvalidDeclsInTypeDefinitions () | LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens () | LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal () + | LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 09cb4273571..4e0deca7c7e 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -95,6 +95,7 @@ type LanguageFeature = | ErrorOnInvalidDeclsInTypeDefinitions | AllowTypedLetUseAndBang | ReturnFromFinal + | ImplicitDIMCoverage /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 85dbf4dd95c..7a896996f65 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -437,6 +437,11 @@ řez od konce + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield implicitní yield diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 370e248e1d6..fa7733a4144 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -437,6 +437,11 @@ Segmentierung ab Ende + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield implizite yield-Anweisung diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index c760506b835..b6c504f0f45 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -437,6 +437,11 @@ segmentación desde el final + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield elemento yield implícito diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 94935d998ff..0a4e78176e8 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -437,6 +437,11 @@ découpage depuis la fin + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield yield implicite diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index e83cb026ed3..1d6d5b280bf 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -437,6 +437,11 @@ sezionamento dalla fine + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield istruzione yield implicita diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 9888850c875..983f9e6d7e3 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -437,6 +437,11 @@ 開始と終了を指定したスライス + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield 暗黙的な yield diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 433af22b44b..4ad8d187cc1 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -437,6 +437,11 @@ 끝에서부터 조각화 + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield 암시적 yield diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 8d167f8c5c4..48efa8e8735 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -437,6 +437,11 @@ wycinanie od końca + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield niejawne słowo kluczowe yield diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 872af8ca069..7f23dd65fcf 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -437,6 +437,11 @@ divisão começando no final + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield yield implícito diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 940bcf5df31..9e75ed6a1a1 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -437,6 +437,11 @@ срезы от конца + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield неявное использование yield diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 28576ca243d..94a8f55db4f 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -437,6 +437,11 @@ uçtan dilimleme + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield örtük yield diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 6c459b3f33c..a0c142ce346 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -437,6 +437,11 @@ 从端切片 + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield 隐式 yield diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 296ef8fa7de..a7de59c8f11 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -437,6 +437,11 @@ 從尾端切割 + + Implicit dispatch slot coverage for default interface member implementations + Implicit dispatch slot coverage for default interface member implementations + + implicit yield 隱含 yield diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index f236ca6599d..f0bddcbf818 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -271,6 +271,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Interop/DIMSlotCoverageTests.fs b/tests/FSharp.Compiler.ComponentTests/Interop/DIMSlotCoverageTests.fs new file mode 100644 index 00000000000..98f16219d40 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Interop/DIMSlotCoverageTests.fs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. +namespace Interop + +open Xunit +open FSharp.Test.Compiler +open FSharp.Test + +/// DIM (Default Interface Method) slot coverage tests for F# interop with C# 8+ interfaces. +/// +/// Testing Dimensions (8): +/// 1. DIM availability: C# interface with DIM vs pure F# interface hierarchy +/// 2. Construct type: F# class vs object expression implementing interfaces +/// 3. Hierarchy shape: Linear (IA->IB) vs diamond (IA->IB,IC->ID) inheritance +/// 4. DIM conflict: Single unambiguous DIM vs conflicting DIMs requiring resolution (FS3352) +/// 5. Member type: Methods vs properties (with DIM getters/setters) +/// 6. Generics: Generic interface instantiation with partial DIM coverage +/// 7. Re-abstraction: C# "abstract" DIM forcing F# to provide implementation +/// 8. Language version: Preview feature gating (DIM support requires --langversion:preview) +/// +/// Why 3-level type depth suffices: Diamond inheritance (IA->IB,IC->ID) is the maximal +/// complexity for DIM resolution—the compiler must find the "most specific" implementation. +/// Deeper hierarchies don't introduce new DIM behaviors; they only repeat the same patterns. +/// +/// Test Coverage by Dimension: +/// DIM availability → Tests 1-2 (DIM shadowing vs pure F# error) +/// Construct type → Tests 7-10 (class tests 1-6, object expression tests 7-10) +/// Hierarchy shape → Tests 4-5 (diamond single DIM, diamond conflict) +/// DIM conflict → Test 5 (FS3352 "most specific implementation") +/// Member type → Test 6 (property with DIM getter) +/// Generics → Tests 13-14 (partial coverage, missing instantiation) +/// Re-abstraction → Tests 11-12 (requires impl, explicit impl succeeds) +/// Language version → Test 15 (pre-feature version errors with FS0361) +module ``DIM Slot Coverage Tests`` = + + let withCSharpLanguageVersion (ver: CSharpLanguageVersion) (cUnit: CompilationUnit) : CompilationUnit = + match cUnit with + | CS cs -> CS { cs with LangVersion = ver } + | _ -> failwith "Only supported in C#" + + let dimTestLib = + CSharp """ +namespace DIMTest { + public interface IA { int M(); } + public interface IB : IA { new int M(); int IA.M() => this.M() + 100; } +} +namespace DiamondSingleDIM { + public interface IA { int M(); } + public interface IB : IA { int IA.M() => 42; } + public interface IC : IA { } + public interface ID : IB, IC { } +} +namespace DiamondConflictDIM { + public interface IA { int M(); } + public interface IB : IA { int IA.M() => 1; } + public interface IC : IA { int IA.M() => 2; } + public interface ID : IB, IC { } +} +namespace PropertyDIM { + public interface IReadable { int Value { get; } } + public interface IWritable : IReadable { new int Value { get; set; } int IReadable.Value => this.Value; } +} +namespace ReabstractTest { + public interface IA { int M(); } + public interface IB : IA { abstract int IA.M(); } +} +namespace GenericDIMTest { + public interface IGet { T Get(); } + public interface IContainer : IGet, IGet { int IGet.Get() => 42; } +} + """ + |> withCSharpLanguageVersion CSharpLanguageVersion.CSharp8 + |> withName "DIMTestLib" + + // Helper: Generate F# code that opens a namespace and implements an interface + let fsharpImplementingInterface ns typeBody = + FSharp $""" +module Test +open {ns} +{typeBody} +""" + + // Helper: Compile with DIM support (preview + reference to C# lib) and expect success + let shouldCompileWithDIM libRef source = + source |> withLangVersionPreview |> withReferences [libRef] |> compile |> shouldSucceed + + // Helper: Compile with DIM support and expect failure with specific error code + let shouldFailWithDIM libRef errorCode source = + source |> withLangVersionPreview |> withReferences [libRef] |> compile |> shouldFail |> withErrorCode errorCode + + // Helper: Compile with old language version and expect failure (tests feature gating) + let shouldFailWithoutFeature libRef langVersion errorCode source = + source |> withLangVersion langVersion |> withReferences [libRef] |> compile |> shouldFail |> withErrorCode errorCode + + [] + let ``DIM shadowing - IB-only implementation succeeds with preview`` () = + fsharpImplementingInterface "DIMTest" "type C() = interface IB with member _.M() = 42" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Pure F# interface hierarchy errors without DIM`` () = + FSharp """ +module Test +type IA = abstract M : int -> int +type IB = inherit IA + abstract M : int -> int +type C() = interface IB with member x.M(y) = y + 3 +""" + |> withLangVersionPreview |> compile |> shouldFail |> withErrorCode 361 + + [] + let ``Explicit implementation of both IA and IB works`` () = + fsharpImplementingInterface "DIMTest" """type C() = + interface IA with member _.M() = 100 + interface IB with member _.M() = 42""" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Diamond with single DIM succeeds`` () = + fsharpImplementingInterface "DiamondSingleDIM" "type C() = interface ID" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Diamond with conflicting DIMs errors with FS3352`` () = + fsharpImplementingInterface "DiamondConflictDIM" "type C() = interface ID" + |> shouldFailWithDIM dimTestLib 3352 + |> withDiagnosticMessageMatches "most specific implementation" + + [] + let ``Property with DIM getter succeeds`` () = + fsharpImplementingInterface "PropertyDIM" """type C() = + let mutable value = 0 + interface IWritable with member _.Value with get() = value and set(v) = value <- v""" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Object expression with DIM succeeds`` () = + fsharpImplementingInterface "DIMTest" "let obj : IB = { new IB with member _.M() = 42 }" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Object expression explicit DIM override succeeds`` () = + fsharpImplementingInterface "DIMTest" """let obj : IB = { new IB with member _.M() = 42 + interface IA with member _.M() = 100 }""" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Object expression diamond with DIM succeeds`` () = + fsharpImplementingInterface "DiamondSingleDIM" "let obj : ID = { new ID }" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Object expression pure F# hierarchy errors`` () = + FSharp """ +module Test +type IA = abstract M : int -> int +type IB = inherit IA + abstract M : int -> int +let obj : IB = { new IB with member x.M(y) = y + 3 } +""" + |> withLangVersionPreview |> compile |> shouldFail |> withErrorCode 3213 + + [] + let ``Re-abstracted member requires implementation`` () = + fsharpImplementingInterface "ReabstractTest" "type C() = interface IB" + |> shouldFailWithDIM dimTestLib 366 + + [] + let ``Re-abstracted with explicit implementation succeeds`` () = + fsharpImplementingInterface "ReabstractTest" """type C() = + interface IA with member _.M() = 42 + interface IB""" + |> shouldCompileWithDIM dimTestLib + + [] + let ``Generic interfaces partial DIM coverage`` () = + fsharpImplementingInterface "GenericDIMTest" """type C() = + interface IContainer with member _.Get() : string = "hello" """ + |> shouldCompileWithDIM dimTestLib + + [] + let ``Generic interfaces - missing required instantiation should fail`` () = + fsharpImplementingInterface "GenericDIMTest" "type C() = interface IContainer" + |> shouldFailWithDIM dimTestLib 366 + + [] + let ``Old language version (pre-feature) requires explicit implementation`` () = + fsharpImplementingInterface "DIMTest" "type C() = interface IB with member _.M() = 42" + |> shouldFailWithoutFeature dimTestLib "9.0" 361 + |> withDiagnosticMessageMatches "implements"