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