-
-
Save controlflow/9996185 to your computer and use it in GitHub Desktop.
nullness inspection | |
{ | |
a?.M(); | |
a.F(); // <= possible NRE | |
a?.M(a != null); // <= expression always true | |
} | |
inspection "Redundant null-propagation" | |
{ | |
var a = new A(); | |
a?.M( // <= redundant ? warning | |
a?.P); // <= redundant ? warning | |
} | |
inspection "Merge sequential null-checks" | |
{ | |
if (a != null && a.P != null && a.P.U != null) | |
if (a != null && a.P.U != null) | |
if (a != null && a.P.M(x) != null) // invocation at end | |
if (_ && a?.P != null && a.P?.U != null) | |
if (a?.P != null && a.P.U != null) | |
if (a == null || a.P == null) | |
if (s.HasValue && s.Value.P != null) | |
if (d != null && d(x) != null) | |
// => | |
if (a?.P?.U != null) | |
if (a?.P.U != null) // short-circuiting | |
if (a?.P.M(x) != null) | |
if (_ && a?.P?.U != null) | |
if (a?.P?.U != null) | |
if (a?.P == null) | |
if (s?.P != null) // value-type | |
if (d?.Invoke(x) != null) // delegate | |
} | |
inspection "Null-propagating method call(s)" | |
{ | |
var t = x.Y; | |
if (t != null) { t.M(); } | |
if (t == null) { } else { t.P.M(); } | |
if (t != null) { | |
var p = t.P as X; | |
p.M(); | |
p?.U(); | |
if (p != null) { G(); } | |
} | |
if (t is X) { ((X) t).M(); } | |
// => | |
var t = x.Y; | |
t?.M(); | |
t?.P.M(); // short-circuiting | |
var p = t?.P as X; // lifted variable + as-cast | |
p?.M(); // lifted method call | |
p?.U(); | |
if (p != null) { G(); } // lifted if | |
(t as X)?.M(); | |
} | |
inspection "Null-propagating method return" | |
{ | |
if (t == null) { return null; } else { return t.M(x).P; } | |
// => | |
return t?.M(x).P; | |
} | |
inspection "Replace control instructions with null-propagation" | |
{ | |
for (;;) { | |
if (a == null) continue; | |
var b = a.B; | |
if (b == null) continue; | |
var d = b.C.D; | |
d.M(); | |
// continue | |
} | |
// => | |
for (;;) { | |
var b = a?.B | |
var d = b?.C.D; // short-circuiting | |
d?.M(); | |
} | |
} | |
inspection "Reduce nested null-checks with null-propagation" | |
{ | |
var a = GetA(); | |
if (a != null) { | |
var b = a.B; | |
if (b != null) { | |
var d = b?.C.D; | |
var e = d.E; | |
if (e != null) e.M(); | |
} | |
} | |
// => | |
var a = GetA(); | |
var b = a?.B; | |
var d = b?.C.D; // short-circuiting | |
var e = d?.E; // lifted variable | |
e?.M(); | |
} | |
inspection "Replace conditional operator with null-propagation" | |
{ | |
var b = (a == null) ? null : a.B; | |
var c = (a != null) ? a.B.C : null; | |
var d = (a != null ? a.B.C : null).D(); | |
var e = s.HasValue ? s.Value.P : null; | |
// => | |
var b = a?.B; | |
var c = a?.B.C; // short-circuiting | |
var d = (a?.B.C).D(); // parentheses required | |
var e = s?.P; // value type | |
} | |
inspection "Replace if statement with null-propagation" | |
{ | |
if (p != null) { t = p.U.T; } else { t = null; } | |
T t = null; if (p != null) { t = p.T; } | |
// => | |
t = p?.U.T; | |
T t = null; t = p?.T; | |
} | |
inspection "Replace conditional operator with null-propagation with ?? operator" | |
{ | |
// NOTE: .C is of ref/non-nullable type | |
var c = (a == null) ? 42 : a.B.C; | |
T c; if (a == null) { c = 42; } else { c = a.B.C; } | |
// => | |
var c = a?.B.C ?? 42; | |
} | |
inspection "Replace null-checked access with null-propagation" | |
{ | |
if (t != null) t = t.T; | |
// => | |
t = t?.T; | |
} | |
inspection "Merge sequential null-checks (using lifted nullable operators)" | |
{ | |
if (t != null && t.M()) | |
if (t != null && t.P == 42) | |
// => | |
if (t?.M() == true) | |
if (t?.P == 42) | |
} | |
// inside LINQ expressions | |
{ | |
xs.Where(x => x != null).Select(x => x.B.C).Where(x => x != null) | |
xs.Where(x => x.A != null).Select(x => x.A.B.C).Where(x => x != null) | |
from x in xs where x.A != null | |
where x.A.B != null select x.A.B | |
// => | |
xs.Select(x => x?.B.C).Where(x => x != null) | |
xs.Select(x => x.A?.B.C).Where(x => x != null) | |
from x in xs where x.A?.B != null select x.A.B | |
} | |
// user's .NotNull(x => x.Y)-like methods: | |
{ | |
T t = a.With(_ => _.B); | |
// U With<T,U>(Func<T, U>) where T: class, where U: class; | |
// note: false positives. Think about names like 'NotNull'/'NotNull'/'IfNotNull'/'Try' | |
// U? With<T,U>(Func<T?,U>) where T: struct, where U: struct; | |
// U? With<T,U>(Func<T?,U?>) where T: struct, where U: struct; | |
// U With<T,U>(Func<T,U>) where T: class; | |
// + note: 'struct => class' and 'class => struct' overloads | |
// + node: overloads with default values | |
// + note: parameter '_' may not be used at all | |
// => | |
T t = a?.B; | |
} | |
// the same, more complex example | |
{ | |
D d = a.NotNull(x => x.B.C).NotNull(x => x.D); | |
// => | |
D d = a?.B.C?.D; // short-circuiting | |
} | |
// delegate invoke | |
{ | |
D d = o as D; | |
d(); // possible NRE | |
// => | |
D d = o as D; | |
d?.Invoke(); | |
} |
// chained null checks + control flow jump to the same place
Only if there is nothing after that (obvious, I know).
// conditional operator + nullable value type
Your refactoring changes type of b
?
U With<T,U>(Func<T, U>) where T: class, where U: class;
Would it consider this by itself? Because by itself it is just a Kestrel combinator, I use it all the time in test code.
Your refactoring changes type of b?
Nope, operator ??
bring back int
type.
Would it consider this by itself?
Maybe. Maybe just with NotNull
name. Kestrel combinator? Seriously? Can you provide an example?
Nope, operator
??
bring backint
type.
Yes, so it changes the type of b
if B
is int?
. Or is it only for non-nullable B
?
Kestrel combinator? Seriously? Can you provide an example?
var client = New.Client().With(c => c.Name = "ABC");
var something = new Something {
A = "A",
B = "B"
}.With(s => s.Event += E)
var something = new Something {
A = "A"
B = new SomethingLikeXmlDocument().With(d => d.LoadUsingMagic("xyz"))
};
Often useful as an object initializer replacement for features that are not supported by object initializers and APIs that do not support chaining.
Or is it only for non-nullable
B
?
Nice catch, only if it is non-nullable. Thanks!
Often useful as an object initializer replacement...
Thanks, nice point. I think we will stick with hardcoded NotNull
name or [ContractAnnotation(null => null)]
or smth like that. Or disabled-by-default inspection nobody will ever find :)
Maybe: detect expression that will behave the same after transformation to nullable type
if (x is T && ((T) x).Value == 42)
// =>
if ((x as T)?.Value == 42) // lifted ==
Offer transformation via context action, not QF.
Maybe: ability to lift whole declarations (only as context action, not suggestion)
if (person != null) {
var something = person.GetSomething(arg, 42);
var component = something["argument"].Component;
component.Foo(something);
}
=>
var something = person?.GetSomething(arg, 42);
var component = something?["argument"].Component;
component?.Foo(something /* only when T is not value type */);
TODO: reverse transformation!
var function = functionDeclaration.DeclaredElement;
if (function == null) return null;
return function.ReturnType;
=>
var function = functionDeclaration.DeclaredElement;
return function?.ReturnType;
Would be nice if it was a Google doc, so we could comment on each line. Actually standard GH repository would work as well.