Completion
An editor asks what could go where the cursor is, and the answer arrives while the line is still half written. That is the only state that matters, because nobody needs a suggestion once the code is complete.
Completion is offered on . and on ::, and is triggered by typing either.
After a dot
A value's own members, whether or not the member has been typed yet.
pub type Point {
pub field x: i32;
pub field y: i32;
pub static fn origin() -> Point { return new Point { x: 0, y: 0 }; }
pub fn shifted(dx: i32) -> Point { return new Point { x: self.x + dx, y: self.y }; }
}
| Written | Offered |
|---|---|
p. |
x, y, shifted |
self. |
x, y, shifted |
Point:: |
origin |
A type on the left is a static access, so only what can be reached that way
comes up. self is a value even though it names its own type, so it offers the
instance members.
What an interface supplies is offered as well, on every type that implements it and on arrays, which are sequences. Completion asks the compiler what members a type has, so a member nobody wrote on the type still comes up.
| Written | Offered |
|---|---|
values. where values is i32[] |
count, and the seven sequence operators |
A union offers its cases through ::, since that is
how a case is reached.
pub union Slot {
empty,
filled(i32),
}
| Written | Offered |
|---|---|
Slot:: |
empty, filled |
A dot with no union named
A pattern and a
case written without its union
both start with a bare ., and both offer the cases of the union in view.
match slot {
. // empty, filled
}
Where a case is written without its union, the union comes from whatever the value is going into, and completion reads it the same way the compiler does.
pub fn chosen() -> Slot {
return . // empty, filled
}
pub fn held(slot: Slot) -> i32 { return 0; }
| Written | Offered where the union is |
|---|---|
return . |
the function's return type |
let s: Slot = . |
the annotation |
s = . |
the variable being assigned to |
held(. |
the parameter |
new Holder { slot: . } |
the field |
new Slot[] { . } |
the array's element type |
Where no union is expected, there are no cases to offer, and the ordinary list
of names in scope comes up instead. let s = . with no
annotation is one such place, and so is let n: i32 = ..
Note
Where two overloads take different unions, the case name picks between them,
so held(. offers the cases of whichever union that call could want.
Where both could want a case of that name, one of them is offered. Writing the
union out says which.
Inside a call
Every parameter the call could still name, offered with its colon already on it, above whatever else is in scope.
pub fn connect(host: string, port: i32 = 8080, tls: bool = true) -> string {
return host;
}
pub fn log(level: i32, parts: ...string) -> i32 {
return level;
}
| Written | Offered |
|---|---|
connect( |
host:, port:, tls: |
connect("localhost", |
port:, tls: |
connect("localhost", tls: false, |
port: |
log(1, |
nothing |
A parameter drops out once something fills it, whether an argument named it or
reached it by position. log offers nothing after its first argument because
parts gathers, and gathering
works only by position.
Everywhere else
Anything that is not after a dot offers what is in scope: locals declared above
the cursor, the enclosing function's parameters, the enclosing type's members,
the file's own declarations, everything in
std, then keywords and the primitive type names.
A local is not offered on the line that declares it, and a local declared below the cursor is not offered at all.