qsharp-lang 1.0.28-dev → 1.0.29-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as wasm from "../lib/web/qsc_wasm.js";
1
2
  import { TargetProfile } from "../lib/web/qsc_wasm.js";
2
3
  import { ICompiler, ICompilerWorker } from "./compiler/compiler.js";
3
4
  import { IDebugService, IDebugServiceWorker } from "./debug-service/debug-service.js";
@@ -7,10 +8,15 @@ export { qsharpLibraryUriScheme };
7
8
  export declare function loadWasmModule(uriOrBuffer: string | ArrayBuffer): Promise<void>;
8
9
  export declare function getLibrarySourceContent(path: string): Promise<string | undefined>;
9
10
  export declare function getDebugService(): Promise<IDebugService>;
11
+ export declare function getProjectLoader(readFile: (path: string) => Promise<string | null>, loadDirectory: (path: string) => Promise<[string, number][]>, getManifest: (path: string) => Promise<{
12
+ manifestDirectory: string;
13
+ } | null>): Promise<wasm.ProjectLoader>;
10
14
  export declare function getDebugServiceWorker(workerArg: string | Worker): IDebugServiceWorker;
11
15
  export declare function getCompiler(): Promise<ICompiler>;
12
16
  export declare function getCompilerWorker(workerArg: string | Worker): ICompilerWorker;
13
- export declare function getLanguageService(): Promise<ILanguageService>;
17
+ export declare function getLanguageService(readFile?: (uri: string) => Promise<string | null>, listDir?: (uri: string) => Promise<[string, number][]>, getManifest?: (uri: string) => Promise<{
18
+ manifestDirectory: string;
19
+ } | null>): Promise<ILanguageService>;
14
20
  export declare function getLanguageServiceWorker(workerArg: string | Worker): ILanguageServiceWorker;
15
21
  export { type Dump, type ShotResult } from "./compiler/common.js";
16
22
  export { type CompilerState } from "./compiler/compiler.js";
package/dist/browser.js CHANGED
@@ -72,6 +72,10 @@ export async function getDebugService() {
72
72
  await instantiateWasm();
73
73
  return new QSharpDebugService(wasm);
74
74
  }
75
+ export async function getProjectLoader(readFile, loadDirectory, getManifest) {
76
+ await instantiateWasm();
77
+ return new wasm.ProjectLoader(readFile, loadDirectory, getManifest);
78
+ }
75
79
  // Create the debugger inside a WebWorker and proxy requests.
76
80
  // If the Worker was already created via other means and is ready to receive
77
81
  // messages, then the worker may be passed in and it will be initialized.
@@ -122,9 +126,9 @@ export function getCompilerWorker(workerArg) {
122
126
  worker.onmessage = (ev) => proxy.onMsgFromWorker(ev.data);
123
127
  return proxy;
124
128
  }
