Skip to content

Commit

Permalink
When @EntryPoint missing assume Main is entry (#1584)
Browse files Browse the repository at this point in the history
This change updates the entry point logic to assume that in the absence
of an explicit `@EntryPoint` attributed callable, a unique `Main` is the
entry point.
<img width="1101" alt="image"
src="https://github.com/microsoft/qsharp/assets/10567287/9b27513b-57e4-47b8-b4a5-fd1bdf266dcd">
  • Loading branch information
swernli authored Jun 7, 2024
1 parent 5577430 commit f0de59c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 11 deletions.
15 changes: 12 additions & 3 deletions compiler/qsc_passes/src/entry_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use thiserror::Error;
#[derive(Clone, Debug, Diagnostic, Error)]
pub enum Error {
#[error("duplicate entry point callable `{0}`")]
#[diagnostic(help("only one callable should be annotated with the entry point attribute"))]
#[diagnostic(help("only one callable named `Main` or one callable with the `@EntryPoint()` attribute must be present if no entry expression is provided"))]
#[diagnostic(code("Qsc.EntryPoint.Duplicate"))]
Duplicate(String, #[label] Span),

Expand All @@ -34,7 +34,7 @@ pub enum Error {
BodyMissing(#[label("cannot have specialization implementation")] Span),

#[error("entry point not found")]
#[diagnostic(help("a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided"))]
#[diagnostic(help("a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided and no callable named `Main` is present"))]
#[diagnostic(code("Qsc.EntryPoint.NotFound"))]
NotFound,
}
Expand Down Expand Up @@ -124,13 +124,19 @@ fn create_entry_from_callables(
fn get_callables(package: &Package) -> Vec<(&CallableDecl, LocalItemId)> {
let mut finder = EntryPointFinder {
callables: Vec::new(),
main: Vec::new(),
};
finder.visit_package(package);
finder.callables
if finder.callables.is_empty() {
finder.main
} else {
finder.callables
}
}

struct EntryPointFinder<'a> {
callables: Vec<(&'a CallableDecl, LocalItemId)>,
main: Vec<(&'a CallableDecl, LocalItemId)>,
}

impl<'a> Visitor<'a> for EntryPointFinder<'a> {
Expand All @@ -139,6 +145,9 @@ impl<'a> Visitor<'a> for EntryPointFinder<'a> {
if item.attrs.iter().any(|a| a == &Attr::EntryPoint) {
self.callables.push((callable, item.id));
}
if callable.name.name.as_ref() == "Main" {
self.main.push((callable, item.id));
}
}
}
}
71 changes: 70 additions & 1 deletion compiler/qsc_passes/src/entry_point/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,46 @@ fn test_entry_point_attr_to_expr() {
}

#[test]
fn test_entry_point_attr_missing() {
fn test_entry_point_attr_missing_implies_main() {
check(
indoc! {"
namespace Test {
operation Main() : Int { 41 + 1 }
}"},
"",
&expect![[r#"
Expr 12 [0-0] [Type Int]: Call:
Expr 11 [32-36] [Type Int]: Var: Item 1
Expr 10 [36-38] [Type Unit]: Unit"#]],
);
}

#[test]
fn test_entry_point_attr_missing_implies_main_alernate_casing_not_allowed() {
check(
indoc! {"
namespace Test {
operation main() : Int { 41 + 1 }
}"},
"",
&expect![[r#"
[
EntryPoint(
NotFound,
),
]
"#]],
);
}

#[test]
fn test_entry_point_attr_missing_without_main_error() {
check(
indoc! {"
namespace Test {
operation Main2() : Int { 41 + 1 }
}"},
"",
&expect![[r#"
[
EntryPoint(
Expand Down Expand Up @@ -104,3 +137,39 @@ fn test_entry_point_attr_multiple() {
"#]],
);
}

#[test]
fn test_entry_point_main_multiple() {
check(
indoc! {"
namespace Test {
operation Main() : Int { 41 + 1 }
}
namespace Test2 {
operation Main() : Int { 40 + 1 }
}"},
"",
&expect![[r#"
[
EntryPoint(
Duplicate(
"Main",
Span {
lo: 32,
hi: 36,
},
),
),
EntryPoint(
Duplicate(
"Main",
Span {
lo: 90,
hi: 94,
},
),
),
]
"#]],
);
}
2 changes: 1 addition & 1 deletion language_service/src/state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ async fn package_type_update_causes_error() {
.update_document(
"single/foo.qs",
1,
"namespace Foo { operation Main() : Unit {} }",
"namespace Foo { operation Test() : Unit {} }",
)
.await;

Expand Down
8 changes: 4 additions & 4 deletions npm/qsharp/test/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ test("language service configuration update", async () => {
"test.qs",
1,
`namespace Sample {
operation main() : Unit {
operation Test() : Unit {
}
}`,
);
Expand All @@ -629,7 +629,7 @@ test("language service configuration update", async () => {
messages: [
"entry point not found\n" +
"\n" +
"help: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided",
"help: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided and no callable named `Main` is present",
],
},
{
Expand Down Expand Up @@ -730,7 +730,7 @@ test("debug service loading source without entry point attr fails - web worker",
[
"test.qs",
`namespace Sample {
operation main() : Result[] {
operation test() : Result[] {
use q1 = Qubit();
Y(q1);
let m1 = M(q1);
Expand All @@ -757,7 +757,7 @@ test("debug service loading source with syntax error fails - web worker", async
[
"test.qs",
`namespace Sample {
operation main() : Result[]
operation test() : Result[]
}
}`,
],
Expand Down
4 changes: 2 additions & 2 deletions wasm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ fn test_entrypoint() {
#[test]
fn test_missing_entrypoint() {
let code = "namespace Sample {
operation main() : Result[] {
operation test() : Result[] {
use q1 = Qubit();
let m1 = M(q1);
return [m1];
Expand All @@ -203,7 +203,7 @@ fn test_missing_entrypoint() {
let result = run_internal(
SourceMap::new([("test.qs".into(), code.into())], Some(expr.into())),
|msg| {
expect![[r#"{"result":{"code":"Qsc.EntryPoint.NotFound","message":"entry point not found\n\nhelp: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided","range":{"end":{"character":1,"line":0},"start":{"character":0,"line":0}},"severity":"error"},"success":false,"type":"Result"}"#]].assert_eq(msg);
expect![[r#"{"result":{"code":"Qsc.EntryPoint.NotFound","message":"entry point not found\n\nhelp: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided and no callable named `Main` is present","range":{"end":{"character":1,"line":0},"start":{"character":0,"line":0}},"severity":"error"},"success":false,"type":"Result"}"#]].assert_eq(msg);
},
1,
);
Expand Down

0 comments on commit f0de59c

Please sign in to comment.