roslyn icon indicating copy to clipboard operation
roslyn copied to clipboard

Inherit documentation for positional C# record parameters from an interface

Open vsfeedback opened this issue 4 years ago • 11 comments

This issue has been moved from a ticket on Developer Community.


Hello!

This is about documentation and what Visual Studio tool tips show for positional record parameters if there's no specific xml-style documentation there for each parameter, but the record implements an interface that has such documentation.

Consider you have this C# code in a Visual Studio (e.g. 16.9.4) code editor window:

public interface IMyInterfaceWithDoc
{
	/// <summary>Some words about A</summary>
	int A { get; }
	/// <summary>Some more words about B</summary>
	int B { get; }
}

public record MyRecord(int A, int B) : IMyInterfaceWithDoc
{
	public int CalcProduct()
	{
		return this. A * this. B;
	}
}

void Main()
{
	var r = new MyRecord(A: 17, B: 3);
	int sum = r.A + r.B;
}

Now please observe that Visual Studio will display the documentation inherited from the underlying interface, whenever you hover with the mouse over the record property named "A" or "B", but - sadly - except for the constructor:

For example, here it works just fine (hovering over the "B"): image

But here, it doesn't: image

What I would like to see: Like in the other cases, in the absence of a dedicated <param ..> documentation snippet, please show inherited documentation inferred from the interface if the target property (which is auto-defined by a positional parameter) machtes a property in the interface that has docs.

This would be very helpful, in order to have more information (with less typing) when using the constructor somewhere in the code.

Thanks!


Original Comments

Feedback Bot on 5/9/2021, 11:14 PM:

Thank you for taking the time to provide your suggestion. We will do some preliminary checks to make sure we can proceed further. We'll provide an update once the issue has been triaged by the product team.

vsfeedback avatar Jun 11 '21 21:06 vsfeedback

I think this is worth looking at. Either an automatic approach would do like suggested, and/or an option like this

/// <param name="A" inheritdoc="IMyInterfaceWithDoc.A"/>

which may be useful in many other situations, too.

KarloX2 avatar Jun 12 '21 05:06 KarloX2

This is a request to automatically have a parameter for a primary constructor inherit documentation from its implicitly-created associated property. In the meantime, this is possible:

/// <param name="B"><inheritdoc cref="IMyInterfaceWithDoc.B" path="/summary"/></param>

sharwell avatar Jun 17 '21 00:06 sharwell

@dotnet/roslyn-compiler Is there an easy way to tell if a primary constructor parameter is associated with an implicitly created property of the same name?

sharwell avatar Jun 17 '21 00:06 sharwell

@jcouv Another request for an 'associated' api here for ide purposes for these symbols.

CyrusNajmabadi avatar Jun 17 '21 02:06 CyrusNajmabadi

Added labels per https://github.com/dotnet/roslyn/blob/main/docs/contributing/API%20Review%20Process.md

jcouv avatar Jun 17 '21 06:06 jcouv

@jcouv for this to be an API proposal, we need to have an actual API shape proposed. @CyrusNajmabadi, I know you have an open PR with a draft: please make an issue following the API request template so we can triage it and talk about it in an upcoming design review.

333fred avatar Jun 17 '21 06:06 333fred

@sharwell the workaround you specified here does not work in VS 2022 17.1.0 (and in fact, neither does specifying a literal string in the <param> element, so it seems like a regression here). Even if it did, it's an unnecessarily and horribly clunky way to achieve something that should have Just Worked out of the box with zero changes required.

New features are great, unfinished new features not so much.

IanKemp avatar Feb 22 '22 17:02 IanKemp

@IanKemp everyone has a different definition of "finished".

CyrusNajmabadi avatar Feb 22 '22 17:02 CyrusNajmabadi

@IanKemp everyone has a different definition of "finished".

A better word would be complete. As in, the design of the record language feature wasn't complete because it didn't consider documentation comments, ergo the implementation wasn't complete either.

IanKemp avatar Feb 23 '22 13:02 IanKemp

It would be nice to support this feature. However, I don't think that this is a record only problem. The current behaviour is consistent with class.

Side Note: <inheritdoc/> works if the signature of the base ctor is the same.

Sample Code

internal static class Intellisencer
{
    public static void CheckIntellisence()
    {
        // Intellisense:
        // - ClassA -> "A ctor."
        // - propA -> "A prop."
        _ = new ClassA(propA: 1);

        // Intellisense:
        // - ClassB -> "A ctor."
        // - propA -> "A prop."
        _ = new ClassB(propA: 1);

        // Intellisense:
        // - ClassB -> ""
        // - propA -> ""
        // - propB -> ""
        _ = new ClassB(propA: 1, propB: 2);

        // Intellisense:
        // - RecordA -> "A ctor."
        // - propA -> "A prop."
        _ = new RecordA(propA: 1);

        // Intellisense:
        // - RecordA -> "A ctor."
        // - propA -> "A prop."
        _ = new RecordB(propA: 1);

        // Intellisense:
        // - RecordB -> ""
        // - propA -> ""
        // - propB -> ""
        _ = new RecordB(propA: 1, propB: 2);
    }
}

/// <summary>A class.</summary>
internal class ClassA
{
    /// <summary>A ctor.</summary>
    /// <param name="propA">A prop.</param>
    public ClassA(int propA) { this.PropA = propA; }
    /// <summary>A property.</summary>
    public int PropA { get; }
}

/// <summary>B Class.</summary>
internal class ClassB : ClassA
{
    /// <inheritdoc/>
    public ClassB(int propA) : this(propA: propA, propB: 0) { }
    /// <inheritdoc/>
    public ClassB(int propA, int propB) : base(propA: propA) { this.PropB = propB; }
    /// <summary>B property.</summary>
    public int PropB { get; }
}


/// <summary>A class.</summary>
internal record RecordA
{
    /// <summary>A ctor.</summary>
    /// <param name="propA">A prop.</param>
    public RecordA(int propA) { this.PropA = propA; }
    /// <summary>A property.</summary>
    public int PropA { get; }
}

/// <summary>B Record.</summary>
internal record RecordB : RecordA
{
    /// <inheritdoc/>
    public RecordB(int propA) : this(propA: propA, propB: 0) { }
    /// <inheritdoc/>
    public RecordB(int propA, int propB) : base(propA: propA) { this.PropB = propB; }
    /// <summary>B property.</summary>
    public int PropB { get; }
}

Emeka-MSFT avatar Nov 03 '23 07:11 Emeka-MSFT

This is a request to automatically have a parameter for a primary constructor inherit documentation from its implicitly-created associated property. In the meantime, this is possible:

/// <param name="B"><inheritdoc cref="IMyInterfaceWithDoc.B" path="/summary"/></param>

This would be a workable solution if it actually worked. Instead, you get:

// B: <inheritdoc cref="P:IMyInterfaceWithDoc.B" path="/summary"></inheritdoc>
var r = new MyRecord(A: 1, B: 2);

// B: <inheritdoc cref="P:IMyInterfaceWithDoc.B" path="/summary"></inheritdoc>
_ = r.B;

AdamLeMmon01 avatar Feb 23 '24 05:02 AdamLeMmon01