Attributes can optionally be specified as being collection attributes. Such attributes allow aspects to contribute values that are used to compute the final value of the attribute. All attributes (synthesized, inherited, local, and production) can be collection attributes by indicating an operation to be used to combine the contributed values of the attribute. Such attribute declarations have the form

<attr-kind> attribute <short-name> :: <type> with <op> ;

where <attr-kind> is synthesized, inherited, local, or production and <op> is a Silver operator or (possibly, user defined) function with the type (<type>, <type>) -> <type>). This operator or function combines contributed values of the attribute; it is not unlike the function passed to a fold function in functional programming.

Collection attributes must use the two collection assignment operators := and <-; the use of the regular assignment operator = is not allowed. The first assignment operator, :=, is used to assign a base value to the collection attribute. This value is similar to the initial or ```zero'' value passed to a fold function. The second assignment operator, ``<-`, contributes additional values to the final value of the attribute. These values are combined with, or folded into, the base value and other contributed values using the operator. Such contributions are often written in aspect productions. Because the order in which values from different contributions are combined in not specified the operator should be commutative._

Example: In the tutorial grammar simple:abstractsyntax, the file contains the declaration of the attribute errors, which is declared as a collection attribute as follows:

synthesized attribute errors :: [String] with ++;

The errors attribute is a list of string values whose various contributions and base value are combined using the list-concatenation operator ++.

In the same file, the add production defines the base value to be the combination of the errors attribute on its two children.

abstract production add 
e::Expr ::= l::Expr r::Expr 
  e.errors := l.errors ++ r.errors ;

In the file an aspect on add checks that the type on each child is numeric by calling the helper function isNumeric.

aspect production add
e::Expr ::= l::Expr r::Expr 
  e.errors <- (if isNumeric(l.type) then [ ]
               else [ "Expression \"" ++ l.pp ++ 
                      "\" must be of type Integer or Float.\n" ]) ++
              (if isNumeric(r.type) then [ ]
               else [ "Expression \"" ++ r.pp ++ 
                      "\" must be of type Integer or Float.\n" ]);

The final value of errors on a tree node constructed by the add production will contain any errors defined on its children (including type errors computed by aspects on the productions defining the children) and any type errors computed in the aspect above.

Threaded attributes can also be declared as collection attributes:

threaded attribute <inh-name>, <syn-name> :: <type> with <op>;

Example: This is useful for patterns such as incrementing a unique identifier counter:

threaded attribute idIn, idOut :: Integer with + occurs on Expr;
propagate idIn, idOut on Expr;
production foo
top::Expr ::= e1::Expr e2::Expr
  e1.idIn <- 1;

  local id::Integer = top.idIn;