Repository: ysugimoto/falco
Pull request #107 (2022-09-28 02:18:52)

Add more implicit conversion fuzy types
According to fastly https://developer.fastly.com/reference/vcl/types/ all primitive types can be casted to string.

> These types all have implicit conversions to strings,
such that their values may be used in contexts where a STRING value is necessary.

We don't allow that in Falco so changed it here to match what Fastly accepts.

Signed-off-by: Sotiris Nanopoulos <sotiris.nanopoulos@reddit.com>

davinci26: That is a different thing after all so it didnt work

Patch:
diff --git a/__generator__/builtin.yml b/__generator__/builtin.yml
index 8bae684..6ef0eb2 100644
--- a/__generator__/builtin.yml
+++ b/__generator__/builtin.yml
@@ -651,8 +651,8 @@ std.collect:
   reference: "https://developer.fastly.com/reference/vcl/functions/miscellaneous/std-collect/"
   on: [RECV, HASH, HIT, MISS, PASS, FETCH, ERROR, DELIVER, LOG]
   arguments:
-    - [STRING]
-    - [STRING, STRING]
+    - [ID]
+    - [ID, STRING]
 
 std.count:
   reference: "https://developer.fastly.com/reference/vcl/functions/miscellaneous/std-count/"
diff --git a/context/builtin.go b/context/builtin.go
index c95f48a..0d33407 100644
--- a/context/builtin.go
+++ b/context/builtin.go
@@ -1380,8 +1380,8 @@ func builtinFunctions() Functions {
 					Items: map[string]*FunctionSpec{},
 					Value: &BuiltinFunction{
 						Arguments: [][]types.Type{
-							[]types.Type{types.StringType},
-							[]types.Type{types.StringType, types.StringType},
+							[]types.Type{types.IDType},
+							[]types.Type{types.IDType, types.StringType},
 						},
 						Scopes:    RECV | HASH | HIT | MISS | PASS | FETCH | ERROR | DELIVER | LOG,
 						Reference: "https://developer.fastly.com/reference/vcl/functions/miscellaneous/std-collect/",
diff --git a/linter/function.go b/linter/function.go
index dcd3149..b4b596e 100644
--- a/linter/function.go
+++ b/linter/function.go
@@ -16,6 +16,14 @@ type functionMeta struct {
 	meta      *ast.Meta
 }
 
+var implicitCoersionTable = map[types.Type][]types.Type{
+	types.TimeType:   {types.StringType},
+	types.RTimeType:  {types.TimeType, types.StringType},
+	types.IPType:     {types.StringType},
+	types.IDType:     {types.StringType},
+	types.StringType: {types.StringType, types.ReqBackendType, types.IntegerType, types.FloatType, types.BoolType, types.IDType, types.RTimeType},
+}
+
 func (l *Linter) lintFunctionArguments(fn *context.BuiltinFunction, calledFn functionMeta, ctx *context.Context) types.Type {
 	// lint empty arguments
 	if len(fn.Arguments) == 0 {
@@ -77,42 +85,13 @@ func (l *Linter) lintFunctionArguments(fn *context.BuiltinFunction, calledFn fun
 	for i, v := range argTypes {
 		arg := l.lint(calledFn.arguments[i], ctx)
 
-		switch v {
-		case types.TimeType:
-			// fuzzy type check: some builtin function expects TIME type,
-			// then actual argument type could be STRING because VCL TIME type could be parsed from STRING.
-			if !expectType(arg, types.TimeType, types.StringType) {
-				l.Error(FunctionArgumentTypeMismatch(
-					calledFn.meta, calledFn.name, i+1, v, arg,
-				).Match(FUNCTION_ARGUMENT_TYPE).Ref(fn.Reference))
-			}
-			continue
-		case types.RTimeType:
-			// fuzzy type check: some builtin function expects RTIME type,
-			// then actual argument type could be STRING because VCL TIME type could be parsed from STRING.
-			if !expectType(arg, types.RTimeType, types.TimeType, types.StringType) {
-				l.Error(FunctionArgumentTypeMismatch(
-					calledFn.meta, calledFn.name, i+1, v, arg,
-				).Match(FUNCTION_ARGUMENT_TYPE).Ref(fn.Reference))
-			}
-			continue
-		case types.IPType:
-			// fuzzy type check: some builtin function expects IP type,
-			// then actual argument type could be STRING because VCL TIME type could be parsed from STRING.
-			if !expectType(arg, types.IPType, types.StringType) {
-				l.Error(FunctionArgumentTypeMismatch(
-					calledFn.meta, calledFn.name, i+1, v, arg,
-				).Match(FUNCTION_ARGUMENT_TYPE).Ref(fn.Reference))
-			}
-		case types.StringType:
-			// fuzzy type check: some builtin function expects STRING type,
-			// then actual argument type could be REQBACKEND because VCL STRING type can be cast from REQBACKEND.
-			if !expectType(arg, types.StringType, types.ReqBackendType) {
+		if t, ok := implicitCoersionTable[v]; ok {
+			if !expectType(arg, append(t, v)...) {
 				l.Error(FunctionArgumentTypeMismatch(
 					calledFn.meta, calledFn.name, i+1, v, arg,
 				).Match(FUNCTION_ARGUMENT_TYPE).Ref(fn.Reference))
 			}
-		default:
+		} else {
 			// Otherwise, strict type check
 			if v != arg {
 				l.Error(FunctionArgumentTypeMismatch(


Test patch:
diff --git a/linter/linter_test.go b/linter/linter_test.go
index cab2d7d..01aa887 100644
--- a/linter/linter_test.go
+++ b/linter/linter_test.go
@@ -1391,14 +1391,22 @@ sub foo {
 	})
 
 	t.Run("fuzzy type check for STRING type argument", func(t *testing.T) {
-		input := `
-sub foo {
-	declare local var.S STRING;
 
-	set var.S = substr(req.backend, 1);
-}
-`
-		assertNoError(t, input)
+		tests := []string{
+			"req.backend",
+			"fastly_info.is_h2",
+			"client.socket.ploss",
+		}
+		for _, c := range tests {
+			input := fmt.Sprintf(`
+			sub foo {
+				declare local var.S STRING;
+				set var.S = substr(%s, 1);
+			}
+			`, c)
+			assertNoError(t, input)
+		}
+
 	})
 }