125
- export async function getLanguageService() {
129
+ export async function getLanguageService(readFile, listDir, getManifest) {
126
130
  await instantiateWasm();
127
- return new QSharpLanguageService(wasm);
131
+ return new QSharpLanguageService(wasm, readFile, listDir, getManifest);
128
132
  }
129
133
  // Create the compiler inside a WebWorker and proxy requests.
130
134
  // If the Worker was already created via other means and is ready to receive
@@ -5,9 +5,9 @@ type Wasm = typeof import("../../lib/node/qsc_wasm.cjs");
5
5
  export interface ICompiler {
6
6
  checkCode(code: string): Promise<VSDiagnostic[]>;
7
7
  getHir(code: string): Promise<string>;
8
- run(code: string, expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
9
- getQir(code: string): Promise<string>;
10
- getEstimates(code: string, params: string): Promise<string>;
8
+ run(sources: [string, string][], expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
9
+ getQir(sources: [string, string][]): Promise<string>;
10
+ getEstimates(sources: [string, string][], params: string): Promise<string>;
11
11
  checkExerciseSolution(user_code: string, exercise_sources: string[], eventHandler: IQscEventTarget): Promise<boolean>;
12
12
  }
13
13
  export type ICompilerWorker = ICompiler & IServiceProxy;
@@ -16,10 +16,10 @@ export declare class Compiler implements ICompiler {
16
16
  private wasm;
17
17
  constructor(wasm: Wasm);
18
18
  checkCode(code: string): Promise<VSDiagnostic[]>;
19
- getQir(code: string): Promise<string>;
20
- getEstimates(code: string, params: string): Promise<string>;
19
+ getQir(sources: [string, string][]): Promise<string>;
20
+ getEstimates(sources: [string, string][], params: string): Promise<string>;
21
21
  getHir(code: string): Promise<string>;
22
- run(code: string, expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
22
+ run(sources: [string, string][], expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
23
23
  checkExerciseSolution(user_code: string, exercise_sources: string[], eventHandler: IQscEventTarget): Promise<boolean>;
24
24
  }
25
25
  export declare function onCompilerEvent(msg: string, eventTarget: IQscEventTarget): void;
@@ -10,28 +10,36 @@ export class Compiler {
10
10
  this.wasm = wasm;
11
11
  globalThis.qscGitHash = this.wasm.git_hash();
12
12
  }
13
+ // Note: This function does not support project mode.
14
+ // see https://github.com/microsoft/qsharp/pull/849#discussion_r1409821143
13
15
  async checkCode(code) {
14
16
  let diags = [];
15
- const languageService = new this.wasm.LanguageService((uri, version, errors) => {
17
+ const languageService = new this.wasm.LanguageService();
18
+ const work = languageService.start_background_work((uri, version, errors) => {
16
19
  diags = errors;
17
- });
20
+ }, () => Promise.resolve(null), () => Promise.resolve([]), () => Promise.resolve(null));
18
21
  languageService.update_document("code", 1, code);
22
+ // Yield to let the language service background worker handle the update
23
+ await Promise.resolve();
24
+ languageService.stop_background_work();
25
+ await work;
26
+ languageService.free();
19
27
  return mapDiagnostics(diags, code);
20
28
  }
21
- async getQir(code) {
22
- return this.wasm.get_qir(code);
29
+ async getQir(sources) {
30
+ return this.wasm.get_qir(sources);
23
31
  }
24
- async getEstimates(code, params) {
25
- return this.wasm.get_estimates(code, params);
32
+ async getEstimates(sources, params) {
33
+ return this.wasm.get_estimates(sources, params);
26
34
  }
27
35
  async getHir(code) {
28
36
  return this.wasm.get_hir(code);
29
37
  }
30
- async run(code, expr, shots, eventHandler) {
38
+ async run(sources, expr, shots, eventHandler) {
31
39
  // All results are communicated as events, but if there is a compiler error (e.g. an invalid
32
40
  // entry expression or similar), it may throw on run. The caller should expect this promise
33
41
  // may reject without all shots running or events firing.
34
- this.wasm.run(code, expr, (msg) => onCompilerEvent(msg, eventHandler), shots);
42
+ this.wasm.run(sources, expr, (msg) => onCompilerEvent(msg, eventHandler), shots);
35
43
  }
36
44
  async checkExerciseSolution(user_code, exercise_sources, eventHandler) {
37
45
  const success = this.wasm.check_exercise_solution(user_code, exercise_sources, (msg) => onCompilerEvent(msg, eventHandler));
@@ -4,7 +4,7 @@ import { IQscEventTarget } from "../compiler/events.js";
4
4
  import { IServiceProxy } from "../worker-proxy.js";
5
5
  type QscWasm = typeof import("../../lib/node/qsc_wasm.cjs");
6
6
  export interface IDebugService {
7
- loadSource(path: string, source: string, target: TargetProfile, entry: string | undefined): Promise<string>;
7
+ loadSource(sources: [string, string][], target: TargetProfile, entry: string | undefined): Promise<string>;
8
8
  getBreakpoints(path: string): Promise<IBreakpointSpan[]>;
9
9
  getLocalVariables(): Promise<Array<IVariable>>;
10
10
  captureQuantumState(): Promise<Array<IQuantumState>>;
@@ -21,7 +21,7 @@ export declare class QSharpDebugService implements IDebugService {
21
21
  private debugService;
22
22
  private code;
23
23
  constructor(wasm: QscWasm);
24
- loadSource(path: string, source: string, target: TargetProfile, entry: string | undefined): Promise<string>;
24
+ loadSource(sources: [string, string][], target: TargetProfile, entry: string | undefined): Promise<string>;
25
25
  getStackFrames(): Promise<IStackFrame[]>;
26
26
  evalNext(bps: number[], eventHandler: IQscEventTarget): Promise<IStructStepResult>;
27
27
  evalStepIn(bps: number[], eventHandler: IQscEventTarget): Promise<IStructStepResult>;
@@ -12,9 +12,11 @@ export class QSharpDebugService {
12
12
  this.wasm = wasm;
13
13
  this.debugService = new wasm.DebugService();
14
14
  }
15
- async loadSource(path, source, target, entry) {
16
- this.code[path] = source;
17
- return this.debugService.load_source(path, source, target, entry);
15
+ async loadSource(sources, target, entry) {
16
+ for (const [path, source] of sources) {
17
+ this.code[path] = source;
18
+ }
19
+ return this.debugService.load_source(sources, target, entry);
18
20
  }
19
21
  async getStackFrames() {
20
22
  const stack_frame_list = this.debugService.get_stack_frames();
@@ -41,7 +41,12 @@ export declare class QSharpLanguageService implements ILanguageService {
41
41
  private languageService;
42
42
  private eventHandler;
43
43
  private code;
44
- constructor(wasm: QscWasm);
44
+ private readFile;
45
+ private backgroundWork;
46
+ constructor(wasm: QscWasm, readFile?: (uri: string) => Promise<string | null>, listDir?: (uri: string) => Promise<[string, number][]>, getManifest?: (uri: string) => Promise<{
47
+ manifestDirectory: string;
48
+ } | null>);
49
+ loadFile(uri: string): Promise<string | null>;
45
50
  updateConfiguration(config: IWorkspaceConfiguration): Promise<void>;
46
51
  updateDocument(documentUri: string, version: number, code: string): Promise<void>;
47
52
  updateNotebookDocument(notebookUri: string, version: number, metadata: INotebookMetadata, cells: {
@@ -65,6 +70,6 @@ export declare class QSharpLanguageService implements ILanguageService {
65
70
  removeEventListener<T extends LanguageServiceEvent["type"]>(type: T, listener: (event: Extract<LanguageServiceEvent, {
66
71
  type: T;
67
72
  }>) => void): void;
68
- onDiagnostics(uri: string, version: number | undefined, diagnostics: VSDiagnostic[]): void;
73
+ onDiagnostics(uri: string, version: number | undefined, diagnostics: VSDiagnostic[]): Promise<void>;
69
74
  }
70
75
  export {};
@@ -5,12 +5,25 @@ import { log } from "../log.js";
5
5
  import { mapDiagnostics, mapUtf16UnitsToUtf8Units, mapUtf8UnitsToUtf16Units, } from "../vsdiagnostic.js";
6
6
  export const qsharpLibraryUriScheme = "qsharp-library-source";
7
7
  export class QSharpLanguageService {
8
- constructor(wasm) {
8
+ constructor(wasm, readFile = () => Promise.resolve(null), listDir = () => Promise.resolve([]), getManifest = () => Promise.resolve(null)) {
9
9
  this.eventHandler = new EventTarget();
10
10
  // We need to keep a copy of the code for mapping diagnostics to utf16 offsets
11
11
  this.code = {};
12
12
  log.info("Constructing a QSharpLanguageService instance");
13
- this.languageService = new wasm.LanguageService(this.onDiagnostics.bind(this));
13
+ this.languageService = new wasm.LanguageService();
14
+ this.backgroundWork = this.languageService.start_background_work(this.onDiagnostics.bind(this), readFile, listDir, getManifest);
15
+ this.readFile = readFile;
16
+ }
17
+ async loadFile(uri) {
18
+ const result = this.code[uri];
19
+ if (result === undefined || result === null) {
20
+ return await this.readFile(uri);
21
+ }
22
+ if (result === null || result === undefined) {
23
+ log.error("File", uri, "wasn't in document map when we expected it to be");
24
+ return null;
25
+ }
26
+ return result;
14
27
  }
15
28
  async updateConfiguration(config) {
16
29
  this.languageService.update_configuration(config);
@@ -37,8 +50,8 @@ export class QSharpLanguageService {
37
50
  this.languageService.close_notebook_document(documentUri, cellUris);
38
51
  }
39
52
  async getCompletions(documentUri, offset) {
40
- const code = this.code[documentUri];
41
- if (code === undefined) {
53
+ const code = await this.loadFile(documentUri);
54
+ if (code === null) {
42
55
  log.error(`getCompletions: expected ${documentUri} to be in the document map`);
43
56
  return { items: [] };
44
57
  }
@@ -50,8 +63,8 @@ export class QSharpLanguageService {
50
63
  return result;
51
64
  }
52
65
  async getHover(documentUri, offset) {
53
- const code = this.code[documentUri];
54
- if (code === undefined) {
66
+ const code = await this.loadFile(documentUri);
67
+ if (code === null) {
55
68
  log.error(`getHover: expected ${documentUri} to be in the document map`);
56
69
  return undefined;
57
70
  }
@@ -63,22 +76,22 @@ export class QSharpLanguageService {
63
76
  return result;
64
77
  }
65
78
  async getDefinition(documentUri, offset) {
66
- const sourceCode = this.code[documentUri];
67
- if (sourceCode === undefined) {
79
+ const sourceCode = await this.loadFile(documentUri);
80
+ if (sourceCode === undefined || sourceCode === null) {
68
81
  log.error(`getDefinition: expected ${documentUri} to be in the document map`);
69
82
  return undefined;
70
83
  }
71
84
  const convertedOffset = mapUtf16UnitsToUtf8Units([offset], sourceCode)[offset];
72
85
  const result = this.languageService.get_definition(documentUri, convertedOffset);
73
86
  if (result) {
74
- let targetCode = this.code[result.source];
75
- if (targetCode === undefined) {
87
+ let targetCode = (await this.loadFile(result.source)) || null;
88
+ if (targetCode === null) {
76
89
  // Inspect the URL protocol (equivalent to the URI scheme + ":").
77
90
  // If the scheme is our library scheme, we need to call the wasm to
78
91
  // provide the library file's contents to do the utf8->utf16 mapping.
79
92
  const url = new URL(result.source);
80
93
  if (url.protocol === qsharpLibraryUriScheme + ":") {
81
- targetCode = wasm.get_library_source_content(url.pathname);
94
+ targetCode = wasm.get_library_source_content(url.pathname) || null;
82
95
  if (targetCode === undefined) {
83
96
  log.error(`getDefinition: expected ${url} to be in the library`);
84
97
  return undefined;
@@ -96,8 +109,8 @@ export class QSharpLanguageService {
96
109
  return result;
97
110
  }
98
111
  async getReferences(documentUri, offset, includeDeclaration) {
99
- const sourceCode = this.code[documentUri];
100
- if (sourceCode === undefined) {
112
+ const sourceCode = await this.loadFile(documentUri);
113
+ if (sourceCode === undefined || sourceCode === null) {
101
114
  log.error(`getReferences: expected ${documentUri} to be in the document map`);
102
115
  return [];
103
116
  }
@@ -106,13 +119,13 @@ export class QSharpLanguageService {
106
119
  if (results && results.length > 0) {
107
120
  const references = [];
108
121
  for (const result of results) {
109
- let resultCode = this.code[result.source];
122
+ let resultCode = await this.loadFile(result.source);
110
123
  // Inspect the URL protocol (equivalent to the URI scheme + ":").
111
124
  // If the scheme is our library scheme, we need to call the wasm to
112
125
  // provide the library file's contents to do the utf8->utf16 mapping.
113
126
  const url = new URL(result.source);
114
127
  if (url.protocol === qsharpLibraryUriScheme + ":") {
115
- resultCode = wasm.get_library_source_content(url.pathname);
128
+ resultCode = wasm.get_library_source_content(url.pathname) || null;
116
129
  if (resultCode === undefined) {
117
130
  log.error(`getReferences: expected ${url} to be in the library`);
118
131
  }
@@ -133,8 +146,8 @@ export class QSharpLanguageService {
133
146
  }
134
147
  }
135
148
  async getSignatureHelp(documentUri, offset) {
136
- const code = this.code[documentUri];
137
- if (code === undefined) {
149
+ const code = await this.loadFile(documentUri);
150
+ if (code === null) {
138
151
  log.error(`expected ${documentUri} to be in the document map`);
139
152
  return undefined;
140
153
  }
@@ -152,8 +165,8 @@ export class QSharpLanguageService {
152
165
  return result;
153
166
  }
154
167
  async getRename(documentUri, offset, newName) {
155
- const code = this.code[documentUri];
156
- if (code === undefined) {
168
+ const code = await this.loadFile(documentUri);
169
+ if (code === null) {
157
170
  log.error(`expected ${documentUri} to be in the document map`);
158
171
  return undefined;
159
172
  }
@@ -161,7 +174,7 @@ export class QSharpLanguageService {
161
174
  const result = this.languageService.get_rename(documentUri, convertedOffset, newName);
162
175
  const mappedChanges = [];
163
176
  for (const [uri, edits] of result.changes) {
164
- const code = this.code[uri];
177
+ const code = await this.loadFile(uri);
165
178
  if (code) {
166
179
  const mappedEdits = edits.map((edit) => {
167
180
  updateSpanFromUtf8ToUtf16(edit.range, code);
@@ -174,8 +187,8 @@ export class QSharpLanguageService {
174
187
  return result;
175
188
  }
176
189
  async prepareRename(documentUri, offset) {
177
- const code = this.code[documentUri];
178
- if (code === undefined) {
190
+ const code = await this.loadFile(documentUri);
191
+ if (code === null) {
179
192
  log.error(`expected ${documentUri} to be in the document map`);
180
193
  return undefined;
181
194
  }
@@ -187,6 +200,8 @@ export class QSharpLanguageService {
187
200
  return result;
188
201
  }
189
202
  async dispose() {
203
+ this.languageService.stop_background_work();
204
+ await this.backgroundWork;
190
205
  this.languageService.free();
191
206
  }
192
207
  addEventListener(type, listener) {
@@ -195,18 +210,21 @@ export class QSharpLanguageService {
195
210
  removeEventListener(type, listener) {
196
211
  this.eventHandler.removeEventListener(type, listener);
197
212
  }
198
- onDiagnostics(uri, version, diagnostics) {
213
+ async onDiagnostics(uri, version, diagnostics) {
199
214
  try {
200
- const code = this.code[uri];
215
+ const code = await this.loadFile(uri);
201
216
  const empty = diagnostics.length === 0;
202
- if (code === undefined && !empty) {
217
+ if (code === null && !empty) {
203
218
  // We need the contents of the document to convert error offsets to utf16.
204
219
  // But the contents aren't available after a document is closed.
205
- // It is possible to get a diagnostics event after a document is closed,
206
- // but it will be done with an empty array, to clear the diagnostics.
207
- // In that case, it's ok not to have the document contents available,
208
- // because there are no offsets to convert.
209
- log.error(`onDiagnostics: expected ${uri} to be in the document map`);
220
+ // It is possible to get diagnostics events for a document after it's been closed,
221
+ // since document events are handled asynchronously by the language service.
222
+ // We just drop those diagnostics, assuming that eventually we will receive further
223
+ // events that will bring the diagnostics up to date.
224
+ // However, if we receive an *empty* array of diagnostics for a document, we don't want
225
+ // to drop that update. It's necessary for the diagnostics to get cleared for a document
226
+ // that has been closed.
227
+ log.warn(`onDiagnostics: received diagnostics for ${uri} which is not in the document map`);
210
228
  return;
211
229
  }
212
230
  const event = new Event("diagnostics");
package/dist/main.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import { ICompiler, ICompilerWorker } from "./compiler/compiler.js";
2
2
  import { ILanguageService, ILanguageServiceWorker, qsharpLibraryUriScheme } from "./language-service/language-service.js";
3
3
  import { IDebugService, IDebugServiceWorker } from "./debug-service/debug-service.js";
4
+ import { ProjectLoader } from "../lib/node/qsc_wasm.cjs";
4
5
  export { qsharpLibraryUriScheme };
5
6
  export declare function getLibrarySourceContent(path: string): Promise<string | undefined>;
6
7
  export declare function getCompiler(): ICompiler;
8
+ export declare function getProjectLoader(readFile: (path: string) => Promise<string | null>, loadDirectory: (path: string) => Promise<[string, number][]>, getManifest: (path: string) => Promise<{
9
+ manifestDirectory: string;
10
+ } | null>): ProjectLoader;
7
11
  export declare function getCompilerWorker(): ICompilerWorker;
8
12
  export declare function getDebugService(): IDebugService;
9
13
  export declare function getDebugServiceWorker(): IDebugServiceWorker;
10
- export declare function getLanguageService(): ILanguageService;
14
+ export declare function getLanguageService(readFile?: (uri: string) => Promise<string | null>, listDir?: (uri: string) => Promise<[string, number][]>, getManifest?: (uri: string) => Promise<{
15
+ manifestDirectory: string;
16
+ } | null>): ILanguageService;
11
17
  export declare function getLanguageServiceWorker(): ILanguageServiceWorker;
package/dist/main.js CHANGED
@@ -31,6 +31,15 @@ export function getCompiler() {
31
31
  }
32
32
  return new Compiler(wasm);
33
33
  }
34
+ export function getProjectLoader(readFile, loadDirectory, getManifest) {
35
+ if (!wasm) {
36
+ wasm = require("../lib/node/qsc_wasm.cjs");
37
+ // Set up logging and telemetry as soon as possible after instantiating
38
+ wasm.initLogging(log.logWithLevel, log.getLogLevel());
39
+ log.onLevelChanged = (level) => wasm?.setLogLevel(level);
40
+ }
41
+ return new wasm.ProjectLoader(readFile, loadDirectory, getManifest);
42
+ }
34
43
  export function getCompilerWorker() {
35
44
  const thisDir = dirname(fileURLToPath(import.meta.url));
36
45
  const worker = new Worker(join(thisDir, "./compiler/worker-node.js"), {
@@ -62,10 +71,10 @@ export function getDebugServiceWorker() {
62
71
  worker.addListener("message", proxy.onMsgFromWorker);
63
72
  return proxy;
64
73
  }
65
- export function getLanguageService() {
74
+ export function getLanguageService(readFile, listDir, getManifest) {
66
75
  if (!wasm)
67
76
  wasm = require("../lib/node/qsc_wasm.cjs");
68
- return new QSharpLanguageService(wasm);
77
+ return new QSharpLanguageService(wasm, readFile, listDir, getManifest);
69
78
  }
70
79
  export function getLanguageServiceWorker() {
71
80
  const thisDir = dirname(fileURLToPath(import.meta.url));