Skip to content

Pragmas Error Emissions

ike709 edited this page Nov 12, 2024 · 19 revisions

#pragma - OpenDream error emission configuration

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

Parity vs. Sanity

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.

Fatal Errors (0000 - 0999)

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."

0000 - Unknown

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.

0001 - BadToken

This code is emitted when an unexpected Token is reached.

0010 - BadDirective

This is emitted when a directive that doesn't exist is used:

#clowns disable // OD0002, directive doesn't exist

0011 - BadExpression

This is emitted when an invalid expression is reached:

var/foo = initial(bar()) // OD0011, Expected field or index for initial(), got proc call result

0012 - MissingExpression

This is emitted when an embedded expression is expected in a string, but not found.

0013 - InvalidArgumentCount

A builtin proc was given an invalid amount of arguments.

length(item1, item2) // OD0013, length() takes 1 argument

0014 - InvalidVarDefinition

A var definition is incomplete or otherwise invalid.

var/ // OD0014, Expected a var declaration

0015 - MissingBody

A code structure that requires a body does not have one.

// OD0015, do-while requires a non-empty block
do
while (TRUE)

0019 - BadLabel

A code label could not be resolved by the compiler.

0050 - InvalidReference

A reference is being treated as a value.

var/meep = .. // OD0050, Attempt to use proc "..()" as value

0100 - BadArgument

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

0101 - InvalidArgumentKey

Emitted when an invalid expression is used as a key for an arguments list.

0102 - ArglistOnlyArgument

NOTE: Reserved for a currently-unimplemented emission.

0200 - HardReservedKeyword

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

0404 - ItemDoesntExist

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

0405 - DanglingOverride

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

0406 - StaticOverride

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

0418 - IAmATeaPot

This is a reserved error for when OpenDream finally implements the HTCPC protocol.

0500 - HardConstContext

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

0501 - WriteToConstant

Emitted on an attempt to override a constant.

var/const/foo = 5
foo = 6 // OD0501, Cannot write to const var

0900 - InvalidInclusion

Emitted when anything other than a constant path is passed to #include

#include 1234 // OD0900, "1234" is not a valid include path

Preprocessor Configurable Errors (1000 - 1999)

1000 - FileAlreadyIncluded

Emitted when the same path is included multiple times.

#include "foo.dm"
#include "foo.dm" // OD1000, File "foo.dm" was already included

1001 - MissingIncludedFile

A file path passed to #include could not be found.

#include "awjeghjoiseg" // OD1001, Could not find included file "awjeghjoiseg"

1002 - InvalidWarningCode

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

1100 - MisplacedDirective

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

1101 - UndefineMissingDirective

Emitted when #undef is declared for a non-existent #define.

1150 - DefinedMissingParen

Note: This probably needs to be changed to an always-fatal error code. Emitted when defined(FOO is missing a closing parenthesis.

1200 - ErrorDirective

Emitted by #error directives. This pragma can be used to reduce the directives to warnings or disable them entirely.

1201 - WarningDirective

Emitted by #warn directives. This pragma can be used to elevate the directives to errors or disable them entirely.

1300 - MiscapitalizedDirective

Enforces the case-sensitivity of preprocessor directives.

#DEFINE FOO 1 // OD1300, #DEFINE is not a valid macro keyword. Did you mean '#define'?

Compiler Configurable Errors (2000 - 2999)

2000 - SoftReservedKeyword

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

2100 - DuplicateVariable

The same variable is defined several times.

var/x = 3;
var/x = 5; // OD2100 - x is already defined!

2101 - DuplicateProcDefinition

The same proc is defined several times.

/proc/foo()
    return
/proc/foo() // OD2101, Global proc foo() is already defined
    return

2205 - PointlessParentCall

A proc attempts to call a parent that does not exist, so the operation does nothing.

/proc/foo()
 . = ..() // OD2205 - Pointless call to parent

2206 - PointlessBuiltinCall

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.

2207 - SuspiciousMatrixCall

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

2208 - FallbackBuiltinArgument

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

2209 - PointlessScopeOperator

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"

2210 - PointlessPositionalArgument

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.

2211 - ProcArgumentGlobal

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"

2300 - MalformedRange

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

2301 - InvalidRange

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!")

2302 - InvalidSetStatement

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"

2303 - InvalidOverride

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

2304 - InvalidIndexOperation

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

2401 - DanglingVarType

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)

2500 - MissingInterpolatedExpression

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

2600 - AmbiguousResourcePath

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

2800 - UnimplementedAccess

Emitted when an attempt is made to use an unimplemented feature (often a proc or var)

Stylistic Configurable Errors (3000 - 3999)

3100 - EmptyBlock

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")

3101 - EmptyProc

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

3200 - UnsafeClientAccess

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

3201 - SuspiciousSwitchCase

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.

3202 - AssignmentInConditional

Emitted when = is used in an if() statement rather than ==

var/x = 5
if (x = 2) // OD3202, Assignment in conditional
  return x // 2

3203 - PickWeightedSyntax

Emitted when using the weighted form of pick()

pick(10;"A", 40;"B") // OD3203, Use of weighted pick() syntax

3204 - AmbiguousInOrder

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

3300 - RuntimeSearchOperator

This emits when using the runtime search operator e.g. foo:bar() instead of foo.bar().

Clone this wiki locally