Skip to content

Conversation

bouwkast
Copy link
Collaborator

Summary of changes

In almost every case checking if an IDuckType is/isn't null is invalid as they are virtually always going to exist. Instead we need to check whether the IDuckType.Instance is/isn't null. This is easy to accidentally do, so this PR adds an analyzer and a code fix to find and throw an error for any occurance of checking IDuckType for null and provides a code fix to change to .Instance

Reason for change

Help enforce us to not accidentally check the IDuckType for null and instead check the Instance for null

Implementation details

Based it off of our other analyzers somewhat.
I've tried to comment the code as much as I could, I mainly built the thing by stepping through the tests in debug and seeing what the various SyntaxNodes were and what to do.

I've excluded the IActivity because we do check it for null to determine which runtime elements we have available for it via some pattern matching.

Test coverage

  • Tests have been added for the normal usages that I've seen:
    • binary and is patterns: duckType != null / duckType is not null
    • generic constraints: where T : IDuckType
    • (object)duckType == null -> duckType.Instance == null

Tested it out on my VS instance and seems to work good 👍

Other details

There was some ChatGPT usage here around the parenthesis / casting and namespace exclusions as that really tripped me up.

@bouwkast
Copy link
Collaborator Author

Build failures are due to the detected errors, some I already have PRs for but will aim to address them before merging this

@bouwkast bouwkast force-pushed the steven/r-and-d-august-2025 branch from 852a4af to 2538a59 Compare August 28, 2025 21:44
Copy link
Member

@andrewlock andrewlock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

category: "CodeQuality",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The IDuckType is almost always never null, check the Instance for null to ensure we have access to the ducked object.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"almost always never" 😅

Suggested change
description: "The IDuckType is almost always never null, check the Instance for null to ensure we have access to the ducked object.");
description: "The IDuckType is almost always non-null, check the Instance for null to ensure we have access to the duck typed object.");

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent an embarrassingly amount of time coming up with how to phrase this 🪦

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 3d3d344

Forgot that the description is not the "Description" the messageFormat is the "Description" and the description is just optional 😖

namespace Datadog.Trace.Tools.Analyzers.DuckTypeAnalyzer
{
/// <summary>
/// Checks fo r null checks against IDuckType instances.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Checks fo r null checks against IDuckType instances.
/// Checks for null checks against IDuckType instances.

Comment on lines +83 to +84
// it is an implicit cast / box to object (unlike the `is null` pattern)
// so we need to undo that before we can check if it is a IDuckType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very interesting, were you aware of this @tonyredondo? If so, we should probably change the analyzer to make you never use == either and always use is😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm not sure of is whether this is just a Roslyn thing

return false;
}

// NOTE: IDuckType? == IDuckType which surprised me
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's because IDuckType is effectively a reference type by default, so the ? is an annotation on that rather than making the struct nullable.

That's why this is bad (it has to box to an object):

public void MyMethod(IDuckType duck);

And this is good (no boxing)

public void MyMethod<T>(T duck);
    where T : IDuckType

Copy link
Collaborator Author

@bouwkast bouwkast Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhh very interesting thanks!

{
void TestMethod(IDuckType? duckType)
{
if (duckType.Instance {{operand}} null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the incoming type is nullable, it's boxed, so I think this actually needs to be:

if (duckType?.Instance {{operand}} null)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's correct but I don't know how to do that 😆

I can poke around a bit more to see how to make it do that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes, this causes Visual Studio to not allow the the code fix to be applied, so I'll work on fixing it.

Comment on lines +287 to +288
// NOTE: The source fixedSource currently only replaces the condition, it does not remove the unnecessary nullability in the constraint.
// NOTE: Leaving that as an exercise for the reader
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the nullability isn't unnecessary (I think), so that's good 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting okay, so I really should get that supported

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants