xref: /freebsd/contrib/bmake/unit-tests/directive-for-empty.mk (revision 8d5c8e21c690b35d0a9a604d6b886fba222cd2fe)
1*8d5c8e21SSimon J. Gerraty# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $
2954401e6SSimon J. Gerraty#
3954401e6SSimon J. Gerraty# Tests for .for loops containing conditions of the form 'empty(var:...)'.
4954401e6SSimon J. Gerraty#
5d5e0a182SSimon J. Gerraty# When a .for loop is expanded, expressions in the body of the loop
6954401e6SSimon J. Gerraty# are replaced with expressions containing the variable values.  This
7954401e6SSimon J. Gerraty# replacement is a bit naive but covers most of the practical cases.  The one
8954401e6SSimon J. Gerraty# popular exception is the condition 'empty(var:Modifiers)', which does not
9d5e0a182SSimon J. Gerraty# look like an expression and is thus not replaced.
10954401e6SSimon J. Gerraty#
11954401e6SSimon J. Gerraty# See also:
12954401e6SSimon J. Gerraty#	https://gnats.netbsd.org/43821
13954401e6SSimon J. Gerraty
14954401e6SSimon J. Gerraty
15954401e6SSimon J. Gerraty# In the body of the .for loop, the expression '${i:M*2*}' is replaced with
16954401e6SSimon J. Gerraty# '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another.  This
17954401e6SSimon J. Gerraty# replacement creates the impression that .for variables were real variables,
18954401e6SSimon J. Gerraty# when in fact they aren't.
19954401e6SSimon J. Gerraty.for i in 11 12 13
20954401e6SSimon J. Gerraty.  if ${i:M*2*}
21148ee845SSimon J. Gerraty# expect+1: 2
22954401e6SSimon J. Gerraty.info 2
23954401e6SSimon J. Gerraty.  endif
24954401e6SSimon J. Gerraty.endfor
25954401e6SSimon J. Gerraty
26954401e6SSimon J. Gerraty
27d5e0a182SSimon J. Gerraty# In conditions, the function call to 'empty' does not look like an
28954401e6SSimon J. Gerraty# expression, therefore it is not replaced.  Since there is no global variable
29*8d5c8e21SSimon J. Gerraty# named 'i', this condition makes for a leaky abstraction.  If the .for
30954401e6SSimon J. Gerraty# variables were real variables, calling 'empty' would work on them as well.
31954401e6SSimon J. Gerraty.for i in 11 12 13
32954401e6SSimon J. Gerraty# Asking for an empty iteration variable does not make sense as the .for loop
33954401e6SSimon J. Gerraty# splits the iteration items into words, and such a word cannot be empty.
34*8d5c8e21SSimon J. Gerraty.  if !empty(i)
35*8d5c8e21SSimon J. Gerraty.    error			# not reached, due to the leaky abstraction
36954401e6SSimon J. Gerraty.  endif
37*8d5c8e21SSimon J. Gerraty# The typical way of mistakenly using 'empty' with variables from .for loops
38*8d5c8e21SSimon J. Gerraty# is pattern matching using the modifiers ':M' or ':N'.
39954401e6SSimon J. Gerraty.  if !empty(i:M*2*)
40954401e6SSimon J. Gerraty.    error
41954401e6SSimon J. Gerraty.  endif
42*8d5c8e21SSimon J. Gerraty# Instead of the 'empty' function, the variables from .for loops can be
43*8d5c8e21SSimon J. Gerraty# queried using conditions of the form '${var:...} != ""'.
44*8d5c8e21SSimon J. Gerraty.  if $i == "12" && ${i:M*2*} != "12"
45*8d5c8e21SSimon J. Gerraty.    error
46954401e6SSimon J. Gerraty.  endif
47954401e6SSimon J. Gerraty.endfor
48954401e6SSimon J. Gerraty
49954401e6SSimon J. Gerraty
50954401e6SSimon J. Gerraty# The idea of replacing every occurrences of 'empty(i' in the body of a .for
51954401e6SSimon J. Gerraty# loop would be naive and require many special cases, as there are many cases
52954401e6SSimon J. Gerraty# that need to be considered when deciding whether the token 'empty' is a
53954401e6SSimon J. Gerraty# function call or not, as demonstrated by the following examples.  For
54d5e0a182SSimon J. Gerraty# expressions like '${i:Modifiers}', this is simpler as a single
55d5e0a182SSimon J. Gerraty# dollar almost always starts an expression.  For counterexamples and
56954401e6SSimon J. Gerraty# edge cases, see directive-for-escape.mk.  Adding another such tricky detail
57954401e6SSimon J. Gerraty# is out of the question.
58954401e6SSimon J. Gerraty.MAKEFLAGS: -df
59954401e6SSimon J. Gerraty.for i in value
60954401e6SSimon J. Gerraty# The identifier 'empty' can only be used in conditions such as .if, .ifdef or
61954401e6SSimon J. Gerraty# .elif.  In other lines the string 'empty(' must be preserved.
62954401e6SSimon J. GerratyCPPFLAGS+=	-Dmessage="empty(i)"
63954401e6SSimon J. Gerraty# There may be whitespace between 'empty' and '('.
64954401e6SSimon J. Gerraty.if ! empty (i)
65954401e6SSimon J. Gerraty.  error
66954401e6SSimon J. Gerraty.endif
67954401e6SSimon J. Gerraty# Even in conditions, the string 'empty(' is not always a function call, it
68954401e6SSimon J. Gerraty# can occur in a string literal as well.
69954401e6SSimon J. Gerraty.if "empty\(i)" != "empty(i)"
70954401e6SSimon J. Gerraty.  error
71954401e6SSimon J. Gerraty.endif
72954401e6SSimon J. Gerraty# In comments like 'empty(i)', the text must be preserved as well.
73954401e6SSimon J. Gerraty#
74954401e6SSimon J. Gerraty# Conditions, including function calls to 'empty', can not only occur in
75954401e6SSimon J. Gerraty# condition directives, they can also occur in the modifier ':?', see
76954401e6SSimon J. Gerraty# varmod-ifelse.mk.
77954401e6SSimon J. GerratyCPPFLAGS+=	-Dmacro="${empty(i):?empty:not-empty}"
78954401e6SSimon J. Gerraty.endfor
79954401e6SSimon J. Gerraty.MAKEFLAGS: -d0
80954401e6SSimon J. Gerraty
81954401e6SSimon J. Gerraty
82954401e6SSimon J. Gerraty# An idea to work around the above problems is to collect the variables from
83954401e6SSimon J. Gerraty# the .for loops in a separate scope.  To match the current behavior, there
84954401e6SSimon J. Gerraty# has to be one scope per included file.  There may be .for loops using the
85954401e6SSimon J. Gerraty# same variable name in files that include each other:
86954401e6SSimon J. Gerraty#
87954401e6SSimon J. Gerraty# outer.mk:	.for i in outer
88954401e6SSimon J. Gerraty#		.  info $i		# outer
89954401e6SSimon J. Gerraty#		.  include "inner.mk"
90954401e6SSimon J. Gerraty# inner.mk:	.    info $i		# (undefined)
91954401e6SSimon J. Gerraty#		.    for i in inner
92954401e6SSimon J. Gerraty#		.      info $i		# inner
93954401e6SSimon J. Gerraty#		.    endfor
94954401e6SSimon J. Gerraty#		.    info $i		# (undefined)
95954401e6SSimon J. Gerraty# outer.mk:	.  info $i		# outer
96954401e6SSimon J. Gerraty#		.endfor
97954401e6SSimon J. Gerraty#
98954401e6SSimon J. Gerraty# This might be regarded another leaky abstraction, but it is in fact useful
99954401e6SSimon J. Gerraty# that variables from .for loops can only affect expressions in the current
100954401e6SSimon J. Gerraty# file.  If variables from .for loops were implemented as global variables,
101954401e6SSimon J. Gerraty# they might interact between files.
102954401e6SSimon J. Gerraty#
103954401e6SSimon J. Gerraty# To emulate this exact behavior for the function 'empty', each file in the
104954401e6SSimon J. Gerraty# stack of included files needs its own scope that is independent from the
105954401e6SSimon J. Gerraty# other files.
106954401e6SSimon J. Gerraty#
107954401e6SSimon J. Gerraty# Another tricky detail are nested .for loops in a single file that use the
108954401e6SSimon J. Gerraty# same variable name.  These are generally avoided by developers, as they
109954401e6SSimon J. Gerraty# would be difficult to understand for humans as well.  Technically, they are
110954401e6SSimon J. Gerraty# possible though.  Assuming there are two nested .for loops, both using the
111954401e6SSimon J. Gerraty# variable 'i'.  When the inner .for loop ends, the inner 'i' needs to be
112954401e6SSimon J. Gerraty# removed from the scope, which would need to make the outer 'i' visible
113954401e6SSimon J. Gerraty# again.  This would suggest to use one variable scope per .for loop.
114954401e6SSimon J. Gerraty#
115954401e6SSimon J. Gerraty# Using a separate scope has the benefit that Var_Parse already allows for
116954401e6SSimon J. Gerraty# a custom scope to be passed as parameter.  This would have another side
117954401e6SSimon J. Gerraty# effect though.  There are several modifiers that actually modify variables,
118954401e6SSimon J. Gerraty# and these modifications happen in the scope that is passed to Var_Parse.
119954401e6SSimon J. Gerraty# This would mean that the combination of a .for variable and the modifiers
120954401e6SSimon J. Gerraty# '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than
121954401e6SSimon J. Gerraty# before.
122954401e6SSimon J. Gerraty
123954401e6SSimon J. Gerraty# TODO: Add code that demonstrates the current interaction between variables
124954401e6SSimon J. Gerraty#  from .for loops and the modifiers mentioned above.
125*8d5c8e21SSimon J. Gerraty
126*8d5c8e21SSimon J. Gerratyall:
127