From 17143d150dd4ea0e46775468382683eb0a884450 Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 19 Mar 2024 17:04:23 -0700 Subject: [PATCH] Add strictly typed variables toolchain rules. BEGIN_PUBLIC Add strictly typed variables to toolchain rules. This should allow us to implement a proper replacement for flag_group END_PUBLIC PiperOrigin-RevId: 617338607 Change-Id: I7f3058578cb5eb17ecc1aa38d2e1459e0742aee9 --- cc/toolchains/cc_toolchain_info.bzl | 17 + cc/toolchains/impl/variables.bzl | 171 +++++++ cc/toolchains/variables/BUILD | 481 ++++++++++++++++++ tests/rule_based_toolchain/variables/BUILD | 52 ++ .../variables/variables_test.bzl | 128 +++++ 5 files changed, 849 insertions(+) create mode 100644 cc/toolchains/impl/variables.bzl create mode 100644 cc/toolchains/variables/BUILD create mode 100644 tests/rule_based_toolchain/variables/BUILD create mode 100644 tests/rule_based_toolchain/variables/variables_test.bzl diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl index 8c0dcd5..42227d4 100644 --- a/cc/toolchains/cc_toolchain_info.bzl +++ b/cc/toolchains/cc_toolchain_info.bzl @@ -45,6 +45,23 @@ ActionTypeSetInfo = provider( }, ) +VariableInfo = provider( + """A variable defined by the toolchain""", + # @unsorted-dict-items + fields = { + "name": "(str) The variable name", + "actions": "(Optional[depset[ActionTypeInfo]]) The actions this variable is available for", + "type": "A type constructed using variables.types.*", + }, +) + +BuiltinVariablesInfo = provider( + doc = "The builtin variables", + fields = { + "variables": "(dict[str, struct(type=type, actions=Optional[depset[ActionTypeInfo]]) A mapping from variable name to variable metadata.", + }, +) + NestedArgsInfo = provider( doc = "A provider representation of Args.add/add_all/add_joined parameters", # @unsorted-dict-items diff --git a/cc/toolchains/impl/variables.bzl b/cc/toolchains/impl/variables.bzl new file mode 100644 index 0000000..6577b34 --- /dev/null +++ b/cc/toolchains/impl/variables.bzl @@ -0,0 +1,171 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Rules for accessing cc build variables in bazel toolchains safely.""" + +load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeSetInfo", "BuiltinVariablesInfo", "VariableInfo") +load(":collect.bzl", "collect_action_types", "collect_provider") + +visibility([ + "//cc/toolchains/variables", + "//tests/rule_based_toolchain/...", +]) + +types = struct( + unknown = dict(name = "unknown", repr = "unknown"), + void = dict(name = "void", repr = "void"), + string = dict(name = "string", repr = "string"), + bool = dict(name = "bool", repr = "bool"), + # File and directory are basically the same thing as string for now. + file = dict(name = "file", repr = "File"), + directory = dict(name = "directory", repr = "directory"), + option = lambda element: dict( + name = "option", + elements = element, + repr = "Option[%s]" % element["repr"], + ), + list = lambda elements: dict( + name = "list", + elements = elements, + repr = "List[%s]" % elements["repr"], + ), + struct = lambda **kv: dict( + name = "struct", + kv = kv, + repr = "struct(%s)" % ", ".join([ + "{k}={v}".format(k = k, v = v["repr"]) + for k, v in sorted(kv.items()) + ]), + ), +) + +def _cc_variable_impl(ctx): + return [VariableInfo( + name = ctx.label.name, + type = json.decode(ctx.attr.type), + actions = collect_action_types(ctx.attr.actions) if ctx.attr.actions else None, + )] + +_cc_variable = rule( + implementation = _cc_variable_impl, + attrs = { + "actions": attr.label_list(providers = [ActionTypeSetInfo]), + "type": attr.string(mandatory = True), + }, + provides = [VariableInfo], +) + +def cc_variable(name, type, **kwargs): + """Defines a variable for both the specified variable, and all nested ones. + + Eg. cc_variable( + name = "foo", + type = types.list(types.struct(bar = types.string)) + ) + + would define two targets, ":foo" and ":foo.bar" + + Args: + name: (str) The name of the outer variable, and the rule. + type: The type of the variable, constructed using types above. + **kwargs: kwargs to pass to _cc_variable. + """ + _cc_variable(name = name, type = json.encode(type), **kwargs) + +def _cc_builtin_variables_impl(ctx): + return [BuiltinVariablesInfo(variables = { + variable.name: struct( + actions = variable.actions, + type = variable.type, + ) + for variable in collect_provider(ctx.attr.srcs, VariableInfo) + })] + +cc_builtin_variables = rule( + implementation = _cc_builtin_variables_impl, + attrs = { + "srcs": attr.label_list(providers = [VariableInfo]), + }, +) + +def get_type(*, name, variables, overrides, actions, args_label, nested_label, fail): + """Gets the type of a variable. + + Args: + name: (str) The variable to look up. + variables: (dict[str, VariableInfo]) Mapping from variable name to + metadata. Top-level variables only + overrides: (dict[str, type]) Mapping from variable names to type. + Can be used for nested variables. + actions: (depset[ActionTypeInfo]) The set of actions for which the + variable is requested. + args_label: (Label) The label for the args that included the rule that + references this variable. Only used for error messages. + nested_label: (Label) The label for the rule that references this + variable. Only used for error messages. + fail: A function to be called upon failure. Use for testing only. + Returns: + The type of the variable "name". + """ + outer = name.split(".")[0] + if outer not in variables: + # With a fail function, we actually need to return since the fail + # function doesn't propagate. + fail("The variable %s does not exist. Did you mean one of the following?\n%s" % (outer, "\n".join(sorted(variables)))) + + # buildifier: disable=unreachable + return types.void + + if variables[outer].actions != None: + valid_actions = variables[outer].actions.to_list() + for action in actions: + if action not in valid_actions: + fail("The variable {var} is inaccessible from the action {action}. This is required because it is referenced in {nested_label}, which is included by {args_label}, which references that action".format( + var = outer, + nested_label = nested_label, + args_label = args_label, + action = action.label, + )) + + # buildifier: disable=unreachable + return types.void + + type = overrides.get(outer, variables[outer].type) + + parent = outer + for part in name.split(".")[1:]: + full = parent + "." + part + + if type["name"] != "struct": + extra_error = "" + if type["name"] == "list" and type["elements"]["name"] == "struct": + extra_error = " Maybe you meant to use iterate_over." + + fail("Attempted to access %r, but %r was not a struct - it had type %s.%s" % (full, parent, type["repr"], extra_error)) + + # buildifier: disable=unreachable + return types.void + + if part not in type["kv"] and full not in overrides: + attrs = [] + for attr, value in sorted(type["kv"].items()): + attrs.append("%s: %s" % (attr, value["repr"])) + fail("Unable to find %r in %r, which had the following attributes:\n%s" % (part, parent, "\n".join(attrs))) + + # buildifier: disable=unreachable + return types.void + + type = overrides.get(full, type["kv"][part]) + parent = full + + return type diff --git a/cc/toolchains/variables/BUILD b/cc/toolchains/variables/BUILD new file mode 100644 index 0000000..d93083d --- /dev/null +++ b/cc/toolchains/variables/BUILD @@ -0,0 +1,481 @@ +load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types") + +package(default_visibility = ["//visibility:public"]) + +cc_variable( + name = "cs_fdo_instrument_path", + actions = [ + "//cc/toolchains/actions:all_cc_link_actions", + "//cc/toolchains/actions:all_compile_actions", + ], + type = types.directory, +) + +cc_variable( + name = "def_file_path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "dependency_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "dependent_module_map_files", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.list(types.file)), +) + +cc_variable( + name = "external_include_paths", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.list(types.directory)), +) + +cc_variable( + name = "fdo_instrument_path", + actions = [ + "//cc/toolchains/actions:all_cc_link_actions", + "//cc/toolchains/actions:all_compile_actions", + ], + type = types.directory, +) + +cc_variable( + name = "fdo_prefetch_hints_path", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "fdo_profile_path", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "force_pic", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # Provided when --force-pic is passed + type = types.option(types.void), +) + +cc_variable( + name = "framework_include_paths", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "gcov_gcno_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "generate_interface_library", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # "yes" or "no" + type = types.option(types.string), +) + +cc_variable( + name = "include", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.file), +) + +cc_variable( + name = "include_paths", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "includes", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.list(types.file)), +) + +cc_variable( + name = "input_file", + actions = ["//cc/toolchains/actions:strip"], + type = types.file, +) + +cc_variable( + name = "interface_library_builder_path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # Should be a file, but contains the string "ignored" when there's no value. + type = types.option(types.string), +) + +cc_variable( + name = "interface_library_input_path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # Should be a file, but contains the string "ignored" when there's no value. + type = types.option(types.string), +) + +cc_variable( + name = "interface_library_output_path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # Should be a file, but contains the string "ignored" when there's no value. + type = types.option(types.string), +) + +cc_variable( + name = "is_cc_test", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.bool), +) + +cc_variable( + name = "is_using_fission", + actions = [ + "//cc/toolchains/actions:all_cc_link_actions", + "//cc/toolchains/actions:all_compile_actions", + ], + type = types.option(types.void), +) + +cc_variable( + name = "legacy_compile_flags", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.string), +) + +cc_variable( + name = "legacy_link_flags", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.list(types.string), +) + +cc_variable( + name = "libraries_to_link", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.list(types.struct( + shared_libraries = types.list(types.struct( + name = types.string, + is_whole_archive = types.bool, + object_files = types.list(types.file), + path = types.file, + type = types.string, + )), + ))), +) + +cc_variable( + name = "libraries_to_link.shared_libraries", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + # See :libraries_to_link. + type = types.unknown, +) + +cc_variable( + name = "libraries_to_link.shared_libraries.is_whole_archive", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.string, +) + +cc_variable( + name = "libraries_to_link.shared_libraries.name", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.string, +) + +cc_variable( + name = "libraries_to_link.shared_libraries.object_files", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.list(types.file), +) + +cc_variable( + name = "libraries_to_link.shared_libraries.path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.file, +) + +cc_variable( + name = "libraries_to_link.shared_libraries.type", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.string, +) + +cc_variable( + name = "library_search_directories", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "linker_param_file", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.file, +) + +cc_variable( + name = "linkstamp_paths", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "lto_indexing_bitcode_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "module_files", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.list(types.file)), +) + +cc_variable( + name = "module_map_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "module_name", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "output_assembly_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "output_execpath", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.directory), +) + +cc_variable( + name = "output_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "output_preprocess_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "per_object_debug_info_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "pic", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.void), +) + +cc_variable( + name = "preprocessor_defines", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.string), +) + +cc_variable( + name = "propellor_optimize_ld_path", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "quote_include_paths", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "runtime_library_search_directories", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.list(types.directory)), +) + +cc_variable( + name = "runtime_solib_name", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "serialized_diagnostics_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "source_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.file, +) + +cc_variable( + name = "strip_debug_symbols", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.void), +) + +cc_variable( + name = "stripopts", + actions = ["//cc/toolchains/actions:strip"], + type = types.list(types.string), +) + +cc_variable( + name = "sysroot", + type = types.directory, +) + +cc_variable( + name = "system_include_paths", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.directory), +) + +cc_variable( + name = "thinlto_index", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "thinlto_indexing_param_file", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "thinlto_input_bitcode_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "thinlto_merged_object_file", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "thinlto_object_suffix_replace", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "thinlto_output_object_file", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "thinlto_param_file", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.file), +) + +cc_variable( + name = "thinlto_prefix_replace", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.option(types.string), +) + +cc_variable( + name = "unfiltered_compile_flags", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.string), +) + +cc_variable( + name = "user_compile_flags", + actions = ["//cc/toolchains/actions:all_compile_actions"], + type = types.list(types.string), +) + +cc_variable( + name = "user_link_flags", + actions = ["//cc/toolchains/actions:all_cc_link_actions"], + type = types.list(types.string), +) + +cc_builtin_variables( + name = "variables", + srcs = [ + ":cs_fdo_instrument_path", + ":def_file_path", + ":dependency_file", + ":dependent_module_map_files", + ":external_include_paths", + ":fdo_instrument_path", + ":fdo_prefetch_hints_path", + ":fdo_profile_path", + ":force_pic", + ":framework_include_paths", + ":gcov_gcno_file", + ":generate_interface_library", + ":include", + ":include_paths", + ":includes", + ":input_file", + ":interface_library_builder_path", + ":interface_library_input_path", + ":interface_library_output_path", + ":is_cc_test", + ":is_using_fission", + ":legacy_compile_flags", + ":legacy_link_flags", + ":libraries_to_link", + ":library_search_directories", + ":linker_param_file", + ":linkstamp_paths", + ":lto_indexing_bitcode_file", + ":module_files", + ":module_map_file", + ":module_name", + ":output_assembly_file", + ":output_execpath", + ":output_file", + ":output_preprocess_file", + ":per_object_debug_info_file", + ":pic", + ":preprocessor_defines", + ":propellor_optimize_ld_path", + ":quote_include_paths", + ":runtime_library_search_directories", + ":runtime_solib_name", + ":serialized_diagnostics_file", + ":source_file", + ":strip_debug_symbols", + ":stripopts", + ":sysroot", + ":system_include_paths", + ":thinlto_index", + ":thinlto_indexing_param_file", + ":thinlto_input_bitcode_file", + ":thinlto_merged_object_file", + ":thinlto_object_suffix_replace", + ":thinlto_output_object_file", + ":thinlto_param_file", + ":thinlto_prefix_replace", + ":unfiltered_compile_flags", + ":user_compile_flags", + ":user_link_flags", + ], + visibility = ["//visibility:public"], +) diff --git a/tests/rule_based_toolchain/variables/BUILD b/tests/rule_based_toolchain/variables/BUILD new file mode 100644 index 0000000..80928c7 --- /dev/null +++ b/tests/rule_based_toolchain/variables/BUILD @@ -0,0 +1,52 @@ +load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types") +load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite") +load(":variables_test.bzl", "TARGETS", "TESTS") + +cc_variable( + name = "str", + type = types.string, +) + +cc_variable( + name = "str_list", + type = types.list(types.string), +) + +cc_variable( + name = "str_option", + type = types.option(types.string), +) + +cc_variable( + name = "struct", + actions = ["//tests/rule_based_toolchain/actions:c_compile"], + type = types.struct( + nested_str = types.string, + nested_str_list = types.list(types.string), + ), +) + +cc_variable( + name = "struct_list", + type = types.list(types.struct( + nested_str = types.string, + nested_str_list = types.list(types.string), + )), +) + +cc_builtin_variables( + name = "variables", + srcs = [ + ":str", + ":str_list", + ":str_option", + ":struct", + ":struct_list", + ], +) + +analysis_test_suite( + name = "test_suite", + targets = TARGETS, + tests = TESTS, +) diff --git a/tests/rule_based_toolchain/variables/variables_test.bzl b/tests/rule_based_toolchain/variables/variables_test.bzl new file mode 100644 index 0000000..be15a45 --- /dev/null +++ b/tests/rule_based_toolchain/variables/variables_test.bzl @@ -0,0 +1,128 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for variables rule.""" + +load("//cc/toolchains:cc_toolchain_info.bzl", "ActionTypeInfo", "BuiltinVariablesInfo", "VariableInfo") +load("//cc/toolchains/impl:variables.bzl", "types", _get_type = "get_type") +load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects") + +visibility("private") + +get_type = result_fn_wrapper(_get_type) + +_ARGS_LABEL = Label("//:args") +_NESTED_LABEL = Label("//:nested_vars") + +def _type(target): + return target[VariableInfo].type + +def _types_represent_correctly_test(env, targets): + env.expect.that_str(_type(targets.str_list)["repr"]).equals("List[string]") + env.expect.that_str(_type(targets.str_option)["repr"]).equals("Option[string]") + env.expect.that_str(_type(targets.struct)["repr"]).equals("struct(nested_str=string, nested_str_list=List[string])") + env.expect.that_str(_type(targets.struct_list)["repr"]).equals("List[struct(nested_str=string, nested_str_list=List[string])]") + +def _get_types_test(env, targets): + c_compile = targets.c_compile[ActionTypeInfo] + cpp_compile = targets.cpp_compile[ActionTypeInfo] + variables = targets.variables[BuiltinVariablesInfo].variables + + def expect_type(key, overrides = {}, expr = None, actions = []): + return env.expect.that_value( + get_type( + variables = variables, + overrides = overrides, + args_label = _ARGS_LABEL, + nested_label = _NESTED_LABEL, + actions = actions, + name = key, + ), + # It's not a string, it's a complex recursive type, but string + # supports .equals, which is all we care about. + factory = subjects.result(subjects.str), + expr = expr or key, + ) + + expect_type("unknown").err().contains( + """The variable unknown does not exist. Did you mean one of the following? +str +str_list +""", + ) + + expect_type("str").ok().equals(types.string) + expect_type("str.invalid").err().equals("""Attempted to access "str.invalid", but "str" was not a struct - it had type string.""") + + expect_type("str_option").ok().equals(types.option(types.string)) + + expect_type("str_list").ok().equals(types.list(types.string)) + + expect_type("str_list.invalid").err().equals("""Attempted to access "str_list.invalid", but "str_list" was not a struct - it had type List[string].""") + + expect_type("struct").ok().equals(_type(targets.struct)) + + expect_type("struct.nested_str_list").ok().equals(types.list(types.string)) + + expect_type("struct_list").ok().equals(_type(targets.struct_list)) + + expect_type("struct_list.nested_str_list").err().equals("""Attempted to access "struct_list.nested_str_list", but "struct_list" was not a struct - it had type List[struct(nested_str=string, nested_str_list=List[string])]. Maybe you meant to use iterate_over.""") + + expect_type("struct.unknown").err().equals("""Unable to find "unknown" in "struct", which had the following attributes: +nested_str: string +nested_str_list: List[string]""") + + expect_type("struct", actions = [c_compile]).ok() + expect_type("struct", actions = [c_compile, cpp_compile]).err().equals( + "The variable struct is inaccessible from the action %s. This is required because it is referenced in %s, which is included by %s, which references that action" % (cpp_compile.label, _NESTED_LABEL, _ARGS_LABEL), + ) + + expect_type("struct.nested_str_list", actions = [c_compile]).ok() + expect_type("struct.nested_str_list", actions = [c_compile, cpp_compile]).err() + + # Simulate someone doing iterate_over = struct_list. + expect_type( + "struct_list", + overrides = {"struct_list": _type(targets.struct)}, + expr = "struct_list_override", + ).ok().equals(_type(targets.struct)) + + expect_type( + "struct_list.nested_str_list", + overrides = {"struct_list": _type(targets.struct)}, + ).ok().equals(types.list(types.string)) + + expect_type( + "struct_list.nested_str_list", + overrides = { + "struct_list": _type(targets.struct), + "struct_list.nested_str_list": types.string, + }, + ).ok().equals(types.string) + +TARGETS = [ + "//tests/rule_based_toolchain/actions:c_compile", + "//tests/rule_based_toolchain/actions:cpp_compile", + ":str", + ":str_list", + ":str_option", + ":struct", + ":struct_list", + ":variables", +] + +# @unsorted-dict-items +TESTS = { + "types_represent_correctly_test": _types_represent_correctly_test, + "get_types_test": _get_types_test, +}