From b78d5297acb72a2c2b86b9c53c4c7b162938d729 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 2 Jun 2026 16:07:41 -0700 Subject: [PATCH 1/3] Update error span for call argument type mismatch It should be just the argument with the problem. Hopefully, this will make it easy to add quick fixes to some scenarios. --- .../src/compile/tests/multiple_packages.rs | 4 +- .../compiler/qsc_frontend/src/typeck/infer.rs | 18 +++--- .../compiler/qsc_frontend/src/typeck/rules.rs | 57 ++++++++++++----- .../compiler/qsc_frontend/src/typeck/tests.rs | 64 +++++++++++++------ .../src/typeck/tests/bounded_polymorphism.rs | 12 ++-- 5 files changed, 104 insertions(+), 51 deletions(-) diff --git a/source/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs b/source/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs index b180007b6d..434dbc7615 100644 --- a/source/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs +++ b/source/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs @@ -496,8 +496,8 @@ fn reexports_still_type_check() { "Bool", "Int", Span { - lo: 128, - hi: 140, + lo: 137, + hi: 139, }, ), ), diff --git a/source/compiler/qsc_frontend/src/typeck/infer.rs b/source/compiler/qsc_frontend/src/typeck/infer.rs index 60ab80413c..198b51f2d6 100644 --- a/source/compiler/qsc_frontend/src/typeck/infer.rs +++ b/source/compiler/qsc_frontend/src/typeck/infer.rs @@ -308,9 +308,9 @@ impl TySource { #[derive(Clone, Debug)] pub(super) enum ArgTy { /// A missing argument, indicating partial application. - Hole(Ty), + Hole(Ty, Span), /// A given argument. - Given(Ty), + Given(Ty, Span), /// A list of arguments. This corresponds literally to tuple syntax, not to any expression of a tuple type. Tuple(Vec), } @@ -319,8 +319,8 @@ impl ArgTy { /// Applies a function `f` to each type in the argument type. fn map(self, f: &mut impl FnMut(Ty) -> Ty) -> Self { match self { - Self::Hole(ty) => Self::Hole(f(ty)), - Self::Given(ty) => Self::Given(f(ty)), + Self::Hole(ty, span) => Self::Hole(f(ty), span), + Self::Given(ty, span) => Self::Given(f(ty), span), Self::Tuple(items) => Self::Tuple(items.into_iter().map(|i| i.map(f)).collect()), } } @@ -333,12 +333,12 @@ impl ArgTy { // However, we do know that the type of Arg must be Eq to the type of Param, so we // add that to the constraints. // Preserve the hole. - (Self::Hole(arg), _) => App { + (Self::Hole(arg, arg_span), _) => App { holes: vec![param.clone()], constraints: vec![Constraint::Eq { expected: param.clone(), actual: arg.clone(), - span, + span: *arg_span, }], errors: Vec::new(), }, @@ -346,12 +346,12 @@ impl ArgTy { // because the hole can be anything. // However, we do know that the type of Arg must be Eq to the type of Param, so we // add that to the constraints. - (Self::Given(arg), _) => App { + (Self::Given(arg, arg_span), _) => App { holes: Vec::new(), constraints: vec![Constraint::Eq { expected: param.clone(), actual: arg.clone(), - span, + span: *arg_span, }], errors: Vec::new(), }, @@ -410,7 +410,7 @@ impl ArgTy { pub(super) fn to_ty(&self) -> Ty { match self { - ArgTy::Hole(ty) | ArgTy::Given(ty) => ty.clone(), + ArgTy::Hole(ty, _) | ArgTy::Given(ty, _) => ty.clone(), ArgTy::Tuple(items) => Ty::Tuple(items.iter().map(Self::to_ty).collect()), } } diff --git a/source/compiler/qsc_frontend/src/typeck/rules.rs b/source/compiler/qsc_frontend/src/typeck/rules.rs index 677d0cf0f2..308a877828 100644 --- a/source/compiler/qsc_frontend/src/typeck/rules.rs +++ b/source/compiler/qsc_frontend/src/typeck/rules.rs @@ -266,23 +266,42 @@ impl<'a> Context<'a> { ExprKind::Block(block) => self.infer_block(block), ExprKind::Call(callee, input) => { let callee = self.infer_expr(callee); - let input = if has_holes(input) { - self.infer_hole_tuple_arg(input) + let input_expr = &**input; + let input = if has_holes(input_expr) { + self.infer_hole_tuple_arg(input_expr) } else { - let ty = self.infer_expr(input); + let ty = self.infer_expr(input_expr); // If the outermost element is a tuple, we must wrap it in an `ArgTy::Tuple`. - match ty { + if let Partial { + ty: Ty::Tuple(tys), + diverges, + } = ty + { + let spans: Vec<_> = if let ExprKind::Tuple(items) = input_expr.kind.as_ref() + { + items.iter().map(|item| item.span).collect() + } else { + vec![input_expr.span; tys.len()] + }; Partial { - ty: Ty::Tuple(tys), + ty: ArgTy::Tuple( + tys.into_iter() + .zip(spans) + .map(|(ty, span)| ArgTy::Given(ty, span)) + .collect(), + ), diverges, - } => Partial { - ty: ArgTy::Tuple(tys.into_iter().map(ArgTy::Given).collect()), - diverges, - }, - _ => Partial { - ty: ArgTy::Given(ty.ty), + } + } else { + let arg_span = if let ExprKind::Paren(inner) = input_expr.kind.as_ref() { + inner.span + } else { + input_expr.span + }; + Partial { + ty: ArgTy::Given(ty.ty, arg_span), diverges: false, - }, + } } }; let output_ty = if callee.ty == Ty::Err { @@ -723,7 +742,7 @@ impl<'a> Context<'a> { ExprKind::Hole => { let ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span)); self.record(expr.id, ty.clone()); - converge(ArgTy::Hole(ty)) + converge(ArgTy::Hole(ty, expr.span)) } ExprKind::Paren(inner) => { let inner = self.infer_hole_tuple_arg(inner); @@ -739,9 +758,17 @@ impl<'a> Context<'a> { tys.push(item.ty); } self.record(expr.id, Ty::Tuple(tys.iter().map(ArgTy::to_ty).collect())); - self.diverge_if_map(ArgTy::Given, diverges, converge(ArgTy::Tuple(tys))) + let span = expr.span; + self.diverge_if_map( + |ty| ArgTy::Given(ty, span), + diverges, + converge(ArgTy::Tuple(tys)), + ) + } + _ => { + let span = expr.span; + self.infer_expr(expr).map(|ty| ArgTy::Given(ty, span)) } - _ => self.infer_expr(expr).map(ArgTy::Given), } } diff --git a/source/compiler/qsc_frontend/src/typeck/tests.rs b/source/compiler/qsc_frontend/src/typeck/tests.rs index 88832b7997..07a8071ef4 100644 --- a/source/compiler/qsc_frontend/src/typeck/tests.rs +++ b/source/compiler/qsc_frontend/src/typeck/tests.rs @@ -476,15 +476,15 @@ fn int_as_double_error() { } "}, "Microsoft.Quantum.Convert.IntAsDouble(false)", - &expect![[r#" + &expect![[r##" #8 62-71 "(a : Int)" : ? #9 63-70 "a : Int" : ? #18 103-147 "Microsoft.Quantum.Convert.IntAsDouble(false)" : Double #19 103-140 "Microsoft.Quantum.Convert.IntAsDouble" : (Int -> Double) #25 140-147 "(false)" : Bool #26 141-146 "false" : Bool - Error(Type(Error(TyMismatch("Int", "Bool", Span { lo: 103, hi: 147 })))) - "#]], + Error(Type(Error(TyMismatch("Int", "Bool", Span { lo: 141, hi: 146 })))) + "##]], ); } @@ -538,7 +538,7 @@ fn single_arg_for_tuple() { #31 124-126 "Ry" : ((Double, Qubit) => Unit is Adj + Ctl) #34 126-129 "(q)" : Qubit #35 127-128 "q" : Qubit - Error(Type(Error(TyMismatch("(Double, Qubit)", "Qubit", Span { lo: 124, hi: 129 })))) + Error(Type(Error(TyMismatch("(Double, Qubit)", "Qubit", Span { lo: 127, hi: 128 })))) "##]], ); } @@ -1732,7 +1732,7 @@ fn call_controlled_error() { Controlled A.Foo([1], q); } "}, - &expect![[r#" + &expect![[r##" #6 31-42 "(q : Qubit)" : Qubit #7 32-41 "q : Qubit" : Qubit #17 72-75 "..." : Qubit @@ -1752,8 +1752,8 @@ fn call_controlled_error() { #39 163-166 "[1]" : Int[] #40 164-165 "1" : Int #41 168-169 "q" : Qubit - Error(Type(Error(TyMismatch("Qubit", "Int", Span { lo: 146, hi: 170 })))) - "#]], + Error(Type(Error(TyMismatch("Qubit", "Int", Span { lo: 163, hi: 166 })))) + "##]], ); } @@ -1775,6 +1775,32 @@ fn adj_requires_unit_return() { ); } +#[test] +fn should_have_been_array() { + check( + indoc! {" + namespace A { + operation Foo(qs: Qubit[]) : Unit is Adj { + Foo(qs[0]) + } + } + "}, + "", + &expect![[r##" + #6 31-44 "(qs: Qubit[])" : Qubit[] + #7 32-43 "qs: Qubit[]" : Qubit[] + #17 59-85 "{\n Foo(qs[0])\n }" : Unit + #19 69-79 "Foo(qs[0])" : Unit + #20 69-72 "Foo" : (Qubit[] => Unit is Adj) + #23 72-79 "(qs[0])" : Qubit + #24 73-78 "qs[0]" : Qubit + #25 73-75 "qs" : Qubit[] + #28 76-77 "0" : Int + Error(Type(Error(TyMismatch("Qubit[]", "Qubit", Span { lo: 73, hi: 78 })))) + "##]], + ); +} + #[test] fn ctl_requires_unit_return() { check( @@ -1979,7 +2005,7 @@ fn fail_in_call_args_checks_non_divergent_types() { #34 67-78 "fail \"true\"" : Int #35 72-78 "\"true\"" : String #36 80-83 "3.0" : Double - Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 60, hi: 84 })))) + Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 80, hi: 83 })))) "##]], ); } @@ -2175,7 +2201,7 @@ fn return_in_call_args_checks_non_divergent_types() { #34 67-80 "return \"true\"" : Int #35 74-80 "\"true\"" : String #36 82-85 "3.0" : Double - Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 60, hi: 86 })))) + Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 82, hi: 85 })))) "##]], ); } @@ -2767,7 +2793,7 @@ fn newtype_cons_wrong_input() { #19 70-76 "NewInt" : (Int -> UDT<"NewInt": Item 1 (Package 2)>) #22 76-81 "(5.0)" : Double #23 77-80 "5.0" : Double - Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 70, hi: 81 })))) + Error(Type(Error(TyMismatch("Int", "Double", Span { lo: 77, hi: 80 })))) "##]], ); } @@ -4134,7 +4160,7 @@ fn functors_in_arg_subset_of_ctl_adj() { operation Bar(q : Qubit) : () is Adj {} Foo(Bar); }", - &expect![[r#" + &expect![[r##" #1 0-150 "{\n operation Foo(op : Qubit => () is Adj + Ctl) : () {}\n operation Bar(q : Qubit) : () is Adj {}\n Foo(Bar);\n }" : Unit #2 0-150 "{\n operation Foo(op : Qubit => () is Adj + Ctl) : () {}\n operation Bar(q : Qubit) : () is Adj {}\n Foo(Bar);\n }" : Unit #7 27-58 "(op : Qubit => () is Adj + Ctl)" : (Qubit => Unit is Adj + Ctl) @@ -4147,8 +4173,8 @@ fn functors_in_arg_subset_of_ctl_adj() { #35 131-134 "Foo" : ((Qubit => Unit is Adj) => Unit) #38 134-139 "(Bar)" : (Qubit => Unit is Adj) #39 135-138 "Bar" : (Qubit => Unit is Adj) - Error(Type(Error(MissingFunctor(Value(CtlAdj), Value(Adj), Span { lo: 131, hi: 139 })))) - "#]], + Error(Type(Error(MissingFunctor(Value(CtlAdj), Value(Adj), Span { lo: 135, hi: 138 })))) + "##]], ); } @@ -4187,7 +4213,7 @@ fn functors_in_arg_nested_arrow_superset_of_adj() { operation Bar(op : Qubit => () is Adj + Ctl) : () {} Foo(Bar); }", - &expect![[r#" + &expect![[r##" #1 0-165 "{\n operation Foo(op : (Qubit => () is Adj) => ()) : () {}\n operation Bar(op : Qubit => () is Adj + Ctl) : () {}\n Foo(Bar);\n }" : Unit #2 0-165 "{\n operation Foo(op : (Qubit => () is Adj) => ()) : () {}\n operation Bar(op : Qubit => () is Adj + Ctl) : () {}\n Foo(Bar);\n }" : Unit #7 27-60 "(op : (Qubit => () is Adj) => ())" : ((Qubit => Unit is Adj) => Unit) @@ -4200,8 +4226,8 @@ fn functors_in_arg_nested_arrow_superset_of_adj() { #40 146-149 "Foo" : (((Qubit => Unit is Adj) => Unit) => Unit) #43 149-154 "(Bar)" : ((Qubit => Unit is Adj) => Unit) #44 150-153 "Bar" : ((Qubit => Unit is Adj) => Unit) - Error(Type(Error(MissingFunctor(Value(CtlAdj), Value(Adj), Span { lo: 146, hi: 154 })))) - "#]], + Error(Type(Error(MissingFunctor(Value(CtlAdj), Value(Adj), Span { lo: 150, hi: 153 })))) + "##]], ); } @@ -4293,7 +4319,7 @@ fn functors_in_arg_array_subset_of_adj() { operation Bar(q : Qubit) : () {} Foo([Bar]); }", - &expect![[r#" + &expect![[r##" #1 0-144 "{\n operation Foo(ops : (Qubit => () is Adj)[]) : () {}\n operation Bar(q : Qubit) : () {}\n Foo([Bar]);\n }" : Unit #2 0-144 "{\n operation Foo(ops : (Qubit => () is Adj)[]) : () {}\n operation Bar(q : Qubit) : () {}\n Foo([Bar]);\n }" : Unit #7 27-57 "(ops : (Qubit => () is Adj)[])" : (Qubit => Unit is Adj)[] @@ -4307,8 +4333,8 @@ fn functors_in_arg_array_subset_of_adj() { #37 126-133 "([Bar])" : (Qubit => Unit)[] #38 127-132 "[Bar]" : (Qubit => Unit)[] #39 128-131 "Bar" : (Qubit => Unit) - Error(Type(Error(MissingFunctor(Value(Adj), Value(Empty), Span { lo: 123, hi: 133 })))) - "#]], + Error(Type(Error(MissingFunctor(Value(Adj), Value(Empty), Span { lo: 127, hi: 132 })))) + "##]], ); } diff --git a/source/compiler/qsc_frontend/src/typeck/tests/bounded_polymorphism.rs b/source/compiler/qsc_frontend/src/typeck/tests/bounded_polymorphism.rs index 8c93c537dd..a10c6de5cc 100644 --- a/source/compiler/qsc_frontend/src/typeck/tests/bounded_polymorphism.rs +++ b/source/compiler/qsc_frontend/src/typeck/tests/bounded_polymorphism.rs @@ -380,8 +380,8 @@ fn transitive_class_check_fail() { #80 516-519 "Foo" : (BigInt -> BigInt) #83 519-524 "(10L)" : BigInt #84 520-523 "10L" : BigInt - Error(Type(Error(MissingClassInteger("'F", Span { lo: 259, hi: 265 })))) - Error(Type(Error(MissingClassInteger("Double", Span { lo: 474, hi: 482 })))) + Error(Type(Error(MissingClassInteger("'F", Span { lo: 263, hi: 264 })))) + Error(Type(Error(MissingClassInteger("Double", Span { lo: 478, hi: 481 })))) "##]], ); } @@ -606,8 +606,8 @@ fn integral_fail() { #45 210-213 "Foo" : (Bool -> Bool) #48 213-219 "(true)" : Bool #49 214-218 "true" : Bool - Error(Type(Error(MissingClassInteger("Double", Span { lo: 176, hi: 184 })))) - Error(Type(Error(MissingClassInteger("Bool", Span { lo: 210, hi: 219 })))) + Error(Type(Error(MissingClassInteger("Double", Span { lo: 180, hi: 183 })))) + Error(Type(Error(MissingClassInteger("Bool", Span { lo: 214, hi: 218 })))) "##]], ); } @@ -736,8 +736,8 @@ fn show_and_eq_should_fail() { #69 344-352 "(1, \"2\")" : (Int, String) #70 345-346 "1" : Int #71 348-351 "\"2\"" : String - Error(Type(Error(TyMismatch("Int", "Bool", Span { lo: 303, hi: 315 })))) - Error(Type(Error(TyMismatch("Int", "String", Span { lo: 341, hi: 352 })))) + Error(Type(Error(TyMismatch("Int", "Bool", Span { lo: 310, hi: 314 })))) + Error(Type(Error(TyMismatch("Int", "String", Span { lo: 348, hi: 351 })))) "##]], ); } From bab1ea6a14c1528295a86cd30d09ae2ffc8738ed Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 3 Jun 2026 11:16:07 -0700 Subject: [PATCH 2/3] Rename input_expr to input --- .../compiler/qsc_frontend/src/typeck/rules.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/source/compiler/qsc_frontend/src/typeck/rules.rs b/source/compiler/qsc_frontend/src/typeck/rules.rs index 308a877828..7424f20846 100644 --- a/source/compiler/qsc_frontend/src/typeck/rules.rs +++ b/source/compiler/qsc_frontend/src/typeck/rules.rs @@ -266,22 +266,21 @@ impl<'a> Context<'a> { ExprKind::Block(block) => self.infer_block(block), ExprKind::Call(callee, input) => { let callee = self.infer_expr(callee); - let input_expr = &**input; - let input = if has_holes(input_expr) { - self.infer_hole_tuple_arg(input_expr) + let input = &**input; + let input = if has_holes(input) { + self.infer_hole_tuple_arg(input) } else { - let ty = self.infer_expr(input_expr); + let ty = self.infer_expr(input); // If the outermost element is a tuple, we must wrap it in an `ArgTy::Tuple`. if let Partial { ty: Ty::Tuple(tys), diverges, } = ty { - let spans: Vec<_> = if let ExprKind::Tuple(items) = input_expr.kind.as_ref() - { + let spans: Vec<_> = if let ExprKind::Tuple(items) = input.kind.as_ref() { items.iter().map(|item| item.span).collect() } else { - vec![input_expr.span; tys.len()] + vec![input.span; tys.len()] }; Partial { ty: ArgTy::Tuple( @@ -293,10 +292,10 @@ impl<'a> Context<'a> { diverges, } } else { - let arg_span = if let ExprKind::Paren(inner) = input_expr.kind.as_ref() { + let arg_span = if let ExprKind::Paren(inner) = input.kind.as_ref() { inner.span } else { - input_expr.span + input.span }; Partial { ty: ArgTy::Given(ty.ty, arg_span), From dc1531daf81f778861f282e842cadf8cbd02652f Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 3 Jun 2026 11:55:26 -0700 Subject: [PATCH 3/3] Rename some variables for clarity --- .../compiler/qsc_frontend/src/typeck/infer.rs | 14 +++++-------- .../compiler/qsc_frontend/src/typeck/rules.rs | 21 ++++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/source/compiler/qsc_frontend/src/typeck/infer.rs b/source/compiler/qsc_frontend/src/typeck/infer.rs index 198b51f2d6..353c5a96e4 100644 --- a/source/compiler/qsc_frontend/src/typeck/infer.rs +++ b/source/compiler/qsc_frontend/src/typeck/infer.rs @@ -326,7 +326,7 @@ impl ArgTy { } /// Applies the argument type to a parameter type, generating constraints and errors. - fn apply(&self, param: &Ty, span: Span) -> App { + fn apply(&self, param: &Ty, call_span: Span) -> App { match (self, param) { // If `arg` is a hole, then it doesn't matter what the param is, // because the hole can be anything. @@ -342,10 +342,6 @@ impl ArgTy { }], errors: Vec::new(), }, - // If `arg` is a hole, then it doesn't matter what the param is, - // because the hole can be anything. - // However, we do know that the type of Arg must be Eq to the type of Param, so we - // add that to the constraints. (Self::Given(arg, arg_span), _) => App { holes: Vec::new(), constraints: vec![Constraint::Eq { @@ -363,14 +359,14 @@ impl ArgTy { errors.push(Error(ErrorKind::TyMismatch( Ty::Tuple(params.clone()).display(), self.to_ty().display(), - span, + call_span, ))); } let mut holes = Vec::new(); let mut constraints = Vec::new(); for (arg, param) in args.iter().zip(params) { - let mut app = arg.apply(param, span); + let mut app = arg.apply(param, call_span); constraints.append(&mut app.constraints); errors.append(&mut app.errors); if app.holes.len() > 1 { @@ -392,7 +388,7 @@ impl ArgTy { constraints: vec![Constraint::Eq { expected: param.clone(), actual: self.to_ty(), - span, + span: call_span, }], errors: Vec::new(), }, @@ -402,7 +398,7 @@ impl ArgTy { errors: vec![Error(ErrorKind::TyMismatch( param.display(), self.to_ty().display(), - span, + call_span, ))], }, } diff --git a/source/compiler/qsc_frontend/src/typeck/rules.rs b/source/compiler/qsc_frontend/src/typeck/rules.rs index 7424f20846..d64db838ca 100644 --- a/source/compiler/qsc_frontend/src/typeck/rules.rs +++ b/source/compiler/qsc_frontend/src/typeck/rules.rs @@ -264,23 +264,24 @@ impl<'a> Context<'a> { } ExprKind::BinOp(op, lhs, rhs) => self.infer_binop(expr.span, *op, lhs, rhs), ExprKind::Block(block) => self.infer_block(block), - ExprKind::Call(callee, input) => { - let callee = self.infer_expr(callee); - let input = &**input; - let input = if has_holes(input) { - self.infer_hole_tuple_arg(input) + ExprKind::Call(callee_expr, input_expr) => { + let callee = self.infer_expr(callee_expr); + let input_expr = &**input_expr; + let input = if has_holes(input_expr) { + self.infer_hole_tuple_arg(input_expr) } else { - let ty = self.infer_expr(input); + let ty = self.infer_expr(input_expr); // If the outermost element is a tuple, we must wrap it in an `ArgTy::Tuple`. if let Partial { ty: Ty::Tuple(tys), diverges, } = ty { - let spans: Vec<_> = if let ExprKind::Tuple(items) = input.kind.as_ref() { + let spans: Vec<_> = if let ExprKind::Tuple(items) = input_expr.kind.as_ref() + { items.iter().map(|item| item.span).collect() } else { - vec![input.span; tys.len()] + vec![input_expr.span; tys.len()] }; Partial { ty: ArgTy::Tuple( @@ -292,10 +293,10 @@ impl<'a> Context<'a> { diverges, } } else { - let arg_span = if let ExprKind::Paren(inner) = input.kind.as_ref() { + let arg_span = if let ExprKind::Paren(inner) = input_expr.kind.as_ref() { inner.span } else { - input.span + input_expr.span }; Partial { ty: ArgTy::Given(ty.ty, arg_span),