xref: /freebsd/contrib/bmake/unit-tests/var-op-expand.mk (revision 759b177aecbfc49ebc900739954ac56b1aa5fc53)
1*759b177aSSimon J. Gerraty# $NetBSD: var-op-expand.mk,v 1.23 2025/03/29 19:08:52 rillig Exp $
22c3632d1SSimon J. Gerraty#
32c3632d1SSimon J. Gerraty# Tests for the := variable assignment operator, which expands its
42c3632d1SSimon J. Gerraty# right-hand side.
512904384SSimon J. Gerraty#
612904384SSimon J. Gerraty# See also:
712904384SSimon J. Gerraty#	varname-dot-make-save_dollars.mk
82c3632d1SSimon J. Gerraty
912904384SSimon J. Gerraty# Force the test results to be independent of the default value of this
1012904384SSimon J. Gerraty# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
1112904384SSimon J. Gerraty# distribution and pkgsrc/devel/bmake.
1206b9b3e0SSimon J. Gerraty.MAKE.SAVE_DOLLARS:=	yes
132c3632d1SSimon J. Gerraty
1406b9b3e0SSimon J. Gerraty# If the right-hand side does not contain a dollar sign, the ':=' assignment
1506b9b3e0SSimon J. Gerraty# operator has the same effect as the '=' assignment operator.
1606b9b3e0SSimon J. GerratyVAR:=			value
1706b9b3e0SSimon J. Gerraty.if ${VAR} != "value"
1806b9b3e0SSimon J. Gerraty.  error
1906b9b3e0SSimon J. Gerraty.endif
2006b9b3e0SSimon J. Gerraty
2106b9b3e0SSimon J. Gerraty# When a ':=' assignment is performed, its right-hand side is evaluated and
2206b9b3e0SSimon J. Gerraty# expanded as far as possible.  Contrary to other situations, '$$' and
23d5e0a182SSimon J. Gerraty# expressions based on undefined variables are preserved though.
24e2eeea75SSimon J. Gerraty#
25d5e0a182SSimon J. Gerraty# Whether an expression is undefined or not is determined at the end
2606b9b3e0SSimon J. Gerraty# of evaluating the expression.  The consequence is that ${:Ufallback} expands
2706b9b3e0SSimon J. Gerraty# to "fallback"; initially this expression is undefined since it is based on
2806b9b3e0SSimon J. Gerraty# the variable named "", which is guaranteed to be never defined, but at the
2906b9b3e0SSimon J. Gerraty# end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned
3006b9b3e0SSimon J. Gerraty# the expression into a defined expression.
3106b9b3e0SSimon J. Gerraty
3206b9b3e0SSimon J. Gerraty
3306b9b3e0SSimon J. Gerraty# literal dollar signs
3406b9b3e0SSimon J. GerratyVAR:=		$$ $$$$ $$$$$$$$
3506b9b3e0SSimon J. Gerraty.if ${VAR} != "\$ \$\$ \$\$\$\$"
3606b9b3e0SSimon J. Gerraty.  error
3706b9b3e0SSimon J. Gerraty.endif
3806b9b3e0SSimon J. Gerraty
3906b9b3e0SSimon J. Gerraty
404fde40d9SSimon J. Gerraty# reference to a variable containing literal dollar signs
4106b9b3e0SSimon J. GerratyREF=		$$ $$$$ $$$$$$$$
4206b9b3e0SSimon J. GerratyVAR:=		${REF}
4306b9b3e0SSimon J. GerratyREF=		too late
4406b9b3e0SSimon J. Gerraty.if ${VAR} != "\$ \$\$ \$\$\$\$"
4506b9b3e0SSimon J. Gerraty.  error
4606b9b3e0SSimon J. Gerraty.endif
4706b9b3e0SSimon J. Gerraty
4806b9b3e0SSimon J. Gerraty
4906b9b3e0SSimon J. Gerraty# reference to an undefined variable
5006b9b3e0SSimon J. Gerraty.undef UNDEF
5106b9b3e0SSimon J. GerratyVAR:=		<${UNDEF}>
524fde40d9SSimon J. Gerraty.if ${VAR} != "<>"
534fde40d9SSimon J. Gerraty.  error
544fde40d9SSimon J. Gerraty.endif
5506b9b3e0SSimon J. GerratyUNDEF=		after
5606b9b3e0SSimon J. Gerraty.if ${VAR} != "<after>"
5706b9b3e0SSimon J. Gerraty.  error
5806b9b3e0SSimon J. Gerraty.endif
5906b9b3e0SSimon J. Gerraty
6006b9b3e0SSimon J. Gerraty
6106b9b3e0SSimon J. Gerraty# reference to a variable whose name is computed from another variable
6206b9b3e0SSimon J. GerratyREF2=		referred to
6306b9b3e0SSimon J. GerratyREF=		REF2
6406b9b3e0SSimon J. GerratyVAR:=		${${REF}}
6506b9b3e0SSimon J. GerratyREF=		too late
6606b9b3e0SSimon J. Gerraty.if ${VAR} != "referred to"
6706b9b3e0SSimon J. Gerraty.  error
6806b9b3e0SSimon J. Gerraty.endif
6906b9b3e0SSimon J. Gerraty
7006b9b3e0SSimon J. Gerraty
7106b9b3e0SSimon J. Gerraty# expression with an indirect modifier referring to an undefined variable
7206b9b3e0SSimon J. Gerraty.undef UNDEF
7306b9b3e0SSimon J. GerratyVAR:=		${:${UNDEF}}
744fde40d9SSimon J. Gerraty.if ${VAR} != ""
754fde40d9SSimon J. Gerraty.  error
764fde40d9SSimon J. Gerraty.endif
7706b9b3e0SSimon J. GerratyUNDEF=		Uwas undefined
7806b9b3e0SSimon J. Gerraty.if ${VAR} != "was undefined"
7906b9b3e0SSimon J. Gerraty.  error
8006b9b3e0SSimon J. Gerraty.endif
8106b9b3e0SSimon J. Gerraty
8206b9b3e0SSimon J. Gerraty
8306b9b3e0SSimon J. Gerraty# expression with an indirect modifier referring to another variable that
8406b9b3e0SSimon J. Gerraty# in turn refers to an undefined variable
85e2eeea75SSimon J. Gerraty#
8606b9b3e0SSimon J. Gerraty# XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of
8706b9b3e0SSimon J. Gerraty# the variable modifier is not preserved.  To preserve it, ParseModifierPart
8806b9b3e0SSimon J. Gerraty# would have to call VarSubstExpr somehow since this is the only piece of
8906b9b3e0SSimon J. Gerraty# code that takes care of this global variable.
9006b9b3e0SSimon J. Gerraty.undef UNDEF
9106b9b3e0SSimon J. GerratyREF=		U${UNDEF}
9206b9b3e0SSimon J. Gerraty#.MAKEFLAGS: -dv
9306b9b3e0SSimon J. GerratyVAR:=		${:${REF}}
9406b9b3e0SSimon J. Gerraty#.MAKEFLAGS: -d0
9506b9b3e0SSimon J. GerratyREF=		too late
9606b9b3e0SSimon J. GerratyUNDEF=		Uwas undefined
9706b9b3e0SSimon J. Gerraty.if ${VAR} != ""
9806b9b3e0SSimon J. Gerraty.  error
9906b9b3e0SSimon J. Gerraty.endif
10006b9b3e0SSimon J. Gerraty
10106b9b3e0SSimon J. Gerraty
10206b9b3e0SSimon J. Gerraty# In variable assignments using the ':=' operator, undefined variables are
10306b9b3e0SSimon J. Gerraty# preserved, no matter how indirectly they are referenced.
10406b9b3e0SSimon J. Gerraty.undef REF3
10506b9b3e0SSimon J. GerratyREF2=		<${REF3}>
10606b9b3e0SSimon J. GerratyREF=		${REF2}
10706b9b3e0SSimon J. GerratyVAR:=		${REF}
1084fde40d9SSimon J. Gerraty.if ${VAR} != "<>"
1094fde40d9SSimon J. Gerraty.  error
1104fde40d9SSimon J. Gerraty.endif
11106b9b3e0SSimon J. GerratyREF3=		too late
11206b9b3e0SSimon J. Gerraty.if ${VAR} != "<too late>"
11306b9b3e0SSimon J. Gerraty.  error
11406b9b3e0SSimon J. Gerraty.endif
11506b9b3e0SSimon J. Gerraty
11606b9b3e0SSimon J. Gerraty
11706b9b3e0SSimon J. Gerraty# In variable assignments using the ':=' operator, '$$' are preserved, no
11806b9b3e0SSimon J. Gerraty# matter how indirectly they are referenced.
11906b9b3e0SSimon J. GerratyREF2=		REF2:$$ $$$$
12006b9b3e0SSimon J. GerratyREF=		REF:$$ $$$$ ${REF2}
12106b9b3e0SSimon J. GerratyVAR:=		VAR:$$ $$$$ ${REF}
12206b9b3e0SSimon J. Gerraty.if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$"
12306b9b3e0SSimon J. Gerraty.  error
12406b9b3e0SSimon J. Gerraty.endif
12506b9b3e0SSimon J. Gerraty
12606b9b3e0SSimon J. Gerraty
12706b9b3e0SSimon J. Gerraty# In variable assignments using the ':=' operator, '$$' are preserved in the
12806b9b3e0SSimon J. Gerraty# expressions of the top level, but not in expressions that are nested.
12906b9b3e0SSimon J. GerratyVAR:=		top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}}
13006b9b3e0SSimon J. Gerraty.if ${VAR} != "top:\$ nest1:\$ nest2:\$"
13106b9b3e0SSimon J. Gerraty.  error
13206b9b3e0SSimon J. Gerraty.endif
13306b9b3e0SSimon J. Gerraty
13406b9b3e0SSimon J. Gerraty
13506b9b3e0SSimon J. Gerraty# In variable assignments using the ':=' operator, there may be expressions
13606b9b3e0SSimon J. Gerraty# containing variable modifiers, and these modifiers may refer to other
13706b9b3e0SSimon J. Gerraty# variables.  These referred-to variables are expanded at the time of
13806b9b3e0SSimon J. Gerraty# assignment.  The undefined variables are kept as-is and are later expanded
13906b9b3e0SSimon J. Gerraty# when evaluating the condition.
14006b9b3e0SSimon J. Gerraty#
14106b9b3e0SSimon J. Gerraty# Contrary to the assignment operator '=', the assignment operator ':='
14206b9b3e0SSimon J. Gerraty# consumes the '$' from modifier parts.
14306b9b3e0SSimon J. GerratyREF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
14406b9b3e0SSimon J. Gerraty.undef REF.undef
14506b9b3e0SSimon J. GerratyVAR:=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
14606b9b3e0SSimon J. GerratyREF.word=	word.after
14706b9b3e0SSimon J. GerratyREF.undef=	undef.after
14806b9b3e0SSimon J. Gerraty.if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after"
14906b9b3e0SSimon J. Gerraty.  error
15006b9b3e0SSimon J. Gerraty.endif
15106b9b3e0SSimon J. Gerraty
15206b9b3e0SSimon J. Gerraty# Just for comparison, the previous example using the assignment operator '='
15306b9b3e0SSimon J. Gerraty# instead of ':='.  The right-hand side of the assignment is not evaluated at
15406b9b3e0SSimon J. Gerraty# the time of assignment but only later, when ${VAR} appears in the condition.
15506b9b3e0SSimon J. Gerraty#
15606b9b3e0SSimon J. Gerraty# At that point, both REF.word and REF.undef are defined.
15706b9b3e0SSimon J. GerratyREF.word=	1:$$ 2:$$$$ 4:$$$$$$$$
15806b9b3e0SSimon J. Gerraty.undef REF.undef
15906b9b3e0SSimon J. GerratyVAR=		${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef}
16006b9b3e0SSimon J. GerratyREF.word=	word.after
16106b9b3e0SSimon J. GerratyREF.undef=	undef.after
16206b9b3e0SSimon J. Gerraty.if ${VAR} != "word.after undef.after, direct: word.after undef.after"
16306b9b3e0SSimon J. Gerraty.  error
16406b9b3e0SSimon J. Gerraty.endif
16506b9b3e0SSimon J. Gerraty
16606b9b3e0SSimon J. Gerraty
16706b9b3e0SSimon J. Gerraty# Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27,
16806b9b3e0SSimon J. Gerraty# if the variable name in a ':=' assignment referred to an undefined variable,
16906b9b3e0SSimon J. Gerraty# there were actually 2 assignments to different variables:
17006b9b3e0SSimon J. Gerraty#
17106b9b3e0SSimon J. Gerraty#	Global["VAR_SUBST_${UNDEF}"] = ""
17206b9b3e0SSimon J. Gerraty#	Global["VAR_SUBST_"] = ""
17306b9b3e0SSimon J. Gerraty#
17406b9b3e0SSimon J. Gerraty# The variable name with the empty value actually included a dollar sign.
17506b9b3e0SSimon J. Gerraty# Variable names with dollars are not used in practice.
17606b9b3e0SSimon J. Gerraty#
17706b9b3e0SSimon J. Gerraty# It might be a good idea to forbid undefined variables on the left-hand side
17806b9b3e0SSimon J. Gerraty# of a variable assignment.
17906b9b3e0SSimon J. Gerraty.undef UNDEF
18006b9b3e0SSimon J. GerratyVAR_ASSIGN_${UNDEF}=	assigned by '='
18106b9b3e0SSimon J. GerratyVAR_SUBST_${UNDEF}:=	assigned by ':='
18206b9b3e0SSimon J. Gerraty.if ${VAR_ASSIGN_} != "assigned by '='"
18306b9b3e0SSimon J. Gerraty.  error
18406b9b3e0SSimon J. Gerraty.endif
18506b9b3e0SSimon J. Gerraty.if defined(${:UVAR_SUBST_\${UNDEF\}})
18606b9b3e0SSimon J. Gerraty.  error
18706b9b3e0SSimon J. Gerraty.endif
18806b9b3e0SSimon J. Gerraty.if ${VAR_SUBST_} != "assigned by ':='"
18906b9b3e0SSimon J. Gerraty.  error
19006b9b3e0SSimon J. Gerraty.endif
191e2eeea75SSimon J. Gerraty
19212904384SSimon J. Gerraty
19312904384SSimon J. Gerraty# The following test case demonstrates that the variable 'LATER' is preserved
19412904384SSimon J. Gerraty# in the ':=' assignment since the variable 'LATER' is not yet defined.
19512904384SSimon J. Gerraty# After the assignment to 'LATER', evaluating the variable 'INDIRECT'
19612904384SSimon J. Gerraty# evaluates 'LATER' as well.
19712904384SSimon J. Gerraty#
19812904384SSimon J. Gerraty.undef LATER
19912904384SSimon J. GerratyINDIRECT:=	${LATER:S,value,replaced,}
20012904384SSimon J. Gerraty.if ${INDIRECT} != ""
20112904384SSimon J. Gerraty.  error
20212904384SSimon J. Gerraty.endif
20312904384SSimon J. GerratyLATER=	late-value
20412904384SSimon J. Gerraty.if ${INDIRECT} != "late-replaced"
20512904384SSimon J. Gerraty.  error
20612904384SSimon J. Gerraty.endif
20712904384SSimon J. Gerraty
20812904384SSimon J. Gerraty
20912904384SSimon J. Gerraty# Same as the test case above, except for the additional modifier ':tl' when
21012904384SSimon J. Gerraty# evaluating the variable 'INDIRECT'.  Nothing surprising here.
21112904384SSimon J. Gerraty.undef LATER
21212904384SSimon J. Gerraty.undef later
21312904384SSimon J. GerratyINDIRECT:=	${LATER:S,value,replaced,}
21412904384SSimon J. Gerraty.if ${INDIRECT:tl} != ""
21512904384SSimon J. Gerraty.  error
21612904384SSimon J. Gerraty.endif
21712904384SSimon J. GerratyLATER=	uppercase-value
21812904384SSimon J. Gerratylater=	lowercase-value
21912904384SSimon J. Gerraty.if ${INDIRECT:tl} != "uppercase-replaced"
22012904384SSimon J. Gerraty.  error
22112904384SSimon J. Gerraty.endif
22212904384SSimon J. Gerraty
22312904384SSimon J. Gerraty
22412904384SSimon J. Gerraty# Similar to the two test cases above, the situation gets a bit more involved
22512904384SSimon J. Gerraty# here, due to the double indirection.  The variable 'indirect' is supposed to
22612904384SSimon J. Gerraty# be the lowercase version of the variable 'INDIRECT'.
22712904384SSimon J. Gerraty#
22812904384SSimon J. Gerraty# The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as
22912904384SSimon J. Gerraty# well, it wouldn't make a difference in this case.  The crucial detail is the
23012904384SSimon J. Gerraty# assignment operator ':=' for the variable 'indirect'.  During this
23112904384SSimon J. Gerraty# assignment, the variable modifier ':S,value,replaced,' is converted to
23212904384SSimon J. Gerraty# lowercase, which turns 'S' into 's', thus producing an unknown modifier.
23312904384SSimon J. Gerraty# In this case, make issues a warning, but in cases where the modifier
23412904384SSimon J. Gerraty# includes a '=', the modifier would be interpreted as a SysV-style
23512904384SSimon J. Gerraty# substitution like '.c=.o', and make would not issue a warning, leading to
23612904384SSimon J. Gerraty# silent unexpected behavior.
23712904384SSimon J. Gerraty#
23812904384SSimon J. Gerraty# As of 2021-11-20, the actual behavior is unexpected.  Fixing it is not
23912904384SSimon J. Gerraty# trivial.  When the assignment to 'indirect' takes place, the expressions
24012904384SSimon J. Gerraty# from the nested expression could be preserved, like this:
24112904384SSimon J. Gerraty#
24212904384SSimon J. Gerraty#	Start with:
24312904384SSimon J. Gerraty#
24412904384SSimon J. Gerraty#		indirect:=	${INDIRECT:tl}
24512904384SSimon J. Gerraty#
24612904384SSimon J. Gerraty#	Since INDIRECT is defined, expand it, remembering that the modifier
24712904384SSimon J. Gerraty#	':tl' must still be applied to the final result.
24812904384SSimon J. Gerraty#
24912904384SSimon J. Gerraty#		indirect:=	${LATER:S,value,replaced,} \
25012904384SSimon J. Gerraty#				OK \
25112904384SSimon J. Gerraty#				${LATER:value=sysv}
25212904384SSimon J. Gerraty#
25312904384SSimon J. Gerraty#	The variable 'LATER' is not defined.  An idea may be to append the
25412904384SSimon J. Gerraty#	remaining modifier ':tl' to each expression that is starting with an
25512904384SSimon J. Gerraty#	undefined variable, resulting in:
25612904384SSimon J. Gerraty#
25712904384SSimon J. Gerraty#		indirect:=	${LATER:S,value,replaced,:tl} \
25812904384SSimon J. Gerraty#				OK \
25912904384SSimon J. Gerraty#				${LATER:value=sysv:tl}
26012904384SSimon J. Gerraty#
26112904384SSimon J. Gerraty#	This would work for the first expression.  The second expression ends
26212904384SSimon J. Gerraty#	with the SysV modifier ':from=to', and when this modifier is parsed,
26312904384SSimon J. Gerraty#	it consumes all characters until the end of the expression, which in
26412904384SSimon J. Gerraty#	this case would replace the suffix 'value' with the literal 'sysv:tl',
26512904384SSimon J. Gerraty#	ignoring that the ':tl' was intended to be an additional modifier.
26612904384SSimon J. Gerraty#
26712904384SSimon J. Gerraty# Due to all of this, this surprising behavior is not easy to fix.
26812904384SSimon J. Gerraty#
26912904384SSimon J. Gerraty.undef LATER
27012904384SSimon J. Gerraty.undef later
27112904384SSimon J. GerratyINDIRECT:=	${LATER:S,value,replaced,} OK ${LATER:value=sysv}
27212904384SSimon J. Gerratyindirect:=	${INDIRECT:tl}
273*759b177aSSimon J. Gerraty# expect+1: Unknown modifier ":s,value,replaced,"
27412904384SSimon J. Gerraty.if ${indirect} != " ok "
27512904384SSimon J. Gerraty.  error
27612904384SSimon J. Gerraty.else
277148ee845SSimon J. Gerraty# expect+1: warning: XXX Neither branch should be taken.
27812904384SSimon J. Gerraty.  warning	XXX Neither branch should be taken.
27912904384SSimon J. Gerraty.endif
28012904384SSimon J. GerratyLATER=	uppercase-value
28112904384SSimon J. Gerratylater=	lowercase-value
282*759b177aSSimon J. Gerraty# expect+1: Unknown modifier ":s,value,replaced,"
28312904384SSimon J. Gerraty.if ${indirect} != "uppercase-replaced ok uppercase-sysv"
284148ee845SSimon J. Gerraty# expect+1: warning: XXX Neither branch should be taken.
28512904384SSimon J. Gerraty.  warning	XXX Neither branch should be taken.
28612904384SSimon J. Gerraty.else
28712904384SSimon J. Gerraty.  error
28812904384SSimon J. Gerraty.endif
28912904384SSimon J. Gerraty
29012904384SSimon J. Gerraty
2912c3632d1SSimon J. Gerratyall:
2922c3632d1SSimon J. Gerraty	@:;
293