Skip to content

Commit

Permalink
Use PackageType::Lib by default (#1737)
Browse files Browse the repository at this point in the history
In most places, we already used the `PackageType::Lib` by default, but
not in the language service. With more library authoring expected in the
future, this tweaks the user experience to avoid errors for missing
entry point until the Q# program as run. As part of these changes, the
behavior of the entry point check is updated to always provide feedback
for other entry point related errors (like duplicates) but only return
an error on missing entry point if the compilation is for an executable.

Of note, this does NOT remove the configuration option for `packageType`
in the qsharp.json, which we can do as a follow up if needed.

This does update the error string that pops up when failing to run/debug
the Q#. If the program is missing an entry point, a more specific error
message is returned:

![image](https://github.com/user-attachments/assets/1e667ac3-dd3d-4c04-b393-49c8ceb1097d)

and the generic compilation failure message is also updated:

![image](https://github.com/user-attachments/assets/78fa1b8c-5ad9-4806-b501-28e01a546049)
  • Loading branch information
swernli authored Jul 15, 2024
1 parent 312af69 commit fc6ec5b
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 71 deletions.
5 changes: 4 additions & 1 deletion compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,13 @@ impl Interpreter {
) -> InterpretResult {
let label = self.next_line_label();

let increment = self
let mut increment = self
.compiler
.compile_fragments_fail_fast(&label, fragments)
.map_err(into_errors)?;
// Clear the entry expression, as we are evaluating fragments and a fragment with a `@EntryPoint` attribute
// should not change what gets executed.
increment.clear_entry();

self.eval_increment(receiver, increment)
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/qsc_frontend/src/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub struct Increment {
pub hir: hir::Package,
}

impl Increment {
pub fn clear_entry(&mut self) {
self.hir.entry = None;
}
}

impl Compiler {
/// Creates a new compiler.
pub fn new(
Expand Down
13 changes: 11 additions & 2 deletions compiler/qsc_passes/src/entry_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#[cfg(test)]
mod tests;

use crate::PackageType;

use super::Error as PassErr;
use miette::Diagnostic;
use qsc_data_structures::span::Span;
Expand Down Expand Up @@ -44,13 +46,14 @@ pub enum Error {
pub(super) fn generate_entry_expr(
package: &mut Package,
assigner: &mut Assigner,
package_type: PackageType,
) -> Vec<super::Error> {
if package.entry.is_some() {
return vec![];
}
let callables = get_callables(package);

match create_entry_from_callables(assigner, callables) {
match create_entry_from_callables(assigner, callables, package_type) {
Ok(expr) => {
package.entry = Some(expr);
vec![]
Expand All @@ -62,6 +65,7 @@ pub(super) fn generate_entry_expr(
fn create_entry_from_callables(
assigner: &mut Assigner,
callables: Vec<(&CallableDecl, LocalItemId)>,
package_type: PackageType,
) -> Result<Expr, Vec<super::Error>> {
if callables.len() == 1 {
let ep = callables[0].0;
Expand Down Expand Up @@ -110,7 +114,12 @@ fn create_entry_from_callables(
Err(vec![PassErr::EntryPoint(Error::Args(ep.input.span))])
}
} else if callables.is_empty() {
Err(vec![PassErr::EntryPoint(Error::NotFound)])
if package_type == PackageType::Exe {
Err(vec![PassErr::EntryPoint(Error::NotFound)])
} else {
// For libraries, no entry point is required. Leave the entry expression empty and return no errors.
Err(Vec::new())
}
} else {
Err(callables
.into_iter()
Expand Down
4 changes: 2 additions & 2 deletions compiler/qsc_passes/src/entry_point/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#![allow(clippy::needless_raw_string_hashes)]

use crate::entry_point::generate_entry_expr;
use crate::{entry_point::generate_entry_expr, PackageType};
use expect_test::{expect, Expect};
use indoc::indoc;
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
Expand All @@ -20,7 +20,7 @@ fn check(file: &str, expr: &str, expect: &Expect) {
);
assert!(unit.errors.is_empty(), "{:?}", unit.errors);

let errors = generate_entry_expr(&mut unit.package, &mut unit.assigner);
let errors = generate_entry_expr(&mut unit.package, &mut unit.assigner, PackageType::Exe);
if errors.is_empty() {
expect.assert_eq(
&unit
Expand Down
9 changes: 2 additions & 7 deletions compiler/qsc_passes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,8 @@ impl PassContext {
let conjugate_errors = conjugate_invert::invert_conjugate_exprs(core, package, assigner);
Validator::default().visit_package(package);

let entry_point_errors = if package_type == PackageType::Exe {
let entry_point_errors = generate_entry_expr(package, assigner);
Validator::default().visit_package(package);
entry_point_errors
} else {
Vec::new()
};
let entry_point_errors = generate_entry_expr(package, assigner, package_type);
Validator::default().visit_package(package);

LoopUni { core, assigner }.visit_package(package);
Validator::default().visit_package(package);
Expand Down
2 changes: 1 addition & 1 deletion language_service/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Default for Configuration {
fn default() -> Self {
Self {
target_profile: Profile::Unrestricted,
package_type: PackageType::Exe,
package_type: PackageType::Lib,
language_features: LanguageFeatures::default(),
lints_config: Vec::default(),
}
Expand Down
60 changes: 4 additions & 56 deletions language_service/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,7 @@ async fn single_document() {
&mut received_errors.borrow_mut(),
"foo.qs",
&(expect![[r#"
[
(
"foo.qs",
Some(
1,
),
[
Pass(
EntryPoint(
NotFound,
),
),
],
[],
),
]
[]
"#]]),
&(expect![[r#"
SourceMap {
Expand Down Expand Up @@ -78,22 +63,7 @@ async fn single_document_update() {
&mut received_errors.borrow_mut(),
"foo.qs",
&(expect![[r#"
[
(
"foo.qs",
Some(
1,
),
[
Pass(
EntryPoint(
NotFound,
),
),
],
[],
),
]
[]
"#]]),
&(expect![[r#"
SourceMap {
Expand Down Expand Up @@ -124,16 +94,7 @@ async fn single_document_update() {
&mut received_errors.borrow_mut(),
"foo.qs",
&(expect![[r#"
[
(
"foo.qs",
Some(
1,
),
[],
[],
),
]
[]
"#]]),
&(expect![[r#"
SourceMap {
Expand Down Expand Up @@ -177,20 +138,7 @@ async fn document_in_project() {
&mut received_errors.borrow_mut(),
"project/src/this_file.qs",
&expect![[r#"
[
(
"project/qsharp.json",
None,
[
Pass(
EntryPoint(
NotFound,
),
),
],
[],
),
]
[]
"#]],
&expect![[r#"
SourceMap {
Expand Down
4 changes: 4 additions & 0 deletions npm/qsharp/test/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,10 @@ test("language service diagnostics - web worker", async () => {

test("language service configuration update", async () => {
const languageService = getLanguageServiceWorker();

// Set the configuration to expect an entry point.
await languageService.updateConfiguration({ packageType: "exe" });

let actualMessages = [];
languageService.addEventListener("diagnostics", (event) => {
actualMessages.push({
Expand Down
8 changes: 6 additions & 2 deletions vscode/src/debugger/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import { isPanelOpen } from "../webviewPanel";
import { FullProgramConfig } from "../programConfig";

const ErrorProgramHasErrors =
"program contains compile errors(s): cannot run. See debug console for more details.";
"The Q# program contains one or more compile errors and cannot run. See debug console for more details.";
const ErrorProgramMissingEntry =
"The Q# program does not contain an entry point and cannot run. See debug console for more details.";
const SimulationCompleted = "Q# simulation completed.";
const ConfigurationDelayMS = 1000;

Expand Down Expand Up @@ -259,7 +261,9 @@ export class QscDebugSession extends LoggingDebugSession {
this.writeToDebugConsole(this.failureMessage);
this.sendErrorResponse(response, {
id: -1,
format: ErrorProgramHasErrors,
format: this.failureMessage.includes("Qsc.EntryPoint.NotFound")
? ErrorProgramMissingEntry
: ErrorProgramHasErrors,
showUser: true,
});
return;
Expand Down

0 comments on commit fc6ec5b

Please sign in to comment.