Help:VE274

From TrainzOnline
Jump to: navigation, search

Variable '<variable name>' already declared in parent scope on class '<class name>'.

This warning is reported by the TrainzScript compiler when a variable name is already in use in a parent/higher scope. This is not a compilation error, but a warning is generated as the duplication is likely unintended, makes script harder to read, and can introduce hard to diagnose bugs. The simplest and safest way to fix the warning is to rename one of the variables.

Example 1
In this example we have a function that attempts to load a number of dependent assets. Firstly, it's important to note that this function will work perfectly fine, but it will also generate the following warning:

  • Variable 'asset' already declared in parent scope on class 'TestClass'.

This warning occurs because the script declares a variable called 'asset' at the top-level function scope, then declares a second variable called 'asset' within the while loop scope. In practice script maintains both variables in memory, and when the variable is referenced you will get whichever was most recently declared. This means that after the second variable declaration, you are unable to access the top level variable until the second one goes out of scope and is destroyed/freed.

void LoadTextureAssets(void)
{
  // Get our asset.
  Asset asset = GetAsset();

  // Clear the array.
  m_textures = new Asset[0];

  int index = 0;
  while (true)
  {
    string kuidTableName = "texture_" + index;
    ++index;

    // Attempt to find the kuid table entry.
    KUID kuid = asset.LookupKUIDTable(kuidTableName);
    if (!kuid)
      break;

    // Attempt to load the asset.
    Asset asset = World.FindAsset(kuid);  // !!!  This line will generate a script warning, because 'asset' is already declared.  !!!
    if (asset)
    {
      m_textures[m_textures.size()] = asset;
      Interface.Log("LoadTextureAssets> Loaded asset: " + asset.GetLocalisedName());
    }

  } // while (true)
}

To fix this warning, you can simply rename the second variable as follows.

    // Attempt to load the asset.
    Asset textureAsset = World.FindAsset(kuid);
    if (textureAsset)
    {
      m_textures[m_textures.size()] = textureAsset;
      Interface.Log("LoadTextureAssets> Loaded asset: " + textureAsset.GetLocalisedName());
    }


Example 2
In this example, a class is declared with a member variable 'textures', and then a function within that class also declares a variable named 'textures'. In this instance, the member variable can still be accessed by using the 'me' keyword, but the warning is still generated as the usage could easily have been a mistake.

class TestClass isclass Library
{
  Asset[] textures = new Asset[0];


  void Init(Asset asset)
  {
    inherited(asset);

    // Load the texture assets.
    Asset[] textures = new Asset[5];  // !!!  This line will generate a script warning, because 'asset' is already declared.  !!!

    bool bHasErrors = false;    
    int i;
    for (i = 0; i < 5; ++i)
    {
      KUID kuid = asset.LookupKUIDTable("texture_" + i);
      if (kuid)
        textures[i] = World.FindAsset(kuid);
 
      if (!textures[i])
      {
        Interface.Log("TestClass.Init> Failed to load texture " + i);
        bHasErrors = true;
      }
    }

    // Copy the loaded textures to the member variable on success.
    if (!bHasErrors)
      me.textures = textures;
  }

}

As in example 1 this script code will work perfectly well, and it's clear to a human reader that the scripter knows what they are doing, but the compiler is not able to make that decision. Also just like example1, the easiest way to fix this warning is just to rename one variable. In this instance, adding the common "m_" prefix to the member variable is probably preferable (i.e. "m_textures").


Example 3
For the final example, let's examine a case where the script does not work as intended. Have a look, and see if you can spot where the scripter has gone wrong.

Trackside FindNextRedSignal(Trackside searchFrom, bool searchDir, float maxDistance)
{
  // Create a tracksearch object to search down the track from the desired position.
  GSTrackSearch trackSearch = searchFrom.BeginTrackSearch(searchDir);

  Trackside signal = null;

  // Search for map objects until we run out.
  MapObject mapObject;
  while (mapObject = trackSearch.SearchNext())
  {
    // If we're past the max distance then break out and give up.
    if (trackSearch.GetDistance() > maxDistance)
      break;

    // Ignore backward facing objects.
    if (!trackSearch.GetFacingRelativeToSearchDirection())
      continue;

    // Check if it has a 'RED' state. (Note that tracksearch may return 'SceneryWithTrack'
    // objects too (like industries) so it IS possible for this cast to fail.
    Trackside trackObj = cast<Trackside>(mapObj);
    if (!trackObj or trackObj.GetSignalState() != Signal.RED)
      continue.

    // We explicitly want 'Signal' objects for this function, so make sure the
    // object is genuinely a signal before we return it, and not some other track
    // object which has a 'RED' state.
    Signal signal = cast<Signal>(trackObj);
    if (!signal)
      continue;

    // We've found a match. Set the result and break out from the search.
    signal = trackObj;
    break;
  }

  if (signal)
    Interface.Log("TestClass.FindNextRedSignal> Found red signal: " + signal.GetLocalisedName());
  else
    Interface.Log("TestClass.FindNextRedSignal> No red signal found");
    

  // Return the found signal, if any.
  return signal;

}

In this example the variable name 'signal' is used multiple times, and the second declaration will break the intention of the function. This is not a 'real world' example, it's just been engineered to demonstrate the possible problems, but it's possible to see how such an error could occur in the real world. In this artificial case, one may draw the conclusion that the final cast was added later on, after testing revealed the function was returning the wrong objects.


See also: Error 273

Personal tools