Skip to content

Commit 3e3d2b3

Browse files
authored
set target profile in the notebook language service via qsharp.init() (#863)
This feature makes `qsharp.init()` generate some hidden output that provides a hint to the language service to set the correct target profile. No magic commands needed! ![target-profile](https://github.com/microsoft/qsharp/assets/16928427/ef768d8c-724a-4854-8254-8c3f905f2a6f)
1 parent 4cdde6a commit 3e3d2b3

File tree

11 files changed

+327
-186
lines changed

11 files changed

+327
-186
lines changed

language_service/src/compilation.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,14 @@ impl Compilation {
7171
}
7272

7373
/// Creates a new `Compilation` by compiling sources from notebook cells.
74-
pub(crate) fn new_notebook<I>(cells: I) -> Self
74+
pub(crate) fn new_notebook<I>(cells: I, target_profile: TargetProfile) -> Self
7575
where
7676
I: Iterator<Item = (Arc<str>, Arc<str>)>,
7777
{
7878
trace!("compiling notebook");
79-
let mut compiler = Compiler::new(
80-
true,
81-
SourceMap::default(),
82-
PackageType::Lib,
83-
TargetProfile::Full,
84-
)
85-
.expect("expected incremental compiler creation to succeed");
79+
let mut compiler =
80+
Compiler::new(true, SourceMap::default(), PackageType::Lib, target_profile)
81+
.expect("expected incremental compiler creation to succeed");
8682

8783
let mut errors = Vec::new();
8884
for (name, contents) in cells {
@@ -126,7 +122,7 @@ impl Compilation {
126122
+ offset
127123
}
128124

129-
/// Regenerates the compilation with the same sources but the passed in configuration options.
125+
/// Regenerates the compilation with the same sources but the passed in workspace configuration options.
130126
pub fn recompile(&mut self, package_type: PackageType, target_profile: TargetProfile) {
131127
let sources: Vec<_> = self
132128
.user_source_contents()
@@ -136,7 +132,7 @@ impl Compilation {
136132

137133
let new = match self.kind {
138134
CompilationKind::OpenProject => Self::new(&sources, package_type, target_profile),
139-
CompilationKind::Notebook => Self::new_notebook(sources.into_iter()),
135+
CompilationKind::Notebook => Self::new_notebook(sources.into_iter(), target_profile),
140136
};
141137
self.package_store = new.package_store;
142138
self.user_package_id = new.user_package_id;

language_service/src/lib.rs

+71-24
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ use log::{error, trace, warn};
2727
use miette::Diagnostic;
2828
pub use project_system::JSFileEntry;
2929
use protocol::{
30-
CompletionList, DiagnosticUpdate, Hover, Location, SignatureHelp, WorkspaceConfigurationUpdate,
30+
CompletionList, DiagnosticUpdate, Hover, Location, NotebookMetadata, SignatureHelp,
31+
WorkspaceConfigurationUpdate,
3132
};
3233
use qsc::{compile::Error, PackageType, TargetProfile};
3334
use qsc_project::FileSystemAsync;
@@ -46,11 +47,12 @@ type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
4647
type AsyncFunction<'a, Arg, Return> = Box<dyn Fn(Arg) -> PinnedFuture<Return> + 'a>;
4748

4849
pub struct LanguageService<'a> {
49-
/// Workspace configuration can include compiler settings
50-
/// that affect error checking and other language server behavior.
51-
/// Currently these settings apply to all documents in the
52-
/// workspace. Per-document configurations are not supported.
53-
configuration: WorkspaceConfiguration,
50+
/// Workspace-wide configuration settings. These can include compiler settings that
51+
/// affect error checking and other language server behavior.
52+
///
53+
/// Some settings can be set both at the compilation scope and at the workspace scope.
54+
/// Compilation-scoped settings take precedence over workspace-scoped settings.
55+
configuration: Configuration,
5456
/// A `CompilationUri` is an identifier for a unique compilation.
5557
/// It is NOT required to be a uri that represents an actual document.
5658
///
@@ -62,7 +64,11 @@ pub struct LanguageService<'a> {
6264
/// The `CompilatinUri` is used when compilation-level errors get reported
6365
/// to the client. Compilation-level errors are defined as errors without
6466
/// an associated source document.
65-
compilations: FxHashMap<CompilationUri, Compilation>,
67+
///
68+
/// `PartialConfiguration` contains configuration overrides for this
69+
/// compilation, explicitly specified through a project manifest (not currently implemented)
70+
/// or notebook metadata.
71+
compilations: FxHashMap<CompilationUri, (Compilation, PartialConfiguration)>,
6672
/// All the documents that we were told about by the client.
6773
///
6874
/// This map doesn't necessarily contain ALL the documents that
@@ -99,12 +105,12 @@ pub enum PendingUpdate {
99105
}
100106

101107
#[derive(Debug)]
102-
struct WorkspaceConfiguration {
108+
struct Configuration {
103109
pub target_profile: TargetProfile,
104110
pub package_type: PackageType,
105111
}
106112

107-
impl Default for WorkspaceConfiguration {
113+
impl Default for Configuration {
108114
fn default() -> Self {
109115
Self {
110116
target_profile: TargetProfile::Full,
@@ -113,6 +119,12 @@ impl Default for WorkspaceConfiguration {
113119
}
114120
}
115121

122+
#[derive(Default)]
123+
struct PartialConfiguration {
124+
pub target_profile: Option<TargetProfile>,
125+
pub package_type: Option<PackageType>,
126+
}
127+
116128
#[derive(Debug)]
117129
struct OpenDocument {
118130
/// This version is the document version provided by the client.
@@ -133,7 +145,7 @@ impl<'a> LanguageService<'a> {
133145
+ 'a,
134146
) -> Self {
135147
LanguageService {
136-
configuration: WorkspaceConfiguration::default(),
148+
configuration: Configuration::default(),
137149
compilations: FxHashMap::default(),
138150
open_documents: FxHashMap::default(),
139151
documents_with_errors: FxHashSet::default(),
@@ -149,6 +161,8 @@ impl<'a> LanguageService<'a> {
149161
/// Updates the workspace configuration. If any compiler settings are updated,
150162
/// a recompilation may be triggered, which will result in a new set of diagnostics
151163
/// being published.
164+
///
165+
/// LSP: workspace/didChangeConfiguration
152166
pub fn update_configuration(&mut self, configuration: &WorkspaceConfigurationUpdate) {
153167
trace!("update_configuration: {configuration:?}");
154168

@@ -214,7 +228,8 @@ impl<'a> LanguageService<'a> {
214228
uri.into()
215229
};
216230
trace!("Loaded project uri {uri} with {} sources", sources.len());
217-
self.compilations.insert(uri.clone(), compilation);
231+
self.compilations
232+
.insert(uri.clone(), (compilation, PartialConfiguration::default()));
218233

219234
// There may be open buffers with sources in the project.
220235
// These buffers need to have their diagnostics reloaded,
@@ -278,8 +293,12 @@ impl<'a> LanguageService<'a> {
278293
/// containing the "%%qsharp" cell magic.
279294
///
280295
/// LSP: notebookDocument/didOpen, notebookDocument/didChange
281-
pub fn update_notebook_document<'b, I>(&mut self, notebook_uri: &str, cells: I)
282-
where
296+
pub fn update_notebook_document<'b, I>(
297+
&mut self,
298+
notebook_uri: &str,
299+
notebook_metadata: &NotebookMetadata,
300+
cells: I,
301+
) where
283302
I: Iterator<Item = (&'b str, u32, &'b str)>, // uri, version, text - basically DidChangeTextDocumentParams in LSP
284303
{
285304
trace!("update_notebook_document: {notebook_uri}");
@@ -290,9 +309,15 @@ impl<'a> LanguageService<'a> {
290309
self.open_documents
291310
.retain(|_, open_doc| notebook_uri != open_doc.compilation.as_ref());
292311

312+
let notebook_configuration = PartialConfiguration {
313+
target_profile: notebook_metadata.target_profile,
314+
package_type: None,
315+
};
316+
let configuration = merge_configurations(&notebook_configuration, &self.configuration);
317+
293318
// Compile the notebook and add each cell into the document map
294-
let compilation =
295-
Compilation::new_notebook(cells.map(|(cell_uri, version, cell_contents)| {
319+
let compilation = Compilation::new_notebook(
320+
cells.map(|(cell_uri, version, cell_contents)| {
296321
trace!("update_notebook_document: cell: {cell_uri} {version}");
297322
self.open_documents.insert(
298323
(*cell_uri).into(),
@@ -302,10 +327,14 @@ impl<'a> LanguageService<'a> {
302327
},
303328
);
304329
(Arc::from(cell_uri), Arc::from(cell_contents))
305-
}));
330+
}),
331+
configuration.target_profile,
332+
);
306333

307-
self.compilations
308-
.insert(compilation_uri.clone(), compilation);
334+
self.compilations.insert(
335+
compilation_uri.clone(),
336+
(compilation, notebook_configuration),
337+
);
309338

310339
self.publish_diagnostics();
311340
}
@@ -430,7 +459,7 @@ impl<'a> LanguageService<'a> {
430459
panic!("{op_name} should not be called before compilation has been initialized",)
431460
});
432461

433-
let res = op(compilation, uri, offset);
462+
let res = op(&compilation.0, uri, offset);
434463
trace!("{op_name} result: {res:?}");
435464
res
436465
}
@@ -443,7 +472,7 @@ impl<'a> LanguageService<'a> {
443472

444473
for (compilation_uri, compilation) in &self.compilations {
445474
trace!("publishing diagnostics for {compilation_uri}");
446-
for (uri, errors) in map_errors_to_docs(compilation_uri, &compilation.errors) {
475+
for (uri, errors) in map_errors_to_docs(compilation_uri, &compilation.0.errors) {
447476
if !self.documents_with_errors.insert(uri.clone()) {
448477
// We already published diagnostics for this document for
449478
// a different compilation.
@@ -486,6 +515,9 @@ impl<'a> LanguageService<'a> {
486515
self.configuration.target_profile = target_profile;
487516
}
488517

518+
// Possible optimization: some projects will have overrides for these configurations,
519+
// so workspace updates won't impact them. We could exclude those projects
520+
// from recompilation, but we don't right now.
489521
trace!("need_recompile after configuration update: {need_recompile}");
490522
need_recompile
491523
}
@@ -495,16 +527,31 @@ impl<'a> LanguageService<'a> {
495527
/// diagnostics for all documents.
496528
fn recompile_all(&mut self) {
497529
for compilation in self.compilations.values_mut() {
498-
compilation.recompile(
499-
self.configuration.package_type,
500-
self.configuration.target_profile,
501-
);
530+
let configuration = merge_configurations(&compilation.1, &self.configuration);
531+
compilation
532+
.0
533+
.recompile(configuration.package_type, configuration.target_profile);
502534
}
503535

504536
self.publish_diagnostics();
505537
}
506538
}
507539

540+
/// Merges workspace configuration with any compilation-specific overrides.
541+
fn merge_configurations(
542+
compilation_scope: &PartialConfiguration,
543+
workspace_scope: &Configuration,
544+
) -> Configuration {
545+
Configuration {
546+
target_profile: compilation_scope
547+
.target_profile
548+
.unwrap_or(workspace_scope.target_profile),
549+
package_type: compilation_scope
550+
.package_type
551+
.unwrap_or(workspace_scope.package_type),
552+
}
553+
}
554+
508555
fn map_errors_to_docs(
509556
compilation_uri: &Arc<str>,
510557
errors: &Vec<Error>,

language_service/src/protocol.rs

+5
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ pub struct ParameterInformation {
9797
pub label: Span,
9898
pub documentation: Option<String>,
9999
}
100+
101+
#[derive(Default)]
102+
pub struct NotebookMetadata {
103+
pub target_profile: Option<TargetProfile>,
104+
}

language_service/src/tests.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![allow(clippy::needless_raw_string_hashes)]
55

66
use crate::{
7-
protocol::{DiagnosticUpdate, WorkspaceConfigurationUpdate},
7+
protocol::{DiagnosticUpdate, NotebookMetadata, WorkspaceConfigurationUpdate},
88
LanguageService,
99
};
1010
use expect_test::{expect, Expect};
@@ -398,6 +398,7 @@ async fn notebook_document_no_errors() {
398398

399399
ls.update_notebook_document(
400400
"notebook.ipynb",
401+
&NotebookMetadata::default(),
401402
[
402403
("cell1", 1, "operation Main() : Unit {}"),
403404
("cell2", 1, "Main()"),
@@ -420,6 +421,7 @@ async fn notebook_document_errors() {
420421

421422
ls.update_notebook_document(
422423
"notebook.ipynb",
424+
&NotebookMetadata::default(),
423425
[
424426
("cell1", 1, "operation Main() : Unit {}"),
425427
("cell2", 1, "Foo()"),
@@ -478,6 +480,7 @@ async fn notebook_update_remove_cell_clears_errors() {
478480

479481
ls.update_notebook_document(
480482
"notebook.ipynb",
483+
&NotebookMetadata::default(),
481484
[
482485
("cell1", 1, "operation Main() : Unit {}"),
483486
("cell2", 1, "Foo()"),
@@ -530,6 +533,7 @@ async fn notebook_update_remove_cell_clears_errors() {
530533

531534
ls.update_notebook_document(
532535
"notebook.ipynb",
536+
&NotebookMetadata::default(),
533537
[("cell1", 1, "operation Main() : Unit {}")].into_iter(),
534538
);
535539

@@ -554,6 +558,7 @@ async fn close_notebook_clears_errors() {
554558

555559
ls.update_notebook_document(
556560
"notebook.ipynb",
561+
&NotebookMetadata::default(),
557562
[
558563
("cell1", 1, "operation Main() : Unit {}"),
559564
("cell2", 1, "Foo()"),

npm/src/language-service/language-service.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import type {
77
IHover,
88
ILocation,
99
ISignatureHelp,
10-
LanguageService,
10+
INotebookMetadata,
1111
IWorkspaceConfiguration,
1212
IWorkspaceEdit,
1313
ITextEdit,
1414
ISpan,
15+
LanguageService,
1516
} from "../../lib/node/qsc_wasm.cjs";
1617
import { log } from "../log.js";
1718
import {
@@ -41,6 +42,7 @@ export interface ILanguageService {
4142
updateNotebookDocument(
4243
notebookUri: string,
4344
version: number,
45+
metadata: INotebookMetadata,
4446
cells: {
4547
uri: string;
4648
version: number;
@@ -155,6 +157,7 @@ export class QSharpLanguageService implements ILanguageService {
155157
async updateNotebookDocument(
156158
notebookUri: string,
157159
version: number,
160+
metadata: INotebookMetadata,
158161
cells: { uri: string; version: number; code: string }[],
159162
): Promise<void> {
160163
// Note: If a cell was deleted, its uri & contents will remain in the map.
@@ -163,7 +166,11 @@ export class QSharpLanguageService implements ILanguageService {
163166
for (const cell of cells) {
164167
this.code[cell.uri] = cell.code;
165168
}
166-
await this.languageService.update_notebook_document(notebookUri, cells);
169+
await this.languageService.update_notebook_document(
170+
notebookUri,
171+
metadata,
172+
cells,
173+
);
167174
}
168175

169176
async closeDocument(documentUri: string): Promise<void> {

npm/test/basics.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ test("language service in notebook", async () => {
613613
);
614614
});
615615

616-
await languageService.updateNotebookDocument("notebook.ipynb", 1, [
616+
await languageService.updateNotebookDocument("notebook.ipynb", 1, {}, [
617617
{ uri: "cell1", version: 1, code: "operation Main() : Unit {}" },
618618
{ uri: "cell2", version: 1, code: "Foo()" },
619619
]);
@@ -625,7 +625,7 @@ test("language service in notebook", async () => {
625625
gotDiagnostics = false;
626626
expectedMessages = [];
627627

628-
await languageService.updateNotebookDocument("notebook.ipynb", 2, [
628+
await languageService.updateNotebookDocument("notebook.ipynb", 2, {}, [
629629
{ uri: "cell1", version: 2, code: "operation Main() : Unit {}" },
630630
{ uri: "cell2", version: 2, code: "Main()" },
631631
]);

0 commit comments

Comments
 (0)