ID:1844695
 
BYOND Version:507.1283
Operating System:Windows 7 Home Premium 64-bit
Web Browser:Chrome 42.0.2311.90
Applies to:Dream Maker
Status: Open

Issue hasn't been assigned a status value.
Edit for clarification:
This bug report has helped to reveal three individual bugs or problems. 1 and 2 are related. 3 is a whole other issue.

1. The runtime access operator : can make the compiler throw an error due to type checking a type that doesn't exist.

2. You can typecast an object's variable to a non-existent type, and it compiles without an error.

3. The look-down path operator : does not function exactly as the DM Guide claims it does. It says that at the beginning of a path, it causes the search to take place from the root. If that's the case, then it must have been broken, and the only way it could feasibly work is if it functioned according to this example:
mob
:thing // If we do a look-down search for thing,

obj
thing // then this thing will be found only if

mob
thing // this thing does not exist.

You can disregard the rest of this original post simply because it may be misleading.

Descriptive Problem Summary:
By using path operators, it is possible to define whole new object types, procs, and vars from within another type's definition. The problem is that vars defined this way are somehow only "partially visible" to the compiler. Such vars are actually compiled and can be accessed indirectly through the vars list, but if you try to access them normally through either . or :, the compiler just won't recognize them, and refuses to compile, throwing an "undefined type" error.

This seems to be a very ancient bug, present in the earliest build available. Apparently defining vars this way worked fine at some point when the DM Guide was being written, because you can see an example of it on Chapter 6, Section 15.3. The example shown there is basically broken, because you can't access the dragon_meat var without using the vars list. It just won't compile if you try to use the normal methods of access.

Numbered Steps to Reproduce Problem:
1. Compile and run the following code:
mob
var
/obj/myobj/var/myvar {myvar = "Impossible!"}
obj/myobj/myobj = new

verb
doesitwork()
set name = "Does this even work?"
// src << myobj:myvar // Doesn't compile.
src << myobj:vars["myvar"] // Actually, it does!

2. Click "Does this even work?" and watch the impossible happen.

3. Next, try uncommenting the commented line of code and see that it doesn't compile, even though it should.

Expected Results:
You can use . or : to read and set vars that were defined at an arbitrary point within another object type's definition.

Actual Results:
The compiler throws an "undefined type" error, even though it can be proven that the var is defined.

Does the problem occur:
Every time? Or how often?
It occurs every time.
In other games?
N/A
In other user accounts?
N/A
On other computers?
N/A

When does the problem NOT occur?
It always occurs.

