xref: /freebsd/contrib/bmake/unit-tests/var-op-expand.mk (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
1# $NetBSD: var-op-expand.mk,v 1.16 2021/12/28 10:47:00 rillig Exp $
2#
3# Tests for the := variable assignment operator, which expands its
4# right-hand side.
5#
6# See also:
7#	varname-dot-make-save_dollars.mk
8
9# Force the test results to be independent of the default value of this
10# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
11# distribution and pkgsrc/devel/bmake.
12.MAKE.SAVE_DOLLARS:=	yes
13
14# If the right-hand side does not contain a dollar sign, the ':=' assignment
15# operator has the same effect as the '=' assignment operator.
16VAR:=			value
17.if ${VAR} != "value"
18.  error
19.endif
20
21# When a ':=' assignment is performed, its right-hand side is evaluated and
22# expanded as far as possible.  Contrary to other situations, '$$' and
23# variable expressions based on undefined variables are preserved though.
24#
25# Whether a variable expression is undefined or not is determined at the end
26# of evaluating the expression.  The consequence is that ${:Ufallback} expands
27# to "fallback"; initially this expression is undefined since it is based on
28# the variable named "", which is guaranteed to be never defined, but at the
29# end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned
30# the expression into a defined expression.
31
32
33# literal dollar signs
34VAR:=		$$ $$$$ $$$$$$$$
35.if ${VAR} != "\$ \$\$ \$\$\$\$"
36.  error
37.endif
38
39
40# reference to a variable containing a literal dollar sign
41REF=		$$ $$$$ $$$$$$$$
42VAR:=		${REF}
43REF=		too late
44.if ${VAR} != "\$ \$\$ \$\$\$\$"
45.  error
46.endif
47
48
49# reference to an undefined variable
50.undef UNDEF
51VAR:=		<${UNDEF}>
52UNDEF=		after
53.if ${VAR} != "<after>"
54.  error
55.endif
56
57
58# reference to a variable whose name is computed from another variable
59REF2=		referred to
60REF=		REF2
61VAR:=		${${REF}}
62REF=		too late
63.if ${VAR} != "referred to"
64.  error
65.endif
66
67
68# expression with an indirect modifier referring to an undefined variable
69.undef UNDEF
70VAR:=		${:${UNDEF}}
71UNDEF=		Uwas undefined
72.if ${VAR} != "was undefined"
73.  error
74.endif
75
76
77# expression with an indirect modifier referring to another variable that
78# in turn refers to an undefined variable
79#
80# XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of
81# the variable modifier is not preserved.  To preserve it, ParseModifierPart
82# would have to call VarSubstExpr somehow since this is the only piece of
83# code that takes care of this global variable.
84.undef UNDEF
85REF=		U${UNDEF}
86#.MAKEFLAGS: -dv
87VAR:=		${:${REF}}
88#.MAKEFLAGS: -d0
89REF=		too late
90UNDEF=		Uwas undefined
91.if ${VAR} != ""
92.  error
93.endif
94
95
96# In variable assignments using the ':=' operator, undefined variables are
97# preserved, no matter how indirectly they are referenced.
98.undef REF3
99REF2=		<${REF3}>
100REF=		${REF2}
101VAR:=		${REF}
102REF3=		too late
103.if ${VAR} != "<too late>"
104.  error
105.endif
106
107
108# In variable assignments using the ':=' operator, '$$' are preserved, no
109# matter how indirectly they are referenced.
110REF2=		REF2:$$ $$$$
111REF=		REF:$$ $$$$ ${REF2}
112VAR:=		VAR:$$ $$$$ ${REF}
113.if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$"
114.  error
115.endif
116
117
118# In variable assignments using the ':=' operator, '$$' are preserved in the
119# expressions of the top level, but not in expressions that are nested.
120VAR:=		top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}}
121.if ${VAR} != "top:\$ nest1:\$ nest2:\$"
122.  error
123.endif
124
125
126# In variable assignments using the ':=' operator, there may be expressions
127# containing variable modifiers, and these modifiers may refer to other
128# variables.  These referred-to variables are expanded at the time of
129# assignment.  The undefined variables are kept as-is and are later expanded
130# when evaluating the condition.
131#
132# Contrary to the assignment operator '=', the assignment operator ':='
133# consumes the '$' from modifier parts.
134REF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
135.undef REF.undef
136VAR:=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
137REF.word=	word.after
138REF.undef=	undef.after
139.if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after"
140.  error
141.endif
142
143# Just for comparison, the previous example using the assignment operator '='
144# instead of ':='.  The right-hand side of the assignment is not evaluated at
145# the time of assignment but only later, when ${VAR} appears in the condition.
146#
147# At that point, both REF.word and REF.undef are defined.
148REF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
149.undef REF.undef
150VAR=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
151REF.word=	word.after
152REF.undef=	undef.after
153.if ${VAR} != "word.after undef.after, direct: word.after undef.after"
154.  error
155.endif
156
157
158# Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27,
159# if the variable name in a ':=' assignment referred to an undefined variable,
160# there were actually 2 assignments to different variables:
161#
162#	Global["VAR_SUBST_${UNDEF}"] = ""
163#	Global["VAR_SUBST_"] = ""
164#
165# The variable name with the empty value actually included a dollar sign.
166# Variable names with dollars are not used in practice.
167#
168# It might be a good idea to forbid undefined variables on the left-hand side
169# of a variable assignment.
170.undef UNDEF
171VAR_ASSIGN_${UNDEF}=	assigned by '='
172VAR_SUBST_${UNDEF}:=	assigned by ':='
173.if ${VAR_ASSIGN_} != "assigned by '='"
174.  error
175.endif
176.if defined(${:UVAR_SUBST_\${UNDEF\}})
177.  error
178.endif
179.if ${VAR_SUBST_} != "assigned by ':='"
180.  error
181.endif
182
183
184# The following test case demonstrates that the variable 'LATER' is preserved
185# in the ':=' assignment since the variable 'LATER' is not yet defined.
186# After the assignment to 'LATER', evaluating the variable 'INDIRECT'
187# evaluates 'LATER' as well.
188#
189.undef LATER
190INDIRECT:=	${LATER:S,value,replaced,}
191.if ${INDIRECT} != ""
192.  error
193.endif
194LATER=	late-value
195.if ${INDIRECT} != "late-replaced"
196.  error
197.endif
198
199
200# Same as the test case above, except for the additional modifier ':tl' when
201# evaluating the variable 'INDIRECT'.  Nothing surprising here.
202.undef LATER
203.undef later
204INDIRECT:=	${LATER:S,value,replaced,}
205.if ${INDIRECT:tl} != ""
206.  error
207.endif
208LATER=	uppercase-value
209later=	lowercase-value
210.if ${INDIRECT:tl} != "uppercase-replaced"
211.  error
212.endif
213
214
215# Similar to the two test cases above, the situation gets a bit more involved
216# here, due to the double indirection.  The variable 'indirect' is supposed to
217# be the lowercase version of the variable 'INDIRECT'.
218#
219# The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as
220# well, it wouldn't make a difference in this case.  The crucial detail is the
221# assignment operator ':=' for the variable 'indirect'.  During this
222# assignment, the variable modifier ':S,value,replaced,' is converted to
223# lowercase, which turns 'S' into 's', thus producing an unknown modifier.
224# In this case, make issues a warning, but in cases where the modifier
225# includes a '=', the modifier would be interpreted as a SysV-style
226# substitution like '.c=.o', and make would not issue a warning, leading to
227# silent unexpected behavior.
228#
229# As of 2021-11-20, the actual behavior is unexpected.  Fixing it is not
230# trivial.  When the assignment to 'indirect' takes place, the expressions
231# from the nested expression could be preserved, like this:
232#
233#	Start with:
234#
235#		indirect:=	${INDIRECT:tl}
236#
237#	Since INDIRECT is defined, expand it, remembering that the modifier
238#	':tl' must still be applied to the final result.
239#
240#		indirect:=	${LATER:S,value,replaced,} \
241#				OK \
242#				${LATER:value=sysv}
243#
244#	The variable 'LATER' is not defined.  An idea may be to append the
245#	remaining modifier ':tl' to each expression that is starting with an
246#	undefined variable, resulting in:
247#
248#		indirect:=	${LATER:S,value,replaced,:tl} \
249#				OK \
250#				${LATER:value=sysv:tl}
251#
252#	This would work for the first expression.  The second expression ends
253#	with the SysV modifier ':from=to', and when this modifier is parsed,
254#	it consumes all characters until the end of the expression, which in
255#	this case would replace the suffix 'value' with the literal 'sysv:tl',
256#	ignoring that the ':tl' was intended to be an additional modifier.
257#
258# Due to all of this, this surprising behavior is not easy to fix.
259#
260.undef LATER
261.undef later
262INDIRECT:=	${LATER:S,value,replaced,} OK ${LATER:value=sysv}
263indirect:=	${INDIRECT:tl}
264# expect+1: Unknown modifier "s,value,replaced,"
265.if ${indirect} != " ok "
266.  error
267.else
268.  warning	XXX Neither branch should be taken.
269.endif
270LATER=	uppercase-value
271later=	lowercase-value
272# expect+1: Unknown modifier "s,value,replaced,"
273.if ${indirect} != "uppercase-replaced ok uppercase-sysv"
274.  warning	XXX Neither branch should be taken.
275.else
276.  error
277.endif
278
279
280all:
281	@:;
282