Skip to content

Commit d069216

Browse files
authored
Remove AsString and disallow callables in interpolated strings (#301)
Follow up to #291: * Remove the `AsString` library function, which was only added because we didn't have string interpolation. Update the samples to use string interpolation instead. * Add a type class for types that can be converted to a string. This excludes callables, because there is no clear non-arbitrary string form to convert them to.
1 parent ec25196 commit d069216

File tree

10 files changed

+181
-34
lines changed

10 files changed

+181
-34
lines changed

compiler/qsc_eval/src/intrinsic.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ pub(crate) fn invoke_intrinsic(
5656
Err(_) => Break(Reason::Error(Error::Output(name_span))),
5757
},
5858

59-
"AsString" => Continue(Value::String(args.to_string().into())),
60-
6159
"CheckZero" => Continue(Value::Bool(qubit_is_zero(
6260
args.try_into().with_span(args_span)?,
6361
))),

compiler/qsc_eval/src/intrinsic/tests.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,22 +147,6 @@ fn message() {
147147
);
148148
}
149149

150-
#[test]
151-
fn to_string() {
152-
check_intrinsic_result("", "AsString(One)", &expect![["One"]]);
153-
}
154-
155-
#[test]
156-
fn to_string_message() {
157-
check_intrinsic_output(
158-
"",
159-
r#"Message(AsString(PauliX))"#,
160-
&expect![[r#"
161-
PauliX
162-
"#]],
163-
);
164-
}
165-
166150
#[test]
167151
fn check_zero() {
168152
check_intrinsic_result(

compiler/qsc_frontend/src/typeck/infer.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@ pub(super) enum Class {
2424
Integral(Ty),
2525
Iterable { container: Ty, item: Ty },
2626
Num(Ty),
27+
Show(Ty),
2728
Unwrap { wrapper: Ty, base: Ty },
2829
}
2930

3031
impl Class {
3132
fn dependencies(&self) -> Vec<&Ty> {
3233
match self {
33-
Self::Add(ty) | Self::Adj(ty) | Self::Eq(ty) | Self::Integral(ty) | Self::Num(ty) => {
34+
Self::Add(ty)
35+
| Self::Adj(ty)
36+
| Self::Eq(ty)
37+
| Self::Integral(ty)
38+
| Self::Num(ty)
39+
| Self::Show(ty) => {
3440
vec![ty]
3541
}
3642
Self::Call { callee, .. } => vec![callee],
@@ -87,6 +93,7 @@ impl Class {
8793
item: f(item),
8894
},
8995
Self::Num(ty) => Self::Num(f(ty)),
96+
Self::Show(ty) => Self::Show(f(ty)),
9097
Self::Unwrap { wrapper, base } => Self::Unwrap {
9198
wrapper: f(wrapper),
9299
base: f(base),
@@ -127,6 +134,7 @@ impl Class {
127134
Class::Num(ty) => check_num(&ty)
128135
.then_some(Vec::new())
129136
.ok_or(ClassError(Class::Num(ty), span)),
137+
Class::Show(ty) => check_show(ty, span),
130138
Class::Unwrap { wrapper, base } => {
131139
// TODO: If the wrapper type is a user-defined type, look up its underlying type.
132140
// https://github.com/microsoft/qsharp/issues/148
@@ -152,6 +160,7 @@ impl Display for Class {
152160
Class::Integral(ty) => write!(f, "Integral<{ty}>"),
153161
Class::Iterable { container, .. } => write!(f, "Iterable<{container}>"),
154162
Class::Num(ty) => write!(f, "Num<{ty}>"),
163+
Class::Show(ty) => write!(f, "Show<{ty}>"),
155164
Class::Unwrap { wrapper, .. } => write!(f, "Unwrap<{wrapper}>"),
156165
}
157166
}
@@ -556,6 +565,18 @@ fn check_num(ty: &Ty) -> bool {
556565
matches!(ty, Ty::Prim(PrimTy::BigInt | PrimTy::Double | PrimTy::Int))
557566
}
558567

568+
fn check_show(ty: Ty, span: Span) -> Result<Vec<Constraint>, ClassError> {
569+
match ty {
570+
Ty::Array(item) => Ok(vec![Constraint::Class(Class::Show(*item), span)]),
571+
Ty::Prim(_) => Ok(Vec::new()),
572+
Ty::Tuple(items) => Ok(items
573+
.into_iter()
574+
.map(|item| Constraint::Class(Class::Show(item), span))
575+
.collect()),
576+
_ => Err(ClassError(Class::Show(ty), span)),
577+
}
578+
}
579+
559580
fn check_has_field(
560581
record: Ty,
561582
name: String,

compiler/qsc_frontend/src/typeck/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ impl<'a> Context<'a> {
280280
for component in components {
281281
match component {
282282
StringComponent::Expr(expr) => {
283+
let span = expr.span;
283284
let expr = self.infer_expr(expr);
285+
self.inferrer.class(span, Class::Show(expr.ty));
284286
diverges = diverges || expr.diverges;
285287
}
286288
StringComponent::Lit(_) => {}

compiler/qsc_frontend/src/typeck/tests.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,158 @@ fn range_full_field_end() {
14581458
);
14591459
}
14601460

1461+
#[test]
1462+
fn interpolate_int() {
1463+
check(
1464+
"",
1465+
r#"$"{4}""#,
1466+
&expect![[r##"
1467+
#0 0-6 "$\"{4}\"" : String
1468+
#1 3-4 "4" : Int
1469+
"##]],
1470+
);
1471+
}
1472+
1473+
#[test]
1474+
fn interpolate_string() {
1475+
check(
1476+
"",
1477+
r#"$"{"foo"}""#,
1478+
&expect![[r##"
1479+
#0 0-10 "$\"{\"foo\"}\"" : String
1480+
#1 3-8 "\"foo\"" : String
1481+
"##]],
1482+
);
1483+
}
1484+
1485+
#[test]
1486+
fn interpolate_qubit() {
1487+
check(
1488+
"",
1489+
r#"{ use q = Qubit(); $"{q}" }"#,
1490+
&expect![[r##"
1491+
#0 0-27 "{ use q = Qubit(); $\"{q}\" }" : String
1492+
#1 0-27 "{ use q = Qubit(); $\"{q}\" }" : String
1493+
#3 6-7 "q" : Qubit
1494+
#5 10-17 "Qubit()" : Qubit
1495+
#7 19-25 "$\"{q}\"" : String
1496+
#8 22-23 "q" : Qubit
1497+
"##]],
1498+
);
1499+
}
1500+
1501+
#[test]
1502+
fn interpolate_function() {
1503+
check(
1504+
indoc! {"
1505+
namespace A {
1506+
function Foo() : () {}
1507+
}
1508+
"},
1509+
r#"$"{A.Foo}""#,
1510+
&expect![[r##"
1511+
#2 30-32 "()" : ()
1512+
#3 38-40 "{}" : ()
1513+
#5 43-53 "$\"{A.Foo}\"" : String
1514+
#6 46-51 "A.Foo" : (() -> ())
1515+
Error(Type(Error(MissingClass(Show(Arrow(Function, Tuple([]), Tuple([]), {})), Span { lo: 46, hi: 51 }))))
1516+
"##]],
1517+
);
1518+
}
1519+
1520+
#[test]
1521+
fn interpolate_operation() {
1522+
check(
1523+
indoc! {"
1524+
namespace A {
1525+
operation Foo() : () {}
1526+
}
1527+
"},
1528+
r#"$"{A.Foo}""#,
1529+
&expect![[r##"
1530+
#2 31-33 "()" : ()
1531+
#3 39-41 "{}" : ()
1532+
#5 44-54 "$\"{A.Foo}\"" : String
1533+
#6 47-52 "A.Foo" : (() => ())
1534+
Error(Type(Error(MissingClass(Show(Arrow(Operation, Tuple([]), Tuple([]), {})), Span { lo: 47, hi: 52 }))))
1535+
"##]],
1536+
);
1537+
}
1538+
1539+
#[test]
1540+
fn interpolate_int_array() {
1541+
check(
1542+
"",
1543+
r#"$"{[1, 2, 3]}""#,
1544+
&expect![[r##"
1545+
#0 0-14 "$\"{[1, 2, 3]}\"" : String
1546+
#1 3-12 "[1, 2, 3]" : (Int)[]
1547+
#2 4-5 "1" : Int
1548+
#3 7-8 "2" : Int
1549+
#4 10-11 "3" : Int
1550+
"##]],
1551+
);
1552+
}
1553+
1554+
#[test]
1555+
fn interpolate_function_array() {
1556+
check(
1557+
indoc! {"
1558+
namespace A {
1559+
function Foo() : () {}
1560+
function Bar() : () {}
1561+
}
1562+
"},
1563+
r#"$"{[A.Foo, A.Bar]}""#,
1564+
&expect![[r##"
1565+
#2 30-32 "()" : ()
1566+
#3 38-40 "{}" : ()
1567+
#6 57-59 "()" : ()
1568+
#7 65-67 "{}" : ()
1569+
#9 70-89 "$\"{[A.Foo, A.Bar]}\"" : String
1570+
#10 73-87 "[A.Foo, A.Bar]" : ((() -> ()))[]
1571+
#11 74-79 "A.Foo" : (() -> ())
1572+
#12 81-86 "A.Bar" : (() -> ())
1573+
Error(Type(Error(MissingClass(Show(Arrow(Function, Tuple([]), Tuple([]), {})), Span { lo: 73, hi: 87 }))))
1574+
"##]],
1575+
);
1576+
}
1577+
1578+
#[test]
1579+
fn interpolate_int_string_tuple() {
1580+
check(
1581+
"",
1582+
r#"$"{(1, "foo")}""#,
1583+
&expect![[r##"
1584+
#0 0-15 "$\"{(1, \"foo\")}\"" : String
1585+
#1 3-13 "(1, \"foo\")" : (Int, String)
1586+
#2 4-5 "1" : Int
1587+
#3 7-12 "\"foo\"" : String
1588+
"##]],
1589+
);
1590+
}
1591+
1592+
#[test]
1593+
fn interpolate_int_function_tuple() {
1594+
check(
1595+
indoc! {"
1596+
namespace A {
1597+
function Foo() : () {}
1598+
}
1599+
"},
1600+
r#"$"{(1, A.Foo)}""#,
1601+
&expect![[r##"
1602+
#2 30-32 "()" : ()
1603+
#3 38-40 "{}" : ()
1604+
#5 43-58 "$\"{(1, A.Foo)}\"" : String
1605+
#6 46-56 "(1, A.Foo)" : (Int, (() -> ()))
1606+
#7 47-48 "1" : Int
1607+
#8 50-55 "A.Foo" : (() -> ())
1608+
Error(Type(Error(MissingClass(Show(Arrow(Function, Tuple([]), Tuple([]), {})), Span { lo: 46, hi: 56 }))))
1609+
"##]],
1610+
);
1611+
}
1612+
14611613
#[test]
14621614
fn newtype_cons() {
14631615
check(

library/std/core.qs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License.
33

44
namespace Microsoft.Quantum.Core {
5-
65
/// # Summary
76
/// Returns the number of elements in an array.
87
///
@@ -97,9 +96,4 @@ namespace Microsoft.Quantum.Core {
9796
let start = r::Start + ((r::End - r::Start) / r::Step) * r::Step;
9897
start..-r::Step..r::Start
9998
}
100-
101-
function AsString<'T>(v : 'T) : String {
102-
body intrinsic;
103-
}
104-
10599
}

pip/samples/sample.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@
205205
" X(q);\n",
206206
" Microsoft.Quantum.Diagnostics.DumpMachine();\n",
207207
" let r = M(q);\n",
208-
" Message(\"The result of the measurement is \" + AsString(r));\n",
208+
" Message($\"The result of the measurement is {r}\");\n",
209209
" Reset(q);\n",
210210
" r\n",
211211
"}\n",
@@ -750,7 +750,7 @@
750750
" let r = M(q);\n",
751751
" if (i % 100000) == 0 {\n",
752752
" DumpMachine();\n",
753-
" Message(\"Result: \" + AsString(r));\n",
753+
" Message($\"Result: {r}\");\n",
754754
" }\n",
755755
" Reset(q);\n",
756756
" }\n",

samples/Grover.qs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ namespace Microsoft.Quantum.Samples.SimpleGrover {
119119
// the result is still detected with the sufficient
120120
// probability.
121121
let nIterations = NIterations(nQubits); // Try setting to 1.
122-
Message("Number of iterations: " + AsString(nIterations));
122+
Message($"Number of iterations: {nIterations}");
123123

124124
let results = GroverSearch(
125125
nQubits,

samples/Teleportation.qs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,7 @@ namespace Microsoft.Quantum.Samples.Teleportation {
163163
for idxRun in 1 .. 10 {
164164
let sent = DrawRandomInt(0, 1) == 1;
165165
let received = TeleportClassicalMessage(sent);
166-
Message(
167-
"Round " + AsString(idxRun) +
168-
": Sent " + AsString(sent) +
169-
", got " + AsString(received) + ".");
166+
Message($"Round {idxRun}: Sent {sent}, got {received}.");
170167
Message(sent==received ? "Teleportation successful!" | "");
171168
}
172169
for idxRun in 1 .. 10 {

samples/qrng.qs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ namespace Microsoft.Quantum.Samples.Qrng {
4444
@EntryPoint()
4545
operation Main() : Int {
4646
let max = 50;
47-
Message("Sampling a random number between 0 and " +
48-
AsString(max) + ": ");
47+
Message($"Sampling a random number between 0 and {max}: ");
4948
return SampleRandomNumberInRange(max);
5049
}
5150
}

0 commit comments

Comments
 (0)