twister: replace dt_compat_enabled_with_alias filter

Originally added in 7733b942246cfbfa354328d28a8a469e1c135f37.

This filter is not well-formed. It's meant to match nodes like
/leds/led_0 in this DTS:

/ {
	aliases {
		led0 = &led0;
	};

	leds {
		compatible = "gpio-leds";
		led0: led_0 {
			gpios = <...>;
			label = "LED 0";
		};
	};
};

Uses look like this:

    filter: dt_compat_enabled_with_alias("gpio-leds", "led0")

But notice how the led_0 node doesn't have compatible "gpio-leds";
it's actually the *parent* node that has that compatible.

Replace this with a new filter, dt_enabled_alias_with_parent_compat(),
which is used like this:

    filter: dt_enabled_alias_with_parent_compat("led0", "gpio-leds")

This has a name and argument order that makes the meaning of the
filter clearer.

Replace in-tree users with the new filter.

Deprecate the old filter and warn about its use using the standard
logging module.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
diff --git a/scripts/pylib/twister/expr_parser.py b/scripts/pylib/twister/expr_parser.py
index cb54d34..95a2785 100644
--- a/scripts/pylib/twister/expr_parser.py
+++ b/scripts/pylib/twister/expr_parser.py
@@ -5,6 +5,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import copy
+import logging
 import os
 import re
 import sys
@@ -18,6 +19,8 @@
              "Please install the ply package using your workstation's\n"
              "package manager or the 'pip' tool.")
 
+_logger = logging.getLogger('twister')
+
 reserved = {
     'and' : 'AND',
     'or' : 'OR',
@@ -233,13 +236,34 @@
             if alias in node.aliases and node.status == "okay":
                 return True
         return False
+    elif ast[0] == "dt_enabled_alias_with_parent_compat":
+        # Checks if the DT has an enabled alias node whose parent has
+        # a given compatible. For matching things like gpio-leds child
+        # nodes, which do not have compatibles themselves.
+        #
+        # The legacy "dt_compat_enabled_with_alias" form is still
+        # accepted but is now deprecated and causes a warning. This is
+        # meant to give downstream users some time to notice and
+        # adjust. Its argument order only made sense under the (bad)
+        # assumption that the gpio-leds child node has the same compatible
+
+        alias = ast[1][0]
+        compat = ast[1][1]
+
+        return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias,
+                                                              compat)
     elif ast[0] == "dt_compat_enabled_with_alias":
         compat = ast[1][0]
         alias = ast[1][1]
-        for node in edt.nodes:
-            if node.status == "okay" and alias in node.aliases and node.matching_compat == compat:
-                return True
-        return False
+
+        _logger.warning('dt_compat_enabled_with_alias("%s", "%s"): '
+                        'this is deprecated, use '
+                        'dt_enabled_alias_with_parent_compat("%s", "%s") '
+                        'instead',
+                        compat, alias, alias, compat)
+
+        return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias,
+                                                              compat)
     elif ast[0] == "dt_chosen_enabled":
         chosen = ast[1][0]
         node = edt.chosen_node(chosen)
@@ -247,6 +271,20 @@
             return True
         return False
 
+def ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, compat):
+    # Helper shared with the now deprecated
+    # dt_compat_enabled_with_alias version.
+
+    for node in edt.nodes:
+        parent = node.parent
+        if parent is None:
+            continue
+        if (node.status == "okay" and alias in node.aliases and
+                    parent.matching_compat == compat):
+            return True
+
+    return False
+
 mutex = threading.Lock()
 
 def parse(expr_text, env, edt):