Appendix D: Common Compiler Errors and Fixes

This appendix catalogs the most common Free Pascal compiler errors and warnings you will encounter, organized by category. For each error, you will find the exact error message, a plain-English explanation, the most common causes, and a concrete example showing both the broken code and the fix.


D.1 Syntax Errors

Error: "Syntax error, ; expected but ELSE found"

What it means: You placed a semicolon before an else clause. In Pascal, if...then...else is a single statement, and a semicolon terminates the then branch prematurely.

Common cause: Writing a semicolon after the statement before else.

{ WRONG }
if X > 0 then
  WriteLn('Positive');    { <-- semicolon here ends the if statement }
else
  WriteLn('Non-positive');

{ FIX }
if X > 0 then
  WriteLn('Positive')     { <-- no semicolon before else }
else
  WriteLn('Non-positive');

Error: "Syntax error, END expected but identifier found"

What it means: The compiler expected an end keyword to close a begin block, record, or class definition, but found something else instead.

Common cause: Mismatched begin/end pairs. You opened a begin block but forgot to close it, or you have an extra begin.

{ WRONG }
procedure DoWork;
begin
  if Ready then
  begin
    Process;
    { Missing 'end' for this begin }
  WriteLn('Done');
end;

{ FIX }
procedure DoWork;
begin
  if Ready then
  begin
    Process;
  end;
  WriteLn('Done');
end;

Tip: Use consistent indentation to make begin/end mismatches visible. Lazarus highlights matching pairs.

Error: "Syntax error, ) expected but , found"

What it means: A function call or declaration has a parenthesis mismatch.

Common cause: Missing closing parenthesis in a nested function call, or using commas where semicolons are expected in a declaration.

