-
-
Notifications
You must be signed in to change notification settings - Fork 98
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
[New DIP] Multiple Template constraints #131
Open
thewilsonator
wants to merge
41
commits into
dlang:master
Choose a base branch
from
thewilsonator:template-constraints
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 39 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
4e20e93
Initial draft of Multiple template constraints
thewilsonator ea94792
Fix code blocks
thewilsonator ae1ab34
Two more
thewilsonator daf7279
Link to implementation
thewilsonator 3a5b70c
Add countUntil example
thewilsonator 9e9fc93
Add note that this applies for functions, methods, classes and struct…
thewilsonator e6573bb
wording
thewilsonator 8568562
Note not implemented yet
thewilsonator 61d147d
Fix error message
thewilsonator 6a15ec6
Again
thewilsonator e388f2a
add missing output
thewilsonator 7b8d07c
add missing `
thewilsonator ee6016d
Fix messages
thewilsonator e0b4d0b
remove less descriptive message
thewilsonator 0e6130c
Correct expressions for if with message
thewilsonator 44a1b16
Add section about static foreach
thewilsonator 3f2eca3
Remove unneeded text in breaking changes
thewilsonator 40d1fa5
make static foreach match the grammar
thewilsonator 298d779
fix
thewilsonator a9b623b
fix
thewilsonator 997e31e
indent
thewilsonator 5d8991e
add countuntil's third overload
thewilsonator 20b9abc
Set status to draft
thewilsonator d084cd6
Make `if` template constraints like `in` contracts.
thewilsonator 0644d18
missing `
thewilsonator a2a3936
Remove reference template text
thewilsonator 281858e
Fix sentence flow.
thewilsonator ba5323e
Add block statement form to description
thewilsonator b539519
add interface and union to the description.
thewilsonator b1d9398
ditto template mixin
thewilsonator 39ade6b
Don't make it sound like only one block constraint is allowed
thewilsonator 6d6cf4d
Reword abstract
thewilsonator d29e19e
Add references
thewilsonator dfc309e
Typo
thewilsonator ada290a
Update DIP1xxx.md
thewilsonator c59a636
Update DIP1xxx.md
thewilsonator 0d607bf
Update DIP1xxx.md
thewilsonator 31f6b87
Update DIP1xxx.md
thewilsonator 3a9c1f4
Fix moar typos!
thewilsonator 5b25117
Restore balance to the ~~force~~ parentheses.
thewilsonator 155efcf
Update DIPs/DIP1xxx.md
mleise File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
# Expression and Block Statement Template Constraints | ||
|
||
| Field | Value | | ||
|-----------------|-----------------------------------------------------------------| | ||
| DIP: | (number/id -- assigned by DIP Manager) | | ||
| Review Count: | 0 | | ||
| Author: | Nicholas Wilson | | ||
| Implementation: | https://github.com/thewilsonator/dmd/tree/template-constraint-dip | | ||
| Status: | Draft | | ||
|
||
## Abstract | ||
|
||
Allow multiple `if` template constraints, for the expression form allow an optional message to be printed in the | ||
event that overload resolution fails (similar to `static assert`), as well as a block statement | ||
form of template constraint that allows the use of `static foreach`. | ||
That is to say, template constraint `if` becomes the static form of contract precondition `in`. | ||
|
||
The template is considered a valid overload iff each of the constraints is satified. | ||
|
||
Expression form: | ||
```D | ||
template all(alias pred) | ||
{ | ||
bool all(Range)(Range range) | ||
if (isInputRange!Range) | ||
if (is(typeof(unaryFun!pred(range.front))), | ||
"`" ~ pred.stringof[1..$-1] ~ "` isn't a unary predicate function for range.front")) | ||
thewilsonator marked this conversation as resolved.
Show resolved
Hide resolved
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. This variant isn't equivalent to the two below, right? Presenting three forms of the same equivalent contracts would help comparing them. |
||
{ | ||
} | ||
} | ||
``` | ||
|
||
Block statement form: | ||
```D | ||
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
if | ||
{ | ||
static assert(isForwardRange!R); | ||
static assert(Rs.length > 0, "need a needle to countUntil with"); | ||
thewilsonator marked this conversation as resolved.
Show resolved
Hide resolved
|
||
static foreach (alias N; Rs) | ||
static assert(isForwardRange!(N) == isInputRange!(N), "needles that are ranges must be forward ranges"); | ||
static foreach (n; needles) | ||
static assert(is(typeof(startsWith!pred(haystack, n))), | ||
"predicate `" ~ pred.stringof "` must be valid for `startsWith!pred(haystack, "~n.stringof~"`)); | ||
} | ||
``` | ||
|
||
Mixed: | ||
|
||
```D | ||
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
if (isForwardRange!R) | ||
if (Rs.length > 0, "need a needle to countUntil with") | ||
if | ||
{ | ||
static foreach (alias N; Rs) | ||
static assert(isForwardRange!(N) == isInputRange!(N), "needles that are ranges must be forward ranges"); | ||
static foreach (n; needles) | ||
static assert(is(typeof(startsWith!pred(haystack, n))), | ||
"predicate `" ~ pred.stringof "` must be valid for `startsWith!pred(haystack, "~n.stringof~"`)); | ||
} | ||
``` | ||
|
||
### Reference | ||
|
||
https://github.com/dlang/phobos/pull/6607 | ||
|
||
https://issues.dlang.org/show_bug.cgi?id=13683 | ||
|
||
## Contents | ||
* [Rationale](#rationale) | ||
* [Description](#description) | ||
* [Breaking Changes and Deprecations](#breaking-changes-and-deprecations) | ||
* [Copyright & License](#copyright--license) | ||
* [Reviews](#reviews) | ||
|
||
## Rationale | ||
|
||
It is well known that compilation error messages due to template contraint overload resolution | ||
are particularly difficult to decipher. This is not helped by the number of overloads and very | ||
precice (and thus complex) constraints placed on the overloads. When overload resolution fails | ||
the compiler will print out all the in scope overloads and their constraints, without indication | ||
of which constraints have failed. | ||
|
||
While it is not possible in the general case to provide useful information as to what constraints | ||
have failed and why, because a constraint may have an arbitrary combination of logic, the vast | ||
majority of constraints are expressed in Conjunctive Normal Form (CNF). In this case it is definitely | ||
possible to provide better daignostics as to which clauses have failed. However the current grammar | ||
provides no way to translate particularly verbose constraints to a user not intimately familiar with | ||
the constraint. | ||
|
||
This DIP therefore proposes to formalise the use of CNF constraints by allowing multiple `if` constraints, | ||
the expression form with an optional message (similar to what was done with contracts in DIP1009), as well as block statements that | ||
allows the use of `static foreach` and to declare `alias`es and `enum`s to eliminate the need for recursive templates in template constraints | ||
(similar to the `in` contract form prior to DIP1009). | ||
This will put the compiler in a much better position to provide useful diagnostics, such as indicating which clauses are not satisfied | ||
and allowing the template author to provide messages in the case of non-intuitive formulations of constraints | ||
e.g. `if (isForwardRange!(R) == isInputRange!(R), "needles that are ranges must be forward ranges")`. | ||
|
||
Using the particularly egregious example of the first overload of `std.algorithm.searching.countUntil`, | ||
its current signature of | ||
|
||
```D | ||
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
if (isForwardRange!R | ||
&& Rs.length > 0 | ||
&& isForwardRange!(Rs[0]) == isInputRange!(Rs[0]) | ||
&& is(typeof(startsWith!pred(haystack, needles[0]))) | ||
&& (Rs.length == 1 | ||
|| is(typeof(countUntil!pred(haystack, needles[1 .. $]))))) | ||
``` | ||
|
||
would be be written using the block statement form to eliminate the recursive constraint as | ||
|
||
```D | ||
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
if (isForwardRange!R) | ||
if (Rs.length > 0, "need a needle to countUntil with") // example message, probably not needed for something this simple | ||
if | ||
{ | ||
static foreach (n; needles) | ||
{ | ||
static assert(isForwardRange!(typeof(n)) == isInputRange!(typeof(n)), | ||
"`"~n.stringof ~"`: needles that are ranges must be forward ranges"); | ||
static assert(is(typeof(startsWith!pred(haystack, n))), | ||
"predicate `" ~ pred.stringof "` must be valid for"~ | ||
"`startsWith!pred(haystack, "~n.stringof~"`)); | ||
} | ||
} | ||
``` | ||
the first two constraints do not require the use of the block statement form to use a static foreach so | ||
they can be done in the expression style. | ||
|
||
This could print on error using `countUntil("foo", inputRangeOfInt)` | ||
``` | ||
example.d(42): Error: template `std.algorithm.searching.countUntil` cannot deduce function from argument types !()(string,NotARange), candidates are: | ||
/path/to/std/algorithm/searching.d(747): std.algorithm.searching.countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles) | ||
not satisfied: `inputRangeOfInt`: needles that are ranges must be forward ranges | ||
not satisfied: predicate `a == b` must be valid for `startsWith!pred(haystack, inputRangeOfInt)` | ||
/path/to/std/algorithm/searching.d(835): std.algorithm.searching.countUntil(alias pred = "a == b", R, N)(R haystack, N needle) if (isInputRange!R && is(typeof(binaryFun!pred(haystack.front, needle)) : bool)) | ||
/path/to/std/algorithm/searching.d(913): std.algorithm.searching.countUntil(alias pred, R)(R haystack) if (isInputRange!R && is(typeof(unaryFun!pred(haystack.front)) : bool)) | ||
``` | ||
|
||
## Description | ||
|
||
Template constraints are changed to allow multiple `if` template constraints. | ||
All constraints must be satisfied for the template to be viable. | ||
Examples given are for `template`s but also apply to constraints on template mixins, functions, methods, classes, | ||
interfaces, structs and unions. | ||
|
||
### Expression Form | ||
|
||
The expression form is: | ||
```D | ||
template foo(T) | ||
if (constraint1!T) | ||
if (constraint2!T) | ||
if (constraint3!T) { ... } | ||
``` | ||
|
||
An optional constraint message can be used to provide a more easily understood description of why a | ||
constraint has not been met. | ||
|
||
```D | ||
template foo(T) | ||
if (isForwardRange!T == isInputRange!T, T.stringof ~" must be a forward range if it is a range") | ||
``` | ||
### Block Statement Form | ||
|
||
The block statement form is: | ||
```D | ||
template foo(T) | ||
if | ||
{ | ||
...constraints... | ||
} | ||
``` | ||
where `...constraints...` may contain only `static assert`, `static foreach` and `static if` statements and `enum` and `alias` declarations. | ||
The declarations are local to the scope of the constraint or `static foreach` or `static if` they are declared in. | ||
Each `static assert` in the block statement in satisfied `static if` statements, | ||
including those in unrolled `static foreach` statements, must pass for the constraint to be satisfied. | ||
|
||
### Grammar changes | ||
|
||
```diff | ||
+Constraints: | ||
+ Constraint | ||
+ Constraint Constraints | ||
|
||
Constraint: | ||
- if ( Expression ) | ||
+ if ( AssertArguments ) | ||
+ if BlockStatement | ||
|
||
FuncDeclaratorSuffix: | ||
Parameters MemberFunctionAttributes[opt] | ||
- TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt] | ||
+ TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt] | ||
|
||
TemplateDeclaration: | ||
- template Identifier TemplateParameters Constraint[opt] { DeclDefs[opt] } | ||
+ template Identifier TemplateParameters Constraints[opt] { DeclDefs[opt] } | ||
|
||
ConstructorTemplate: | ||
- this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt] : | ||
- this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraint[opt] FunctionBody | ||
+ this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt] : | ||
+ this TemplateParameters Parameters MemberFunctionAttributes[opt] Constraints[opt] FunctionBody | ||
|
||
ClassTemplateDeclaration: | ||
- class Identifier TemplateParameters Constraint[opt] BaseClassList[opt] AggregateBody | ||
- class Identifier TemplateParameters BaseClassList[opt] Constraint[opt] AggregateBody | ||
+ class Identifier TemplateParameters Constraints[opt] BaseClassList[opt] AggregateBody | ||
+ class Identifier TemplateParameters BaseClassList[opt] Constraints[opt] AggregateBody | ||
|
||
InterfaceTemplateDeclaration: | ||
- interface Identifier TemplateParameters Constraint[opt] BaseInterfaceList[opt] AggregateBody | ||
- interface Identifier TemplateParameters BaseInterfaceList Constraint AggregateBody | ||
+ interface Identifier TemplateParameters Constraints[opt] BaseInterfaceList[opt] AggregateBody | ||
+ interface Identifier TemplateParameters BaseInterfaceList Constraints AggregateBody | ||
|
||
StructTemplateDeclaration: | ||
- struct Identifier TemplateParameters Constraint[opt] AggregateBody | ||
+ struct Identifier TemplateParameters Constraints[opt] AggregateBody | ||
|
||
UnionTemplateDeclaration: | ||
- union Identifier TemplateParameters Constraint[opt] AggregateBody | ||
+ union Identifier TemplateParameters Constraints[opt] AggregateBody | ||
|
||
TemplateMixinDeclaration: | ||
- mixin template Identifier TemplateParameters Constraint[opt] { DeclDefs[opt] } | ||
+ mixin template Identifier TemplateParameters Constraints[opt] { DeclDefs[opt] } | ||
``` | ||
|
||
|
||
## Breaking Changes and Deprecations | ||
|
||
N/A. The current template constraint syntax becomes a single expression constraint. | ||
|
||
## Copyright & License | ||
|
||
Copyright (c) 2018 by the D Language Foundation | ||
|
||
Licensed under [Creative Commons Zero 1.0](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt) | ||
|
||
## Reviews | ||
|
||
The DIP Manager will supplement this section with a summary of each review stage | ||
of the DIP process beyond the Draft Review. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I know it's not a "unary predicate", but the user needs to know why.
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.
You do now. Before you would have first had to determine which sub constraint had failed.
To provide more information the compiler needs to determine if
unaryFun!pred
failed instantiation, and then ifunaryFun!pred(range.front)
is type correct.I'm not saying it can't be done, in fact it is enabled by this DIP but that relies on errors in speculative code which has a very poor signal to noise ratio, or a set of heuristics as to what template constraints tend to look like.