Skip to content

Commit 29145c5

Browse files
authored
UDT field completions (#1954)
Builds on #1947 . Enables: 1. Field access completions, for example: ```qsharp let x = Complex(1.0, 2.0); x.| ``` At the cursor location indicated by `|`, completions should include `Real` since it's a field of `Complex`. 2. Field assignment completions, for example: ```qsharp struct Foo { bar: Int } new Foo { | } ``` Here, completions should include `Bar`.
1 parent e4df79d commit 29145c5

File tree

21 files changed

+936
-125
lines changed

21 files changed

+936
-125
lines changed

compiler/qsc_ast/src/ast.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,16 @@ impl WithSpan for Expr {
835835
}
836836
}
837837

838+
/// The identifier in a field access expression.
839+
#[derive(Clone, Debug, Default, PartialEq)]
840+
pub enum FieldAccess {
841+
/// The field name.
842+
Ok(Box<Ident>),
843+
/// The field access was missing a field name.
844+
#[default]
845+
Err,
846+
}
847+
838848
/// An expression kind.
839849
#[derive(Clone, Debug, Default, PartialEq)]
840850
pub enum ExprKind {
@@ -862,7 +872,7 @@ pub enum ExprKind {
862872
/// A failure: `fail "message"`.
863873
Fail(Box<Expr>),
864874
/// A field accessor: `a::F` or `a.F`.
865-
Field(Box<Expr>, Box<Ident>),
875+
Field(Box<Expr>, FieldAccess),
866876
/// A for loop: `for a in b { ... }`.
867877
For(Box<Pat>, Box<Expr>, Box<Block>),
868878
/// An unspecified expression, _, which may indicate partial application or a typed hole.
@@ -1030,11 +1040,14 @@ fn display_conjugate(
10301040
Ok(())
10311041
}
10321042

1033-
fn display_field(mut indent: Indented<Formatter>, expr: &Expr, id: &Ident) -> fmt::Result {
1043+
fn display_field(mut indent: Indented<Formatter>, expr: &Expr, field: &FieldAccess) -> fmt::Result {
10341044
write!(indent, "Field:")?;
10351045
indent = set_indentation(indent, 1);
10361046
write!(indent, "\n{expr}")?;
1037-
write!(indent, "\n{id}")?;
1047+
match field {
1048+
FieldAccess::Ok(i) => write!(indent, "\n{i}")?,
1049+
FieldAccess::Err => write!(indent, "\nErr")?,
1050+
}
10381051
Ok(())
10391052
}
10401053

compiler/qsc_ast/src/mut_visit.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Licensed under the MIT License.
33

44
use crate::ast::{
5-
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAssign, FieldDef, FunctorExpr,
6-
FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path, PathKind,
7-
QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent, StructDecl,
8-
TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
5+
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
6+
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
7+
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
8+
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
99
};
1010
use qsc_data_structures::span::Span;
1111

@@ -288,7 +288,9 @@ pub fn walk_expr(vis: &mut impl MutVisitor, expr: &mut Expr) {
288288
ExprKind::Fail(msg) => vis.visit_expr(msg),
289289
ExprKind::Field(record, name) => {
290290
vis.visit_expr(record);
291-
vis.visit_ident(name);
291+
if let FieldAccess::Ok(name) = name {
292+
vis.visit_ident(name);
293+
}
292294
}
293295
ExprKind::For(pat, iter, block) => {
294296
vis.visit_pat(pat);

compiler/qsc_ast/src/visit.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Licensed under the MIT License.
33

44
use crate::ast::{
5-
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAssign, FieldDef, FunctorExpr,
6-
FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path, PathKind,
7-
QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent, StructDecl,
8-
TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
5+
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
6+
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
7+
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
8+
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
99
};
1010

1111
pub trait Visitor<'a>: Sized {
@@ -257,7 +257,9 @@ pub fn walk_expr<'a>(vis: &mut impl Visitor<'a>, expr: &'a Expr) {
257257
ExprKind::Fail(msg) => vis.visit_expr(msg),
258258
ExprKind::Field(record, name) => {
259259
vis.visit_expr(record);
260-
vis.visit_ident(name);
260+
if let FieldAccess::Ok(name) = name {
261+
vis.visit_ident(name);
262+
}
261263
}
262264
ExprKind::For(pat, iter, block) => {
263265
vis.visit_pat(pat);

compiler/qsc_codegen/src/qsharp.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ use std::io::Write;
1414
use std::vec;
1515

1616
use qsc_ast::ast::{
17-
self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Functor,
18-
FunctorExpr, FunctorExprKind, Ident, Idents, ImportOrExportItem, Item, ItemKind, Lit,
19-
Mutability, Pat, PatKind, Path, PathKind, Pauli, QubitInit, QubitInitKind, QubitSource, SetOp,
20-
SpecBody, SpecDecl, SpecGen, Stmt, StmtKind, StringComponent, TernOp, TopLevelNode, Ty, TyDef,
21-
TyDefKind, TyKind, UnOp,
17+
self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind,
18+
FieldAccess, Functor, FunctorExpr, FunctorExprKind, Ident, Idents, ImportOrExportItem, Item,
19+
ItemKind, Lit, Mutability, Pat, PatKind, Path, PathKind, Pauli, QubitInit, QubitInitKind,
20+
QubitSource, SetOp, SpecBody, SpecDecl, SpecGen, Stmt, StmtKind, StringComponent, TernOp,
21+
TopLevelNode, Ty, TyDef, TyDefKind, TyKind, UnOp,
2222
};
2323
use qsc_ast::ast::{Namespace, Package};
2424
use qsc_ast::visit::Visitor;
@@ -483,7 +483,7 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
483483
self.write("fail ");
484484
self.visit_expr(msg);
485485
}
486-
ExprKind::Field(record, name) => {
486+
ExprKind::Field(record, ast::FieldAccess::Ok(name)) => {
487487
self.visit_expr(record);
488488
self.write(".");
489489
self.visit_ident(name);
@@ -716,7 +716,8 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
716716
}
717717
ExprKind::Err
718718
| ExprKind::Path(PathKind::Err(_))
719-
| ExprKind::Struct(PathKind::Err(_), ..) => {
719+
| ExprKind::Struct(PathKind::Err(_), ..)
720+
| ExprKind::Field(_, FieldAccess::Err) => {
720721
unreachable!();
721722
}
722723
}

compiler/qsc_frontend/src/lower.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
typeck::{self, convert},
1111
};
1212
use miette::Diagnostic;
13-
use qsc_ast::ast::{self, Ident, Idents, PathKind};
13+
use qsc_ast::ast::{self, FieldAccess, Ident, Idents, PathKind};
1414
use qsc_data_structures::{index_map::IndexMap, span::Span, target::TargetCapabilityFlags};
1515
use qsc_hir::{
1616
assigner::Assigner,
@@ -576,7 +576,7 @@ impl With<'_> {
576576
hir::ExprKind::Conjugate(self.lower_block(within), self.lower_block(apply))
577577
}
578578
ast::ExprKind::Fail(message) => hir::ExprKind::Fail(Box::new(self.lower_expr(message))),
579-
ast::ExprKind::Field(container, name) => {
579+
ast::ExprKind::Field(container, FieldAccess::Ok(name)) => {
580580
let container = self.lower_expr(container);
581581
let field = self.lower_field(&container.ty, &name.name);
582582
hir::ExprKind::Field(Box::new(container), field)
@@ -682,7 +682,8 @@ impl With<'_> {
682682
}
683683
ast::ExprKind::Err
684684
| &ast::ExprKind::Path(ast::PathKind::Err(_))
685-
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => hir::ExprKind::Err,
685+
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..)
686+
| ast::ExprKind::Field(_, FieldAccess::Err) => hir::ExprKind::Err,
686687
};
687688

688689
hir::Expr {

compiler/qsc_frontend/src/resolve.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,12 @@ pub(super) type Names = IndexMap<NodeId, Res>;
4343
#[must_use]
4444
pub fn path_as_field_accessor<'a>(
4545
names: &Names,
46-
path: &'a ast::Path,
46+
path: &'a impl Idents,
4747
) -> Option<(NodeId, Vec<&'a ast::Ident>)> {
48-
if path.segments.is_some() {
49-
let parts: Vec<&Ident> = path.iter().collect();
50-
let first = parts.first().expect("path should have at least one part");
51-
if let Some(&Res::Local(node_id)) = names.get(first.id) {
52-
return Some((node_id, parts));
53-
}
48+
let parts: Vec<&Ident> = path.iter().collect();
49+
let first = parts.first().expect("path should have at least one part");
50+
if let Some(&Res::Local(node_id)) = names.get(first.id) {
51+
return Some((node_id, parts));
5452
}
5553
// If any of the above conditions are not met, return None.
5654
None
@@ -653,6 +651,38 @@ impl Resolver {
653651
}
654652
}
655653

654+
fn resolve_path_kind(&mut self, kind: NameKind, path: &ast::PathKind) -> Result<(), Error> {
655+
match path {
656+
PathKind::Ok(path) => self.resolve_path(kind, path).map(|_| ()),
657+
PathKind::Err(incomplete_path) => {
658+
// First we check if the the path can be resolved as a field accessor.
659+
// We do this by checking if the first part of the path is a local variable.
660+
if let (NameKind::Term, Some(incomplet_path)) = (kind, incomplete_path) {
661+
let first = incomplet_path
662+
.segments
663+
.first()
664+
.expect("path `segments` should have at least one element");
665+
match resolve(
666+
NameKind::Term,
667+
&self.globals,
668+
self.locals.get_scopes(&self.curr_scope_chain),
669+
first,
670+
&None,
671+
) {
672+
Ok(res) if matches!(res, Res::Local(_)) => {
673+
// The path is a field accessor.
674+
self.names.insert(first.id, res.clone());
675+
return Ok(());
676+
}
677+
Err(err) if !matches!(err, Error::NotFound(_, _)) => return Err(err), // Local was found but has issues.
678+
_ => return Ok(()), // The path is assumed to not be a field accessor, so move on.
679+
}
680+
}
681+
Ok(())
682+
}
683+
}
684+
}
685+
656686
fn resolve_path(&mut self, kind: NameKind, path: &ast::Path) -> Result<Res, Error> {
657687
let name = &path.name;
658688
let segments = &path.segments;
@@ -1428,8 +1458,8 @@ impl AstVisitor<'_> for With<'_> {
14281458
visitor.visit_expr(output);
14291459
});
14301460
}
1431-
ast::ExprKind::Path(PathKind::Ok(path)) => {
1432-
if let Err(e) = self.resolver.resolve_path(NameKind::Term, path) {
1461+
ast::ExprKind::Path(path) => {
1462+
if let Err(e) = self.resolver.resolve_path_kind(NameKind::Term, path) {
14331463
self.resolver.errors.push(e);
14341464
};
14351465
}

compiler/qsc_frontend/src/typeck/rules.rs

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use super::{
88
};
99
use crate::resolve::{self, Names, Res};
1010
use qsc_ast::ast::{
11-
self, BinOp, Block, Expr, ExprKind, Functor, Ident, Lit, NodeId, Pat, PatKind, Path, PathKind,
12-
QubitInit, QubitInitKind, Spec, Stmt, StmtKind, StringComponent, TernOp, TyKind, UnOp,
11+
self, BinOp, Block, Expr, ExprKind, FieldAccess, Functor, Ident, Idents, Lit, NodeId, Pat,
12+
PatKind, Path, PathKind, QubitInit, QubitInitKind, Spec, Stmt, StmtKind, StringComponent,
13+
TernOp, TyKind, UnOp,
1314
};
1415
use qsc_data_structures::span::Span;
1516
use qsc_hir::{
@@ -268,16 +269,20 @@ impl<'a> Context<'a> {
268269
}
269270
ExprKind::Field(record, name) => {
270271
let record = self.infer_expr(record);
271-
let item_ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span));
272-
self.inferrer.class(
273-
expr.span,
274-
Class::HasField {
275-
record: record.ty,
276-
name: name.name.to_string(),
277-
item: item_ty.clone(),
278-
},
279-
);
280-
self.diverge_if(record.diverges, converge(item_ty))
272+
if let FieldAccess::Ok(name) = name {
273+
let item_ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span));
274+
self.inferrer.class(
275+
expr.span,
276+
Class::HasField {
277+
record: record.ty,
278+
name: name.name.to_string(),
279+
item: item_ty.clone(),
280+
},
281+
);
282+
self.diverge_if(record.diverges, converge(item_ty))
283+
} else {
284+
converge(Ty::Err)
285+
}
281286
}
282287
ExprKind::For(item, container, body) => {
283288
let item_ty = self.infer_pat(item);
@@ -394,7 +399,7 @@ impl<'a> Context<'a> {
394399
Lit::String(_) => converge(Ty::Prim(Prim::String)),
395400
},
396401
ExprKind::Paren(expr) => self.infer_expr(expr),
397-
ExprKind::Path(PathKind::Ok(path)) => self.infer_path(expr, path),
402+
ExprKind::Path(path) => self.infer_path_kind(expr, path),
398403
ExprKind::Range(start, step, end) => {
399404
let mut diverges = false;
400405
for expr in start.iter().chain(step).chain(end) {
@@ -532,9 +537,7 @@ impl<'a> Context<'a> {
532537
self.typed_holes.push((expr.id, expr.span));
533538
converge(self.inferrer.fresh_ty(TySource::not_divergent(expr.span)))
534539
}
535-
ExprKind::Err
536-
| ast::ExprKind::Path(ast::PathKind::Err(_))
537-
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => converge(Ty::Err),
540+
ExprKind::Err | ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => converge(Ty::Err),
538541
};
539542

540543
self.record(expr.id, ty.ty.clone());
@@ -570,24 +573,23 @@ impl<'a> Context<'a> {
570573
record
571574
}
572575

573-
fn infer_path(&mut self, expr: &Expr, path: &Path) -> Partial<Ty> {
574-
match resolve::path_as_field_accessor(self.names, path) {
575-
// If the path is a field accessor, we infer the type of first segment
576-
// as an expr, and the rest as subsequent fields.
577-
Some((first_id, parts)) => {
578-
let record = converge(
579-
self.table
580-
.terms
581-
.get(first_id)
582-
.expect("local should have type")
583-
.clone(),
584-
);
585-
let (first, rest) = parts
586-
.split_first()
587-
.expect("path should have at least one part");
588-
self.record(first.id, record.ty.clone());
589-
self.infer_path_parts(record, rest, expr.span.lo)
576+
fn infer_path_kind(&mut self, expr: &Expr, path: &PathKind) -> Partial<Ty> {
577+
match path {
578+
PathKind::Ok(path) => self.infer_path(expr, path),
579+
PathKind::Err(incomplete_path) => {
580+
if let Some(incomplete_path) = incomplete_path {
581+
// If this is a field access, infer the fields,
582+
// but leave the whole expression as `Err`.
583+
let _ = self.infer_path_as_field_access(&incomplete_path.segments, expr);
584+
}
585+
converge(Ty::Err)
590586
}
587+
}
588+
}
589+
590+
fn infer_path(&mut self, expr: &Expr, path: &Path) -> Partial<Ty> {
591+
match self.infer_path_as_field_access(path, expr) {
592+
Some(record) => record,
591593
// Otherwise we infer the path as a namespace path.
592594
None => match self.names.get(path.id) {
593595
None => converge(Ty::Err),
@@ -620,6 +622,31 @@ impl<'a> Context<'a> {
620622
}
621623
}
622624

625+
fn infer_path_as_field_access(
626+
&mut self,
627+
path: &impl Idents,
628+
expr: &Expr,
629+
) -> Option<Partial<Ty>> {
630+
// If the path is a field accessor, we infer the type of first segment
631+
// as an expr, and the rest as subsequent fields.
632+
if let Some((first_id, parts)) = resolve::path_as_field_accessor(self.names, path) {
633+
let record = converge(
634+
self.table
635+
.terms
636+
.get(first_id)
637+
.expect("local should have type")
638+
.clone(),
639+
);
640+
let (first, rest) = parts
641+
.split_first()
642+
.expect("path should have at least one part");
643+
self.record(first.id, record.ty.clone());
644+
Some(self.infer_path_parts(record, rest, expr.span.lo))
645+
} else {
646+
None
647+
}
648+
}
649+
623650
fn infer_hole_tuple<T>(
624651
&mut self,
625652
hole: fn(Ty) -> T,

0 commit comments

Comments
 (0)