# How do compilers implement symbolic (rather than textual) insertion?

From a programming language designer's point of view, how is "symbolic" insertion implemented as opposed to "textual" insertion using pre-processor macros?

I understand the basics of how "textual" insertion works in C and C++. That is, the pre-processor might transform:

#include "person.hpp"

class employee
{
person p;
};


into:

#ifndef PERSON_HPP
#define PERSON_HPP

class person
{
public:
uint32_t height_;
};

#endif // PERSON_HPP

class employee
{
person p;
};


This is required because the size of the employee class can only be determined if the compiler knows the size of the person class. One disadvantage of this is slow compilation speed. A program consisting of a single source code file main.cpp might have thousands of lines inserted into it by the pre-processor before compilation begins. This can be avoided in some cases by forward declaring the class and using pointers. The employee class cannot use the person class until it is defined, (which is why the implementation file will still include "person.h") but it does remove header file dependencies and increases compilation speed.

class person;

class employee
{
person* p;
};


For a language like Java, on the other hand, no textual insertion is required. Instead, "symbolic" insertion is performed. So the file below would not be "fleshed out" with the definitions of the dependent software modules.

import com.example.library.person;

class employee {
private person p;
};


I can't wrap my head around how the compiler accomplishes this. I imagine that because Java is garbage collected, it doesn't need to know the size before it is declared, in much the same way as classes can be forward declared in C++ without including the entire file. But how does the compiler know that p->some_function_that_does_not_exist(23) is valid?

This question was originally asked on stack overflow.

• In Java person is a pointer. – adrianN Sep 7 '17 at 5:08
• I don't really see what the issue is. In C/C++, you tell the compiler "You can find the definition of the stuff I used here" and it finds it. In Java, you tell the compiler "You can find the definition of the stuff I used here" and it finds it. How can you understand one but not the other? – David Richerby Sep 7 '17 at 13:10

The Java compiler reads the files you listed with the import statements to see the definitions. For historical reasons C compiler doesn't do this and prefers to get everything in one file, hence the preprocessor.

In the end, there is no magic. If you want to make sure at compiletime that the functions that you're calling exist, then the compiler has to see the definitions. Dynamic languages like Python don't do this check, so they have to see less of the code.

• They don't need to see the definitions, they just need to see the (interface) declarations. – Derek Elkins Sep 7 '17 at 6:53
• In Ada, when you "with P", does the compiler actually read the source file of P, or the file that consists of intermediate code of P? (Sorry about this off-topic question, but it has bothered me since 1980's). – scaaahu Sep 7 '17 at 7:08

I can't wrap my head around how the compiler accomplishes this.

The compiler processes the source files in multiple passes.

In the first pass, it gathers information about types and their members, effectively producing the information that would be contained in a header file in C++.

In the second pass, it looks at method bodies and uses information from the first pass to figure out what the members mean and whether the code is valid.

I imagine that because Java is garbage collected, it doesn't need to know the size before it is declared

Kind of. When you write private person p; in Java, that declares a reference (basically a fancy word for "a pointer that you can't use pointer arithmetic on") to person, so the compiler doesn't need to know what fields person contains to figure out the size of the containing class.