Call-by-value
The R Language Definition says this (in section 4.3.3 Argument Evaluation)
The semantics of invoking a function in R argument are call-by-value. In general, supplied arguments behave as if they are local variables initialized with the value supplied and the name of the corresponding formal argument. Changing the value of a supplied argument within a function will not affect the value of the variable in the calling frame. [Emphasis added]
Whilst this does not describe the mechanism by which copy-on-modify works, it does mention that changing an object passed to a function doesn’t affect the original in the calling frame.
Additional information, particularly on the copy-on-modify aspect are given in the description of SEXP
s in the R Internals manual, section 1.1.2 Rest of Header. Specifically it states [Emphasis added]
The
named
field is set and accessed by theSET_NAMED
andNAMED
macros, and take values0
,1
and2
. R has a ‘call by value’
illusion, so an assignment likeb <- a
appears to make a copy of
a
and refer to it asb
. However, if
neithera
norb
are subsequently altered there is no need to copy.
What really happens is that a new symbolb
is bound to the same
value asa
and thenamed
field on the value object is set (in this
case to2
). When an object is about to be altered, thenamed
field
is consulted. A value of2
means that the object must be duplicated
before being changed. (Note that this does not say that it is
necessary to duplicate, only that it should be duplicated whether
necessary or not.) A value of0
means that it is known that no other
SEXP
shares data with this object, and so it may safely be altered.
A value of1
is used for situations likedim(a) <- c(7, 2)
where in principle two copies of a exist for the duration of the
computation as (in principle)a <- `dim<-`(a, c(7, 2))
but for no longer, and so some primitive functions can be optimized to
avoid a copy in this case.
Whilst this doesn’t describe the situation whereby objects are passed to functions as arguments, we might deduce that the same process operates, especially given the information from the R Language definition quoted earlier.
Promises in function evaluation
I don’t think it is quite correct to say that a promise is passed to the function. The arguments are passed to the function and the actual expressions used are stored as promises (plus a pointer to the calling environment). Only when an argument gets evaluated is the expression stored in the promise retrieved and evaluated within the environment indicated by the pointer, a process known as forcing.
As such, I don’t believe it is correct to talk about pass-by-reference in this regard. R has call-by-value semantics but tries to avoid copying unless a value passed to an argument is evaluated and modified.
The NAMED mechanism is an optimisation (as noted by @hadley in the comments) which allows R to track whether a copy needs to be made upon modification. There are some subtleties involved with exactly how the NAMED mechanism operates, as discussed by Peter Dalgaard (in the R Devel thread @mnel cites in their comment to the question)