...

Source file src/github.com/bazelbuild/buildtools/warn/types.go

Documentation: github.com/bazelbuild/buildtools/warn

     1  /*
     2  Copyright 2020 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package warn
    18  
    19  import (
    20  	"regexp"
    21  
    22  	"github.com/bazelbuild/buildtools/build"
    23  	"github.com/bazelbuild/buildtools/bzlenv"
    24  )
    25  
    26  // Type describes an expression type in Starlark.
    27  type Type int
    28  
    29  // List of known types
    30  const (
    31  	Unknown Type = iota
    32  	Bool
    33  	Ctx
    34  	CtxActions
    35  	CtxActionsArgs
    36  	Depset
    37  	Dict
    38  	Int
    39  	None
    40  	String
    41  	List
    42  	Float
    43  )
    44  
    45  func (t Type) String() string {
    46  	return [...]string{
    47  		"unknown",
    48  		"bool",
    49  		"ctx",
    50  		"ctx.actions",
    51  		"ctx.actions.args",
    52  		"depset",
    53  		"dict",
    54  		"int",
    55  		"none",
    56  		"string",
    57  		"list",
    58  		"float",
    59  	}[t]
    60  }
    61  
    62  var intRegexp = regexp.MustCompile(`^([0-9]+|0[Xx][0-9A-Fa-f]+|0[Oo][0-7]+)$`)
    63  
    64  // DetectTypes tries to infer the type of expressions in the current file, using basic heuristics.
    65  //
    66  // Warning: the types inferred by the function might change in the future, as we update the
    67  // heuristics.
    68  func DetectTypes(f *build.File) map[build.Expr]Type {
    69  	variables := make(map[int]Type)
    70  	result := make(map[build.Expr]Type)
    71  
    72  	var walk func(e *build.Expr, env *bzlenv.Environment)
    73  	walk = func(e *build.Expr, env *bzlenv.Environment) {
    74  		// Postorder: determining types of subnodes may help with this node's type
    75  		walkOnce(*e, env, walk)
    76  
    77  		nodeType := Unknown
    78  		defer func() {
    79  			if nodeType != Unknown {
    80  				result[*e] = nodeType
    81  			}
    82  		}()
    83  
    84  		switch node := (*e).(type) {
    85  		case *build.StringExpr:
    86  			nodeType = String
    87  		case *build.DictExpr:
    88  			nodeType = Dict
    89  		case *build.ListExpr:
    90  			nodeType = List
    91  		case *build.LiteralExpr:
    92  			if intRegexp.MatchString(node.Token) {
    93  				nodeType = Int
    94  			} else {
    95  				nodeType = Float
    96  			}
    97  		case *build.Comprehension:
    98  			if node.Curly {
    99  				nodeType = Dict
   100  			} else {
   101  				nodeType = List
   102  			}
   103  		case *build.CallExpr:
   104  			if ident, ok := (node.X).(*build.Ident); ok {
   105  				switch ident.Name {
   106  				case "bool":
   107  					nodeType = Bool
   108  				case "int":
   109  					nodeType = Int
   110  				case "float":
   111  					nodeType = Float
   112  				case "str":
   113  					nodeType = String
   114  				case "depset":
   115  					nodeType = Depset
   116  				case "dict":
   117  					nodeType = Dict
   118  				case "list":
   119  					nodeType = List
   120  				}
   121  			} else if dot, ok := (node.X).(*build.DotExpr); ok {
   122  				if result[dot.X] == CtxActions && dot.Name == "args" {
   123  					nodeType = CtxActionsArgs
   124  				}
   125  			}
   126  		case *build.ParenExpr:
   127  			nodeType = result[node.X]
   128  		case *build.Ident:
   129  			switch node.Name {
   130  			case "True", "False":
   131  				nodeType = Bool
   132  				return
   133  			case "None":
   134  				nodeType = None
   135  				return
   136  			case "ctx":
   137  				binding := env.Get(node.Name)
   138  				if binding != nil && binding.Kind == bzlenv.Parameter {
   139  					nodeType = Ctx
   140  					return
   141  				}
   142  			}
   143  			binding := env.Get(node.Name)
   144  			if binding != nil {
   145  				if t, ok := variables[binding.ID]; ok {
   146  					nodeType = t
   147  				}
   148  			}
   149  		case *build.DotExpr:
   150  			if result[node.X] == Ctx && node.Name == "actions" {
   151  				nodeType = CtxActions
   152  			}
   153  		case *build.BinaryExpr:
   154  			switch node.Op {
   155  			case ">", ">=", "<", "<=", "==", "!=", "in", "not in":
   156  				// Boolean
   157  				nodeType = Bool
   158  
   159  			case "+", "-", "*", "/", "//", "%", "|":
   160  				// We assume these operators can only applied to expressions of the same type and
   161  				// preserve the type
   162  				if t, ok := result[node.X]; ok {
   163  					nodeType = t
   164  				} else if t, ok := result[node.Y]; ok {
   165  					if node.Op != "%" || t == String {
   166  						// The percent operator is special because it can be applied to to arguments of
   167  						// different types (`"%s\n" % foo`), and we can't assume that the expression has
   168  						// type X if the right-hand side has the type X.
   169  						nodeType = t
   170  					}
   171  				}
   172  			}
   173  		case *build.AssignExpr:
   174  			t, ok := result[node.RHS]
   175  			if !ok {
   176  				return
   177  			}
   178  			if node.Op == "%=" && t != String {
   179  				// If the right hand side is not a string, the left hand side can still be a string
   180  				return
   181  			}
   182  			ident, ok := (node.LHS).(*build.Ident)
   183  			if !ok {
   184  				return
   185  			}
   186  			binding := env.Get(ident.Name)
   187  			if binding == nil {
   188  				return
   189  			}
   190  			variables[binding.ID] = t
   191  		}
   192  	}
   193  	var expr build.Expr = f
   194  	walk(&expr, bzlenv.NewEnvironment())
   195  
   196  	return result
   197  }
   198  
   199  // walkOnce is a wrapper for bzlint.WalkOnceWithEnvironment which skips the left hand side for
   200  // named parameters of functions. E.g. for `foo(x, y = z)` it visits `foo`, `x`, and `z`.
   201  // In the following example `x` in the last line shouldn't be recognised as int, but 'y' should:
   202  //
   203  //	x = 3
   204  //	y = 5
   205  //	foo(x = y)
   206  func walkOnce(node build.Expr, env *bzlenv.Environment, fct func(e *build.Expr, env *bzlenv.Environment)) {
   207  	switch expr := node.(type) {
   208  	case *build.CallExpr:
   209  		fct(&expr.X, env)
   210  		for _, param := range expr.List {
   211  			if as, ok := param.(*build.AssignExpr); ok {
   212  				fct(&as.RHS, env)
   213  			} else {
   214  				fct(&param, env)
   215  			}
   216  		}
   217  	default:
   218  		bzlenv.WalkOnceWithEnvironment(expr, env, fct)
   219  	}
   220  }
   221  

View as plain text