-
Notifications
You must be signed in to change notification settings - Fork 205
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
Specify that constant assertions are always evaluated #1271
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,9 @@ Status: Draft | |
|
||
## CHANGELOG | ||
|
||
2020.10.26 | ||
- Specify that constant evaluation executes all assertions. | ||
|
||
2020.10.14 | ||
- Include selector `!` among the null-shorting constructs. | ||
|
||
|
@@ -1008,6 +1011,33 @@ void main() { | |
} | ||
``` | ||
|
||
### Constant assertions | ||
|
||
Currently assertions are enabled only when the compiler is passed an `enable-asserts` flag. | ||
With null safety, when an assert is evaluated as part of a constant invocation of a `const` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could call them There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. I think I did that deliberately when I introduced assertions into initializer lists (where they are not statements). |
||
constructor, any `assert`s in the initializer list are evaluated. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that sentence is garbled. That said, an assertion "being enabled" is a concept more than simply saying what its semantics are in different cases. Implementations can still completely remove run-time assertions from the compiled output when assertions are disabled, because they have the "null-semantics": completes normally with no side effects. |
||
The `enable-asserts` flag then only applies to *run-time assertions*. | ||
|
||
More precisely, a assert _s_ of the form <code>assert(_e_<sub>1</sub>, _e_<sub>2</sub>)</code> | ||
is executed as follows: | ||
* If the execution of _s_ happens during evaluation of a const invocation of the | ||
surrounding `const` constructor, then _e_<sub>1</sub> is evaluated to a constant value _c_. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about Then we don't have to mention 'completes normally' as if it's part of the constant evaluation semantics. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. We already do the "compile-time error if expression would throw" for constant evaluation. |
||
It is a compile-time error if this evaluation would have thrown, or if _c_ is not the | ||
`bool` value `true`, and otherwise _s_ completes normally. | ||
* Otherwise (when it's not part of a constant evaluation), if run-time assertions are enabled | ||
then then _e_<sub>1</sub> is evaluated to a value _v_. | ||
If _v_ is not an instance of the type `bool`, then a dynamic error occurs. | ||
If _v_ is the value `false` then _e_<sub>2</sub> is evaluated to a value _m_, | ||
and an object, _o_, implementing the class `AssertionError` and containing _m_ is created. | ||
Then execution of _s_ throws _o_ and a stack trace corresponding | ||
to the current evaluation context. | ||
If _v_ is the value `true` then execution of *s* completes normally. | ||
* Otherwise (when execution is not not part of a constant evaluation | ||
and run-time assertions are disabled), execution of _s_ completes normally without evaluating | ||
either of _e_<sub>1</sub> or _e_<sub>2</sub>. | ||
|
||
(An assert of the form <code>assert(_e_)</code> is equivalent to <code>assert(_e_, null)</code>.) | ||
|
||
### Null promotion | ||
|
||
The machinery of type promotion is extended to promote the type of variables | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text in section 'Assert' of the language specification already says almost everything which is said here.
Wouldn't it suffice to have a very short section here? We would then rely on the existing specification text for all the details:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's tricky, because we don't actually separate compile-time and run-time in the specification, we just specify the meaning of constants such that they can be pre-computed, but we don't actually say that you have to do so.
We still need a way to specify that something is happening as part of constant evaluation because it matters in some situations. For example
const x = 1 ~/ 0;
is a compile-time error instead of throwing aDivisionByNullError
... andassert(false, e2)
shouldn't need to evaluatee2
, so we don't need to require thate2
is a potentially constant expression.Which actually does mean that we can't reasonably delay evaluation of constant expressions until run-time unless we allow compile-time errors at run-time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, we specify that evaluation of a constant object expression occurs at run time and then it is (recursively, bottom-up) canonicalized. It makes no difference whether that constant object expression is in a constant context, whether it is used in the initialization of a
const
variable, etc., it is a rule for<constObjectExpression>
anywhere. Of course, if it is in a constant context then theconst
keyword may be absent, it is still statically known to be there.This rule is compatible with an evaluation at compile time. However, it doesn't require an implementation to perform the evaluation at compile time and reuse that result every time it is needed at run time. For instance, the expression could be evaluated at compile time and the result could be discarded, and then evaluation and canonicalization could be done at run time. Also, because the constant sublanguage is small and Turing-incomplete, execution is guaranteed to be possible at compile time. So when we want to determine whether an error should be emitted, we can evaluate the constant object expression.
So if we do say 'Assertions are enabled during the evaluation of a constant object expression' then it is allowed for an implementation to raise the compile-time error early ("at compile time"). That's probably exactly what we want.
This also means that if a tool gives rise to an execution where a constant object expression is actually evaluated at run time then there will be a run-time error, even though we set out to say that these errors should occur at compile-time. But tools don't have to do this, so in practice we can maintain that these errors occur early.
If an execution uses hot reload then compilation will occur at run time anyway.
Don't you think that would be OK for tool teams to work with?