Expressions
Member access and qualification
The dot for instances, the double colon for base, static and namespace names, optional chaining, and the explicit-type marker.
since 0.1.0-alpha.1linuxwindowswasm
Description
Two operators navigate to a member, and they mean different things:
.navigates an instance:obj.field,obj.method(args).::navigates the non-instantiated side: a base class (this.Counter::value), a constructor label (Type::Label), a namespace (NS::name), or the static side of a class. Inside a generic function, the left operand may also be a type parameter of that function, resolved separately for each concrete instantiation.
There is one rule behind both: :: means "the non-instantiated version".
?. is optional chaining. a?.m() and a?.b evaluate to None when a is None, without evaluating the call's arguments, and otherwise behave like .. The result type is the member's type or None.
::<...> is the explicit-type marker. It attaches to the complete callee, as in N::f::<T>() and this.Base::m::<T>(). Followed by an argument list it fixes the type arguments of a call or construction. Without an argument list it makes a reference to a generic function, as in var f = identity::<int>;.
Qualifying a base, a namespace and an optional receiver
class Counter {
distinct int value = 1;
}
class Gauge {
distinct int value = 2;
}
class Both : Counter, Gauge {
int sum() => this.Counter::value + this.Gauge::value;
}
class Animal {
string speak() => "...";
}
class Dog : Animal {
string speak() => "Woof";
string both() => "${this.speak()} / ${this.Animal::speak()}";
}
class Node {
string name = "node";
}
namespace Util {
int twice(int n) => n * 2;
}
T identity<T>(T x) => x;
void main() {
console.writeln(Both().sum());
console.writeln(Dog().both());
console.writeln(Util::twice(4));
Node? missing = None;
Node? present = Node();
console.writeln(missing?.name ?? "none");
console.writeln(present?.name ?? "none");
var pinned = identity::<int>;
console.writeln(pinned(3));
}
main();
3
Woof / ...
8
none
node
3
Rules
.reaches instance members;::reaches base-class, static, label and namespace members.- Qualifying with a base class (
this.Animal::speak()) calls that class's own member where the bare name is unambiguous, bypassing any override in the derived class. Where the bare name is ambiguous betweendistinctpaths, it selects the path and dispatches on the runtime class (lang.distinct). - Two unrelated declarations of one name and type in two bases are a compile error unless the class restates the member. A field declared
distinctin either base keeps its own slot, reachable only by qualification (this.Counter::value). A bare read of such a field (b.value) is a compile error: the compiler refuses to guess which slot is meant. A qualifier may be any class on the path. - Optional chaining
?.skips the call and its arguments when the receiver isNone. - A
::-reached callable that is not followed by(is a method reference. Seelang.method-references. - A base qualifier
x.Base::membernames one of the bases ofx's class.Baseis decided in this order. If the name, looked up where the access is written, is a base ofx's class, it is that base, even whenx's class also has a member namedBase. Otherwise, ifx's class has a field, method or accessor namedBase,x.Basereads that member:h.Node::von a field namedNodereads the field. OtherwiseBaseis matched by name against the bases ofx's class, soc.Counter::valuealso works outside the namespace that declaresCounter. When the static type ofxis not a class (a type parameter or an interface),x.Baseis a qualifier only whenBasenames a top-level class. When the match by name finds two different bases, the qualifier is ambiguous and the access is a compile error:
error: ambiguous base qualifier 'A': 'C' has more than one base class named 'A'
- The qualifier is looked up where the access is written first, so inside
namespace Nthe qualifierAnamesN::Aeven when another base isM::A; alias the other (use M::A as MA;) to name it. The qualified class must declare or inherit the member:
error: type 'N::A' has no member 'w'
Examples
Optional chaining does not evaluate the arguments of a skipped call
class Probe {
int add(int k) => k;
}
int count = 0;
int tick() {
count = count + 1;
return count;
}
Probe? none = None;
Probe? some = Probe();
console.writeln(none?.add(tick()) ?? -1);
console.writeln(count);
console.writeln(some?.add(tick()) ?? -1);
console.writeln(count);
-1
0
1
1
class Counter { distinct int value = 1; }
class Gauge { distinct int value = 2; }
class Both : Counter, Gauge { }
Both b = Both();
console.writeln(b.value);
// error: ambiguous read of 'value : int' (distinct on multiple bases); qualify with '::'
Notes
Member access or a call on a value of a union type, including an optional T?, is a compile error until the value has been narrowed to one member. Narrow with if (x != None) { ... } or use ?..
See also
- Map — An associative collection from keys of type
Kto values of typeV, with value semantics. - Method references — Naming a function, method or labeled constructor without calling it yields an ordinary function value.