IFDEF Changes explained

What changed

The biggest change in the v10 compiler is how ifdefs are parsed. In the past we had a preprocessor, a tokenizer and a parser. The preprocessor would be provided with a list of defines, remove all code that was {$IFDEF}'d out and hand that to the parser. The new compiler does this completely different: It tries to parse ifdefs as if they were regular code concepts like statements. The reason we did this is to allow for a big future feature (can’t talk about that one yet), and make shared files a LOT faster, since the file only has to be parsed once, not once per platform.

How it works

How it works very much depends on where the parser is. There’s a lot of recovery involved but the general rule is that ifdefs now have to be at the same level as their else/endif and have to encapsulate a full concept. For example, this is allowed:

{$IFDEF CLR}
method Test;
{$ENDIF}

This is not:

method {$IFDEF CLR}Test{$ELSE}Test2{$ENDIF};

To do above, it should look like:

{$IFDEF CLR}
method Test;
{$ELSE}
method Test2;
{$ENDIF}

Ifdefs are allowed around and inside types, but if started before a type, they have to end after it, not inside:
Not allowed:

{$IFDEF TEST}
type ABC = class 
  method Test;
{$ENDIF}
end;

Allowed:

{$IFDEF TEST}
type ABC = class 
  method Test;
end;
{$ENDIF}

and

type ABC = class 
{$IFDEF TEST}
  method Test;
{$ENDIF}
end;

Types

In any place that accepts a type references ifdef is allowed too, to create a type that refers to a different one:

var x: {$IFDEF CLR}System.String{$ELSEIF JAVA}java.lang.String{$ENDIF};

This becomes a System.String or java.lang.String for CLR, Java and an “Error Type” on any other platform (Error types fail when they are seen by the compiler).

Statements

For statements the same rules as above apply, ifdefs have to contain a balanced statement.
Not allowed:

begin
  if a = 15 then begin {$IFDEF TEST}
    Step1;
  end else begin {$ENDIF}
     Step2;
  end;

Allowed:

begin
  {$IFDEF TEST2}
  if a = 15 then begin 
    {$IFDEF TEST}
    Step1;
    {$ENDIF}
  end else begin 
     Step2;
  end;
  {$ENDIF}

Expressions

Ifdefs in expressions are only allowed at the start of any expression like:

MyMethod.DoCall({$IFDEF TEST}a{$ELSE}b{$ENDIF}.Name);

Attributes

Attributes can be ifdefd out in any way, as long as the ifdef captures the whole attribute and not part of it.

Methods

One thing that compiler now enforces (which I think will aid big time in cross platform development) is that the interface and implementation of a method has to be consistent in it’s ifdefs. The compiler will error if the ifdef expression doesn’t match:

{$IFDEF A}
type
  MyClass = public class
  public 
    {$IFDEF B}
    method Test;
    {$ENDIF}
  end;
{$ENDIF}
...
{$IFDEF A and B}
method MyClass.Test;
begin
end;
{$ENDIF}

Here the ifdef around Test has to be “A and B” to match the combined ifdef of the type above.

Ifdefs around method modifiers are allowed (like {$IFDEF TEST}virtual;{$ENDIF}), but not for empty as that would conditionally break the link between the interface and implementation. Additionally the compiler enforces that the signature of a method in the interfaces matches the implementation exactly, not just logically.

Uses

For uses we have logic that parses pretty much anything you can throw at it.

Other cases

We’ve added recovery and lookahead/back code for a lot of special cases that we hit in our own code but did have to do a few smaller changes. We’d like to hear about bugs you find so we can see if they’re fixable in the new compiler.

1 Like

Thanks a lot for these Post,
it makes the new rules more clear (at least for me :slight_smile: )

Hi,
Im migrating code compiling ok with Fire 9.3. Is a little project sharing code between android and IOS. Have 262 errors when try to build on element.

Can plase tell me what is wrong with this code?

