Skip to content

Commit 7a91721

Browse files
authored
Add opt-in language features to the Qsharp.json manifest (#1089)
Closes #543 ## Overview This PR adds two things: 1. The ability to opt-in to language features via the project manifest, `qsharp.init()`, or command-line flags 2. A simple feature, `v2-preview-syntax`, ostensibly created to allow users to opt-in to breaking syntax that's coming in the future, but really created so I could wire up and show how a real language feature works. Only one syntax change was made in this feature: removal of scoped qubit blocks. This is because a scoped qubit block is identical to a scoped block with a qubit in it. `use qubit = Qubit () {};` is equivalent to `{ use qubit = Qubit(); }`. We therefore prefer the latter. ## Implementation Notes This PR includes the addition of `ParserContext`. It became clear to me that language features need to be globally available to _at least_ the parser, and probably other areas of the compiler later. Instead of plumbing this all around manually as an argument, I promoted `Scanner` into a `ParserContext`. This PR also refactors manifest loading in `projectSystem.ts` -- we had some duplicate code in `getManifestThrowsOnParseFailure` and `getManifest`. I DRY'd that up. ## FAQ ### Why opt-in features? In our discussions of evolving the language, it became clear that we prioritize both improving the language, including in breaking ways, and maintaining compatibility. As a result, introducing the notion of being able to opt-in to a specific language feature became a blocker for most further language design work. ### How will packages with different feature sets work together? In general, we should aim to write features that don't break compatibility with other packages. The language server and all compiler scenarios assess language features on a per-package basis, so as long as the individual compilation units are sane and well-formed, their feature sets should be interoperable. ## Tests These tests are considered _passing_ if turning on the language feature `v2-preview-syntax` causes the compiler to reject scoped qubit block syntax. ✅ Histogram: https://github.com/microsoft/qsharp/assets/12157751/631dded2-d016-4933-83fe-bdff67852f50 ✅ Language Service https://github.com/microsoft/qsharp/assets/12157751/964574f6-c6bd-4225-acd4-6eb2afaec41c ✅ Jupyter Notebook https://github.com/microsoft/qsharp/assets/12157751/fc5cc8b8-1c47-4dd9-b478-a233f16fc9e3 ✅ `qsc` https://github.com/microsoft/qsharp/assets/12157751/48e746fc-f7fe-4e9a-a658-d428b640d062 ✅ `qsi` https://github.com/microsoft/qsharp/assets/12157751/8154de70-80c5-444c-ac4e-f33d775a2fa9 ✅ Debugger https://github.com/microsoft/qsharp/assets/12157751/5aa7fb5a-3ec4-43fe-b256-6b75c808fe7e ✅ Language service inside of notebook cells ![image](https://github.com/microsoft/qsharp/assets/12157751/b710cc71-ce7d-4732-8acb-f315778239a1) ✅ QIR Generation https://github.com/microsoft/qsharp/assets/12157751/eed50ca5-85c4-4434-9443-a6c67ef10337
1 parent 224219f commit 7a91721

File tree

77 files changed

+1308
-363
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1308
-363
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ probability = "0.20"
5454
indenter = "0.3"
5555
regex-lite = "0.1"
5656
rustc-hash = "1.1.0"
57-
serde = "1.0"
57+
serde = { version = "1.0", features = [ "derive" ] }
5858
serde-wasm-bindgen = "0.6"
5959
wasm-bindgen = "0.2"
6060
wasm-bindgen-futures = "0.4"

compiler/qsc/benches/eval.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use criterion::{criterion_group, criterion_main, Criterion};
55
use indoc::indoc;
66
use qsc::{interpret::Interpreter, PackageType};
7+
use qsc_data_structures::language_features::LanguageFeatures;
78
use qsc_eval::output::GenericReceiver;
89
use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
910

@@ -20,6 +21,7 @@ pub fn teleport(c: &mut Criterion) {
2021
sources,
2122
PackageType::Exe,
2223
RuntimeCapabilityFlags::all(),
24+
LanguageFeatures::default(),
2325
)
2426
.expect("code should compile");
2527
b.iter(move || {
@@ -38,6 +40,7 @@ pub fn deutsch_jozsa(c: &mut Criterion) {
3840
sources,
3941
PackageType::Exe,
4042
RuntimeCapabilityFlags::all(),
43+
LanguageFeatures::default(),
4144
)
4245
.expect("code should compile");
4346
b.iter(move || {
@@ -56,6 +59,7 @@ pub fn large_file(c: &mut Criterion) {
5659
sources,
5760
PackageType::Exe,
5861
RuntimeCapabilityFlags::all(),
62+
LanguageFeatures::default(),
5963
)
6064
.expect("code should compile");
6165
b.iter(move || {
@@ -86,6 +90,7 @@ pub fn array_append(c: &mut Criterion) {
8690
sources,
8791
PackageType::Exe,
8892
RuntimeCapabilityFlags::all(),
93+
LanguageFeatures::default(),
8994
)
9095
.expect("code should compile");
9196
b.iter(move || {
@@ -116,6 +121,7 @@ pub fn array_update(c: &mut Criterion) {
116121
sources,
117122
PackageType::Exe,
118123
RuntimeCapabilityFlags::all(),
124+
LanguageFeatures::default(),
119125
)
120126
.expect("code should compile");
121127
b.iter(move || {
@@ -134,6 +140,7 @@ pub fn array_literal(c: &mut Criterion) {
134140
sources,
135141
PackageType::Exe,
136142
RuntimeCapabilityFlags::all(),
143+
LanguageFeatures::default(),
137144
)
138145
.expect("code should compile");
139146
b.iter(move || {
@@ -169,6 +176,7 @@ pub fn large_nested_iteration(c: &mut Criterion) {
169176
sources,
170177
PackageType::Exe,
171178
RuntimeCapabilityFlags::all(),
179+
LanguageFeatures::default(),
172180
)
173181
.expect("code should compile");
174182
b.iter(move || {

compiler/qsc/benches/large.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use criterion::{criterion_group, criterion_main, Criterion};
55
use qsc::compile::{self, compile};
6+
use qsc_data_structures::language_features::LanguageFeatures;
67
use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap};
78
use qsc_passes::PackageType;
89

@@ -20,6 +21,7 @@ pub fn large_file(c: &mut Criterion) {
2021
sources,
2122
PackageType::Exe,
2223
RuntimeCapabilityFlags::all(),
24+
LanguageFeatures::default(),
2325
);
2426
assert!(reports.is_empty());
2527
})

compiler/qsc/src/bin/qsc.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use log::info;
99
use miette::{Context, IntoDiagnostic, Report};
1010
use qsc::compile::compile;
1111
use qsc_codegen::qir_base;
12+
use qsc_data_structures::language_features::LanguageFeatures;
1213
use qsc_frontend::{
1314
compile::{PackageStore, RuntimeCapabilityFlags, SourceContents, SourceMap, SourceName},
1415
error::WithSource,
@@ -55,6 +56,10 @@ struct Cli {
5556
/// Path to a Q# manifest for a project
5657
#[arg(short, long)]
5758
qsharp_json: Option<PathBuf>,
59+
60+
/// Language features to compile with
61+
#[arg(short, long)]
62+
features: Vec<String>,
5863
}
5964

6065
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@@ -79,6 +84,8 @@ fn main() -> miette::Result<ExitCode> {
7984
dependencies.push(store.insert(qsc::compile::std(&store, capabilities)));
8085
}
8186

87+
let mut features = LanguageFeatures::from_iter(cli.features);
88+
8289
let mut sources = cli
8390
.sources
8491
.iter()
@@ -93,12 +100,23 @@ fn main() -> miette::Result<ExitCode> {
93100
let mut project_sources = project.sources;
94101

95102
sources.append(&mut project_sources);
103+
104+
features.merge(LanguageFeatures::from_iter(
105+
manifest.manifest.language_features,
106+
));
96107
}
97108
}
98109

99110
let entry = cli.entry.unwrap_or_default();
100111
let sources = SourceMap::new(sources, Some(entry.into()));
101-
let (unit, errors) = compile(&store, &dependencies, sources, package_type, capabilities);
112+
let (unit, errors) = compile(
113+
&store,
114+
&dependencies,
115+
sources,
116+
package_type,
117+
capabilities,
118+
features,
119+
);
102120
let package_id = store.insert(unit);
103121
let unit = store.get(package_id).expect("package should be in store");
104122

compiler/qsc/src/bin/qsi.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use miette::{Context, IntoDiagnostic, Report, Result};
99
use num_bigint::BigUint;
1010
use num_complex::Complex64;
1111
use qsc::interpret::{self, InterpretResult, Interpreter};
12+
use qsc_data_structures::language_features::LanguageFeatures;
1213
use qsc_eval::{
1314
output::{self, Receiver},
1415
val::Value,
@@ -47,6 +48,10 @@ struct Cli {
4748
/// Path to a Q# manifest for a project
4849
#[arg(short, long)]
4950
qsharp_json: Option<PathBuf>,
51+
52+
/// Language features to compile with
53+
#[arg(short, long)]
54+
features: Vec<String>,
5055
}
5156

5257
struct TerminalReceiver;
@@ -80,6 +85,8 @@ fn main() -> miette::Result<ExitCode> {
8085
.map(read_source)
8186
.collect::<miette::Result<Vec<_>>>()?;
8287

88+
let mut features = LanguageFeatures::from_iter(cli.features);
89+
8390
if sources.is_empty() {
8491
let fs = StdFs;
8592
let manifest = Manifest::load(cli.qsharp_json)?;
@@ -88,6 +95,10 @@ fn main() -> miette::Result<ExitCode> {
8895
let mut project_sources = project.sources;
8996

9097
sources.append(&mut project_sources);
98+
99+
features.merge(LanguageFeatures::from_iter(
100+
manifest.manifest.language_features,
101+
));
91102
}
92103
}
93104
if cli.exec {
@@ -96,6 +107,7 @@ fn main() -> miette::Result<ExitCode> {
96107
SourceMap::new(sources, cli.entry.map(std::convert::Into::into)),
97108
PackageType::Exe,
98109
RuntimeCapabilityFlags::all(),
110+
features,
99111
) {
100112
Ok(interpreter) => interpreter,
101113
Err(errors) => {
@@ -115,6 +127,7 @@ fn main() -> miette::Result<ExitCode> {
115127
SourceMap::new(sources, None),
116128
PackageType::Lib,
117129
RuntimeCapabilityFlags::all(),
130+
features,
118131
) {
119132
Ok(interpreter) => interpreter,
120133
Err(errors) => {

compiler/qsc/src/compile.rs

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

44
use miette::{Diagnostic, Report};
5+
use qsc_data_structures::language_features::LanguageFeatures;
56
use qsc_frontend::{
67
compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, SourceMap},
78
error::WithSource,
@@ -27,8 +28,15 @@ pub fn compile(
2728
sources: SourceMap,
2829
package_type: PackageType,
2930
capabilities: RuntimeCapabilityFlags,
31+
language_features: LanguageFeatures,
3032
) -> (CompileUnit, Vec<Error>) {
31-
let mut unit = qsc_frontend::compile::compile(store, dependencies, sources, capabilities);
33+
let mut unit = qsc_frontend::compile::compile(
34+
store,
35+
dependencies,
36+
sources,
37+
capabilities,
38+
language_features,
39+
);
3240
let mut errors = Vec::new();
3341
for error in unit.errors.drain(..) {
3442
errors.push(WithSource::from_map(&unit.sources, error.into()));

compiler/qsc/src/incremental.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::compile::{self, compile, core, std};
55
use miette::Diagnostic;
6+
use qsc_data_structures::language_features::LanguageFeatures;
67
use qsc_frontend::{
78
compile::{OpenPackageStore, PackageStore, RuntimeCapabilityFlags, SourceMap},
89
error::WithSource,
@@ -37,6 +38,7 @@ impl Compiler {
3738
sources: SourceMap,
3839
package_type: PackageType,
3940
capabilities: RuntimeCapabilityFlags,
41+
language_features: LanguageFeatures,
4042
) -> Result<Self, Errors> {
4143
let core = core();
4244
let mut store = PackageStore::new(core);
@@ -47,15 +49,27 @@ impl Compiler {
4749
dependencies.push(id);
4850
}
4951

50-
let (unit, errors) = compile(&store, &dependencies, sources, package_type, capabilities);
52+
let (unit, errors) = compile(
53+
&store,
54+
&dependencies,
55+
sources,
56+
package_type,
57+
capabilities,
58+
language_features,
59+
);
5160
if !errors.is_empty() {
5261
return Err(errors);
5362
}
5463

5564
let source_package_id = store.insert(unit);
5665
dependencies.push(source_package_id);
5766

58-
let frontend = qsc_frontend::incremental::Compiler::new(&store, dependencies, capabilities);
67+
let frontend = qsc_frontend::incremental::Compiler::new(
68+
&store,
69+
dependencies,
70+
capabilities,
71+
language_features,
72+
);
5973
let store = store.open();
6074

6175
Ok(Self {

compiler/qsc/src/interpret.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use num_bigint::BigUint;
2626
use num_complex::Complex;
2727
use qsc_codegen::qir_base::BaseProfSim;
2828
use qsc_data_structures::{
29+
language_features::LanguageFeatures,
2930
line_column::{Encoding, Range},
3031
span::Span,
3132
};
@@ -123,12 +124,13 @@ impl Interpreter {
123124
sources: SourceMap,
124125
package_type: PackageType,
125126
capabilities: RuntimeCapabilityFlags,
127+
language_features: LanguageFeatures,
126128
) -> Result<Self, Vec<Error>> {
127129
let mut lowerer = qsc_eval::lower::Lowerer::new();
128130
let mut fir_store = fir::PackageStore::new();
129131

130-
let compiler =
131-
Compiler::new(std, sources, package_type, capabilities).map_err(into_errors)?;
132+
let compiler = Compiler::new(std, sources, package_type, capabilities, language_features)
133+
.map_err(into_errors)?;
132134

133135
for (id, unit) in compiler.package_store() {
134136
fir_store.insert(
@@ -359,8 +361,15 @@ impl Debugger {
359361
sources: SourceMap,
360362
capabilities: RuntimeCapabilityFlags,
361363
position_encoding: Encoding,
364+
language_features: LanguageFeatures,
362365
) -> Result<Self, Vec<Error>> {
363-
let interpreter = Interpreter::new(true, sources, PackageType::Exe, capabilities)?;
366+
let interpreter = Interpreter::new(
367+
true,
368+
sources,
369+
PackageType::Exe,
370+
capabilities,
371+
language_features,
372+
)?;
364373
let source_package_id = interpreter.source_package;
365374
Ok(Self {
366375
interpreter,

compiler/qsc/src/interpret/debug/tests.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use indoc::indoc;
77
use miette::Result;
8+
use qsc_data_structures::language_features::LanguageFeatures;
89
use qsc_eval::{output::CursorReceiver, val::Value};
910
use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
1011
use qsc_passes::PackageType;
@@ -70,6 +71,7 @@ fn stack_traces_can_cross_eval_session_and_file_boundaries() {
7071
source_map,
7172
PackageType::Lib,
7273
RuntimeCapabilityFlags::all(),
74+
LanguageFeatures::default(),
7375
)
7476
.expect("Failed to compile base environment.");
7577

@@ -144,6 +146,7 @@ fn stack_traces_can_cross_file_and_entry_boundaries() {
144146
source_map,
145147
PackageType::Exe,
146148
RuntimeCapabilityFlags::all(),
149+
LanguageFeatures::default(),
147150
)
148151
.expect("Failed to compile base environment.");
149152

0 commit comments

Comments
 (0)