Skip to content


This guide explains why an untyped variable error may occur and describes how to fix it.

A variable in Rel is considered to be untyped if the system can’t determine statically — without running the actual query — the type of the variable. The following example returns an untyped variable error:

def R(x, y) {x = 1}
Error example from the RAI Console

This example defines the relation R containing the variable x of x = 1 value and type Int. However, it doesn’t declare a type or value for y. Therefore, the system can’t determine the data type of y and reports an untyped variable error.

To avoid this, you need to provide information about the data type using one of the approaches below. You can:

  • Specify explicitly the data type of y.
  • Assign literal value(s) to y.
  • Define y in terms of another relation.
  • Defer the type inference by using the annotation @inline.

See the Example section to learn how to fix the untyped variable error using each of these strategies.

An error similar to the untyped variable error is the ungrounded variable error, which you may encounter after fixing the untyped variable error. Once the query passes the front-end compiler, ungrounded errors occur because the query optimizer attempts to evaluate the variable but finds that the variable can take on an infinite amount of values.

See Grounded Variables in the Rel Language Reference manual and Grounded and Ungrounded Variables in the Rel Primer: Advanced Syntax guide for more details.


As previously mentioned, this definition results in an untyped variable error for y:

def R(x, y) {x = 1}

To avoid the error, you need to provide the system with more type information. For example, say you want y to be of type String. You can resolve the error using one of the approaches below.

Specify the Data Type

The most common way to fix the untyped variable error is by explicitly stating the data type of y:

// read query
def R(x, y in String) {x = 1}

Be careful of running into an ungrounded error if you query R with def output = R. Even though this would mean that y is now a String, it could be any string, which makes R an infinitely large (ungrounded) relation. Instead, use concrete string values to query R: def output = R(1, "abc").

Use Literal Values

You can tell the system that y is of type String by assigning a literal value to y:

// read query
def R(x, y) {x = 1 and y = "abc"}

The system now knows that y is a String, but you also have to set the value. Note that, in general, it’s not possible to define all allowed values within the body. So in such cases, it’s advisable to use the option where you specify the data type.

Define the Variable via Another Relation

Another way to define the data type of y is by defining y in terms of another relation, for example S:

// read query
bound S = String
def R(x, y) {x = 1 and S(y)}

The type of y can be inferred to be String thanks to the bound declaration of S. Alternatively, consider the following declaration:

// read query
def S(a in String) {...}
def R(x, y) {x = 1 and S(y)}

This can also work, assuming that you replace ... with a valid Rel logic, such as def S(a in String) {a=string[0.5]}.

Now, how you define the declaration S determines whether or not y is a bound variable and can take only a finite number of values.

Use @inline

You can also defer the type inference by annotating the declaration of R with @inline:

// read query
@inline def R(x, y) {x=1}

By doing so, the relation R is not materialized. Instead, its definition is expanded at every instance where it is used, and the type is inferred based on its respective context.

Was this doc helpful?