The TRLC static analyser and linter can be used to find misleading or problematic types and user defined checks. It is recommended to use it.
The linter is enabled by default, so running trlc filename.rsl
will
also run the linter. The static analyser can be enabled with the
--verify
option, i.e: trlc --verify filename.rsl
.
You can explicitly turn the linter off with the --no-lint
option.
Final is a type attribute that is inherited. However for readability it is a good idea to explicitly state it again.
package Clarify_Final
final type T {
x Integer
}
type Q extends T {
}
Generates this message:
type Q extends T {
^ clarify_final.rsl:7: issue: consider clarifying that this record is final [clarify_final]
| Parent record Clarify_Final.T is final, making this record
| also final. Marking it explicitly as final
| clarifies this to casual readers.
A curiosity of the TRLC expression grammar (which strongly derives from the Ada grammar, which shares this particularity) is that unary minus followed by a stronger binding operator doesn't have the precedence you think it does. For example:
package Unary_Minus_Precedence
type T {
x Integer
}
checks T {
-x / 3 < 0, "non-obvious meaning"
(-x) / 3 < 0, "clear meaning"
-(x / 3) < 0, "clear meaning"
}
Will generate the following message:
-x / 3 < 0, "non-obvious meaning"
^ unary_minus_precedence.rsl:8: issue: expression means -(x / 3), place explicit brackets to clarify intent [unary_minus_precedence]
Abstract types that are never extended can't be used. Most likely they are remnants of some refactoring.
package Abstract_Leaf_Types
abstract type T {
description String
}
Will generate:
abstract type T {
^ abstract_leaf_types.rsl:3: issue: abstract type T does not have any extensions [abstract_leaf_types]
Some features in TRLC can be deprecated as the language evolves. Features that are scheduled to be removed for version 3.0.0 are flagged by the linter. The linter can notify you when you're using such a construct, and how it should be done in the future.
Currently there are no deprecated features in TRLC.
Tuple types with their custom separators offer a lot of potential for convenient syntax. There is however a specific combination that is problematic:
package Separator_Based_Literal_Ambiguity
tuple T {
value_1 Integer
separator x
value_2 optional Integer
}
type Q {
value T
}
Consider possible assignments to value
:
0
(tuple where value_1 = integer 0)0x0
(tuple where value_1 = hex integer 0x0, but value_2 is null)0 x 0
(tuple where value_1 = integer 0, and value_2 is integer 0)
This is only the case of literal separators x
and b
, and only when
they follow an integer or decimal type. The linter generates a message
in this case:
separator x
^ separator_based_literal_ambiguity.rsl:5: issue: x separator after integer component creates ambiguities [separator_based_literal_ambiguity]
| For example 0x100 would be a base 16 literal
| instead of the tuple segment 0 x 100.
Some arrays bounds produce arrays that could never be written. For example:
package Impossible_Array_Types
type T {
x Integer [10 .. 3]
y Integer [0 .. 0]
}
This generates:
x Integer [10 .. 3]
^ impossible_array_types.rsl:4: issue: upper bound must be at least 10 [impossible_array_types]
y Integer [0 .. 0]
^ impossible_array_types.rsl:5: issue: this array makes no sense [impossible_array_types]
Related to the check above, you could create arrays that shouldn't be arrays.
package Weird_Array_Types
type T {
x Integer [1 .. 1]
y Integer [0 .. 1]
}
Would produce the following messages:
x Integer [1 .. 1]
^ weird_array_types.rsl:4: issue: array of fixed size 1 should not be an array [weird_array_types]
| An array with a fixed size of 1 should not
| be an array at all.
y Integer [0 .. 1]
^ weird_array_types.rsl:5: issue: consider making this array an optional Integer [weird_array_types]
| An array with 0 to 1 components should just
| be an optional Integer instead.
Evaluating null in any context other than equality is an error. For example:
package Evaluation_Of_Null
type Requirement {
description optional String
}
checks Requirement {
len(description) >= 10, "too short"
}
Would generate:
len(description) >= 10, "too short"
^^^^^^^^^^^ evaluation_of_null.rsl:8: issue: expression could be null [vcg-evaluation-of-null]
| example record_type triggering error:
| Requirement bad_potato {
| /* description is null */
| }
To fix this you should prefix the check with a guard, for example like this:
checks Requirement {
description != null implies len(description) >= 10, "too short"
}
Dividing by zero is always bad. For example:
package Div_By_Zero
type T {
x Integer
y Integer
}
checks T {
x > 2, fatal "x too small"
y > 2, fatal "y too small"
100 / (111 - x * y) > 0, "example"
}
Would generate:
100 / (111 - x * y) > 0, "example"
^ div_by_zero.rsl:12: issue: divisor could be 0 [vcg-div-by-zero]
| example record_type triggering error:
| T bad_potato {
| x = 3
| y = 37
| }
To fix this either don't do such weird things ;) or perhaps use a prime number instead of 111:
checks T {
x > 2, fatal "x too small"
y > 2, fatal "y too small"
100 / (113 - x * y) > 0, "example"
}
With that fix the static analyser doesn't complain anymore as there is no way to get 113 by multiplying two numbers greater than 2.
Going out of bounds is also a classic error:
package Array_Index
type T {
x Integer [1 .. 10]
}
checks T {
len(x) >= 3 implies x[3] > 0, "too small"
}
Would generate:
len(x) >= 3 implies x[3] > 0, "too small"
^ array_index.rsl:8: issue: array index could be larger than len(x) [vcg-array-index]
| example record_type triggering error:
| T bad_potato {
| x = [1, 1, 1]
| }
You could fix this by either remembering that arrays start at 0 (so
change to len(x) >= 4
) or perhaps by making sure the array is always
at least 4 elements long (i.e. x Integer [4 .. 10]
).
Sometimes checks are just tautologies, and so they don't really serve any purpose.
package Always_True
type T {
description optional String
}
type Q extends T {
freeze description = "potato"
}
checks Q {
description != null, "description cannot be empty"
}
Would generate predictably:
description != null, "description cannot be empty"
^^ always_true.rsl:12: issue: expression is always true [vcg-always-true]
Maybe you meant to attach this check to type T instead of Q? I.e. like so:
package Always_True_Fixed
type T {
description optional String
}
checks T {
description != null, "description cannot be empty"
}
type Q extends T {
freeze description = "potato"
}
The static analyser does not consider this check to be always true now, since you could have instances of just T, even though Q would always make it true.