Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic Blazor inheritance doesn't compile with the C# 9 Nullable setting enabled #30531

Closed
kaleidocore opened this issue Feb 27, 2021 · 6 comments
Assignees
Labels
area-blazor Includes: Blazor, Razor Components External This is an issue in a component not contained in this repository. It is open for tracking purposes. feature-razor.language nullable
Milestone

Comments

@kaleidocore
Copy link

kaleidocore commented Feb 27, 2021

Describe the bug

Generic Razor components that themselves inherit from a generic base class can't have any constraints (except struct) if the new and great .Net5 / C# 9 Nullable setting is enabled. The compiler errors you get are very puzzling but clearly incorrect:

Error CS0263 Partial declarations of 'TestComponent' must not specify different base classes
Error CS0115 'TestComponent.BuildRenderTree(RenderTreeBuilder)': no suitable method found to override

  • Removing or setting the #nullable option to disable makes the project compile
  • Removing all generic constraints in the codebehind makes the project compile
  • Changing the generic constraints in the codebehind to struct makes the project compile

Note that this only happens if the component itself has a @typeparam and inherits from a generic base class, while the .Net 5 / C# 9 Nullable feature is activated.

To Reproduce

Here's a very minimal repro repo: https://github.com/kaleidocore/BlazorGenericsBug

Further technical details

.NET SDK (reflecting any global.json):
Version: 5.0.103
Commit: 72dec52dbd

Runtime Environment:
OS Name: Windows
OS Version: 10.0.18363
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\5.0.103\

Host (useful for support):
Version: 5.0.3
Commit: c636bbdc8a

.NET SDKs installed:
2.0.2 [C:\Program Files\dotnet\sdk]
2.1.202 [C:\Program Files\dotnet\sdk]
2.1.402 [C:\Program Files\dotnet\sdk]
2.1.520 [C:\Program Files\dotnet\sdk]
2.1.812 [C:\Program Files\dotnet\sdk]
3.1.405 [C:\Program Files\dotnet\sdk]
5.0.102 [C:\Program Files\dotnet\sdk]
5.0.103 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Visual Studio 2019, Version 16.8.5

@kaleidocore kaleidocore changed the title Generic inheritance doesn't compile with the new C# 9 Nullable setting enabled Generic Blazor inheritance doesn't compile with the C# 9 Nullable setting enabled Feb 27, 2021
@pranavkm pranavkm added area-blazor Includes: Blazor, Razor Components nullable labels Feb 27, 2021
@kaleidocore
Copy link
Author

kaleidocore commented Feb 28, 2021

@pranavkm I don't know if you looked at the linked repro repo when you added the tags but I had needlessly used a Directory.Build.props file rather than just the #nullable enable switch in the codebehind. I've updated the repro with just the switch and removed the Directory.Build.props to further reduce complexity.

@ghost
Copy link

ghost commented Mar 1, 2021

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@captainsafia
Copy link
Member

Spent some time looking into this and I'm 100% sure its a compiler bug related to dotnet/roslyn#45960.

In particular, the issue seems to be related to have multiple partial declarations of a generic class within an enabled nullability context.

I tried changing the code generation for the Razor component in the following ways to see if the issue was related to anything we had control over:

First, adding a generic constraint to the generated code.

-    public partial class TestComponent<T> : TestComponentBase<T>
+    public partial class TestComponent<T> : TestComponentBase<T> where T : class 

No luck here. Also tried disabling nullability around the class declaration here.

-    public partial class TestComponent<T> : TestComponentBase<T>
+#nullable disable
+    public partial class TestComponent<T> : TestComponentBase<T>

But as mentioned above, this doesn't really do anything as long as nullability is enabled in the partial implementation in the C# file that the Razor compiler doesn't touch.

With this in mind, I stepped back and decided to play with generic base classes + partial implementations + nullability in a simple console app.

The following code sample compiles fine and captures some of the properties that I think exist in this problem:

  • An abstract class definition
  • An generic class implementation of the abstract class
  • Two partial classes that extend the generic class
public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

public class TestComponentBase<T> : Foo
        where T : class
{
        public int BaseVal { get; set; }

        public override void Bar() {
            return;
        }
}

public abstract class Foo
{
    public abstract void Bar();
}

The issue doesn't repro in this scenario. At this point, I chased another hunch. Maybe the issue only repros with abstract classes that implement an interface?

I tried the following but no dice:

public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

public class TestComponentBase<T> : Foo
        where T : class
{
        public int BaseVal { get; set; }

        public override void Bar() {
            return;
        }
}

public abstract class Foo : IFoo
{
    public int Baz { get; set; }
    public abstract void Bar();
}

public interface IFoo
{
    int Baz { get; set; }
}

At this point, I came across dotnet/roslyn#45960 which reminded me to put tickering with the nullability context at the forefront of my investigation. So I tried the following:

#nullable disable
public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

#nullable enable

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

And reproed the bug. OK. So what we're dealing with is exactly related to #45960.

Then I realized that I missed something when I was trying:

No luck here. Also tried disabling nullability around the class declaration here.

-    public partial class TestComponent<T> : TestComponentBase<T>
+#nullable disable
+    public partial class TestComponent<T> : TestComponentBase<T>

And realized that I missed something important here. Since the issue is related to nullability mismatch, we want to enable nullability around the class declaration, not disable it, to match the nullability context in the other class declaration. Updating the code generation to produce the following:

#nullable enable
public partial class TestComponent<T> : TestComponentBase<T>

resolves the issue.

This is just a workaround for the compiler bug referenced above but I think we can safely enable nullability around the class declaration for the meantime. I'll have to see if it is possible for us to strictly limit this to partial generic classes.

Side note, I also spotted #27218 and #26971 as possible dupes of this issue in the repo.

@captainsafia captainsafia added External This is an issue in a component not contained in this repository. It is open for tracking purposes. and removed investigate Working labels Aug 16, 2021
@captainsafia captainsafia modified the milestones: 6.0-rc1, 6.0.0 Aug 18, 2021
@MithrilMan
Copy link

just to have confirmation, does this mean that actually in this scenario in net5 we can't use nullable but it will work on .net 6?

@captainsafia
Copy link
Member

captainsafia commented Sep 2, 2021

@MithrilMan Not quite. We debugged this down to an issue in the compiler so it will depend on what version of the compiler you are using. See dotnet/roslyn#45960 for more info.

@mkArtakMSFT
Copy link
Member

Closing as a dupe of the linked roslyn issue.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components External This is an issue in a component not contained in this repository. It is open for tracking purposes. feature-razor.language nullable
Projects
None yet
Development

No branches or pull requests

6 participants