You can not ifdef just the header of a method. You will need to either ifdef the entire method, or (better) restructure it to have the same signature on all platforms (possibly using a dummy/alias type.

For example, ou could define Activity = Object for non-Java, and either pass a (dummy) value, or add a second overload w/o parameter that called the other one.

eg:

{$IF NOT JAVA}
type Activity = Object
{$ENDIF}

{$IF NOT JAVA}
method doCheckLogin;
begin
  docheckLogin(nil);
end;
{$ENDIF}

method doCheckLogin(aActivity: Activity);
...

in general, id recommend rethinking your cross-platform design/abstraction, if you need to rely on a lot of places where you need to pass extra parameters on Android only; it might make sense to instead “register” the Activity on a. property once, but then have the same methods you can call from either platform, w/o passing it explicitly…

1 Like

Excelent!

Another problem

Yep. as Carlo explained, that’s no longer supported. IFDEFs must wrap full code structures. easiest fix is to use defined instead.

if defined("IOS") and (e.name = ...) then
  fData ...
else
  fData ...

(ps. if you post code instead of screenshots, that makes it a dozen times easier to help you by posting revised versions. you can use Cmd-Option-C to copy code PLUS error messages.)

I attach test project to demonstrate few our problems :
ClassLibrary2.zip (14.3 KB)

  1. $INCLUDE support is not working as in v9 because of :
  • internal parse errors (Delphi formatting that was partially fixed)
  • .inc files that are added to a project cannot be found because they are in different directories so we must add relative paths in define or copy .inc files
  • if the .inc file is parsed somehow, the compiler is expecting other code than uses section (like in our sample)
  1. initialization and finalization unit sections are not ignored anymore for other platforms than Delphi.
  2. For many common classes we have Delphi destructor like :
    {$IFNDEF MANAGED} destructor Destroy ; override ; {$ENDIF}
    that compiler reports as “(E1) semicolon (:wink: expected, got identifier.”
  3. adding uses section in implementation part of unit - compiler ignores define and tries to parse Delphi units in .NET :
    {$IFDEF DCC} uses System.SysUtils ; {$ENDIF}
    raising (E26) Unknown namespace “System.SysUtils” in uses list.

We have huge libraries sharing code for Delphi, .NET and Java projects that compile perfect in v9, but not in v10. This article explained most issues that we have and must fix. I will report more problems after we isolate them as test cases.

Agree

The problem with some piece of code is incompatible with another platform, as this:

  {$IFDEF JAVA}
  if aBriefCase.TableNames.length > 0 then
  {$ELSE}
  if aBriefCase.tableNames.count > 0 then
  {$ENDIF}

How to solve?

Another problem, how to standarize signature for a class, if inhertes from classes specifics for a platform?

  {$IFDEF JAVA}
  DataAccess = public class(android.app.Application, SharedPreferences.OnSharedPreferenceChangeListener)
  {$ELSE}
  DataAccess = public class(IDARemoteDataAdapterDelegate)
  {$ENDIF}
  private
...
  end;

Another situation. Seems like the moethod to be overrided need to use the same parameters name?

This was valid in Elements 9.3

method onMenuItemSelected(aFeatureId: Integer; anItem: MenuItem): Boolean; override;

Compiler complains now.

Must use the same parameters names. This is valid.

method onMenuItemSelected(featureId: Integer; item: MenuItem): Boolean; override;

Please confirm.

Edited: Seems like ever if use the same variable name compilers complain

method onCreateOptionsMenu(menu: Menu): Boolean; override; // E385 The method "method onCreateOptionsMenu(Menu): nullable Boolean" does not match that found in the base class ("method onCreateOptionsMenu(Menu): RemObjects.Oxygene.System.Boolean")
                                                           // H17 Return value does not match, is "nullable Boolean, should be "RemObjects.Oxygene.System.Boolean"

Ok dont show that error details until i copied… so after POST i see the solution was declare in this way.

method onCreateOptionsMenu(menu: Menu): RemObjects.Oxygene.System.Boolean; override;

Why cant use default boolean? theres a mistake in the uses order?

Easy, and in fact this gets cleaner:

if (defined("JAVA") and (aBriefCase.TableNames.length > 0)) or (not defined("JAVA") and (aBriefCase.tableNames.count > 0)) then
...

Best way would be to define an alias, for example:

type
  {$IFDEF JAVA}
  DataAccessBase = android.app.Application;
  {$ELSE}
  DataAccessBase = Object;
  {$ENDIF}

  DataAccess = public class(DataAccessBase)
  {$ENDIF}
  private
    ...
  end;

that sounds like a bug, the compiler thinking the return is a nullable Boolean. I’d need a complete test case that shows this to comment more.

Thanks, logged as bugs://79236 << for Artur’s issues

I haven’t yet gotten around to even installing v10, but wondering if this would work:

var numTableNames = if defined("JAVA") then aBriefCase.TableNames.length 
               else if not defined("JAVA") then aBriefCase.tableNames.count
               else 0;

Also, does this new syntax still result in code not applicable to the "current’ platform being rendered as a comment vs “active” code ? (I fervently hope so)

yes, this will work. the code eliminated by defined() (or $IFDEF) needs to be syntactically correct, but its identifiers don’t need to exist. e.g. you can do

if defined(“TOFFEE”) and UIDevice.currentDevice.paradigm = iPad then...
  // runs only  for TOFFEE

and it will compile ok for ECHOES, simply skipping the whole block, even though Echoes does not know UIDevice. Boolean short-circuit rules will apply, e.g. unknown stuff can be ANDed with an undefined define, and ORed with a defined define, eg:

if defined(“TOFFEE”) or Environment.OS = macOS then... 
 // runs conditionally for ECHOES and unconditionally for TOFFEE. eg ECHOES will check Environment, while TOFFEE will skip the check.

What you can NOT do is have syntacticly invalid code in an $IFDEF or anundefined define, eg:

{$IFDEF FOOBAR}
for each to bar while begin // will fail even if FOOBAR isn’t defined. 
{$ENDIF}

hth.

What about this one from Sugar.Data (by the way, when is it gonna jump to RTL2?)

  SQLiteConnection = public {$IFDEF PUREJAVA}interface{$ELSE}class{$ENDIF} {$IFDEF ANDROID}mapped to android.database.sqlite.SQLiteDatabase{$ENDIF}
  {$IFDEF PUREJAVA}mapped to java.sql.Connection{$ENDIF}{$IFDEF ECHOES}(IDisposable){$ENDIF}

Sometimes it is an interface, sometimes a class.

I have similar issue but with records and classes :

TMyPoint = public {$IFDEF JAVA}class{$ELSE}record{$ENDIF}

You’d have to condition the whole thing.

ok, but why?

In VCL and .NET we have records (memory management related requirements) but in Java records are expensive because every record’s property change does cloning. So we use class type to speed up things.