Skip to content

Commit

Permalink
Merge pull request #185 from franklinsch/mem-structs
Browse files Browse the repository at this point in the history
Support local variables of struct types
  • Loading branch information
franklinsch authored May 7, 2018
2 parents 3273bb3 + 858a716 commit ac7ebb9
Show file tree
Hide file tree
Showing 35 changed files with 719 additions and 153 deletions.
66 changes: 47 additions & 19 deletions Sources/AST/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,6 @@ public struct FunctionDeclaration: SourceEntity {
public var explicitParameters: [Parameter] {
return parameters.filter { !$0.isImplicit }
}

/// The parameters of the function, as variable declaration values.
public var parametersAsVariableDeclarations: [VariableDeclaration] {
return parameters.map { parameter in
return VariableDeclaration(declarationToken: nil, identifier: parameter.identifier, type: parameter.type)
}
}

public var mutatingToken: Token {
return modifiers.first { $0.kind == .mutating }!
Expand All @@ -215,8 +208,11 @@ public struct FunctionDeclaration: SourceEntity {
public var isPublic: Bool {
return hasModifier(kind: .public)
}

// Contextual information for the scope defined by the function.
public var scopeContext: ScopeContext? = nil

public init(funcToken: Token, attributes: [Attribute], modifiers: [Token], identifier: Identifier, parameters: [Parameter], closeBracketToken: Token, resultType: Type?, body: [Statement], closeBraceToken: Token) {
public init(funcToken: Token, attributes: [Attribute], modifiers: [Token], identifier: Identifier, parameters: [Parameter], closeBracketToken: Token, resultType: Type?, body: [Statement], closeBraceToken: Token, scopeContext: ScopeContext? = nil) {
self.funcToken = funcToken
self.attributes = attributes
self.modifiers = modifiers
Expand All @@ -226,6 +222,7 @@ public struct FunctionDeclaration: SourceEntity {
self.resultType = resultType
self.body = body
self.closeBraceToken = closeBraceToken
self.scopeContext = scopeContext
}

private func hasModifier(kind: Token.Kind) -> Bool {
Expand All @@ -250,6 +247,9 @@ public struct InitializerDeclaration: SourceEntity {
public var sourceLocation: SourceLocation {
return initToken.sourceLocation
}

// Contextual information for the scope defined by the function.
public var scopeContext: ScopeContext? = nil

/// The non-implicit parameters of the initializer.
public var explicitParameters: [Parameter] {
Expand All @@ -259,26 +259,22 @@ public struct InitializerDeclaration: SourceEntity {
/// A function declaration equivalent of the initializer.
public var asFunctionDeclaration: FunctionDeclaration {
let dummyIdentifier = Identifier(identifierToken: Token(kind: .identifier("init"), sourceLocation: initToken.sourceLocation))
return FunctionDeclaration(funcToken: initToken, attributes: attributes, modifiers: modifiers, identifier: dummyIdentifier, parameters: parameters, closeBracketToken: closeBracketToken, resultType: nil, body: body, closeBraceToken: closeBracketToken)
}

/// The parameters of the initializer, as variable declaration values.
public var parametersAsVariableDeclarations: [VariableDeclaration] {
return asFunctionDeclaration.parametersAsVariableDeclarations
return FunctionDeclaration(funcToken: initToken, attributes: attributes, modifiers: modifiers, identifier: dummyIdentifier, parameters: parameters, closeBracketToken: closeBracketToken, resultType: nil, body: body, closeBraceToken: closeBracketToken, scopeContext: scopeContext)
}

public var isPublic: Bool {
return asFunctionDeclaration.isPublic
}

public init(initToken: Token, attributes: [Attribute], modifiers: [Token], parameters: [Parameter], closeBracketToken: Token, body: [Statement], closeBraceToken: Token) {
public init(initToken: Token, attributes: [Attribute], modifiers: [Token], parameters: [Parameter], closeBracketToken: Token, body: [Statement], closeBraceToken: Token, scopeContext: ScopeContext? = nil) {
self.initToken = initToken
self.attributes = attributes
self.modifiers = modifiers
self.parameters = parameters
self.closeBracketToken = closeBracketToken
self.body = body
self.closeBraceToken = closeBraceToken
self.scopeContext = scopeContext
}
}

Expand Down Expand Up @@ -333,6 +329,10 @@ public struct Parameter: SourceEntity {
return .spanning(identifier, to: type)
}

public var asVariableDeclaration: VariableDeclaration {
return VariableDeclaration(declarationToken: nil, identifier: identifier, type: type)
}

public init(identifier: Identifier, type: Type, implicitToken: Token?) {
self.identifier = identifier
self.type = type
Expand Down Expand Up @@ -416,6 +416,10 @@ public struct Type: SourceEntity {
}
}

public var isUserDefinedType: Bool {
return !isBuiltInType
}

public var isEventType: Bool {
return self == .basicType(.event)
}
Expand Down Expand Up @@ -562,6 +566,7 @@ public indirect enum Expression: SourceEntity {
case variableDeclaration(VariableDeclaration)
case bracketedExpression(Expression)
case subscriptExpression(SubscriptExpression)
case sequence([Expression])

public var sourceLocation: SourceLocation {
switch self {
Expand All @@ -576,6 +581,7 @@ public indirect enum Expression: SourceEntity {
case .variableDeclaration(let variableDeclaration): return variableDeclaration.sourceLocation
case .bracketedExpression(let bracketedExpression): return bracketedExpression.sourceLocation
case .subscriptExpression(let subscriptExpression): return subscriptExpression.sourceLocation
case .sequence(let expressions): return expressions.first!.sourceLocation
}
}

Expand All @@ -601,11 +607,27 @@ public indirect enum Expression: SourceEntity {
}

public var enclosingType: String? {
guard case .identifier(let identifier) = self else {
return nil
switch self {
case .identifier(let identifier): return identifier.enclosingType ?? identifier.name
case .inoutExpression(let inoutExpression): return inoutExpression.expression.enclosingType
case .binaryExpression(let binaryExpression): return binaryExpression.lhs.enclosingType
case .bracketedExpression(let expression): return expression.enclosingType
case .variableDeclaration(let variableDeclaration): return variableDeclaration.identifier.name
case .subscriptExpression(let subscriptExpression): return subscriptExpression.baseIdentifier.enclosingType
default : return nil
}
}

public var enclosingIdentifier: Identifier? {
switch self {
case .identifier(let identifier): return identifier
case .inoutExpression(let inoutExpression): return inoutExpression.expression.enclosingIdentifier
case .variableDeclaration(let variableDeclaration): return variableDeclaration.identifier
case .binaryExpression(let binaryExpression): return binaryExpression.lhs.enclosingIdentifier
case .bracketedExpression(let expression): return expression.enclosingIdentifier
case .subscriptExpression(let subscriptExpression): return subscriptExpression.baseIdentifier
default : return nil
}

return identifier.enclosingType
}

public var isLiteral: Bool {
Expand Down Expand Up @@ -793,6 +815,12 @@ public struct IfStatement: SourceEntity {
public var sourceLocation: SourceLocation {
return .spanning(ifToken, to: condition)
}

// Contextual information for the scope defined by the if body.
public var ifBodyScopeContext: ScopeContext? = nil

// Contextual information for the scope defined by the else body.
public var elseBodyScopeContext: ScopeContext? = nil

public var endsWithReturnStatement: Bool {
return body.contains { statement in
Expand Down
1 change: 1 addition & 0 deletions Sources/AST/ASTDumper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ public class ASTDumper {
case .self(let token): self.dump(token)
case .variableDeclaration(let variableDeclaration): self.dump(variableDeclaration)
case .subscriptExpression(let subscriptExpression): self.dump(subscriptExpression)
case .sequence(let expressions): expressions.forEach { self.dump($0) }
}
}
}
Expand Down
9 changes: 0 additions & 9 deletions Sources/AST/ASTPass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@ public struct AnyASTPass: ASTPass {
return base.postProcess(subscriptExpression: subscriptExpression, passContext: passContext)
}


public func postProcess(returnStatement: ReturnStatement, passContext: ASTPassContext) -> ASTPassResult<ReturnStatement> {
return base.postProcess(returnStatement: returnStatement, passContext: passContext)
}
Expand All @@ -282,11 +281,3 @@ public struct AnyASTPass: ASTPass {
return base.postProcess(ifStatement: ifStatement, passContext: passContext)
}
}

extension ASTPass {
public func enclosingTypeIdentifier(in passContext: ASTPassContext) -> Identifier {
return passContext.contractBehaviorDeclarationContext?.contractIdentifier ??
passContext.structDeclarationContext?.structIdentifier ??
passContext.contractStateDeclarationContext!.contractIdentifier
}
}
12 changes: 12 additions & 0 deletions Sources/AST/ASTPassContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ extension ASTPassContext {
get { return self[IsFunctionCallContextEntry.self] }
set { self[IsFunctionCallContextEntry.self] = newValue }
}

/// The identifier of the enclosing type (a contract or a struct).
public var enclosingTypeIdentifier: Identifier? {
return contractBehaviorDeclarationContext?.contractIdentifier ??
structDeclarationContext?.structIdentifier ??
contractStateDeclarationContext?.contractIdentifier
}

/// Whether we are visiting a node in a function declaration or initializer.
public var inFunctionOrInitializer: Bool {
return functionDeclarationContext != nil || functionDeclarationContext != nil
}
}

/// A entry used to index in an `ASTPassContext`.
Expand Down
18 changes: 16 additions & 2 deletions Sources/AST/ASTVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public struct ASTVisitor<Pass: ASTPass> {

processResult.passContext.functionDeclarationContext = functionDeclarationContext

processResult.passContext.scopeContext!.localVariables.append(contentsOf: functionDeclaration.parametersAsVariableDeclarations)
processResult.passContext.scopeContext!.parameters.append(contentsOf: functionDeclaration.parameters)

processResult.element.body = processResult.element.body.map { statement in
return processResult.combining(visit(statement, passContext: processResult.passContext))
Expand All @@ -221,7 +221,7 @@ public struct ASTVisitor<Pass: ASTPass> {
processResult.passContext.initializerDeclarationContext = initializerDeclarationContext

let functionDeclaration = initializerDeclaration.asFunctionDeclaration
processResult.passContext.scopeContext!.localVariables.append(contentsOf: functionDeclaration.parametersAsVariableDeclarations)
processResult.passContext.scopeContext!.parameters.append(contentsOf: functionDeclaration.parameters)

processResult.element.body = processResult.element.body.map { statement in
return processResult.combining(visit(statement, passContext: processResult.passContext))
Expand Down Expand Up @@ -305,6 +305,10 @@ public struct ASTVisitor<Pass: ASTPass> {
processResult.element = .variableDeclaration(processResult.combining(visit(variableDeclaration, passContext: processResult.passContext)))
case .subscriptExpression(let subscriptExpression):
processResult.element = .subscriptExpression(processResult.combining(visit(subscriptExpression, passContext: processResult.passContext)))
case .sequence(let elements):
processResult.element = .sequence(elements.map { element in
return processResult.combining(visit(element, passContext: passContext))
})
}

let postProcessResult = pass.postProcess(expression: processResult.element, passContext: processResult.passContext)
Expand Down Expand Up @@ -432,11 +436,21 @@ public struct ASTVisitor<Pass: ASTPass> {
processResult.element.body = processResult.element.body.map { statement in
return processResult.combining(visit(statement, passContext: processResult.passContext))
}

if processResult.element.ifBodyScopeContext == nil {
processResult.element.ifBodyScopeContext = processResult.passContext.scopeContext
}

processResult.passContext.scopeContext = scopeContext

processResult.element.elseBody = processResult.element.elseBody.map { statement in
return processResult.combining(visit(statement, passContext: processResult.passContext))
}

if processResult.element.elseBodyScopeContext == nil {
processResult.element.elseBodyScopeContext = processResult.passContext.scopeContext
}

processResult.passContext.scopeContext = scopeContext

let postProcessResult = pass.postProcess(ifStatement: processResult.element, passContext: processResult.passContext)
Expand Down
36 changes: 31 additions & 5 deletions Sources/AST/ASTVisitorContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,48 @@ public struct InitializerDeclarationContext {

/// Contextual information used when visiting a scope, such as the local variables which are accessible in that
/// scope.
public struct ScopeContext {
public struct ScopeContext: Equatable {
public var parameters = [Parameter]()
public var localVariables = [VariableDeclaration]()

public init(localVariables: [VariableDeclaration] = []) {
public init(parameters: [Parameter] = [], localVariables: [VariableDeclaration] = []) {
self.parameters = parameters
self.localVariables = localVariables
}

public func containsParameterDeclaration(for name: String) -> Bool {
return parameters.contains { $0.identifier.name == name }
}

public func containsVariableDeclaration(for name: String) -> Bool {
return localVariables.contains { $0.identifier.name == name }
}

public func variableDeclaration(for name: String) -> VariableDeclaration? {
return localVariables.first(where: { $0.identifier.name == name })
public func containsDeclaration(for name: String) -> Bool {
return containsParameterDeclaration(for: name) || containsVariableDeclaration(for: name)
}

public func declaration(for name: String) -> VariableDeclaration? {
let all = localVariables + parameters.map { $0.asVariableDeclaration }
return all.first(where: { $0.identifier.name == name })
}

public func type(for variable: String) -> Type.RawType? {
return localVariables.first(where: { $0.identifier.name == variable })?.type.rawType
let all = localVariables + parameters.map { $0.asVariableDeclaration }
return all.first(where: { $0.identifier.name == variable })?.type.rawType
}

/// Returns the parameter name for the enclosing identifier of the given expression.
///
/// For example, when given the expression "a.foo.x", the function will return "a" if "a" is a parameter to the
/// function.
public func enclosingParameter(expression: Expression, enclosingTypeName: String) -> String? {
guard expression.enclosingType != enclosingTypeName,
let enclosingIdentifier = expression.enclosingIdentifier,
containsParameterDeclaration(for: enclosingIdentifier.name) else {
return nil
}

return enclosingIdentifier.name
}
}
7 changes: 1 addition & 6 deletions Sources/AST/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,6 @@ public struct Environment {
public func isInitializerCall(_ functionCall: FunctionCall) -> Bool {
return isStructDeclared(functionCall.identifier.name)
}

/// Whether the given type is a reference type (a contract).
public func isReferenceType(_ type: RawTypeIdentifier) -> Bool {
// TODO: it should be possible to pass structs by value as well.
return isContractDeclared(type) || isStructDeclared(type)
}

/// Whether a property is defined in a type.
public func isPropertyDefined(_ property: String, enclosingType: RawTypeIdentifier) -> Bool {
Expand Down Expand Up @@ -313,6 +307,7 @@ public struct Environment {
return type(ofArrayLiteral: arrayLiteral, enclosingType: enclosingType, scopeContext: scopeContext)
case .dictionaryLiteral(let dictionaryLiteral):
return type(ofDictionaryLiteral: dictionaryLiteral, enclosingType: enclosingType, scopeContext: scopeContext)
case .sequence(_): fatalError()
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/IRGen/Function/FunctionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ struct FunctionContext {
/// The type in which the function is declared.
var enclosingTypeName: String

/// Whether the function is declared in a contract.
var isInContractFunction: Bool
/// Whether the function is declared in a struct.
var isInStructFunction: Bool
}
10 changes: 7 additions & 3 deletions Sources/IRGen/Function/IULIACallerCapabilityChecks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ struct IULIACallerCapabilityChecks {

let type = environment.type(of: callerCapability.identifier.name, enclosingType: functionContext.enclosingTypeName)!
let offset = environment.propertyOffset(for: callerCapability.name, enclosingType: functionContext.enclosingTypeName)!

switch type {
case .fixedSizeArrayType(_, let size):
return (0..<size).map { index in
"_flintCallerCheck := add(_flintCallerCheck, \(IULIARuntimeFunction.isValidCallerCapability.rawValue)(sload(add(\(offset), \(index)))))"
let check = IULIARuntimeFunction.isValidCallerCapability(address: "sload(add(\(offset), \(index)))")
return "_flintCallerCheck := add(_flintCallerCheck, \(check)"
}.joined(separator: "\n")
case .arrayType(_):
return "_flintCallerCheck := add(_flintCallerCheck, \(IULIARuntimeFunction.isCallerCapabilityInArray.rawValue)(\(offset)))"
let check = IULIARuntimeFunction.isCallerCapabilityInArray(arrayOffset: offset)
return "_flintCallerCheck := add(_flintCallerCheck, \(check))"
default:
return "_flintCallerCheck := add(_flintCallerCheck, \(IULIARuntimeFunction.isValidCallerCapability.rawValue)(sload(\(offset))))"
let check = IULIARuntimeFunction.isValidCallerCapability(address: "sload(\(offset)))")
return "_flintCallerCheck := add(_flintCallerCheck, \(check)"
}
}

Expand Down
9 changes: 6 additions & 3 deletions Sources/IRGen/Function/IULIAContractInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct IULIAContractInitializer {
var isContractFunction = false

var functionContext: FunctionContext {
return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInContractFunction: isContractFunction)
return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInStructFunction: !isContractFunction)
}

var parameterNames: [String] {
Expand All @@ -34,11 +34,11 @@ struct IULIAContractInitializer {

/// The function's parameters and caller capability binding, as variable declarations in a `ScopeContext`.
var scopeContext: ScopeContext {
var localVariables = initializerDeclaration.parametersAsVariableDeclarations
var localVariables = [VariableDeclaration]()
if let capabilityBinding = capabilityBinding {
localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding)))
}
return ScopeContext(localVariables: localVariables)
return ScopeContext(parameters: initializerDeclaration.parameters, localVariables: localVariables)
}

func rendered() -> String {
Expand All @@ -61,10 +61,13 @@ struct IULIAContractInitializer {

let body = IULIAFunctionBody(functionDeclaration: initializerDeclaration.asFunctionDeclaration, typeIdentifier: typeIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: isContractFunction).rendered()

// TODO: Remove IULIARuntimeFunctionDeclaration.store once constructor code and function code is unified.

return """
\(parameterBindings)
\(defaultValuesAssignments)
\(body)
\(IULIARuntimeFunctionDeclaration.store)
"""
}

Expand Down
Loading

0 comments on commit ac7ebb9

Please sign in to comment.