public class HBAssignmentNode extends HBExpressionNode {
  @Child private HBExpressionNode targetNode;
  @Child private HBExpressionNode valueNode;

  public HBAssignmentNode(HBExpressionNode targetNode, HBExpressionNode valueNode) {
    this.targetNode = targetNode;
    this.valueNode = valueNode;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.targetNode.accept(visitor);
    this.valueNode.accept(visitor);
    visitor.visit(this);
  }

  public HBExpressionNode getTargetNode() {
    return this.targetNode;
  }

  public HBExpressionNode getValueNode() {
    return this.valueNode;
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    if (!(this.targetNode instanceof HBIdentifierNode)) {
      throw new RuntimeException("Cannot yet handle assignment of non-identifier node: " + this.targetNode.toString());
    }
    HBIdentifierNode identifierNode = (HBIdentifierNode)this.targetNode;

    Object value = this.valueNode.executeGeneric(frame);
    FrameSlot frameSlot = frame.getFrameDescriptor().findFrameSlot(identifierNode.getName());
    frame.setObject(frameSlot, value);
    return value;
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder("HBAssignmentNode");
    result.append("(");
    result.append(this.targetNode.toString());
    result.append(" = ");
    result.append(this.valueNode.toString());
    result.append(")");
    return result.toString();
  }
} public class HBBlockNode extends HBStatementNode {
  @Children private final HBStatementNode[] bodyNodes;

  public HBBlockNode(HBStatementNode[] bodyNodes) {
    this.bodyNodes = bodyNodes;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    visitor.enter(this);
    for (HBStatementNode node : this.bodyNodes) {
      node.accept(visitor);
    }
    visitor.leave(this);
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    for (HBStatementNode bodyNode : bodyNodes) {
      bodyNode.executeVoid(frame);
    }
    return null;
  }

  @Override
  public String toString() {
    List<String> nodes = Arrays.stream(this.bodyNodes).map(n -> String.valueOf(n)).collect(Collectors.toList());
    return "{" + String.join(", ", nodes) + "}";
  }
} public class HBCallNode extends HBExpressionNode {
  @Child private HBExpressionNode targetNode;
  @Children private final HBExpressionNode[] argumentNodes;

  public HBCallNode(HBExpressionNode targetNode, HBExpressionNode[] argumentNodes) {
    this.targetNode = targetNode;
    this.argumentNodes = argumentNodes;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.targetNode.accept(visitor);
    for (HBExpressionNode parameterNode : argumentNodes) {
      parameterNode.accept(visitor);
    }
    visitor.visit(this);
  }

  public HBExpressionNode getTargetNode() {
    return this.targetNode;
  }

  public HBExpressionNode[] getArgumentNodes() {
    return this.argumentNodes;
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    Object target = this.targetNode.executeGeneric(frame);
    CallTarget callTarget;

    Layout layout = new Layout(target, null);

    for (HBNode argumentNode : this.argumentNodes) {
      Object argumentValue = argumentNode.executeGeneric(frame);
      layout.addArgument(argumentValue);
    }

    if (target instanceof Method) {
      Method method = (Method)target;
      callTarget = method.getCallTarget();
      layout.setReceiver(method.getReceiver());

    } else if (target instanceof Function) {
      Function function = (Function)target;
      callTarget = function.getCallTarget();

    } else {
      throw new Error("Cannot call to: " + target.toString());
    }

    Object[] arguments = layout.getCallArguments();
    return callTarget.call(arguments);
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    result.append("HBCallNode(");
    result.append(this.targetNode.toString());
    result.append(", (");
    List<String> parameters = Arrays.stream(this.argumentNodes).map(p -> p.toString()).collect(Collectors.toList());
    result.append(String.join(", ", parameters));
    result.append(")");
    result.append(")");
    return result.toString();
  }
} @NodeInfo(language = "HB")
public abstract class HBExpressionNode extends HBStatementNode implements Typeable {
  @CompilationFinal Type type;