{ WRONG }
WriteLn(IntToStr(X + Y);     { Missing closing paren for IntToStr }

{ FIX }
WriteLn(IntToStr(X + Y));

Error: "Syntax error, BEGIN expected but procedure found"

What it means: The compiler reached a procedure/function declaration where it expected the main program's begin block.

Common cause: Missing begin for the main program block, or declaring a procedure after the main begin.

{ WRONG }
program Test;

procedure Helper;       { OK }
begin
  WriteLn('helper');
end;

WriteLn('main');        { Error: no begin before this }
end.

{ FIX }
program Test;

procedure Helper;
begin
  WriteLn('helper');
end;

begin                   { Main program begin }
  WriteLn('main');
end.

Error: "Syntax error, OF expected but ; found"

What it means: A case statement is missing the of keyword.

{ WRONG }
case Choice;
  1: WriteLn('One');
end;

{ FIX }
case Choice of
  1: WriteLn('One');
end;

Error: "Unexpected end of file"

What it means: The compiler reached the end of the source file while still expecting more tokens.

Common causes: - Missing the final end. (with a period) at the end of a program or unit. - An unclosed string literal (missing closing quote). - An unclosed comment ({ without }, or (* without *)).

{ WRONG }
program Test;
begin
  WriteLn('Hello');
end                    { Missing period }

{ FIX }
program Test;
begin
  WriteLn('Hello');
end.                   { Period terminates the program }

Error: "Syntax error, STRING expected but identifier found"

What it means: Often caused by writing string in a context where the compiler expects a type name, or by a mode mismatch.

Common cause in ObjFPC mode: Using generic syntax without the generic keyword.

{ WRONG in {$mode objfpc} }
type
  TList<T> = class    { Delphi syntax }
  end;

{ FIX }
type
  generic TList<T> = class    { ObjFPC syntax }
  end;

D.2 Type Errors

Error: "Incompatible types: got X expected Y"

What it means: You are assigning or passing a value of one type where a different type is required.

{ WRONG }
var
  S: String;
  N: Integer;
begin
  S := 'Hello';
  N := S;              { Cannot assign String to Integer }
end;

{ FIX }
N := Length(S);        { Or whatever conversion is appropriate }
N := StrToInt(S);      { If S contains a number }

Error: "Incompatible types: got ShortString expected AnsiString"

What it means: You are mixing string types. This commonly occurs when {$H+}` and `{$H-} modes are mixed.

{ FIX: Ensure consistent {$H+} at the top of all units }
{$mode objfpc}{$H+}

Error: "Error: Operator is not overloaded: X + Y"

What it means: You used an operator with types for which that operator is not defined.

{ WRONG }
var
  A: Boolean;
  B: Integer;
begin
  B := A + 5;          { Cannot add Boolean and Integer }
end;

{ FIX }
if A then
  B := 1 + 5
else
  B := 0 + 5;

{ Or use Ord: }
B := Ord(A) + 5;

Error: "Can't assign values to const variable"

What it means: You tried to modify a const parameter or a true constant.

{ WRONG }
procedure Process(const X: Integer);
begin
  X := X + 1;          { Cannot modify const parameter }
end;

{ FIX: Use a local variable }
procedure Process(const X: Integer);
var
  LocalX: Integer;
begin
  LocalX := X + 1;
end;

{ Or change to a value parameter: }
procedure Process(X: Integer);
begin
  X := X + 1;          { This modifies the local copy }
end;

Error: "Incompatible type for arg no. N: got X expected var Y"

What it means: You passed an expression or a constant to a var parameter. var parameters require a variable (an lvalue) because the procedure may modify it.

{ WRONG }
procedure Increment(var X: Integer);
begin
  Inc(X);
end;

Increment(5);          { Cannot pass a literal to var }
Increment(A + B);      { Cannot pass an expression to var }

{ FIX }
var
  N: Integer;
begin
  N := 5;
  Increment(N);        { Pass a variable }
end;

Error: "Error: Type mismatch between X and Y"

What it means: Two types that look the same are actually different named types.

type
  TAge = Integer;
  TCount = Integer;

{ These are distinct types in {$mode objfpc} with strict type checking.
  In most cases this works fine, but if you see this error, cast explicitly: }
var
  A: TAge;
  C: TCount;
begin
  A := 25;
  C := TCount(A);     { Explicit cast }
end;

Error: "Illegal type conversion: X to Y"

What it means: You used an invalid typecast. Not all types can be cast to each other.

{ WRONG }
var
  S: String;
  N: Integer;
begin
  N := Integer(S);     { Cannot cast String to Integer }
end;

{ FIX: Use a conversion function }
N := StrToInt(S);

D.3 Scope and Declaration Errors

Error: "Identifier not found X"

What it means: You used a name that has not been declared in the current scope.

Common causes: - Typo in the identifier name. - Forgot to declare a variable. - Forgot to add a unit to the uses clause. - The identifier is declared in a different scope (inside another procedure, in the implementation section of a unit, etc.).

{ WRONG }
begin
  Writeln(Greeting);   { 'Greeting' not declared }
end;

{ FIX }
var
  Greeting: String;
begin
  Greeting := 'Hello';
  WriteLn(Greeting);
end;

{ Or if it's in a unit: }
uses
  MyUtils;             { Add the unit that declares Greeting }

Error: "Duplicate identifier X"

What it means: Two declarations in the same scope share the same name.

{ WRONG }
var
  Count: Integer;
  Count: Integer;      { Duplicate }

{ FIX: Remove the duplicate, or use different names }
var
  Count: Integer;
  ItemCount: Integer;

Error: "Forward declaration not solved X"

What it means: You declared a procedure or function with forward but never provided the implementation.

{ WRONG }
procedure DoWork; forward;

{ ... but DoWork is never implemented }

{ FIX: Provide the implementation }
procedure DoWork; forward;

procedure DoWork;
begin
  WriteLn('Working');
end;

This error also appears for methods declared in a class definition but not implemented.

Error: "Unit X not found"

What it means: The compiler cannot find the unit you listed in the uses clause.

Common causes: - Typo in the unit name. - The unit is not installed (e.g., a Lazarus-specific unit used in a plain FPC project). - The unit search path does not include the directory containing the unit.

{ WRONG }
uses
  SysUtil;             { Typo: should be SysUtils }

{ FIX }
uses
  SysUtils;

To add a custom unit search path, use the -Fu compiler option or set it in Lazarus under Project > Project Options > Compiler Options > Paths > Other unit files.

Error: "Illegal expression" or "Variable identifier expected"

What it means: A statement or expression appears where only a variable is allowed.

{ WRONG }
5 := X;                { Cannot assign to a literal }
A + B := C;            { Cannot assign to an expression }

{ FIX }
X := 5;
C := A + B;

D.4 File and I/O Errors

Error: "File not found X"

What it means (compile time): An {$I filename} include directive or uses clause references a file that does not exist at the expected path.

Common cause: Wrong filename, wrong directory, or missing the .inc extension.

{ WRONG }
{$I missing_file.inc}

{ FIX: Verify the file exists and the path is correct }
{$I settings.inc}

Runtime Error 2: "File not found"

What it means: Your program tried to open a file that does not exist (via Reset on a nonexistent file).

{ WRONG }
AssignFile(F, 'nonexistent.txt');
Reset(F);              { Runtime error if file does not exist }

{ FIX: Check first }
AssignFile(F, 'data.txt');
if FileExists('data.txt') then
begin
  Reset(F);
  { ... }
  CloseFile(F);
end
else
  WriteLn('File not found!');

Runtime Error 5: "File access denied"

What it means: The program does not have permission to open the file, or the file is locked by another process.

Fix: Check file permissions, ensure no other program has the file open, and verify you are not trying to write to a read-only location.

Runtime Error 100: "Disk read error" / Runtime Error 101: "Disk write error"

What it means: A physical I/O error occurred, or you attempted to read past the end of a file, or write to a full disk.

Fix: Use {$I-} and check IOResult for graceful error handling:

{$I-}
Reset(F);
if IOResult <> 0 then
begin
  WriteLn('Cannot open file');
  Halt(1);
end;
{$I+}

D.5 OOP Errors

Error: "Abstract method X not implemented"

What it means: You created an instance of a class that has abstract methods without implementing them in a descendant.

{ WRONG }
type
  TShape = class
    function Area: Double; virtual; abstract;
  end;

var
  S: TShape;
begin
  S := TShape.Create;   { Error: TShape has abstract methods }
end;

{ FIX: Create a descendant that implements the abstract method }
type
  TCircle = class(TShape)
    Radius: Double;
    function Area: Double; override;
  end;

function TCircle.Area: Double;
begin
  Result := Pi * Radius * Radius;
end;

var
  S: TShape;
begin
  S := TCircle.Create;   { OK: TCircle implements Area }
end;

Error: "No matching implementation for interface method X"

What it means: A class declares that it implements an interface but is missing one or more of the interface's methods.

{ WRONG }
type
  IDrawable = interface
    procedure Draw;
    procedure Resize(Factor: Double);
  end;

  TWidget = class(TInterfacedObject, IDrawable)
    procedure Draw;
    { Missing: procedure Resize(Factor: Double); }
  end;

{ FIX: Implement all interface methods }
  TWidget = class(TInterfacedObject, IDrawable)
    procedure Draw;
    procedure Resize(Factor: Double);
  end;

Error: "method X not found in class Y"

What it means: You tried to override or call a method that does not exist in the parent class.

{ WRONG }
type
  TBase = class
    procedure DoWork;
  end;

  TChild = class(TBase)
    procedure DoStuff; override;   { 'DoStuff' does not exist in TBase }
  end;

{ FIX: Match the parent method name exactly }
  TChild = class(TBase)
    procedure DoWork; override;    { Correct name }
  end;

Note: The parent method must also be declared virtual or dynamic for override to work.

Error: "Calling convention mismatch"

What it means: The method signature in the implementation does not match the declaration, including calling convention modifiers.

Fix: Ensure the implementation exactly matches the declaration, including all modifiers (virtual, override, reintroduce, stdcall, etc.).

Error: "Constructor or Destructor must be a method"

What it means: You declared a constructor or destructor outside of a class definition.

{ WRONG }
constructor Create;    { Not inside a class }
begin
end;

{ FIX: Declare inside a class }
type
  TMyClass = class
    constructor Create;
  end;

constructor TMyClass.Create;
begin
  inherited Create;
end;

Error: "Virtual or dynamic methods not allowed in records"

What it means: Records (even advanced records with methods) do not support virtual method dispatch. Only classes do.

{ WRONG }
type
  TPoint = record
    X, Y: Double;
    procedure Draw; virtual;    { Not allowed in record }
  end;

{ FIX: Use a class, or remove 'virtual' }
type
  TPoint = record
    X, Y: Double;
    procedure Draw;             { Regular method is fine }
  end;

D.6 Warnings and Hints

While not errors, these messages indicate potential problems. Good practice is to compile with zero warnings.

Warning: "Variable X does not seem to be initialized"

What it means: You are using a local variable before assigning it a value. The variable contains garbage.

{ WARNING }
function Compute: Integer;
var
  Total: Integer;       { Not initialized }
begin
  Total := Total + 5;   { Warning: Total is garbage here }
  Result := Total;
end;

{ FIX }
function Compute: Integer;
var
  Total: Integer;
begin
  Total := 0;           { Initialize first }
  Total := Total + 5;
  Result := Total;
end;

Warning: "Function result variable does not seem to be set"

What it means: A function might return without setting Result (or the function name).

{ WARNING }
function IsPositive(X: Integer): Boolean;
begin
  if X > 0 then
    Result := True;
  { What if X <= 0? Result is unset }
end;

{ FIX }
function IsPositive(X: Integer): Boolean;
begin
  Result := X > 0;
end;

Hint: "Local variable X not used"

What it means: You declared a variable but never referenced it.

Fix: Remove the unused variable, or prefix it with an underscore if it is intentionally unused (some coding standards).

Hint: "Value parameter X is assigned but never used"

What it means: You assigned a value to a value parameter, but the result is never used. Since value parameters are copies, the assignment has no effect outside the procedure.

Fix: If you intend to modify the caller's variable, change to a var parameter. Otherwise, remove the assignment.

Warning: "Comparison always evaluates to True/False"

What it means: The compiler detected a comparison whose result is known at compile time.

{ WARNING }
var
  B: Byte;   { Range 0..255 }
begin
  if B < 0 then         { Always False: Byte cannot be negative }
    WriteLn('Negative');
end;

{ FIX: Remove the dead code or use an appropriate type }
var
  N: Integer;            { Use Integer if negative values are possible }
begin
  if N < 0 then
    WriteLn('Negative');
end;

Warning: "Implicit string type conversion"

What it means: The compiler is converting between string types (e.g., ShortString to AnsiString) automatically. This may lose data or have performance implications.

Fix: Use explicit conversions, or ensure consistent string types and {$H+} settings across all units.


D.7 Runtime Errors

These are not compiler errors but errors that occur when the program runs.

Code Name Common Cause
1 Invalid function number Obsolete DOS function call
2 File not found Reset on nonexistent file
3 Path not found Directory does not exist
4 Too many open files OS file handle limit reached
5 Access denied Permission problem
6 Invalid file handle Using a file variable that was not opened
100 Disk read error I/O failure during read
101 Disk write error I/O failure during write or disk full
103 File not open Read/Write on a file that was not opened
104 File not open for input Read on a file opened with Rewrite
105 File not open for output Write on a file opened with Reset
106 Invalid numeric format Read of a number from text with invalid characters
200 Division by zero Integer div or mod by zero
201 Range check error Array index out of bounds or value out of range (with {$R+})
202 Stack overflow Too-deep recursion or very large local variables
203 Heap overflow New/GetMem failed — out of memory
204 Invalid pointer operation Dispose/FreeMem on invalid pointer
205 Floating point overflow Result too large for floating-point type
207 Invalid floating point operation e.g., Sqrt(-1), Ln(0)
210 Object not initialized Calling a method on a nil object reference
211 Call to abstract method Called an abstract method at runtime
215 Arithmetic overflow Integer overflow (with {$Q+})
216 Access violation Dereferencing nil or invalid pointer
217 Unhandled exception An exception propagated to the top level
227 Assertion failed Assert(False) or condition evaluated to False

Enabling Runtime Checks

During development, enable all runtime checks to catch bugs early:

{$R+}    { Range checking }
{$Q+}    { Overflow checking }
{$S+}    { Stack checking }

Disable them in release builds for performance:

{$R-}
{$Q-}
{$S-}

Or in Lazarus: Project > Project Options > Compiler Options > Debugging > Check for range/overflow.


D.8 Troubleshooting Strategy

When you encounter a compiler error you do not recognize:

  1. Read the full error message carefully. It includes the file name, line number, and column.
  2. Look at the line indicated and the line before it. Many errors are caused by a mistake on the previous line (e.g., a missing semicolon).
  3. Fix the first error only, then recompile. Later errors are often cascading consequences of the first one.
  4. Check begin/end balance. Mismatched blocks cause confusing errors far from the actual problem.
  5. Check the uses clause. Many "identifier not found" errors mean you forgot to include the right unit.
  6. Search the Free Pascal documentation. The error code (e.g., "Error 3036") is documented in the FPC user manual.
  7. Ask for the verbose error. Compile with fpc -vew to see errors, warnings, and notes all at once.

This appendix covers the errors you will encounter most frequently. As you gain experience, you will recognize these patterns instantly and fix them in seconds.