Entity has an implicit conversion from a two-element tuple that builds an Interval:
public static implicit operator Entity((Entity left, Entity right) interval)
=> new Interval(interval.left, true, interval.right, true);
Measured on master:
Entity e = ((Entity)1, (Entity)5);
// [1; 5] left inclusive = True, right inclusive = True
// and 1 belongs to it
Two things wrong with that, and they compound:
It invents information. A tuple carries two endpoints and nothing about whether either is included. The conversion has to pick, and whatever it picks is not in the input. This is the same objection @Happypig375 raised in #861 about the array-to-set conversions, which were removed in #866 for exactly this reason:
converting an array to a set is inherently information-losing because it removes order and duplicate elements. This shouldn't even be a conversion at all; explicit set construction is used for basically all the set types out there.
Order and duplicates going missing, and closedness appearing out of nowhere, are the same defect seen from opposite ends: the conversion and its input disagree about what information exists.
It picks the opposite of how the syntax reads. (1, 5) is the open interval in essentially all mathematical notation — [1, 5] is the closed one. So a caller writing what looks like an open interval gets a closed one, and the endpoints they meant to exclude are members. Nothing warns them.
Suggested fix
Remove the conversion. The explicit forms already exist, read unambiguously, and are what the notation problem calls for:
Entity e = MathS.Interval(1, 5); // the closed one, said out loud
Entity e = MathS.Interval(1, false, 5, false); // the open one
Entity e = new Interval(1, true, 5, true);
Changing the conversion to produce an open interval instead would be worse: it silently flips the meaning of existing code, and still invents the closedness.
Why now
Removing a public conversion is breaking, so it wants the 2.0 window while it is still in preview — the same reason #861 was filed as a question rather than left as a comment. After 2.0 it waits for 3.0.
The blast radius is worth measuring before deciding; for the array conversion it turned out to be exactly one place in the repository. I have not measured this one yet.
Not part of this
The audit that turned this up also looked at the numeric conversions, which are fine — a widening from any integer or float type keeps its value. One that looked suspicious does not reproduce: Real.op_Implicit(ERational) has a lossy branch when DowncastingEnabled is off, but Entity e = someERational does not reach it, giving an exact Rational either way, and asking specifically for a Real with downcasting disabled is arguably a request for a decimal.
Entityhas an implicit conversion from a two-element tuple that builds anInterval:Measured on
master:Two things wrong with that, and they compound:
It invents information. A tuple carries two endpoints and nothing about whether either is included. The conversion has to pick, and whatever it picks is not in the input. This is the same objection @Happypig375 raised in #861 about the array-to-set conversions, which were removed in #866 for exactly this reason:
Order and duplicates going missing, and closedness appearing out of nowhere, are the same defect seen from opposite ends: the conversion and its input disagree about what information exists.
It picks the opposite of how the syntax reads.
(1, 5)is the open interval in essentially all mathematical notation —[1, 5]is the closed one. So a caller writing what looks like an open interval gets a closed one, and the endpoints they meant to exclude are members. Nothing warns them.Suggested fix
Remove the conversion. The explicit forms already exist, read unambiguously, and are what the notation problem calls for:
Changing the conversion to produce an open interval instead would be worse: it silently flips the meaning of existing code, and still invents the closedness.
Why now
Removing a public conversion is breaking, so it wants the 2.0 window while it is still in preview — the same reason #861 was filed as a question rather than left as a comment. After 2.0 it waits for 3.0.
The blast radius is worth measuring before deciding; for the array conversion it turned out to be exactly one place in the repository. I have not measured this one yet.
Not part of this
The audit that turned this up also looked at the numeric conversions, which are fine — a widening from any integer or float type keeps its value. One that looked suspicious does not reproduce:
Real.op_Implicit(ERational)has a lossy branch whenDowncastingEnabledis off, butEntity e = someERationaldoes not reach it, giving an exactRationaleither way, and asking specifically for aRealwith downcasting disabled is arguably a request for a decimal.