summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-28 18:32:58 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-28 18:32:58 +0100
commit406de22ee5cd74dc6f67743bad4710415bb50c41 (patch)
tree37536600378cd325956201ea175469bc22db2b1a
parent2d56e3c5e245bf8824bf0ea8f1f1a05cb9716dc5 (diff)
Remove method call syntax kind
-rw-r--r--src/ide/analyze.rs4
-rw-r--r--src/ide/complete.rs6
-rw-r--r--src/ide/highlight.rs117
-rw-r--r--src/model/eval.rs150
-rw-r--r--src/syntax/ast.rs31
-rw-r--r--src/syntax/kind.rs5
-rw-r--r--src/syntax/node.rs20
-rw-r--r--src/syntax/parser.rs22
-rw-r--r--tests/ref/compiler/highlight.pngbin0 -> 137139 bytes
-rw-r--r--tests/ref/math/syntax.pngbin54038 -> 54023 bytes
-rw-r--r--tests/typ/compiler/highlight.typ63
-rw-r--r--tests/typ/math/matrix.typ2
12 files changed, 222 insertions, 198 deletions
diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs
index c170186f..65d9ded8 100644
--- a/src/ide/analyze.rs
+++ b/src/ide/analyze.rs
@@ -7,9 +7,7 @@ use crate::World;
/// Try to determine a set of possible values for an expression.
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
match node.cast::<ast::Expr>() {
- Some(
- ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::MethodCall(_),
- ) => {
+ Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FuncCall(_)) => {
if let Some(parent) = node.parent() {
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
return analyze(world, parent);
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index 83202e30..143e22b9 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -4,7 +4,7 @@ use if_chain::if_chain;
use super::{analyze, plain_docs_sentence, summarize_font_family};
use crate::model::{methods_on, CastInfo, Scope, Value};
-use crate::syntax::{ast, LinkedNode, Source, SyntaxKind, SyntaxNode};
+use crate::syntax::{ast, LinkedNode, Source, SyntaxKind};
use crate::util::{format_eco, EcoString};
use crate::World;
@@ -936,9 +936,7 @@ impl<'a> CompletionContext<'a> {
if let Some(parent) = node.parent() {
if let Some(v) = parent.cast::<ast::ForLoop>() {
- if node.prev_sibling().as_deref().map(SyntaxNode::kind)
- != Some(SyntaxKind::In)
- {
+ if node.prev_sibling_kind() != Some(SyntaxKind::In) {
let pattern = v.pattern();
if let Some(key) = pattern.key() {
defined.insert(key.take());
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index d8f15f00..ede13d7f 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -85,8 +85,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
match node.kind() {
SyntaxKind::Markup
if node.parent_kind() == Some(SyntaxKind::TermItem)
- && node.next_sibling().as_ref().map(|v| v.kind())
- == Some(SyntaxKind::Colon) =>
+ && node.next_sibling_kind() == Some(SyntaxKind::Colon) =>
{
Some(Category::ListTerm)
}
@@ -116,17 +115,12 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Math => None,
SyntaxKind::MathIdent => highlight_ident(node),
+ SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
SyntaxKind::MathDelimited => None,
SyntaxKind::MathAttach => None,
SyntaxKind::MathFrac => None,
- SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
-
- SyntaxKind::Hashtag => node
- .next_sibling()
- .filter(|node| node.cast::<ast::Expr>().map_or(false, |e| e.hashtag()))
- .and_then(|node| node.leftmost_leaf())
- .and_then(|node| highlight(&node)),
+ SyntaxKind::Hashtag => highlight_hashtag(node),
SyntaxKind::LeftBrace => Some(Category::Punctuation),
SyntaxKind::RightBrace => Some(Category::Punctuation),
SyntaxKind::LeftBracket => Some(Category::Punctuation),
@@ -206,18 +200,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Keyed => None,
SyntaxKind::Unary => None,
SyntaxKind::Binary => None,
- SyntaxKind::FieldAccess => match node.parent_kind() {
- Some(
- SyntaxKind::Markup
- | SyntaxKind::Math
- | SyntaxKind::MathFrac
- | SyntaxKind::MathAttach,
- ) => Some(Category::Interpolated),
- Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
- _ => None,
- },
+ SyntaxKind::FieldAccess => None,
SyntaxKind::FuncCall => None,
- SyntaxKind::MethodCall => None,
SyntaxKind::Args => None,
SyntaxKind::Spread => None,
SyntaxKind::Closure => None,
@@ -245,49 +229,60 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
/// Highlight an identifier based on context.
fn highlight_ident(node: &LinkedNode) -> Option<Category> {
- match node.parent_kind() {
- Some(SyntaxKind::FuncCall) => Some(Category::Function),
- Some(SyntaxKind::FieldAccess)
- if node.parent().and_then(|p| p.parent_kind())
- == Some(SyntaxKind::SetRule)
- && node.next_sibling().is_none() =>
- {
- Some(Category::Function)
- }
- Some(SyntaxKind::FieldAccess)
- if node
- .parent()
- .and_then(|p| p.parent())
- .filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
- .and_then(|gp| gp.parent())
- .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
- && node.next_sibling().is_none() =>
- {
- Some(Category::Function)
- }
- Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
- Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
- Some(Category::Function)
- }
- Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
- Some(Category::Function)
- }
- Some(SyntaxKind::SetRule) => Some(Category::Function),
- Some(SyntaxKind::ShowRule)
- if node.prev_sibling().as_ref().map(|v| v.kind())
- == Some(SyntaxKind::Show) =>
- {
- Some(Category::Function)
+ // Are we directly before an argument list?
+ let next_leaf_kind = node.next_leaf().map(|leaf| leaf.kind());
+ if matches!(next_leaf_kind, Some(SyntaxKind::LeftParen | SyntaxKind::LeftBracket)) {
+ return Some(Category::Function);
+ }
+
+ // Are we in math?
+ if node.kind() == SyntaxKind::MathIdent {
+ return Some(Category::Interpolated);
+ }
+
+ // Find the first non-field access ancestor.
+ let mut ancestor = node;
+ while ancestor.parent_kind() == Some(SyntaxKind::FieldAccess) {
+ ancestor = ancestor.parent()?;
+ }
+
+ // Are we directly before a show rule colon?
+ if next_leaf_kind == Some(SyntaxKind::Colon)
+ && ancestor.parent_kind() == Some(SyntaxKind::ShowRule)
+ {
+ return Some(Category::Function);
+ }
+
+ // Are we (or an ancestor field access) directly after a hashtag.
+ if ancestor.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Hashtag) {
+ return Some(Category::Interpolated);
+ }
+
+ // Are we behind a dot, that is behind another identifier?
+ let prev = node.prev_leaf()?;
+ if prev.kind() == SyntaxKind::Dot {
+ let prev_prev = prev.prev_leaf()?;
+ if is_ident(&prev_prev) {
+ return highlight_ident(&prev_prev);
}
- Some(
- SyntaxKind::Markup
- | SyntaxKind::Math
- | SyntaxKind::MathFrac
- | SyntaxKind::MathAttach,
- ) => Some(Category::Interpolated),
- _ if node.kind() == SyntaxKind::MathIdent => Some(Category::Interpolated),
- _ => None,
}
+
+ None
+}
+
+/// Highlight a hashtag based on context.
+fn highlight_hashtag(node: &LinkedNode) -> Option<Category> {
+ let next = node.next_sibling()?;
+ let expr = next.cast::<ast::Expr>()?;
+ if !expr.hashtag() {
+ return None;
+ }
+ highlight(&next.leftmost_leaf()?)
+}
+
+/// Whether the node is one of the two identifier nodes.
+fn is_ident(node: &LinkedNode) -> bool {
+ matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent)
}
#[cfg(test)]
diff --git a/src/model/eval.rs b/src/model/eval.rs
index b63069bf..d0751a1f 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -364,7 +364,6 @@ impl Eval for ast::Expr {
Self::Parenthesized(v) => v.eval(vm),
Self::FieldAccess(v) => v.eval(vm),
Self::FuncCall(v) => v.eval(vm),
- Self::MethodCall(v) => v.eval(vm),
Self::Closure(v) => v.eval(vm),
Self::Unary(v) => v.eval(vm),
Self::Binary(v) => v.eval(vm),
@@ -918,12 +917,51 @@ impl Eval for ast::FuncCall {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let callee_expr = self.callee();
- let callee_span = callee_expr.span();
- let callee = callee_expr.eval(vm)?;
- let mut args = self.args().eval(vm)?;
+ let span = self.span();
+ let callee = self.callee();
+ let in_math = in_math(&callee);
+ let callee_span = callee.span();
+ let args = self.args();
+
+ // Try to evaluate as a method call. This is possible if the callee is a
+ // field access and does not evaluate to a module.
+ let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee {
+ let target = access.target();
+ let method = access.field();
+ let method_span = method.span();
+ let method = method.take();
+ let point = || Tracepoint::Call(Some(method.clone()));
+ if methods::is_mutating(&method) {
+ let args = args.eval(vm)?;
+ let value = target.access(vm)?;
+
+ let value = if let Value::Module(module) = &value {
+ module.get(&method).cloned().at(method_span)?
+ } else {
+ return methods::call_mut(value, &method, args, span)
+ .trace(vm.world, point, span);
+ };
+
+ (value, args)
+ } else {
+ let target = target.eval(vm)?;
+ let args = args.eval(vm)?;
+ let value = if let Value::Module(module) = &target {
+ module.get(&method).cloned().at(method_span)?
+ } else {
+ return methods::call(vm, target, &method, args, span)
+ .trace(vm.world, point, span);
+ };
+ (value, args)
+ }
+ } else {
+ (callee.eval(vm)?, args.eval(vm)?)
+ };
- if in_math(&callee_expr) && !matches!(callee, Value::Func(_)) {
+ // Handle math special cases for non-functions:
+ // Combining accent symbols apply themselves while everything else
+ // simply displays the arguments verbatim.
+ if in_math && !matches!(callee, Value::Func(_)) {
if let Value::Symbol(sym) = &callee {
let c = sym.get();
if let Some(accent) = combining_accent(c) {
@@ -932,7 +970,6 @@ impl Eval for ast::FuncCall {
return Ok(Value::Content((vm.items.math_accent)(base, accent)));
}
}
-
let mut body = (vm.items.text)('('.into());
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
@@ -944,8 +981,14 @@ impl Eval for ast::FuncCall {
return Ok(Value::Content(callee.display() + body));
}
+ // Finally, just a normal function call!
+ if vm.depth >= MAX_CALL_DEPTH {
+ bail!(span, "maximum function call depth exceeded");
+ }
+
let callee = callee.cast::<Func>().at(callee_span)?;
- complete_call(vm, &callee, args, self.span())
+ let point = || Tracepoint::Call(callee.name().map(Into::into));
+ callee.call(vm, args).trace(vm.world, point, span)
}
}
@@ -957,59 +1000,6 @@ fn in_math(expr: &ast::Expr) -> bool {
}
}
-fn complete_call(
- vm: &mut Vm,
- callee: &Func,
- args: Args,
- span: Span,
-) -> SourceResult<Value> {
- if vm.depth >= MAX_CALL_DEPTH {
- bail!(span, "maximum function call depth exceeded");
- }
-
- let point = || Tracepoint::Call(callee.name().map(Into::into));
- callee.call(vm, args).trace(vm.world, point, span)
-}
-
-impl Eval for ast::MethodCall {
- type Output = Value;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let span = self.span();
- let method = self.method();
-
- let result = if methods::is_mutating(&method) {
- let args = self.args().eval(vm)?;
- let value = self.target().access(vm)?;
-
- if let Value::Module(module) = &value {
- if let Value::Func(callee) =
- module.get(&method).cloned().at(method.span())?
- {
- return complete_call(vm, &callee, args, self.span());
- }
- }
-
- methods::call_mut(value, &method, args, span)
- } else {
- let value = self.target().eval(vm)?;
- let args = self.args().eval(vm)?;
-
- if let Value::Module(module) = &value {
- if let Value::Func(callee) = module.get(&method).at(method.span())? {
- return complete_call(vm, callee, args, self.span());
- }
- }
-
- methods::call(vm, value, &method, args, span)
- };
-
- let method = method.take();
- let point = || Tracepoint::Call(Some(method.clone()));
- result.trace(vm.world, point, span)
- }
-}
-
impl Eval for ast::Args {
type Output = Args;
@@ -1223,8 +1213,12 @@ impl Eval for ast::WhileLoop {
fn is_invariant(expr: &SyntaxNode) -> bool {
match expr.cast() {
Some(ast::Expr::Ident(_)) => false,
- Some(ast::Expr::MethodCall(call)) => {
- is_invariant(call.target().as_untyped())
+ Some(ast::Expr::MathIdent(_)) => false,
+ Some(ast::Expr::FieldAccess(access)) => {
+ is_invariant(access.target().as_untyped())
+ }
+ Some(ast::Expr::FuncCall(call)) => {
+ is_invariant(call.callee().as_untyped())
&& is_invariant(call.args().as_untyped())
}
_ => expr.children().all(is_invariant),
@@ -1434,7 +1428,7 @@ impl Access for ast::Expr {
Self::Ident(v) => v.access(vm),
Self::Parenthesized(v) => v.access(vm),
Self::FieldAccess(v) => v.access(vm),
- Self::MethodCall(v) => v.access(vm),
+ Self::FuncCall(v) => v.access(vm),
_ => {
let _ = self.eval(vm)?;
bail!(self.span(), "cannot mutate a temporary value");
@@ -1479,22 +1473,22 @@ impl ast::FieldAccess {
}
}
-impl Access for ast::MethodCall {
+impl Access for ast::FuncCall {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
- let span = self.span();
- let method = self.method().take();
- let world = vm.world();
-
- if !methods::is_accessor(&method) {
- let _ = self.eval(vm)?;
- bail!(span, "cannot mutate a temporary value");
+ if let ast::Expr::FieldAccess(access) = self.callee() {
+ let method = access.field().take();
+ if methods::is_accessor(&method) {
+ let span = self.span();
+ let world = vm.world();
+ let args = self.args().eval(vm)?;
+ let value = access.target().access(vm)?;
+ let result = methods::call_access(value, &method, args, span);
+ let point = || Tracepoint::Call(Some(method.clone()));
+ return result.trace(world, point, span);
+ }
}
- let args = self.args().eval(vm)?;
- let value = self.target().access(vm)?;
- let result = methods::call_access(value, &method, args, span);
-
- let point = || Tracepoint::Call(Some(method.clone()));
- result.trace(world, point, span)
+ let _ = self.eval(vm)?;
+ bail!(self.span(), "cannot mutate a temporary value");
}
}
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 5704f171..78d895ff 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -157,10 +157,8 @@ pub enum Expr {
Binary(Binary),
/// A field access: `properties.age`.
FieldAccess(FieldAccess),
- /// An invocation of a function: `f(x, y)`.
+ /// An invocation of a function or method: `f(x, y)`.
FuncCall(FuncCall),
- /// An invocation of a method: `array.push(v)`.
- MethodCall(MethodCall),
/// A closure: `(x, y) => z`.
Closure(Closure),
/// A let binding: `let x = 1`.
@@ -239,7 +237,6 @@ impl AstNode for Expr {
SyntaxKind::Binary => node.cast().map(Self::Binary),
SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess),
SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
- SyntaxKind::MethodCall => node.cast().map(Self::MethodCall),
SyntaxKind::Closure => node.cast().map(Self::Closure),
SyntaxKind::LetBinding => node.cast().map(Self::Let),
SyntaxKind::SetRule => node.cast().map(Self::Set),
@@ -299,7 +296,6 @@ impl AstNode for Expr {
Self::Binary(v) => v.as_untyped(),
Self::FieldAccess(v) => v.as_untyped(),
Self::FuncCall(v) => v.as_untyped(),
- Self::MethodCall(v) => v.as_untyped(),
Self::Closure(v) => v.as_untyped(),
Self::Let(v) => v.as_untyped(),
Self::Set(v) => v.as_untyped(),
@@ -335,7 +331,6 @@ impl Expr {
Self::Parenthesized(_) => true,
Self::FieldAccess(_) => true,
Self::FuncCall(_) => true,
- Self::MethodCall(_) => true,
Self::Let(_) => true,
Self::Set(_) => true,
Self::Show(_) => true,
@@ -1403,7 +1398,7 @@ impl FieldAccess {
}
node! {
- /// An invocation of a function: `f(x, y)`.
+ /// An invocation of a function or method: `f(x, y)`.
FuncCall
}
@@ -1420,28 +1415,6 @@ impl FuncCall {
}
node! {
- /// An invocation of a method: `array.push(v)`.
- MethodCall
-}
-
-impl MethodCall {
- /// The expression to call the method on.
- pub fn target(&self) -> Expr {
- self.0.cast_first_match().unwrap_or_default()
- }
-
- /// The name of the method.
- pub fn method(&self) -> Ident {
- self.0.cast_last_match().unwrap_or_default()
- }
-
- /// The arguments to the method.
- pub fn args(&self) -> Args {
- self.0.cast_last_match().unwrap_or_default()
- }
-}
-
-node! {
/// A function call's argument list: `(12pt, y)`.
Args
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index b2b65a62..cf973e6a 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -210,10 +210,8 @@ pub enum SyntaxKind {
Binary,
/// A field access: `properties.age`.
FieldAccess,
- /// An invocation of a function: `f(x, y)`.
+ /// An invocation of a function or method: `f(x, y)`.
FuncCall,
- /// An invocation of a method: `array.push(v)`.
- MethodCall,
/// A function call's argument list: `(12pt, y)`.
Args,
/// Spreaded arguments or an argument sink: `..x`.
@@ -416,7 +414,6 @@ impl SyntaxKind {
Self::Binary => "binary expression",
Self::FieldAccess => "field access",
Self::FuncCall => "function call",
- Self::MethodCall => "method call",
Self::Args => "call arguments",
Self::Spread => "spread",
Self::Closure => "closure",
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index ed000788..049275ed 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -681,11 +681,6 @@ impl<'a> LinkedNode<'a> {
self.parent.as_deref()
}
- /// Get the kind of this node's parent.
- pub fn parent_kind(&self) -> Option<SyntaxKind> {
- Some(self.parent()?.node.kind())
- }
-
/// Get the first previous non-trivia sibling node.
pub fn prev_sibling(&self) -> Option<Self> {
let parent = self.parent()?;
@@ -713,6 +708,21 @@ impl<'a> LinkedNode<'a> {
Some(next)
}
}
+
+ /// Get the kind of this node's parent.
+ pub fn parent_kind(&self) -> Option<SyntaxKind> {
+ Some(self.parent()?.node.kind())
+ }
+
+ /// Get the kind of this node's first previous non-trivia sibling.
+ pub fn prev_sibling_kind(&self) -> Option<SyntaxKind> {
+ Some(self.prev_sibling()?.node.kind())
+ }
+
+ /// Get the kind of this node's next non-trivia sibling.
+ pub fn next_sibling_kind(&self) -> Option<SyntaxKind> {
+ Some(self.next_sibling()?.node.kind())
+ }
}
/// Access to leafs.
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 1b5c10a3..602d9f2c 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -374,12 +374,12 @@ fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usiz
}
fn math_args(p: &mut Parser) {
- p.assert(SyntaxKind::Text);
-
let m = p.marker();
- let mut arg = p.marker();
+ p.convert(SyntaxKind::LeftParen);
+
let mut namable = true;
let mut named = None;
+ let mut arg = p.marker();
while !p.eof() && !p.at(SyntaxKind::Dollar) {
if namable
@@ -418,11 +418,14 @@ fn math_args(p: &mut Parser) {
maybe_wrap_in_math(p, arg, named);
}
- p.wrap(m, SyntaxKind::Args);
- if !p.eat_if(SyntaxKind::Text) {
+ if p.at(SyntaxKind::Text) && p.current_text() == ")" {
+ p.convert(SyntaxKind::RightParen);
+ } else {
p.expected("closing paren");
p.balanced = false;
}
+
+ p.wrap(m, SyntaxKind::Args);
}
fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) {
@@ -512,14 +515,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
if p.eat_if(SyntaxKind::Dot) {
p.expect(SyntaxKind::Ident);
- if p.directly_at(SyntaxKind::LeftParen)
- || p.directly_at(SyntaxKind::LeftBracket)
- {
- args(p);
- p.wrap(m, SyntaxKind::MethodCall);
- } else {
- p.wrap(m, SyntaxKind::FieldAccess)
- }
+ p.wrap(m, SyntaxKind::FieldAccess);
continue;
}
diff --git a/tests/ref/compiler/highlight.png b/tests/ref/compiler/highlight.png
new file mode 100644
index 00000000..317a2128
--- /dev/null
+++ b/tests/ref/compiler/highlight.png
Binary files differ
diff --git a/tests/ref/math/syntax.png b/tests/ref/math/syntax.png
index 0b738511..ce83f9f2 100644
--- a/tests/ref/math/syntax.png
+++ b/tests/ref/math/syntax.png
Binary files differ
diff --git a/tests/typ/compiler/highlight.typ b/tests/typ/compiler/highlight.typ
new file mode 100644
index 00000000..6c6ec802
--- /dev/null
+++ b/tests/typ/compiler/highlight.typ
@@ -0,0 +1,63 @@
+#set page(width: auto)
+
+```typ
+#set hello()
+#set hello()
+#set hello.world()
+#set hello.my.world()
+
+#show heading: func
+#show module.func: func
+#show module.func: it => {}
+#foo(ident: ident)
+
+#hello
+#hello()
+#hello.world
+#hello.world()
+#hello().world()
+#hello.my.world
+#hello.my.world()
+#hello.my().world
+#hello.my().world()
+
+$ hello $
+$ hello() $
+$ hello.world $
+$ hello.world() $
+$ hello().world() $
+$ hello.my.world $
+$ hello.my.world() $
+$ hello.my().world $
+$ hello.my().world() $
+
+$ emph(hello) $
+$ emph(hello()) $
+$ emph(hello.world) $
+$ emph(hello.world()) $
+$ emph(hello().world()) $
+$ emph(hello.my.world) $
+$ emph(hello.my.world()) $
+$ emph(hello.my().world) $
+$ emph(hello.my().world()) $
+
+$ #hello $
+$ #hello() $
+$ #hello.world $
+$ #hello.world() $
+$ #hello().world() $
+$ #hello.my.world $
+$ #hello.my.world() $
+$ #hello.my().world $
+$ #hello.my().world() $
+
+#{ hello }
+#{ hello() }
+#{ hello.world }
+#{ hello.world() }
+#{ hello().world() }
+#{ hello.my.world }
+#{ hello.my.world() }
+#{ hello.my().world }
+#{ hello.my().world() }
+```
diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ
index aa99bb1b..3f65a683 100644
--- a/tests/typ/math/matrix.typ
+++ b/tests/typ/math/matrix.typ
@@ -23,5 +23,5 @@ $ f(x, y) := cases(
#set math.vec(delim: "%")
---
-// Error: 9-12 missing argument: lower index
+// Error: 8-13 missing argument: lower index
$ binom(x^2) $