  public Type getType() {
    return this.type;
  }

  public void setType(Type type) {
    this.type = type;
  }
} public abstract class HBFunctionNode extends HBStatementNode {
  private final String name;
  @Child private HBFunctionRootNode rootNode;
  @CompilationFinal FunctionType functionType;

  public HBFunctionNode(HBLanguage language, String name, HBBlockNode block, SourceSection sourceSection) {
    this.name = name;
    this.rootNode = new HBFunctionRootNode(language, sourceSection, new FrameDescriptor(), block);
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    visitor.enter(this);
    this.rootNode.accept(visitor);
    visitor.leave(this);
  }

  public String getName() {
    return name;
  }

  public CallTarget getCallTarget() {
    return Truffle.getRuntime().createCallTarget(this.rootNode);
  }

  public Type getFunctionType() {
    return this.functionType;
  }

  public void setFunctionType(FunctionType functionType) {
    this.functionType = functionType;
  }

  @Specialization
  public Object cachedExecuteGeneric(
    VirtualFrame frame,
    @Cached("createCreateBindingsNode()") CreateBindingsNode createBindingsNode
  ) {
    Bindings bindings = createBindingsNode.executeCreateBindings(frame);
    Function function = new Function(this.functionType, bindings);

    FrameSlot frameSlot = frame.getFrameDescriptor().findOrAddFrameSlot(this.name);
    frame.setObject(frameSlot, function);
    return null;
  }

  protected CreateBindingsNode createCreateBindingsNode() {
    return CreateBindingsNodeGen.create(this.functionType);
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder("HBFunctionNode(");
    result.append(this.rootNode.toString());
    result.append(")");
    return result.toString();
  }
} public class HBIdentifierNode extends HBExpressionNode {
  private final String name;
  @CompilationFinal Resolution resolution;
  // Node to execute to access the value. This is generated at runtime.
  @Child private HBNode accessNode;

  public HBIdentifierNode(Token token) {
    this.name = token.val;
  }

  public void accept(InferenceVisitor visitor) throws NameNotFoundException {
    visitor.visit(this);
  }

  public String getName() {
    return this.name;
  }

  public void setResolution(Resolution resolution) {
    this.resolution = resolution;
    this.setType(resolution.getType());
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    if (this.accessNode == null) {
      if (this.resolution.isLocal()) {
        this.accessNode = GetLocalNodeGen.create(this.name);
      } else {
        this.accessNode = GetNonLocalNodeGen.create(this.name);
      }
    }
    return this.accessNode.executeGeneric(frame);
  }

  @Override
  public String toString() {
    return "HBIdentifierNode(" + this.name + ")";
  }
} public class HBIntegerLiteralNode extends HBExpressionNode {
  private final long value;

  public HBIntegerLiteralNode(String value) {
    this.value = Long.decode(value);
  }

  public void accept(InferenceVisitor visitor) {
    visitor.visit(this);
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    return this.value;
  }
} public class HBLetDeclarationNode extends HBStatementNode {
  private final String left;
  @Child private HBExpressionNode rightNode;

  public HBLetDeclarationNode(String left, HBExpressionNode rightNode) {
    this.left = left;
    this.rightNode = rightNode;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.rightNode.accept(visitor);
    visitor.visit(this);
  }

  public String getLeft() {
    return this.left;
  }

  public HBExpressionNode getRightNode() {
    return this.rightNode;
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    Object value = this.rightNode.executeGeneric(frame);
    FrameSlot frameSlot = frame.getFrameDescriptor().findOrAddFrameSlot(this.left);
    frame.setObject(frameSlot, value);
    return null;
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder("HBLetDeclarationNode");
    result.append("(");
    result.append(this.left);
    result.append(" = ");
    result.append(this.rightNode.toString());
    result.append(")");
    return result.toString();
  }
} @NodeInfo(shortName = "&&")
public class HBLogicalAndNode extends HBBinaryOperatorNode {
  public HBLogicalAndNode(HBExpressionNode leftNode, HBExpressionNode rightNode) {
    super(leftNode, rightNode);
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.leftNode.accept(visitor);
    this.rightNode.accept(visitor);
    visitor.visit(this);
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    // TODO: Better truthiness!
    Object leftValue = this.leftNode.executeGeneric(frame);
    Object rightValue = this.rightNode.executeGeneric(frame);
    return (leftValue != null) && (rightValue != null);
  }
} public class HBPropertyNode extends HBExpressionNode {
  @Child private HBExpressionNode targetNode;
  private final String propertyName;
  @CompilationFinal Property property;

  public HBPropertyNode(HBExpressionNode targetNode, Token token) {
    this.targetNode = targetNode;
    this.propertyName = token.val;
  }

  public HBPropertyNode(HBExpressionNode targetNode, String propertyName) {
    this.targetNode = targetNode;
    this.propertyName = propertyName;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.targetNode.accept(visitor);
    visitor.visit(this);
  }

  public HBExpressionNode getTargetNode() {
    return this.targetNode;
  }

  public String getPropertyName() {
    return this.propertyName;
  }

  public Property getProperty() {
    return this.property;
  }

  public void setProperty(Property property) {
    this.property = property;
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    Object parent = this.targetNode.executeGeneric(frame);

    if (this.type instanceof MethodType) {
      MethodType methodType = (MethodType)this.type;
      return new Method(parent, methodType.getCallTarget(), methodType.getName());
    }

    Class<?> parentClass = parent.getClass();
    try {
      Field field = parentClass.getField(this.propertyName);
      return field.get(parent);
    } catch (NoSuchFieldException | IllegalAccessException error) {
      throw new RuntimeException(error);
    }
  }

  @Override
  public String toString() {
    return "HBPropertyNode(" + this.targetNode.toString() + "." + this.propertyName.toString() + ")";
  }
} public abstract class HBReturnNode extends HBStatementNode {
  @Child protected HBExpressionNode expressionNode;
  @CompilationFinal Type returnType;

  public HBReturnNode() {
    this.expressionNode = null;
  }

  public HBReturnNode(HBExpressionNode expressionNode) {
    this.expressionNode = expressionNode;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    if (this.expressionNode != null) {
      this.expressionNode.accept(visitor);
    }
    visitor.visit(this);
  }

  public HBExpressionNode getExpressionNode() {
    return this.expressionNode;
  }

  public Type getReturnType() {
    return this.returnType;
  }

  public void setReturnType(Type returnType) {
    this.returnType = returnType;
  }

  @Specialization(guards = {"expressionNode != null"})
  public Object returnUsingExpression(VirtualFrame frame) {
    Object returnValue = this.expressionNode.executeGeneric(frame);
    throw new HBReturnException(returnValue);
  }

  @Specialization(guards = {"expressionNode == null"})
  public Object returnNull(VirtualFrame frame) {
    throw new HBReturnException(null);
  }
} public class HBSourceRootNode extends RootNode implements InferenceVisitable {
  @Child private HBBlockNode bodyNode;
  private final SourceSection sourceSection;
  @CompilationFinal private BuiltinScope builtinScope;

  public HBSourceRootNode(HBLanguage language, SourceSection sourceSection, FrameDescriptor frameDescriptor, HBBlockNode bodyNode) {
    super(language, frameDescriptor);
    this.sourceSection = sourceSection;
    this.bodyNode = bodyNode;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    visitor.enter(this);
    this.bodyNode.accept(visitor);
    visitor.leave(this);
  }

  public void setBuiltinScope(BuiltinScope builtinScope) {
    this.builtinScope = builtinScope;
  }

  @Override
  public Object execute(VirtualFrame frame) {
    FrameDescriptor frameDescriptor = frame.getFrameDescriptor();

    try {
      Bindings bindings = new Bindings(this.builtinScope);
      FrameSlot frameSlot = frameDescriptor.findOrAddFrameSlot(Bindings.IDENTIFIER);
      frame.setObject(frameSlot, bindings);

      this.bodyNode.executeVoid(frame);
    } catch (Exception exception) {
      throw new RuntimeException(exception);
    }
    return null;
  }

  @Override
  public SourceSection getSourceSection() {
    return this.sourceSection;
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder("HBSourceRootNode(");
    result.append(bodyNode.toString());
    result.append(")");
    return result.toString();
  }
} public class HBStringLiteralNode extends HBExpressionNode {
  private final String value;

  public HBStringLiteralNode(String value) {
    assert value.charAt(0) == '"';
    assert value.charAt(value.length() - 1) == '"';
    this.value = value.substring(1, value.length() - 1);
  }

  public void accept(InferenceVisitor visitor) {
    visitor.visit(this);
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    return this.value;
  }

  @Override
  public String toString() {
    return "\"" + this.value + "\"";
  }
} public class SumType extends CompositeType {
  public final Type[] subTypes;

  public SumType(Type[] subTypes) {
    this.subTypes = subTypes;
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Not yet implemented");
  }

  /**
   * Other type must be equal to or smaller than this union for equality
   * to hold.
   */
  @Override
  public boolean equals(Type otherType) {
    // Equality check first.
    if (this == otherType) {
      return true;
    }
    // Then check if we contain the other type.
    for (Type subType : this.subTypes) {
      if (subType.equals(otherType)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    boolean bar = false;
    for (Type subType : this.subTypes) {
      if (bar) {
        result.append(" | ");
      } else {
        bar = true;
      }
      result.append(subType.toString());
    }
    return result.toString();
  }
} public final class BooleanType extends ConcreteType {
  public static final BooleanType SINGLETON = new BooleanType();

  private BooleanType() {
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Not yet implemented");
  }
} public final class IntegerType extends BootstrappableConcreteType {
  public static String BUILTIN_NAME = "Integer";

  private MethodType toString;

  @Override
  public String getBootstrapName() {
    return IntegerType.BUILTIN_NAME;
  }

  @Override
  public void bootstrapBuiltins(BuiltinNodes builtins) {
    this.toString = builtins.createMethodType(this, new Type[]{}, new UnknownType(), IntegerNodes.ToStringNode.class);
  }

  @Override
  public void bootstrapTypes(Module builtinModule) {
    this.toString = this.toString.cloneWithReturnType(builtinModule.getType(StringType.BUILTIN_NAME));
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    switch (name) {
      case "toString":
        return MethodProperty.fromType(this.toString);
      default:
        throw new PropertyNotFoundException(this, name);
    }
  }

  @Override
  public String toString() {
    return "$Integer";
  }
} public final class MethodType extends ConcreteType {
  // Type on which this method is defined.
  private final Type receiverType;
  private final Type[] parameterTypes;
  private final Type returnType;
  private final String name;
  private final CallTarget callTarget;

  public MethodType(
    Type receiverType,
    Type[] parameterTypes,
    Type returnType,
    String name,
    CallTarget callTarget
  ) {
    this.receiverType = receiverType;
    this.parameterTypes = parameterTypes;
    this.returnType = returnType;
    this.name = name;
    this.callTarget = callTarget;
  }

  public Type getReceiverType() {
    return this.receiverType;
  }

  public Type[] getParameterTypes() {
    return this.parameterTypes;
  }

  public Type getReturnType() {
    return this.returnType;
  }

  public String getName() {
    return this.name;
  }

  public CallTarget getCallTarget() {
    return this.callTarget;
  }

  /**
   * Get a new method instance with a different return type. Methods are
   * currently immutable, so this is used during bootstrap to create methods
   * with an unknown return type and then overwrite them with ones with a
   * real return type.
   */
  public MethodType cloneWithReturnType(Type returnType) {
    return new MethodType(this.receiverType, this.parameterTypes, returnType, this.name, this.callTarget);
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Not yet implemented");
  }
} public final class NullType extends ConcreteType {
  public static final NullType SINGLETON = new NullType();

  private NullType() {
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Not yet implemented");
  }
} public final class StringType extends BootstrappableConcreteType {
  public static String BUILTIN_NAME = "String";

  private MethodType toUpperCase;

  @Override
  public String getBootstrapName() {
    return StringType.BUILTIN_NAME;
  }

  @Override
  public void bootstrapBuiltins(BuiltinNodes builtins) {
    this.toUpperCase = builtins.createMethodType(this, new Type[]{}, this, StringNodes.ToUpperCaseNode.class);
  }

  @Override
  public void bootstrapTypes(Module builtinModule) {
    return;
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    switch (name) {
      case "toUpperCase":
        return MethodProperty.fromType(this.toUpperCase);
      default:
        throw new PropertyNotFoundException(this, name);
    }
  }

  @Override
  public String toString() {
    // NOTE: Concrete types' string representations are prefixed with "$"
    //   to indicate their internal, concrete nature (for now).
    return "$String";
  }
} public final class FunctionType extends ConcreteType {
  private final Type[] parameterTypes;
  private final Type returnType;
  private final String name;
  private final CallTarget callTarget;
  // Scope where the function was declared.
  private Scope declarationScope;
  // Scope inside the function (ie. of its block).
  private Scope ownScope;

  public FunctionType(
    Type[] parameterTypes,
    Type returnType,
    String name,
    CallTarget callTarget
  ) {
    this.parameterTypes = parameterTypes;
    this.returnType = returnType;
    this.name = name;
    this.callTarget = callTarget;
  }

  public Type[] getParameterTypes() {
    return this.parameterTypes;
  }

  public Type getReturnType() {
    return this.returnType;
  }

  public String getName() {
    return this.name;
  }

  public CallTarget getCallTarget() {
    return this.callTarget;
  }

  public Scope getOwnScope() {
    if (this.ownScope != null && !this.ownScope.isClosed()) {
      throw new RuntimeException("Scope not yet closed");
    }
    return this.ownScope;
  }

  public void setOwnScope(Scope scope) {
    if (this.ownScope != null) {
      throw new RuntimeException("Cannot re-set scope");
    }
    this.ownScope = scope;
  }

  public Scope getDeclarationScope() {
    if (this.declarationScope != null && !this.declarationScope.isClosed()) {
      throw new RuntimeException("Scope not yet closed");
    }
    return this.declarationScope;
  }

  public void setDeclarationScope(Scope declarationScope) {
    if (this.declarationScope != null) {
      throw new RuntimeException("Cannot re-set scope");
    }
    this.declarationScope = declarationScope;
  }

  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Not yet implemented");
  }
} public abstract class Property {
  // The type of the thing of which this is a property. For example in
  // `foo.bar` where `foo: Foo`, the parent would be the type `Foo`.
  private final Type parent;
  // The name of the property, in the previous example `bar`.
  private final String name;

  protected Property(Type parent, String name) {
    this.parent = parent;
    this.name = name;
  }

  // Returns the type of the property (right now just `MethodType`).
  public abstract Type getType();
} public class PropertyNotFoundException extends TypeException {
  public PropertyNotFoundException(String message) {
    super(message);
  }

  public PropertyNotFoundException(Type type, String name) {
    super("Unknown property " + name + " of " + type.toString());
  }
} public abstract class Type {
  public abstract Property getProperty(String name) throws PropertyNotFoundException;

  public void assertEquals(Type otherType) throws TypeMismatchException {
    // Rudimentary pointer comparison by default.
    if (this != otherType) {
      throw new TypeMismatchException(this, otherType);
    }
  }

  /**
   * Returns whether or not this type is equal to another type.
   */
  public boolean equals(Type otherType) {
    try {
      this.assertEquals(otherType);
    } catch (TypeMismatchException exception) {
      return false;
    }
    return true;
  }
} public class TypeMismatchException extends TypeException {
  public TypeMismatchException(String message) {
    super(message);
  }

  public TypeMismatchException(Type leftType, Type rightType) {
    super("Type " + String.valueOf(leftType) + " does not match " + String.valueOf(rightType));
  }
} public class UnknownType extends Type {
  @Override
  public Property getProperty(String name) throws PropertyNotFoundException {
    throw new PropertyNotFoundException("Unknown type cannot have property: " + name);
  }
} public class BuiltinScope extends AbstractScope implements Scope, Iterable<String> {
  private final Map<String, Type> types;

  public BuiltinScope(Index index) {
    Map<String, Type> types = new HashMap<>();
    // Bootstrap the global types.
    Index.Module builtin = index.getBuiltin();
    for (String name : builtin) {
      types.put(name, builtin.get(name));
    }
    this.types = types;
    // You can't modify the builtin scope!
    this.close();
  }

  @Override
  public void accept(Resolution resolution) throws NameNotFoundException {
    resolution.pushScope(this);
    if (this.types.containsKey(resolution.getName())) {
      resolution.setType(this.types.get(resolution.getName()));
    } else {
      throw new NameNotFoundException("Name not found in BuiltinScope: " + resolution.getName());
    }
  }

  @Override
  public void setLocal(String name, Type type) {
    throw new RuntimeException("Cannot modify BuiltinScope: " + name);
  }

  @Override
  public Scope getParent() {
    throw new RuntimeException("Cannot getParent of BuiltinScope");
  }

  @Override
  public List<Resolution> getNonLocalResolutions() {
    throw new RuntimeException("Cannot getNonLocalResolutions of BuiltinScope");
  }

  @Override
  public Iterator<String> iterator() {
    return this.types.keySet().iterator();
  }
} public class LocalScope extends AbstractScope implements Scope {
  private Scope parent;
  // Types of local variables.
  private Map<String, Type> types;

  public LocalScope(Scope parent) {
    this.parent = parent;
    this.types = new HashMap<>();
  }

  @Override
  public void accept(Resolution resolution) throws NameNotFoundException {
    resolution.pushScope(this);
    if (this.types.containsKey(resolution.getName())) {
      resolution.setType(this.types.get(resolution.getName()));
    } else {
      this.parent.accept(resolution);
    }
  }

  @Override
  public void setLocal(String name, Type type) {
    this.assertIsNotClosed();
    this.types.put(name, type);
  }

  @Override
  public Scope getParent() {
    return this.parent;
  }
} public class NameNotFoundException extends TypeException {
  public NameNotFoundException(String message) {
    super(message);
  }
} public final class Resolution {
  private String name;
  private Type type;
  // First item is nearest parent, last item is furthest/highest parent.
  private List<Scope> path;

  public Resolution(String name) {
    this.name = name;
    this.type = null;
    this.path = new ArrayList<>();
  }

  public String getName() {
    return this.name;
  }

  public void pushScope(Scope scope) {
    this.path.add(scope);
  }

  public Type getType() {
    return this.type;
  }

  public void setType(Type type) {
    this.type = type;
  }

  public boolean isLocal() {
    this.assertPathIsNotEmpty();
    return this.path.size() == 1;
  }

  public boolean isNonLocal() {
    this.assertPathIsNotEmpty();
    return this.path.size() > 1;
  }

  public Scope getHighestScope() {
    this.assertPathIsNotEmpty();
    return this.path.get(this.path.size() - 1);
  }

  private void assertPathIsNotEmpty() {
    if (this.path.isEmpty()) {
      throw new RuntimeException("Empty path in resolution");
    }
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    result.append("Resolution@").append(Integer.toHexString(hashCode()));
    result.append("{");
    boolean comma = false;
    for (Scope scope : this.path) {
      if (comma) {
        result.append(", ");
      } else {
        comma = true;
      }
      result.append(scope.toString());
    }
    result.append("}");
    return result.toString();
  }
} public interface Scope {
  // Loose visitor pattern: scopes accept visitors; if they have the name the
  // resolution is looking for then they push themselves onto the path and
  // assign the type, otherwise they push themselves onto the path and
  // recurse to their parent scope.
  public void accept(Resolution resolution) throws NameNotFoundException;

  public Resolution resolve(String name) throws NameNotFoundException;

  // Get the type for the given name. Should create a `Resolution` and call
  // the `accept` recursion under the hood.
  public Type get(String name) throws NameNotFoundException;

  public void setLocal(String name, Type type);

  public Scope getParent();

  // Close the scope to prevent further modifications.
  public void close();

  // Returns whether or not the scope has been closed.
  public boolean isClosed();

  // Should only be called on closed scopes.
  public List<Resolution> getNonLocalResolutions();
} public class SourceScope extends AbstractScope implements Scope {
  private final BuiltinScope parent;
  private Map<String, Type> types;

  public SourceScope(BuiltinScope parent) {
    this.parent = parent;
    this.types = new HashMap<>();
  }

  @Override
  public void accept(Resolution resolution) throws NameNotFoundException {
    resolution.pushScope(this);
    if (this.types.containsKey(resolution.getName())) {
      resolution.setType(this.types.get(resolution.getName()));
    } else {
      this.parent.accept(resolution);
    }
  }

  @Override
  public void setLocal(String name, Type type) {
    this.assertIsNotClosed();
    this.types.put(name, type);
  }

  @Override
  public Scope getParent() {
    return this.parent;
  }
}
import org.hummingbirdlang.nodes.HBAssignmentNode;
import org.hummingbirdlang.nodes.HBBlockNode;
import org.hummingbirdlang.nodes.HBCallNode;
import org.hummingbirdlang.nodes.HBExpressionNode;
import org.hummingbirdlang.nodes.HBFunctionNode;
import org.hummingbirdlang.nodes.HBIdentifierNode;
import org.hummingbirdlang.nodes.HBIntegerLiteralNode;
import org.hummingbirdlang.nodes.HBLetDeclarationNode;
import org.hummingbirdlang.nodes.HBLogicalAndNode;
import org.hummingbirdlang.nodes.HBPropertyNode;
import org.hummingbirdlang.nodes.HBReturnNode;
import org.hummingbirdlang.nodes.HBSourceRootNode;
import org.hummingbirdlang.nodes.HBStringLiteralNode;
import org.hummingbirdlang.types.composite.SumType;
import org.hummingbirdlang.types.concrete.BooleanType;
import org.hummingbirdlang.types.concrete.IntegerType;
import org.hummingbirdlang.types.concrete.MethodType;
import org.hummingbirdlang.types.concrete.NullType;
import org.hummingbirdlang.types.concrete.StringType;
import org.hummingbirdlang.types.FunctionType;
import org.hummingbirdlang.types.Property;
import org.hummingbirdlang.types.PropertyNotFoundException;
import org.hummingbirdlang.types.Type;
import org.hummingbirdlang.types.TypeMismatchException;
import org.hummingbirdlang.types.UnknownType;
import org.hummingbirdlang.types.scope.BuiltinScope;
import org.hummingbirdlang.types.scope.LocalScope;
import org.hummingbirdlang.types.scope.NameNotFoundException;
import org.hummingbirdlang.types.scope.Resolution;
import org.hummingbirdlang.types.scope.Scope;
import org.hummingbirdlang.types.scope.SourceScope;
package org.hummingbirdlang.types.realize;


/**
 * Visitor-pattern system for recursively inferring type information on
 * nodes in the AST.
 */
public final class InferenceVisitor {
  private final Index index;
  private BuiltinScope builtinScope;
  private Scope currentScope;

  public InferenceVisitor(Index index) {
    this.builtinScope = new BuiltinScope(index);
    this.currentScope = new SourceScope(this.builtinScope);