-
Notifications
You must be signed in to change notification settings - Fork 209
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
Loosen const requirements for asserts #4260
Comments
This would add a second kind of asserts to Let's strawman it as Currently an It's an assertion, it shouldn't change the behavior of the constructor. It can, but it shouldn't have side effects. That's still the biggest issue with this function. Currently With a runtime assert, you can write: final (int, int) pair;
const Foo(int x, int y) : runtimeAssert(++x == x && --y == y), this.pair = (x, y); A And you can create closures over the scope: const Foo.capture(int x, int y)
: runtimeAssert(_escape = ((int dx, int dy) => (x += dx, y += dy)) != null),
runtimeAssert(_escape?.call(10, 20)) != null),
this.pair = (x, y);
static (int, int) Function(int, int)? _escape; The compiler can now see And if you want to be very clever, it can be used to tell whether an invocation is const factory Discern(int x, int y) = Discern._;
const Discern._(int x, int y, [bool isConst = true, String? _betterString])
: runtimeAssert(
(isConst = false) || (_betterString = "({$x.toRadixString(16)}, ${y.toRadixString(16)})" != null)),
this.text = _betterString ?? "($x, $y)",
this._isConst = isConst;
final bool _isConst;
String toString() => "${_isConst ? "const ": "")Discern($x, $y)"; I would totally use this! const factory RegExp(this.pattern, {...flags...}) = RegExp._;
const RegExp_(this.pattern, {...flags..., bool isConst = false, _CompiledRegExp? _compiled})
: runtimeAssert(_compiled = _validateAndCompilePattern(pattern, ...flags..) != null),
this._compiled = _compiled,
// ...
final _CompiledRegExp? _compiled;
static _constCompilationCache = Expando<_CompiledRegExp>();
_CompiledRegExp get _ensureCompiled => _compiled ??
(_constCompilationCache[this] ??= _validateAndCompilePattern(pattern, ...flags..); Which probably means it's not a good idea, or not a well-focused idea, if I'm going to exploit it for something It's a powerful feature in several ways, which probably means that thinking of it as only for So, consider instead this feature (strawman-syntax!): A It's like a promotion of the context to "not constant" which only happens if all paths leading to the code has ensured that With that, you can do runtime asserts as: assert(const || (any.expression.here)); which is satisfied trivially during I can get my Can maybe even use that final int x;
const Foo([int? x]) : assert(x is int), x = const ? x : (x ?? 0); Here the compiler knows that void foo( final bool isConst, bool assertionsEnabled, [int? x]) {
int y;
bool b = (isConst || assertionsEnabled) && (x is int || (throw AssertionError("nope")));
y = (isConst && b) ? x : (x ?? 0);
// ...
} which does promote Such a So another option is to have two versions of the constructor, with the same name, and overloading on const Foo(int x, int y) : assert(...), this.pair = (x, y);
new Foo(int x, int y) : assert(non-const-checks), this.pair = _computePair(x, y); It's overloading, but only on const Foo(int x, int y)
: assert(...),
this.pair = (x, y),
new:
assert(non-const-checks),
this.pair = _computePair(x, y); There are two completely separate initializer lists, one for Less strictly powerful (might cause some duplication, cannot be used in a redirecting const Foo.redir(int x, int y) : this(x, y), new: this._runtime(x, y); so that it redirects based on const-ness. (Can do that for factory constructor too: const factory Foo.facredir(int x, int y) = Foo, new: Foo._runtime; Syntax is not awesome. It's also not awesome to have a slightly complicated language feature for conditional redirection, and then only use it for const Foo.redirc(int x, int y): if (const) this(x, y) else this._runtime(x, y); (Using Without any new language feature, current solutions are indeed limited. You can have only a non-constant constructor. Which makes it impossible to create default values or other values that need to be const, hence this request. You can have two constructors: const Data.unchecked(this.value);
Data(this.value) : assert(value.contains("banana")); But then, why use an assert at all, if you can get the validation at all times, and throw a better error? Data(this.value) {
if (!value.contains("banana")) {
throw ArgumentError.value(value, "value", "Has no banana!");
}
} (You can get the better error too with a non-constant assert, as assert(value.contains("banana"), throw ArgumentError.value(value, "value", "Has no banana!")) but you can't with a constant assert. I'd like to change that, make it so that the second expression Nothing prevents using You can just not validate. Which has its own issues if the validation is important. (But then it shouldn't be only an Another alternative would be extending the abilities of constant evaluation, fx to include some of the following possible operations (always with potentially constant receiver and arguments that evaluate to instances of platform types - mainly for lists/sets/maps):
That won't ever solve the problem, it'll just push the limits out a little further. The next request will be to support something like |
That may be true. If the parameter is not nullable, and the parameter class is unlikely to ever get a constant-capable subclass (unlike fx I don't recommend making things |
You know, the first thing that comes to mind here is actually overloading. class Foo {
const Foo(this.bar);
Foo(this.bar) : assert(bar.whatever != null);
} Well, that and constant/stable accessors. honestly though, in this case, you probably cant use const here anyway as-is. perhaps you should focus more on making sure that the source object is pre-validated, such as by having the argument be a variant where |
Currently, everything in a const constructor must be a compile-time constant, including the asserts:
This limits the usefulness of
assert()
. Often, I get into a situation where I'd love to have a constant class but also still want to check that the constructor arguments are valid when the class is constructed at runtime. Like so:The above example comes from someone's StackOverflow question. Here's another example that is closer to what I needed just a minute ago:
Today, my only solution is to either move the assert to somewhere else, or make the class non-const.
I understand that an assert like
box.nonFinalItem != null
can't be run at compile time, and therefore can't be const.But as a developer, when I'm writing an assert like that, I'm not expecting the assert to be checked at compile time. I expect it to be checked at runtime, when the widget is created dynamiclly.
I wonder if it's possible to loosen the requirement of
const
-ness in asserts. Can non-const asserts be permitted to be attached to const constructors, with the understanding that they won't be run at compile time?The text was updated successfully, but these errors were encountered: