Skip to content

Commit 22ef7f2

Browse files
committed
Updated "Tagged Strings" to be eager and allow typed expressions.
1 parent c5af6b7 commit 22ef7f2

File tree

1 file changed

+63
-31
lines changed

1 file changed

+63
-31
lines changed

working/tagged-strings/feature-specification.md

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ Summary: Use Dart's string literal syntax to create values of user-defined types
88
by allowing an identifier before a string to identify a "tag processor" that
99
controls how the string literal and its interpolated expressions are evaluted.
1010

11+
See also: [#1479][], [#1987][]
12+
13+
[#1479]: https://github.com/dart-lang/language/issues/1479
14+
[#1987]: https://github.com/dart-lang/language/issues/1987
15+
1116
## Motivation
1217

1318
JavaScript has a feature called [tagged template literals][]. This proposal
@@ -96,15 +101,12 @@ The above code is essentially seen by the compiler as:
96101
```dart
97102
var add = exprStringLiteral(['2 + 3'], []);
98103
var subtract = exprStringLiteral(['7 - 5'], []);
99-
var multiply = exprStringLiteral(['4 * ', ' / '],
100-
[() => add, () => subtract]);
104+
var multiply = exprStringLiteral(['4 * ', ' / '], [add, subtract]);
101105
```
102106

103107
The literal text parts are pulled out into one list. The interpolated
104-
expressions are each wrapped in closures and put into a second list. Then these
105-
are passed to a function whose name is based on the tag identifier. Wrapping
106-
the interpolated expressions in closures gives the tag processor control over
107-
when or if the expressions are evaluated.
108+
expressions are put into a second list. Then these are passed to a function
109+
whose name is based on the tag identifier.
108110

109111
Since the intent of this feature is brevity, we expect users to choose short tag
110112
names like `expr` here, `html`, `css`, etc. Since those names are likely to
@@ -118,11 +120,11 @@ that looks something like:
118120
```dart
119121
Code exprStringLiteral(
120122
List<String> strings,
121-
List<Object? Function()) values) {
123+
List<Object> values) {
122124
var buffer = StringBuffer();
123125
for (var i = 0; i < values.length; i++) {
124126
buffer.write(strings[i]);
125-
var value = values[i]();
127+
var value = values[i];
126128
if (value is Expression) {
127129
buffer.write('(' + value.toSource() + ')');
128130
} else {
@@ -135,7 +137,10 @@ Code exprStringLiteral(
135137
}
136138
```
137139

138-
Note that this toy implementation implicitly wraps values that are subexpressions in parentheses to avoid precedence errors. The interpolated expressions passed to a tag processor do not need to evaluate to strings. It's up to the processor to define which kinds of values are allowed.
140+
Note that this toy implementation implicitly wraps values that are
141+
subexpressions in parentheses to avoid precedence errors. The interpolated
142+
expressions passed to a tag processor do not need to evaluate to strings. It's
143+
up to the processor to define which kinds of values are allowed.
139144

140145
Note also that the tag handler does not have to *return* a string either. Here
141146
it returns `Code`. While tag strings are based on Dart string literal syntax,
@@ -228,26 +233,65 @@ A tagged string is an identifier followed by a series of adjacent string
228233
literals which may contain interpolated expressions. This is treated as
229234
syntactic sugar for a function call with two list arguments.
230235
231-
### Desugaring
232-
233236
The tag identifier is suffixed with `StringLiteral` to determine the tag
234237
processor name.
235238
239+
### Static typing
240+
241+
It is a compile-time error if:
242+
243+
* The tag processor name does not resolve to a function that can be called
244+
with two positional arguments and no named arguments.
245+
* `List<String>` cannot be assigned to the first parameter's type.
246+
* `List<T>` cannot be assigned to the first parameter's type for some `T`.
247+
This inferred `T` is called the *interpolated expression type*.
248+
* Any interpolated expression in the string literal cannot be assigned to the
249+
interpolated expression type.
250+
251+
The latter two rules mean that a tag processor can restrict the types of
252+
expressions that are allowed in interpolation by specifying an element type on
253+
the second parameter to the function. For example:
254+
255+
```dart
256+
Expression exprStringLiteral(
257+
List<String> strings,
258+
List<Expression> values) {
259+
var buffer = StringBuffer();
260+
for (var i = 0; i < values.length; i++) {
261+
buffer.write(strings[i]);
262+
buffer.write('(' + values[i].toSource() + ')');
263+
}
264+
265+
buffer.write(strings.last);
266+
return Expression.parse(buffer.toString());
267+
}
268+
269+
main() {
270+
var identifier = expr "foo";
271+
var add = expr "$identifier + $identifier"; // OK.
272+
273+
var notExpr = 123;
274+
var subtract = expr "$identifier - $notExpr"; // <-- Error.
275+
}
276+
```
277+
278+
The marked line has a compile error because `notExpr` has type `int`, which
279+
cannot be assigned to the expected interpolated expression type `Code.`
280+
281+
The type of a tagged string literal expression is the return type of the
282+
corresponding tagged string literal function.
283+
284+
### Desugaring
285+
236286
Adjacent strings are implicitly concatenated into a single string as in current
237287
Dart.
238288

239289
The string is split into string parts and interpolation expressions. All of the
240290
string literal parts from the `SINGLE_LINE_*` and `MULTI_LINE_*` rules are
241291
collected in order and put in an object that implements `List<String>`.
242292

243-
Each `expression` is wrapped in a closure of type `Object? Function()` that
244-
evaluates and returns the expression when invoked. These closures are collected
245-
in order into an object that implements `List<Object? Function()>`.
246-
247-
**TODO: What if an interpolated expression uses `await`? We could implicitly
248-
make the function `async` in that case and require the template function to
249-
handle a future result. Or we could make it a compile-time error like we do
250-
when using `await` in the initializer of a `late` variable.**
293+
Every `expression` from those rules is collected in order into an object that
294+
implements `List<T>` where `T` is the interpolated expression type.
251295

252296
The structure of the grammar is such that the list of string parts will always
253297
be one element longer than the list of expressions. If there are no expressions,
@@ -272,18 +316,6 @@ The tagged string literal is replaced with a call to the tag processor function.
272316
The list of string parts and expressions (which may be empty) are passed to that
273317
function as positional arguments.
274318

275-
### Static typing
276-
277-
It is a compile-time error if:
278-
279-
* The tag named suffixed with `StringLiteral` does not resolve to a function
280-
that can be called with two positional arguments.
281-
* `List<String>` cannot be assigned to the first parameter's type.
282-
* `List<Object? Function()>` cannot be assigned to the first parameter's type.
283-
284-
The type of a tagged string literal expression is the return type of the
285-
corresponding tagged string literal function.
286-
287319
## Runtime semantics
288320

289321
This feature is purely syntactic sugar, so there are no runtime semantics

0 commit comments

Comments
 (0)