Skip to content

QIR Profiles Selection via Project Field or EntryPoint argument #2591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
07e3a33
WIP
ScottCarda-MS Jun 18, 2025
e04f825
Running with project profile instead of workspace profile
ScottCarda-MS Jun 20, 2025
92ed7ee
fixed casing in profile string
ScottCarda-MS Jun 23, 2025
d506385
remove workspace setting for profile
ScottCarda-MS Jun 23, 2025
191752f
use is_single_file field to infer profile based on target for non-pro…
ScottCarda-MS Jun 26, 2025
1b75db0
a bit of cleanup
ScottCarda-MS Jun 26, 2025
c389eb2
remove obsolete test
ScottCarda-MS Jun 26, 2025
c1e20a1
fix LS tests
ScottCarda-MS Jul 7, 2025
12b5e4e
Merge branch 'main' into sccarda/ProjectProfiles
ScottCarda-MS Jul 7, 2025
2fd7d0b
try fixing format, not sure this will work
ScottCarda-MS Jul 7, 2025
bd5dd19
fixes formatting
ScottCarda-MS Jul 7, 2025
6118648
waitForDiagnosticsToBeEmpty
ScottCarda-MS Jul 8, 2025
b95d8c3
Added check for entrypoint profile and error
ScottCarda-MS Jul 9, 2025
a9e07cf
prepare_package_store checks for entry point and either overwrites ca…
ScottCarda-MS Jul 9, 2025
f602e50
Work off of EntryPoint argument, which is a Profile name
ScottCarda-MS Jul 10, 2025
d5684ec
friendly name for profiles in qsharp.json
ScottCarda-MS Jul 10, 2025
9bb86c2
isSingleFile used in standard "run" compilation
ScottCarda-MS Jul 10, 2025
6812a9f
fix playground not running
ScottCarda-MS Jul 10, 2025
8635c4f
Use the entry profile when we actually run the program, not just with…
ScottCarda-MS Jul 11, 2025
2aec7eb
Remove profile selection dropdown from profile
ScottCarda-MS Jul 11, 2025
c88ff11
Merge branch 'main' into sccarda/SingleFileProfiles
ScottCarda-MS Jul 14, 2025
41c5340
"adaptive_ri" and "adaptive_rif"
ScottCarda-MS Jul 14, 2025
22d4897
unused import
ScottCarda-MS Jul 14, 2025
7de00bc
fixed test
ScottCarda-MS Jul 14, 2025
2e89e7b
minor change
ScottCarda-MS Jul 15, 2025
e9fa9b5
placed is_single_file for has_manifest on packageGraphSources
ScottCarda-MS Jul 21, 2025
c5bec69
missed a spot
ScottCarda-MS Jul 21, 2025
f539f68
little bit of cleaning
ScottCarda-MS Jul 21, 2025
61343a1
move error struct out of parser and into qsc
ScottCarda-MS Jul 21, 2025
8f3af9a
completions for EntryPoint argument
ScottCarda-MS Jul 21, 2025
7ba14f5
Merge branch 'main' into sccarda/SingleFileProfiles
ScottCarda-MS Jul 21, 2025
2440fa0
test fix
ScottCarda-MS Jul 21, 2025
d05dab5
test fix
ScottCarda-MS Jul 21, 2025
17b13c9
Don't aumotatically update the profile for project scenario
ScottCarda-MS Jul 21, 2025
c9b8fa6
fix help message
ScottCarda-MS Jul 21, 2025
a18e467
added `Adaptive_RIF` to the error message
ScottCarda-MS Jul 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion source/compiler/qsc/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

use miette::{Diagnostic, Report};
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
use qsc_data_structures::{
language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
};
pub use qsc_frontend::compile::Dependencies;
use qsc_frontend::{
compile::{CompileUnit, PackageStore, SourceMap},
Expand Down Expand Up @@ -45,6 +47,11 @@ pub enum ErrorKind {
/// `OpenQASM` compilation errors.
#[diagnostic(transparent)]
OpenQasm(#[from] crate::qasm::error::Error),

#[error(
"The @EntryPoint attribute with a profile argument is not allowed in a Q# project (with qsharp.json). Please specify the profile in qsharp.json instead."
)]
EntryPointProfileInProject(#[label] Span),
}

/// Compiles a package from its AST representation.
Expand Down
1 change: 1 addition & 0 deletions source/compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub enum Error {
NoEntryPoint,
#[error("unsupported runtime capabilities for code generation")]
#[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
#[diagnostic(help("@EntryPoint attribute argument should be 'Base' or 'Adaptive_RI'"))]
UnsupportedRuntimeCapabilities,
#[error("expression does not evaluate to an operation")]
#[diagnostic(code("Qsc.Interpret.NotAnOperation"))]
Expand Down
4 changes: 3 additions & 1 deletion source/compiler/qsc/src/interpret/package_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn import_and_call_reexport() {
indoc! {"
import Foo.DependencyA.Foo;
function Main() : Unit {
Foo([1, 2]);
Foo([1, 2]);
Foo.DependencyA.MagicFunction();
}"}
.into(),
Expand Down Expand Up @@ -50,6 +50,7 @@ fn import_and_call_reexport() {
)]
.into_iter()
.collect(),
has_manifest: true,
};

// This builds all the dependencies
Expand Down Expand Up @@ -129,6 +130,7 @@ fn directly_call_reexport() {
)]
.into_iter()
.collect(),
has_manifest: true,
};

