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

Annotate metadata for nullability, leaving only builders and conventions #23381

Merged
11 commits merged into from
Nov 19, 2020

Conversation

roji
Copy link
Member

@roji roji commented Nov 18, 2020

Here's another bucket of nullability. After this, all of metadata is annotated (core, relational, providers) except for builders, conventions and some remaining internal stuff (which I will do later). I've left some nullability warnings where I think a closer look is warranted.

Feel free to push commits directly to this branch, or I can do corrections if you tell me to.

@@ -1502,7 +1502,7 @@ public virtual IEnumerable<ForeignKey> GetDerivedReferencingForeignKeys()

private Navigation AddNavigation(MemberIdentity navigationMember, ForeignKey foreignKey, bool pointsToPrincipal)
{
var name = navigationMember.Name;
var name = navigationMember.Name!;
Copy link
Member

Choose a reason for hiding this comment

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

Is this safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think so... This private AddNavigation gets called from two sites, where a non-None MemberIdentity is constructed... Do you see a problem?

We may want to think about the design of MemberIdentity to make it friendlier...

Copy link
Member

Choose a reason for hiding this comment

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

Depends on caller. But if we are using just name (rather than memberInfo from MemberIdentity then string parameter could have just worked too.

Copy link
Member

Choose a reason for hiding this comment

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

MemberIdentity.Name should be non-nullable

Copy link
Member Author

Choose a reason for hiding this comment

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

At least theoretically there's the None value of MemberIdentity. Should Name be non-nullable and just assert for that case?

Copy link
Member

Choose a reason for hiding this comment

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

  • Add IsNone to replace MemberIdentity.Name == null checks

And either:

  1. Make MemberIdentity.Name non-nullable and throw if it's called on None

Or:

  1. Annotate IsNone and call it before calling MemberIdentity.Name

I think the first option requires less code changes

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, am doing the first option.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

@AndriySvyryd I took a look at this as the last nullability leftover.

First, there seem to be a lot of cases (or enough) where MemberIdentity.Name is accessed and null really is expected. If we make Name throw for null, that means that all those call sites have to be changed to identity.IsNone() ? null : identity.Name, which needlessly complicates the code (this is option 1 above). This needs to be weighed against the gain in removing bangs in sites where we know the identity isn't empty (and don't even need/want to check) - I think it may not be worth it.

We could do option 2 and annotated IsNone while leaving Name nullable, but there seems to be little value in doing that - callers can just as well compare Name to null, instead of checking IsNone.

So I think the best thing may be to leave the code as-is - but let me know what you prefer and I'll do it.

Not totally related, but depending on the above we can also obsolete IsNone, and either introduce HasValue (property instead of method like IsNone, and also positive instead of negative), or better yet, do nothing at all - Name can just be checked directly.

Copy link
Member

Choose a reason for hiding this comment

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

Sure, removing IsNone is fine, even if it means leaving the bangs in

@@ -26,8 +28,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata
/// </summary>
public class LazyLoaderParameterBindingFactory : ServiceParameterBindingFactory
{
private static readonly MethodInfo _loadMethod = typeof(ILazyLoader).GetMethod(nameof(ILazyLoader.Load));
private static readonly MethodInfo _loadAsyncMethod = typeof(ILazyLoader).GetMethod(nameof(ILazyLoader.LoadAsync));
private static readonly MethodInfo _loadMethod = typeof(ILazyLoader).GetMethod(nameof(ILazyLoader.Load))!;
Copy link
Member

Choose a reason for hiding this comment

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

GetRequiredMethod?

Copy link
Member Author

Choose a reason for hiding this comment

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

So... are we removing GetRequiredMethod in the end?

Copy link
Member

Choose a reason for hiding this comment

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

I can neither confirm nor deny.

Copy link
Member Author

Choose a reason for hiding this comment

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

So many words to just write NCND

@@ -32,7 +34,7 @@ public static class RelationalForeignKeyExtensions
var principalType = foreignKey.PrincipalKey.IsPrimaryKey()
? foreignKey.PrincipalEntityType
: foreignKey.PrincipalKey.DeclaringEntityType;
var principalTable = StoreObjectIdentifier.Create(principalType, StoreObjectType.Table);
var principalTable = StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!;
Copy link
Member

Choose a reason for hiding this comment

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

Bug alert: this and below should use storeObject.StoreObjectType instead of StoreObjectType.Table. Also if principalTable or duplicatePrincipalTable is null this should throw (or return false), basically the same as when principalColumns is null

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW the two current call sites for this seem to always pass Table, so this shouldn't cause any trouble currently.

Fixed the above, hopefully as you wanted - if not let me know and I'll correct in a future PR.

Copy link
Member

Choose a reason for hiding this comment

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

It would be better to move the

principalTable is null
|| duplicatePrincipalTable is null

condition to the next if

Copy link
Member Author

Choose a reason for hiding this comment

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

No problem, I will do that in the next PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +100 to +113
return dbFunction.Schema is null
? _sqlExpressionFactory.Function(
dbFunction.Name,
arguments,
dbFunction.IsNullable,
argumentsPropagateNullability,
method.ReturnType.UnwrapNullableType())
: _sqlExpressionFactory.Function(
dbFunction.Schema,
dbFunction.Name,
arguments,
dbFunction.IsNullable,
argumentsPropagateNullability,
method.ReturnType.UnwrapNullableType());
Copy link
Member

Choose a reason for hiding this comment

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

😢

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree...

Copy link
Member

Choose a reason for hiding this comment

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

We don't need 2 different calls. 2nd Function allows nullable schema as parameter. Please revert this change.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is not annotated in this way :trollface: The implementation also has a Check.NotNull on the schema... Should this be changed?

Copy link
Member Author

Choose a reason for hiding this comment

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

@smitpatel am merging as-is for now, we can revisit this afterwards and improve.

@ghost
Copy link

ghost commented Nov 19, 2020

Hello @roji!

Because this pull request has the auto-merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost
Copy link

ghost commented Nov 19, 2020

Apologies, while this PR appears ready to be merged, I've been configured to only merge when all checks have explicitly passed. The following integrations have not reported any progress on their checks and are blocking auto-merge:

  1. Azure Pipelines

These integrations are possibly never going to report a check, and unblocking auto-merge likely requires a human being to update my configuration to exempt these integrations from requiring a passing check.

Give feedback on this
From the bot dev team

We've tried to tune the bot such that it posts a comment like this only when auto-merge is blocked for exceptional, non-intuitive reasons. When the bot's auto-merge capability is properly configured, auto-merge should operate as you would intuitively expect and you should not see any spurious comments.

Please reach out to us at fabricbotservices@microsoft.com to provide feedback if you believe you're seeing this comment appear spuriously. Please note that we usually are unable to update your bot configuration on your team's behalf, but we're happy to help you identify your bot admin.

@ghost ghost merged commit e45256e into main Nov 19, 2020
@ghost ghost deleted the MetadataNullability3 branch November 19, 2020 14:40
if (navigation != null)
{
return ExpandNavigation(root, entityReference, navigation, convertedType != null);
}

var skipNavigation = memberIdentity.MemberInfo != null
? entityType.FindSkipNavigation(memberIdentity.MemberInfo)
: entityType.FindSkipNavigation(memberIdentity.Name);
: memberIdentity.Name is not null
Copy link
Member

Choose a reason for hiding this comment

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

whyyyy?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not to worry, I will revert this in the next PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

roji added a commit that referenced this pull request Nov 20, 2020
roji added a commit that referenced this pull request Nov 21, 2020
@roji roji linked an issue Feb 2, 2021 that may be closed by this pull request
23 tasks
This pull request was closed.
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.

Annotate EF Core for nullable reference types
3 participants