func detectTypes()

in warn/types.go [58:175]


func detectTypes(f *build.File) map[build.Expr]Type {
	variables := make(map[int]Type)
	result := make(map[build.Expr]Type)

	var walk func(e *build.Expr, env *bzlenv.Environment)
	walk = func(e *build.Expr, env *bzlenv.Environment) {
		// Postorder: determining types of subnodes may help with this node's type
		walkOnce(*e, env, walk)

		nodeType := Unknown
		defer func() {
			if nodeType != Unknown {
				result[*e] = nodeType
			}
		}()

		switch node := (*e).(type) {
		case *build.StringExpr:
			nodeType = String
		case *build.DictExpr:
			nodeType = Dict
		case *build.ListExpr:
			nodeType = List
		case *build.LiteralExpr:
			nodeType = Int
		case *build.Comprehension:
			if node.Curly {
				nodeType = Dict
			} else {
				nodeType = List
			}
		case *build.CallExpr:
			if ident, ok := (node.X).(*build.Ident); ok {
				switch ident.Name {
				case "depset":
					nodeType = Depset
				case "dict":
					nodeType = Dict
				case "list":
					nodeType = List
				}
			} else if dot, ok := (node.X).(*build.DotExpr); ok {
				if result[dot.X] == CtxActions && dot.Name == "args" {
					nodeType = CtxActionsArgs
				}
			}
		case *build.ParenExpr:
			nodeType = result[node.X]
		case *build.Ident:
			switch node.Name {
			case "True", "False":
				nodeType = Bool
				return
			case "None":
				nodeType = None
				return
			case "ctx":
				binding := env.Get(node.Name)
				if binding != nil && binding.Kind == bzlenv.Parameter {
					nodeType = Ctx
					return
				}
			}
			binding := env.Get(node.Name)
			if binding != nil {
				if t, ok := variables[binding.ID]; ok {
					nodeType = t
				}
			}
		case *build.DotExpr:
			if result[node.X] == Ctx && node.Name == "actions" {
				nodeType = CtxActions
			}
		case *build.BinaryExpr:
			switch node.Op {
			case ">", ">=", "<", "<=", "==", "!=", "in", "not in":
				// Boolean
				nodeType = Bool

			case "+", "-", "*", "/", "//", "%", "|":
				// We assume these operators can only applied to expressions of the same type and
				// preserve the type
				if t, ok := result[node.X]; ok {
					nodeType = t
				} else if t, ok := result[node.Y]; ok {
					if node.Op != "%" || t == String {
						// The percent operator is special because it can be applied to to arguments of
						// different types (`"%s\n" % foo`), and we can't assume that the expression has
						// type X if the right-hand side has the type X.
						nodeType = t
					}
				}
			}
		case *build.AssignExpr:
			t, ok := result[node.RHS]
			if !ok {
				return
			}
			if node.Op == "%=" && t != String {
				// If the right hand side is not a string, the left hand side can still be a string
				return
			}
			ident, ok := (node.LHS).(*build.Ident)
			if !ok {
				return
			}
			binding := env.Get(ident.Name)
			if binding == nil {
				return
			}
			variables[binding.ID] = t
		}
	}
	var expr build.Expr = f
	walk(&expr, bzlenv.NewEnvironment())

	return result
}