// This builds all the dependencies
Expand Down
5 changes: 4 additions & 1 deletion source/compiler/qsc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pub mod incremental;
pub mod interpret;
pub mod location;
pub mod packages;
pub mod target;

pub use qsc_formatter::formatter;

Expand Down Expand Up @@ -80,4 +79,8 @@ pub mod partial_eval {
pub use qsc_partial_eval::Error;
}

pub mod target {
pub use qsc_data_structures::target::Profile;
}

pub mod qasm;
32 changes: 29 additions & 3 deletions source/compiler/qsc/src/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
};
use qsc_circuit::circuit_to_qsharp::circuits_to_qsharp;
use qsc_data_structures::language_features::LanguageFeatures;
use qsc_frontend::compile::SourceMap;
use qsc_frontend::compile::{SourceMap, check_for_entry_profile};
use qsc_passes::PackageType;
use qsc_project::PackageGraphSources;
use rustc_hash::FxHashMap;
Expand All @@ -24,6 +24,7 @@ pub struct BuildableProgram {
pub user_code: qsc_project::PackageInfo,
pub user_code_dependencies: Vec<(PackageId, Option<Arc<str>>)>,
pub dependency_errors: Vec<compile::Error>,
pub capabilities: TargetCapabilityFlags,
}

impl BuildableProgram {
Expand Down Expand Up @@ -78,16 +79,40 @@ fn convert_circuit_sources(
/// Given a program config, prepare the package store by compiling all dependencies in the correct order and inserting them.
#[must_use]
pub fn prepare_package_store(
capabilities: TargetCapabilityFlags,
mut capabilities: TargetCapabilityFlags,
package_graph_sources: PackageGraphSources,
) -> BuildableProgram {
let mut dependency_errors = Vec::new();

// Convert circuit files in user code to generated Q# before entry profile check
let mut sources = package_graph_sources.root.sources.clone();
sources = convert_circuit_sources(sources, &mut dependency_errors);

// Check if the entry profile is set in the source code.
let entry_profile = check_for_entry_profile(
&SourceMap::new(sources.clone(), None),
package_graph_sources.root.language_features,
);

// If the entry profile is set, we need to ensure that the user code is compiled with it.
if let Some((profile, span)) = entry_profile {
// If this is a project (qsharp.json present), emit error with span
if package_graph_sources.has_manifest {
dependency_errors.push(Error::from_map(
&SourceMap::new(sources, None),
ErrorKind::EntryPointProfileInProject(span),
));
} else {
capabilities = profile.into();
}
}

let (std_id, mut package_store) = package_store_with_stdlib(capabilities);

let mut canonical_package_identifier_to_package_id_mapping = FxHashMap::default();

let (ordered_packages, user_code) = package_graph_sources.compilation_order();

let mut dependency_errors = Vec::new();
let ordered_packages = if let Ok(o) = ordered_packages {
o
} else {
Expand Down Expand Up @@ -163,5 +188,6 @@ pub fn prepare_package_store(
dependency_errors,
user_code,
user_code_dependencies,
capabilities,
}
}
2 changes: 2 additions & 0 deletions source/compiler/qsc/src/packages/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ fn mock_program() -> Project {
package_type: Some(qsc_project::PackageType::Lib),
},
)]),
has_manifest: true,
};
Project {
lints: vec![],
errors: vec![],
path: "project/qsharp.json".into(),
name: "project".into(),
project_type: qsc_project::ProjectType::QSharp(package_graph_sources),
target_profile: qsc_data_structures::target::Profile::Unrestricted,
}
}

Expand Down
56 changes: 0 additions & 56 deletions source/compiler/qsc/src/target.rs

This file was deleted.

52 changes: 52 additions & 0 deletions source/compiler/qsc_data_structures/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,55 @@ impl Default for TargetCapabilityFlags {
TargetCapabilityFlags::empty()
}
}

use std::str::FromStr;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Profile {
Unrestricted,
Base,
AdaptiveRI,
AdaptiveRIF,
}

impl Profile {
#[must_use]
pub fn to_str(&self) -> &'static str {
match self {
Self::Unrestricted => "Unrestricted",
Self::Base => "Base",
Self::AdaptiveRI => "Adaptive_RI",
Self::AdaptiveRIF => "Adaptive_RIF",
}
}
}

impl From<Profile> for TargetCapabilityFlags {
fn from(value: Profile) -> Self {
match value {
Profile::Unrestricted => Self::all(),
Profile::Base => Self::empty(),
Profile::AdaptiveRI => Self::Adaptive | Self::QubitReset | Self::IntegerComputations,
Profile::AdaptiveRIF => {
Self::Adaptive
| Self::QubitReset
| Self::IntegerComputations
| Self::FloatingPointComputations
}
}
}
}