Did the problem NOT occur in any earlier versions? If so, what was the last version that worked? (Visit http://www.byond.com/download/build to download old versions for testing.)
I can't really tell. The builds don't go back far enough. I guess that it might have broke somewhere between 2.0 and 354.

Workarounds:
Use the name of the var as an index to the vars list, like vars["varname"], quotes included.
This was actually noted by Dan at some point in the past as being how things were designed to work. The way you're defining the variable is effectively causing the actual variable to exist at runtime but not compile-time, which means it'll exist in the runtime vars list but not the compile-time lookup list.
I think you're code is wrong. Either of these work fine for me:
        /obj/myobj/var/myvar = "Impossible!"
/obj/myobj/var {myvar = "Impossible!"}


You effectively wrote:
        /obj/myobj/var/myvar/myvar = "Impossible!"
In response to Nadrew
Nadrew wrote:
This was actually noted by Dan at some point in the past as being how things were designed to work.
Somehow I'm not surprised.

The way you're defining the variable is effectively causing the actual variable to exist at runtime but not compile-time, which means it'll exist in the runtime vars list but not the compile-time lookup list.
This is exactly the problem that I am experiencing. The variable thinks it's an electron and is trying to occupy a quantum superposition! In the relatively mundane logic of programming, a self-contradictory state like this one is a bug and should never happen.

DarkCampainger wrote:
I think you're code is wrong. Either of these work fine for me:
        /obj/myobj/var/myvar = "Impossible!"
/obj/myobj/var {myvar = "Impossible!"}


You effectively wrote:
        /obj/myobj/var/myvar/myvar = "Impossible!"
Thanks for clearing that up. Somehow I thought I was using the format for a modified type, but I guess that's only used when creating instances with new or newlist().

This means what I was actually doing was setting a typecasted variable to a string, and for some reason the compiler doesn't like that particular case, which then results in a self-contradictory state.

I was beginning to think Schrödinger was secretly developing zombie dragons, but that was actually a whole other, unrelated bug. The issue with that example from the guide is that the look-up and look-down path operators no longer work for the purpose of defining vars, which means the mysterious dragon_meat doesn't exist at all; not even in the vars list, but the compiler still has no complaints until you actually try to access it. This makes me think that this functionality broke at some point, and is therefore also a bug. Should I make a second report for that case as well?
The guide example is wrong, see:
http://www.byond.com/forum/?post=812946#comment2320135

I can't remember if we filed a bug report to have the guide corrected. I can't find one with the search, so maybe not.
Yeah...the code in the OP is quite funky. I think this is the intent (seems more like what he's trying to do compared to DC's post).

mob
var

obj/myobj/myobj = new // make a new instance of the type

verb
doesitwork()
/obj/myobj {var/myvar = "impossible!"} // You can define the type, even after it's used.
set name = "Does this even work?"
src << myobj:vars["myvar"] // works!
src << myobj:myvar // works!



By prefacing a type path with /, you are effectively saying "forget everything else in the type path..lets go back to the root!"

You're still declaring a variable for the entire /obj/myobj type, though.
In response to DarkCampainger
DarkCampainger wrote:
The guide example is wrong, see:
http://www.byond.com/forum/?post=812946#comment2320135

I can't remember if we filed a bug report to have the guide corrected. I can't find one with the search, so maybe not.
You seem to have proved yourself wrong without realizing it. In the guide it says:
The : operator searches for a child starting in the current node and then in all its children if necessary. It is for this reason that it is called the look-down path operator. At the beginning of a path, it causes the search to take place from the root.
If the underlined statement is correct, then it shouldn't be necessary to use the / operator, and : alone should be sufficient. For this reason, I doubt that the guide was wrong at the time of writing. I suspect that something actually broke.


Super Saiyan X, I know that there are ways of writing it to avoid the bug, but that's not what I'm trying to do here. I want to fix what is clearly a bug. It makes me paranoid to know that it's even possible for variables to hide in your code undetected.
In response to Multiverse7
Hmm, that's a good point. The reference doesn't make any mention of this, so all we really have to go on is the guide and current behavior.

I feel like it makes more sense for it to work from the current node, and you can use a slash to force it to start from the root. Otherwise, how would you make it start from the current node? ".:"? That seems less intuitive, as things usually default to relative. It's up to Tom/Lummox JR, but those are my 2 cents.

Also, as to having variables "hide" in your code, that's unrelated to the path operators. And they aren't really hidden, you've just typecasted them to a non-existent type, so any attempt to access them in a way that would use said type information results in an error.
In response to Multiverse7
Multiverse7 wrote:
Super Saiyan X, I know that there are ways of writing it to avoid the bug, but that's not what I'm trying to do here.

Uh, what I showed you isn't "avoiding the bug"; it's not a bug if it works with properly written code.

I want to fix what is clearly a bug. It makes me paranoid to know that it's even possible for variables to hide in your code undetected.

The only bug here is that something is being added to the vars list, and not actually being available to the object, because your code is wrong (nonsensical in terms of what you've written/expected, but fine in terms of the syntax) - and unintended things are happening because DM doesn't know it's wrong.

Nonsensical things work in DM for whatever reason, for example this works flawlessly:

mob
/obj/myobj/var/myvar {var/myvar = "Impossible!"} //the same as /obj/myobj/var/myvar/var/myvar = "Impossible!"
verb
test()
var/obj/myobj/o = /obj/myobj
world << initial(o.myvar)

var/obj/myobj/o2 = new o

world << o2.vars["myvar"]
world << o2:myvar
world << o2.myvar

but the definition makes no sense.

I don't think Nadrew is right about Dan saying this is documented behavior; this is just the compiler being weird with type definitions - which is not new at all.

Because, this compiles, and gives the same results as your OP code:
obj
myobj
var
myvar
tits
tom
lummox
byond
dan
myvar = "Impossible!"

mob

verb
output_myvar_varslist()
var/obj/myobj/o = /obj/myobj

var/o2 = new o

world << o2:vars["myvar"]
/* output_myvar() // doesn't compile
var/obj/myobj/o = /obj/myobj

var/o2 = new o

world << o2.myvar */


output_all_types()
for(var/x in typesof(/datum))
world << x


DM ignores everything after var/ and the last child, unless it's a var modifier (or valid type, for typecasting); even if you define it using the regular tree structure.

It has nothing to do with modified types structure, or the path operators.

Should the compiler not add it to the vars list and just ignore it? Should it still be added, and also be available to the object? Is this a quirky DM behavior that there is no reason to fix, because everything will be okay if your code isn't written like shit?
In response to DarkCampainger
DarkCampainger wrote:
I feel like it makes more sense for it to work from the current node, and you can use a slash to force it to start from the root. Otherwise, how would you make it start from the current node? ".:"?
No, the search would simply give a higher priority to the current node. Then, if the requested node was not found within the current node, then it would fall back to the root and recursively search down from that point instead.

For example:
mob
:thing // If we do a look-down search for thing,

obj
thing // then this thing will be found only if

mob
thing // this thing does not exist.


Also, as to having variables "hide" in your code, that's unrelated to the path operators. And they aren't really hidden, you've just typecasted them to a non-existent type, so any attempt to access them in a way that would use said type information results in an error.
You are right about that. There is no /myvar type defined, so why isn't the compiler catching that fact? This is the real bug.
In response to Super Saiyan X
Super Saiyan X wrote:
DM ignores everything after var/ and the last child, unless it's a var modifier; even if you define it using the regular tree structure.
Then, it should stop ignoring cases like that, and treat them like the compiler errors that they should be.
In response to DarkCampainger
DarkCampainger wrote:
Also, as to having variables "hide" in your code, that's unrelated to the path operators. And they aren't really hidden, you've just typecasted them to a non-existent type, so any attempt to access them in a way that would use said type information results in an error.

That shouldn't make : fail at compile-time; it should produce a run-time error.
In response to Super Saiyan X
Super Saiyan X wrote:
That shouldn't make : fail at compile-time; it should produce a run-time error.
That's irrelevant. It should be impossible for the compiler to even make it that far. It should produce an error at the moment it finds a var typecasted to a non-existent type, and not just wait until you use it.
In response to Multiverse7
Multiverse7 wrote:
Super Saiyan X wrote:
That shouldn't make : fail at compile-time; it should produce a run-time error.
That's irrelevant. It should be impossible for the compiler to even make it that far. It should produce an error at the moment it finds a var typecasted to a non-existent type.

That's a feature request, not a bug, then. : should probably do no type checking, aside from seeing if the requested variable exists in the namespace.
In response to Super Saiyan X
I wouldn't call that a feature request. It's a defect of the language, resulting in defective vars. In any case, you are probably right. : itself shouldn't be producing a compiler error.
Let me clarify what the problems are, since this thread is now confusing. I also added this to the original post. This bug report has helped to reveal three individual bugs or problems. 1 and 2 are related. 3 is a whole other issue.

1. The runtime access operator : can make the compiler throw an error due to type checking a type that doesn't exist.

2. You can typecast an object's variable to a non-existent type, and it compiles without an error.

3. The look-down path operator : does not function exactly as the DM Guide claims it does. It says that at the beginning of a path, it causes the search to take place from the root. If that's the case, then it must have been broken, and the only way it could feasibly work is if it functioned according to this example:
mob
:thing // If we do a look-down search for thing,

obj
thing // then this thing will be found only if

mob
thing // this thing does not exist.
Note that the original example works fine if you replace

        /obj/myobj/var/myvar {myvar = "Impossible!"}


with

        /obj/myobj/var/myvar = "Impossible!"


Which is frankly what it should be anyway. I've never seen that syntax before for defining variables. Is that mentioned in the guide somewhere?
In response to MisterPerson
MisterPerson wrote:
Note that the original example works fine if you replace

        /obj/myobj/var/myvar {myvar = "Impossible!"}


with

        /obj/myobj/var/myvar = "Impossible!"


Which is frankly what it should be anyway. I've never seen that syntax before for defining variables. Is that mentioned in the guide somewhere?

If the code was written properly, yes, the syntax is documented throughout the guide. You can turn -any- code on DM into a single line using braces and semi-colons, because that's how DM used to be written long ago, much like any C-like language.

The white-spaced based syntax came along later. The usage of the / operator just means it's an absolute path, so you can literally place it anywhere. The most general use of the brace/colon syntax is for types in new and newlist, though, which is documented in newlist's reference entry.

For example, this very compressed c-style DM code:
mob {verb {outThing() {world << thing;} outThing2() {world << thing2;}} var { thing = 1; thing2 = 2;}}


is the same as:
mob {
verb {
outThing() {
world << thing;
}
outThing2() {
world << thing2;
}
}
var {
thing = 1;
thing2 = 2;
}
}

// or simpler,
mob
verb
outThing()
world << thing
outThing2()
world << thing2
var
thing = 1
thing2 = 2


You can play around a lot with the syntax because a lot of quirky things work (interchange ; and , etc):
mob {var/thing = 1; var thing2 = 2}

mob
var {thing = 1; thing2 = 2}
mob
var thing = 1, thing2 = 2

//you could even write verbs like this, for whatever reason:
mob::verb::hey()
world << "hey world!"
newlist documents modified types, which this is clearly not.

If you're going to use curly brace delineation, you can't mix it like that. This line as a replacement also works just fine:

        {/obj/myobj/var/myvar = "Impossible!"}
In response to MisterPerson
MisterPerson wrote:
newlist documents modified types, which this is clearly not.

If you're going to use curly brace delineation, you can't mix it like that. This line as a replacement also works just fine:

>       {/obj/myobj/var/myvar = "Impossible!"}
>

You can mix it just fine, actually. But, that's not the point. The main functionality being used was the / operator - not so much the braces/semicolon, that's just obfuscated DM syntax. With the / operator, you can do define anything anywhere, even in the middle of another type's tree.