diff --git a/hclsyntax/expression.go b/hclsyntax/expression.go index e81553b3..c4a353c4 100644 --- a/hclsyntax/expression.go +++ b/hclsyntax/expression.go @@ -252,6 +252,10 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti } } + extraUnknown := &functionCallUnknown{ + name: e.Name, + } + // For historical reasons, we represent namespaced function names // as strings with :: separating the names. If this was an attempt // to call a namespaced function then we'll try to distinguish @@ -274,6 +278,9 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti } } + extraUnknown.name = name + extraUnknown.namespace = namespace + if len(avail) == 0 { // TODO: Maybe use nameSuggestion for the other available // namespaces? But that'd require us to go scan the function @@ -291,6 +298,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti Context: e.Range().Ptr(), Expression: e, EvalContext: ctx, + Extra: extraUnknown, }, } } else { @@ -308,6 +316,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti Context: e.Range().Ptr(), Expression: e, EvalContext: ctx, + Extra: extraUnknown, }, } } @@ -331,6 +340,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti Context: e.Range().Ptr(), Expression: e, EvalContext: ctx, + Extra: extraUnknown, }, } } @@ -678,6 +688,27 @@ func (e *functionCallDiagExtra) FunctionCallError() error { return e.functionCallError } +// FunctionCallUnknownDiagExtra is an interface implemented by a value in the Extra +// field of some diagnostics to indicate when the error was caused by a call to +// an unknown function. +type FunctionCallUnknownDiagExtra interface { + CalledFunctionName() string + CalledFunctionNamespace() string +} + +type functionCallUnknown struct { + name string + namespace string +} + +func (e *functionCallUnknown) CalledFunctionName() string { + return e.name +} + +func (e *functionCallUnknown) CalledFunctionNamespace() string { + return e.namespace +} + type ConditionalExpr struct { Condition Expression TrueResult Expression diff --git a/hclsyntax/expression_typeparams_test.go b/hclsyntax/expression_typeparams_test.go index 4ee9a77e..c7fdbd22 100644 --- a/hclsyntax/expression_typeparams_test.go +++ b/hclsyntax/expression_typeparams_test.go @@ -24,6 +24,71 @@ func TestExpressionDiagnosticExtra(t *testing.T) { ctx *hcl.EvalContext assert func(t *testing.T, diags hcl.Diagnostics) }{ + // Errors for unknown function calls + { + "boop()", + &hcl.EvalContext{ + Functions: map[string]function.Function{ + "zap": function.New(&function.Spec{ + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.DynamicVal, fmt.Errorf("the expected error") + }, + }), + }, + }, + func(t *testing.T, diags hcl.Diagnostics) { + t.Helper() + for _, diag := range diags { + extra, ok := hcl.DiagnosticExtra[FunctionCallUnknownDiagExtra](diag) + if !ok { + continue + } + + if got, want := extra.CalledFunctionName(), "boop"; got != want { + t.Errorf("wrong called function name %q; want %q", got, want) + } + ns := extra.CalledFunctionNamespace() + if ns != "" { + t.Fatal("expected no namespace, got", ns) + } + return + } + t.Fatalf("None of the returned diagnostics implement FunctionCallUnknownDiagExtra\n%s", diags.Error()) + }, + }, + { + "ns::source::boop()", + &hcl.EvalContext{ + Functions: map[string]function.Function{ + "zap": function.New(&function.Spec{ + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + return cty.DynamicVal, fmt.Errorf("the expected error") + }, + }), + }, + }, + func(t *testing.T, diags hcl.Diagnostics) { + t.Helper() + for _, diag := range diags { + extra, ok := hcl.DiagnosticExtra[FunctionCallUnknownDiagExtra](diag) + if !ok { + continue + } + + if got, want := extra.CalledFunctionName(), "boop"; got != want { + t.Errorf("wrong called function name %q; want %q", got, want) + } + ns := extra.CalledFunctionNamespace() + if ns != "ns::source::" { + t.Fatal("expected namespace ns::source::, got", ns) + } + return + } + t.Fatalf("None of the returned diagnostics implement FunctionCallUnknownDiagExtra\n%s", diags.Error()) + }, + }, // Error messages describing inconsistent result types for conditional expressions. { "boop()",