-
Notifications
You must be signed in to change notification settings - Fork 113
Pragmas Error Emissions
Through the #pragma
directive, the errors and warnings emitted by OD can be configured to suit your codebase.
For the current default pragma settings and an up-to-date list of all pragmas, see here
There are many undocumented, strange behaviours within the BYOND implementation of DM and its engine. This project aims to replicate this behaviour as best as we can, in order to maximize your ability to migrate onto our software.
However, much of this behaviour results in code operating in a way that is not what the programmer intended, and not all codebases require all the cursed behaviour in order to successfully run. This creates a design problem: Do we prefer creating an engine that prioritizes parity over sanity, or sanity over parity?
The answer, is "both." Many of the possible errors emittable by the compiler can be enabled or disabled arbitrarily through #pragma
directives:
#pragma ErrorDirective warning // Turns #error directives into simple warnings
#error "*deathgasp" // This only creates a warning on-compile.
Every possible warning has a name and a code, and can be referenced either way within one of these directives.
The default configuration file is aimed at providing a reasonable version of DM, balancing parity and sanity. Only behaviour that is clearly unintentional is raised as an error
. Configuration files for builds that prefer parity, or prefer sanity, are coming soon:tm:.
The following is a (hopefully!) exhaustive list of every error code and its use. If it is incomplete, you can help by expanding it.
Errors with codes less than 1000 are fatal. They cannot be disabled through #pragma
directives. Errors of this genre usually are fundamental errors in the grammar of the source code given, making reasonable compilation impossible. In C++ lingo, any code that hits one of these errors is "ill-formed."
This code is the default for errors which have yet to be assigned a proper error code. They are always fatal, and the only information available is the error message you receive upon this error occurring.
This code is emitted when an unexpected Token is reached.
This is emitted when a directive that doesn't exist is used:
#clowns disable // OD0002, directive doesn't exist
This is emitted when an invalid expression is reached:
var/foo = initial(bar()) // OD0011, Expected field or index for initial(), got proc call result
This is emitted when an embedded expression is expected in a string, but not found.
A builtin proc was given an invalid amount of arguments.
length(item1, item2) // OD0013, length() takes 1 argument
A var definition is incomplete or otherwise invalid.
var/ // OD0014, Expected a var declaration
A code structure that requires a body does not have one.
// OD0015, do-while requires a non-empty block
do
while (TRUE)
A code label could not be resolved by the compiler.
A reference is being treated as a value.
var/meep = .. // OD0050, Attempt to use proc "..()" as value
Emitted when a known-invalid argument is being passed to a proc.
/proc/foo()
return nameof(__TYPE__) // OD0100, Attempt to get nameof(__TYPE__) in global proc
Emitted when an invalid expression is used as a key for an arguments list.
NOTE: Reserved for a currently-unimplemented emission.
NOTE: Currently unimplemented. This scenario currently emits "Expected end of object statement" errors. This code is emitted for keywords that are reserved and cannot be reasonably unreserved.
var/if = 7; // OD0200 - if is hard-reserved and cannot be the name of a variable
A generic error to imply that something is missing, such as a variable.
var/y = 5;
x = 3; // OD0404 - the variable x has yet to be defined
NOTE: Currently unimplemented. This fires when a proc's override is defined, but not the original proc.
/proc/foo() // original
/foo() // override
/poo() // OD0405 - no definition for /proc/poo() found
This fires if you attempt to override a static, which is ill-formed in DM.
/foo
var/static/x = 5
/foo/bar
x = 6 // OD0406 - cannot override a static
This is a reserved error for when OpenDream finally implements the HTCPC protocol.
A non-constant is being used in a scenario that requires a constant.
var/const/foo = bar() // OD0500, Const var must be set to a constant
Emitted on an attempt to override a constant.
var/const/foo = 5
foo = 6 // OD0501, Cannot write to const var
Emitted when anything other than a constant path is passed to #include
#include 1234 // OD0900, "1234" is not a valid include path
Emitted when the same path is included multiple times.
#include "foo.dm"
#include "foo.dm" // OD1000, File "foo.dm" was already included
A file path passed to #include
could not be found.
#include "awjeghjoiseg" // OD1001, Could not find included file "awjeghjoiseg"
A pragma referenced an error/warning code that does not exist.
It is not recommended to elevate this to an error currently as OD's pragmas are constantly changing and doing so makes it harder for us to test against your code.
#pragma foo error // OD1002, Warning 'foo' does not exist
A preprocessor directive is being used in an invalid context.
var/foo = 5 #warn Foo is 5 // OD1001, There can only be whitespace before a preprocessor directive
Emitted when #undef
is declared for a non-existent #define
.
Note: This probably needs to be changed to an always-fatal error code.
Emitted when defined(FOO
is missing a closing parenthesis.
Emitted by #error
directives. This pragma can be used to reduce the directives to warnings or disable them entirely.
Emitted by #warn
directives. This pragma can be used to elevate the directives to errors or disable them entirely.
Enforces the case-sensitivity of preprocessor directives.
#DEFINE FOO 1 // OD1300, #DEFINE is not a valid macro keyword. Did you mean '#define'?
For keywords that SHOULD be reserved, but aren't in every context. 'null' and 'defined', for instance.
var/null = 5 // OD2000, 'null' is not a valid variable name
The same variable is defined several times.
var/x = 3;
var/x = 5; // OD2100 - x is already defined!
The same proc is defined several times.
/proc/foo()
return
/proc/foo() // OD2101, Global proc foo() is already defined
return
A proc attempts to call a parent that does not exist, so the operation does nothing.
/proc/foo()
. = ..() // OD2205 - Pointless call to parent
A call to a builtin proc doesn't actually do anything, or does something you may not expect it to.
/obj/var/static/x = 6
/proc/foo()
var/obj/O = new
O.x = 22
var/x = initial(O.x) // OD2206 - Calling initial on a static returns its current value; x becomes 22 here.
A variant of BadArgument
for matrix()
specifically.
var/foo = matrix(a, b, c, d, e) // OD2207 - Calling matrix() with 5 arguments will always error when called at runtime
A built-in proc is coercing an invalid argument type to a fallback value and producing a potentially-unexpected result.
sin("foo") // OD2208, Invalid value treated as 0, sin(0) will always be 0
Emits when the ::
scope operator, when used on a proc path, will return null. This occurs for any identifier other than name
.
world.log << (/datum/proc/foo::name) // prints "foo"
world.log << (/datum/proc/foo::desc) // OD2209 - scope operator returns null on proc variables other than "name"
A numerical argument key was used in a proc call that made no difference.
// OD2210 - The argument at index 1 is a positional argument with a redundant index ("1 = value" at argument 1). This does not function like a named argument and is likely a mistake.
// OD2210 - The argument at index 2 is a positional argument with a redundant index ("2 = value" at argument 2). This does not function like a named argument and is likely a mistake.
// OD2210 - The argument at index 3 is a positional argument with a redundant index ("3 = value" at argument 3). This does not function like a named argument and is likely a mistake.
severity = pickweight(
1 = 8,
2 = 5,
3 = 3
)
Note that putting those arguments in any different order would have been an error.
Placing a slash at the beginning of a parameter will treat it as a global var definition in BYOND. It is not treated this way in OpenDream, but it is still not the preferred way to define a parameter.
/proc/foo(/var/mob/user) // OD2211 - Proc argument "/var/mob/user" starting with "/var/" will create a global variable. Replace with "var/mob/user"
A range (such as within the case statement of a switch block) contains a null
value, which is constant but still coerced to zero and may produce unexpected results.
/proc/example()
var/x = 5
switch(x)
if(null to 10) // OD2300 - Malformed range, lower bound is coerced from null to 0
return x // x is returned
A range (such as within the case statement of a switch block) is invalid, as it features non-constant values, or strings. If not an error, strings and other non-numeric objects will be transmuted into zeroes. Most of these cases do actually compile in BYOND, so this is a non-fatal error.
/proc/huh()
var/x = "c"
switch(x)
if("a" to "z") // OD2301 - this will become 0 to 0
CRASH("No crash happens!")
A set statement has a definition which is invalid, likely because it is non-const. If not an error, the statement will be made equivalent to the last statement run. Since string interpolations like "A string with a [MACRO_CONSTANT]"
are non-constant, this can result in unexpected behaviour:
#define WORD "hello"
/verb/hello
set name = "hello there"
set desc = "You use this verb to say [WORD]!" //OD2302 - The description will be set to "hello there"
Emitted when a global proc is overridden, which BYOND silently ignores.
/proc/foo()
return 1
/foo() // OD2303, Global procs cannot be overridden - 'foo()' override will be ignored
return 0
/proc/main()
world.log << foo() // returns 1
Emitted attempting to index a datum, which is now a runtime error in BYOND 515.1641+
var/datum/foo = new
world.log << foo["bar"] // OD2304, "Invalid index operation. datum[] index operations are not valid starting in BYOND 515.1641
Note: Not currently implemented. For types inferred by a particular var definition and nowhere else, that ends up not existing (not forced-fatal because BYOND doesn't always error)
A text macro (\ref
, \a
, \him
, etc) was missing a required interpolated expression.
var/message = "\a vendomat" //OD2500 - This will produce a slightly malformed string
Resource paths are case-insensitive, even when the filesystem is not. This emission means multiple files were found that could match the resource.
var/file = 'resource.txt' // OD2600 - Ambiguous resource path if both the files 'resource.txt' and 'RESOURCE.txt' exist
Emitted when an attempt is made to use an unimplemented feature (often a proc or var)
Fires if a control-flow keyword is used without its corresponding block. Does not fire for empty functions. Can be suppressed with ;
/proc/test()
if(TRUE) // OD3100 - Empty block detected
else if(TRUE)
; // A null statement. Not considered an empty block.
else
CRASH("The concept of truth has failed")
Fires if a proc declaration is entirely empty, and encourages the addition of an explicit return
statement.
/proc/foo() // OD3101, Empty proc detected - add an explicit "return" statement
Emitted when client
is accessed with .
instead of ?.
. Intended to enforce continuous null-checking of clients.
Note: This is a naive lint, and does not consider if the client has already been null-checked (see example code below).
/proc/foo(var/client/client)
if(client.foo()) // OD3200, Unsafe "client" access. Use the "?." operator instead
return
else if(client?.foo()) // Does not emit
return
else if(client != null && client.foo()) // OD3200, Unsafe "client" access. Use the "?." operator instead
// This lint is too simple and will still emit even though we explicitly checked that client is not null
return
Emits when else if()
is used as a switch()
case, as BYOND treats this as follows:
// This is the code you wrote:
var/x = 5
switch(x)
if(1 to 10)
return x
else if(11 to 20) // OD3201, Expected "if" or "else" - "else if" is ambiguous as a switch case and may cause unintended flow
return x*2
else
return null
// This is how BYOND interprets it:
var/x = 5
switch(x)
if(1 to 10)
return x
else
if(11 to 20)
return x*2
else
return null
Note: This is a naive lint and will always emit for else if()
cases, even in scenarios when the specific logic of the switch()
does not result in unexpected behavior.
Emitted when =
is used in an if()
statement rather than ==
var/x = 5
if (x = 2) // OD3202, Assignment in conditional
return x // 2
Emitted when using the weighted form of pick()
pick(10;"A", 40;"B") // OD3203, Use of weighted pick() syntax
The order of operations for in
can be easy to get wrong. This emits when the order of operations around in
can be ambiguous.
// Parsed as ("item1" in (a || "item2")) in b
if ("item1" in a || "item2" in b) // OD3204, Order of operations for "in" may not be what is expected. Use parentheses to be more explicit.
return TRUE
// Parsed as "item1" in (a || b)
if ("item1" in a || b) // OD3204, Order of operations for "in" may not be what is expected. Use parentheses to be more explicit.
return TRUE
// This is probably what you want
if (("item1" in a) || b)
return TRUE
// This is fine
if ("item1" in (a || b))
return TRUE
This emits when using the runtime search operator e.g. foo:bar()
instead of foo.bar()
.