impl FromStr for Profile {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"adaptive_ri" => Ok(Self::AdaptiveRI),
"adaptive_rif" => Ok(Self::AdaptiveRIF),
"base" => Ok(Self::Base),
"unrestricted" => Ok(Self::Unrestricted),
_ => Err(()),
}
}
}
18 changes: 17 additions & 1 deletion source/compiler/qsc_frontend/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use qsc_data_structures::{
index_map::{self, IndexMap},
language_features::LanguageFeatures,
span::Span,
target::TargetCapabilityFlags,
target::{Profile, TargetCapabilityFlags},
};
use qsc_hir::{
assigner::Assigner as HirAssigner,
Expand Down Expand Up @@ -530,6 +530,22 @@ pub fn parse_all(
(package, errors)
}

#[must_use]
pub fn check_for_entry_profile(
sources: &SourceMap,
language_features: LanguageFeatures,
) -> Option<(Profile, Span)> {
let (ast_package, parse_errors) = parse_all(sources, language_features);

if !parse_errors.is_empty() {
return None;
}

let mut check = preprocess::DetectEntryPointProfile::new();
check.visit_package(&ast_package);
check.profile
}

pub(crate) struct ResolveResult {
pub names: Names,
pub locals: Locals,
Expand Down
31 changes: 30 additions & 1 deletion source/compiler/qsc_frontend/src/compile/preprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use qsc_ast::{
TopLevelNode, UnOp,
},
mut_visit::{MutVisitor, walk_stmt},
visit::Visitor,
};
use qsc_data_structures::span::Span;
use qsc_data_structures::{span::Span, target::Profile};
use qsc_hir::hir;
use std::rc::Rc;

Expand All @@ -18,6 +19,34 @@ use super::{SourceMap, TargetCapabilityFlags};
#[cfg(test)]
mod tests;

/// Transformation to detect `@EntryPoint` attribute in the AST.
#[derive(Default)]
pub struct DetectEntryPointProfile {
pub profile: Option<(Profile, Span)>,
}

impl DetectEntryPointProfile {
#[must_use]
pub fn new() -> Self {
Self { profile: None }
}
}

impl Visitor<'_> for DetectEntryPointProfile {
fn visit_attr(&mut self, attr: &Attr) {
if hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::EntryPoint) {
// Try to parse the argument as a profile name
if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() {
if let ExprKind::Path(PathKind::Ok(path)) = inner.kind.as_ref() {
if let Ok(profile) = Profile::from_str(path.name.name.as_ref()) {
self.profile = Some((profile, path.span));
}
}
}
}
}
}

#[derive(PartialEq, Hash, Clone, Debug)]
pub struct TrackedName {
pub name: Rc<str>,
Expand Down
22 changes: 18 additions & 4 deletions source/compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use crate::{
};
use miette::Diagnostic;
use qsc_ast::ast::{self, FieldAccess, Ident, Idents, PathKind};
use qsc_data_structures::{index_map::IndexMap, span::Span, target::TargetCapabilityFlags};
use qsc_data_structures::{
index_map::IndexMap,
span::Span,
target::{Profile, TargetCapabilityFlags},
};
use qsc_hir::{
assigner::Assigner,
hir::{self, LocalItemId, Visibility},
Expand Down Expand Up @@ -376,10 +380,19 @@ impl With<'_> {
match hir::Attr::from_str(attr.name.name.as_ref()) {
Ok(hir::Attr::EntryPoint) => match &*attr.arg.kind {
ast::ExprKind::Tuple(args) if args.is_empty() => Some(hir::Attr::EntryPoint),
// @EntryPoint(Profile)
ast::ExprKind::Paren(inner)
if matches!(inner.kind.as_ref(), ast::ExprKind::Path(PathKind::Ok(path))
if Profile::from_str(path.name.name.as_ref()).is_ok()) =>
{
Some(hir::Attr::EntryPoint)
}
// Any other form is not valid so generates an error.
_ => {
self.lowerer
.errors
.push(Error::InvalidAttrArgs("()".to_string(), attr.arg.span));
self.lowerer.errors.push(Error::InvalidAttrArgs(
"empty or profile name".to_string(),
attr.arg.span,
));
None
}
},
Expand Down Expand Up @@ -455,6 +468,7 @@ impl With<'_> {
.push(Error::InvalidAttrArgs("()".to_string(), attr.arg.span));
}
}
// lower the attribute even if it has invalid args
Some(hir::Attr::Test)
}
Err(()) => {
Expand Down
2 changes: 1 addition & 1 deletion source/compiler/qsc_frontend/src/lower/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn test_entrypoint_attr_wrong_args() {
&expect![[r#"
[
InvalidAttrArgs(
"()",
"empty or profile name",
Span {
lo: 33,
hi: 40,
Expand Down
Loading