Repository: typst/typst
File: crates/typst/src/eval/call.rs (Rust)

Code before:
                let point = || Tracepoint::Call(Some(method.get().clone()));
                return Ok(MethodCall::Special(value.trace(vm.world(), point, span)?));
            }
            target => (target.clone(), args),
        }
    } else {
        let target = target_expr.eval(vm)?;
        let args = args.eval(vm)?.spanned(span);
        (target, args)
    };

    if let Value::Plugin(plugin) = &target {
        // Call plugin methods by converting args to bytes.
        let bytes = args.all::<Bytes>()?;
        args.finish()?;
        let value = plugin.call(&method, bytes).at(span)?.into_value();
        Ok(MethodCall::Special(value))
    } else if let Some(callee) = target.ty().scope().get(&method) {
        args.insert(0, target_expr.span(), target);
        Ok(MethodCall::Normal(callee.clone(), args))
    } else if matches!(
        target,
        Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
    ) {
        // Certain value types may have their own ways to access method fields.
        // e.g. `$arrow.r(v)$`, `func.with(fill: red)`
        let value = target.field(&method).at(method.span())?;
        Ok(MethodCall::Normal(value, args))
    } else {
        // Otherwise we cannot find any methods to call for this type.
        bail!(missing_method_error(target, method))
    }
}

/// Produce an error that the target's type doesn't have this method.
fn missing_method_error(target: Value, method: Ident) -> SourceDiagnostic {
    let mut error = error!(
        method.span(),
        "type {} has no method `{}`",
        target.ty(),
        method.as_str(),
    );

    match target {
        Value::Dict(ref dict) if matches!(dict.get(&method), Ok(Value::Func(_))) => {
            error.hint(eco_format!(
                "to call the function stored in the dictionary, surround \
                    the field access with parentheses, e.g. `(dict.{})(..)`",
                method.as_str(),
            ));
        }

Review comment (bug):
`func.with` is a wrong example here, I think. This branch covers things like `table.cell[..]`. `func.with` is handled by `target.ty().scope().get(&method)`.

Code after:
                let value = call_method_mut(target, &field, args, span);
                let point = || Tracepoint::Call(Some(field.get().clone()));
                return Ok(FieldCall::Resolved(value.trace(vm.world(), point, span)?));
            }
            target => (target.clone(), args),
        }
    } else {
        let target = target_expr.eval(vm)?;
        let args = args.eval(vm)?.spanned(span);
        (target, args)
    };

    if let Value::Plugin(plugin) = &target {
        // Call plugins by converting args to bytes.
        let bytes = args.all::<Bytes>()?;
        args.finish()?;
        let value = plugin.call(&field, bytes).at(span)?.into_value();
        Ok(FieldCall::Resolved(value))
    } else if let Some(callee) = target.ty().scope().get(&field) {
        args.insert(0, target_expr.span(), target);
        Ok(FieldCall::Normal(callee.clone(), args))
    } else if matches!(
        target,
        Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
    ) {
        // Certain value types may have their own ways to access method fields.
        // e.g. `$arrow.r(v)$`, `table.cell[..]`
        let value = target.field(&field).at(field.span())?;
        Ok(FieldCall::Normal(value, args))
    } else {
        // Otherwise we cannot call this field.
        bail!(missing_field_call_error(target, field))
    }
}

/// Produce an error when we cannot call the field.
fn missing_field_call_error(target: Value, field: Ident) -> SourceDiagnostic {
    let mut error =
        error!(field.span(), "type {} has no method `{}`", target.ty(), field.as_str());

    match target {
        Value::Dict(ref dict) if matches!(dict.get(&field), Ok(Value::Func(_))) => {
            error.hint(eco_format!(
                "to call the function stored in the dictionary, surround \
                the field access with parentheses, e.g. `(dict.{})(..)`",
                field.as_str(),
            ));
        }
        _ if target.field(&field).is_ok() => {
            error.hint(eco_format!(
                "did you mean to access the field `{}`?",