diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 7bf923dd50..fe411efdfe 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Fix missing "No implementation was given" error when F# class inherits from a C# class with `abstract override` members without providing an implementation. ([Issue #7776](https://github.com/dotnet/fsharp/issues/7776), [PR #19503](https://github.com/dotnet/fsharp/pull/19503)) * Fix CLIEvent properties to be correctly recognized as events: `IsEvent` returns `true` and `XmlDocSig` uses `E:` prefix instead of `P:`. ([Issue #10273](https://github.com/dotnet/fsharp/issues/10273), [PR #18584](https://github.com/dotnet/fsharp/pull/18584)) * Fix extra sequence point at the end of match expressions. ([Issue #12052](https://github.com/dotnet/fsharp/issues/12052), [PR #19278](https://github.com/dotnet/fsharp/pull/19278)) * Fix wrong sequence point range for `return`/`yield`/`return!`/`yield!` inside computation expressions. ([Issue #19248](https://github.com/dotnet/fsharp/issues/19248), [PR #19278](https://github.com/dotnet/fsharp/pull/19278)) diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index 0e564c3add..121b087f5e 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -571,7 +571,7 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = FilterItemsInSuperTypesBasedOnItemsInSubTypes nmf (fun item1 items -> not (items |> List.exists (fun item2 -> equivTest item1 item2))) itemLists /// Filter the overrides of methods or properties, either keeping the overrides or keeping the dispatch slots. - static let FilterOverrides findFlag (isVirt:'a->bool, isNewSlot, isDefiniteOverride, isFinal, equivSigs, nmf:'a->string) items = + static let FilterOverrides findFlag (isVirt:'a->bool, isNewSlot, isDefiniteOverride, isFinal, isAbstract, equivSigs, nmf:'a->string) items = let equivVirts x y = isVirt x && isVirt y && equivSigs x y let filterDefiniteOverrides = List.filter(isDefiniteOverride >> not) @@ -610,9 +610,10 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = // (a) not virtual // (b) is a new slot or // (c) not equivalent + // (d) is abstract (e.g. C# 'abstract override' re-abstracting a base virtual method) // We keep virtual finals around for error detection later on |> FilterItemsInSubTypesBasedOnItemsInSuperTypes nmf (fun newItem priorItem -> - (isVirt newItem && isFinal newItem) || not (isVirt newItem) || isNewSlot newItem || not (equivVirts newItem priorItem) ) + (isVirt newItem && isFinal newItem) || not (isVirt newItem) || isNewSlot newItem || isAbstract newItem || not (equivVirts newItem priorItem) ) // Remove any abstract slots in supertypes that are (a) hidden by another newslot and (b) implemented // We leave unimplemented ones around to give errors, e.g. for @@ -649,6 +650,7 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = (fun minfo -> minfo.IsNewSlot), (fun minfo -> minfo.IsDefiniteFSharpOverride), (fun minfo -> minfo.IsFinal), + (fun minfo -> minfo.IsAbstract), MethInfosEquivByNameAndSig EraseNone true g amap m, (fun minfo -> minfo.LogicalName)) @@ -664,7 +666,8 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = ((fun (pinfo: PropInfo) -> pinfo.IsVirtualProperty), (fun pinfo -> pinfo.IsNewSlot), (fun pinfo -> pinfo.IsDefiniteFSharpOverride), - (fun _ -> false), + (fun _ -> false), // isFinal + (fun _ -> false), // isAbstract PropsGetterSetterEquiv (PropInfosEquivByNameAndSig EraseNone g amap m), (fun pinfo -> pinfo.PropertyName)) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/ObjectOrientedTypeDefinitions/AbstractMembers/AbstractMembers.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/ObjectOrientedTypeDefinitions/AbstractMembers/AbstractMembers.fs index 7ee84f82ef..3b7ca2e21a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/ObjectOrientedTypeDefinitions/AbstractMembers/AbstractMembers.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/ObjectOrientedTypeDefinitions/AbstractMembers/AbstractMembers.fs @@ -291,3 +291,130 @@ let x4 = new TestLib.B() |> compile |> shouldFail |> withErrorCode 759 + + // Regression tests for https://github.com/dotnet/fsharp/issues/7776 + + /// C# 'abstract override' re-abstracts a virtual method from a base class. + /// F# classes inheriting from such a class must provide an implementation. + let private csLibWithAbstractOverride = + CSharp """ +namespace CSharpLib +{ + public abstract class AbstractClass + { + public abstract override string ToString(); + } + + public abstract class AbstractClassWithCustomMethod + { + public virtual int GetValue() => 42; + } + + public abstract class ReAbstractCustomMethod : AbstractClassWithCustomMethod + { + public abstract override int GetValue(); + } +} +""" + |> withName "CSharpAbstractOverrideLib" + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override ToString - missing implementation should error`` () = + FSharp """ +module Test + +open CSharpLib + +type T() = + inherit AbstractClass() +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldFail + |> withErrorCode 365 + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override ToString - with implementation should succeed`` () = + FSharp """ +module Test + +open CSharpLib + +type T() = + inherit AbstractClass() + override _.ToString() = "T" +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override custom method - missing implementation should error`` () = + FSharp """ +module Test + +open CSharpLib + +type T() = + inherit ReAbstractCustomMethod() +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldFail + |> withErrorCode 365 + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override custom method - with implementation should succeed`` () = + FSharp """ +module Test + +open CSharpLib + +type T() = + inherit ReAbstractCustomMethod() + override _.GetValue() = 100 +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override - F# abstract subclass should be allowed`` () = + FSharp """ +module Test + +open CSharpLib + +[] +type T() = + inherit AbstractClass() +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/7776 + [] + let ``Abstract override ToString - object expression must implement`` () = + FSharp """ +module Test + +open CSharpLib + +let x = { new AbstractClass() with + override _.ToString() = "obj" } +""" + |> asLibrary + |> withReferences [csLibWithAbstractOverride] + |> compile + |> shouldSucceed