diff --git a/Sources/AST/AST.swift b/Sources/AST/AST.swift index 0557b6f8..751aca0d 100644 --- a/Sources/AST/AST.swift +++ b/Sources/AST/AST.swift @@ -45,24 +45,33 @@ public struct ContractDeclaration: SourceEntity { } } +/// A member in a contract behavior declaration. +/// +/// - functionDeclaration: The declaration of a function. +/// - initializerDeclaration: The declaration of an initializer. +public enum ContractBehaviorMember: Equatable { + case functionDeclaration(FunctionDeclaration) + case initializerDeclaration(InitializerDeclaration) +} + /// A Flint contract behavior declaration, i.e. the functions of a contract for a given caller capability group. public struct ContractBehaviorDeclaration: SourceEntity { public var contractIdentifier: Identifier public var capabilityBinding: Identifier? public var callerCapabilities: [CallerCapability] - public var functionDeclarations: [FunctionDeclaration] + public var members: [ContractBehaviorMember] public var closeBracketToken: Token public var sourceLocation: SourceLocation { return .spanning(contractIdentifier, to: closeBracketToken) } - public init(contractIdentifier: Identifier, capabilityBinding: Identifier?, callerCapabilities: [CallerCapability], closeBracketToken: Token, functionDeclarations: [FunctionDeclaration]) { + public init(contractIdentifier: Identifier, capabilityBinding: Identifier?, callerCapabilities: [CallerCapability], closeBracketToken: Token, members: [ContractBehaviorMember]) { self.contractIdentifier = contractIdentifier self.capabilityBinding = capabilityBinding self.callerCapabilities = callerCapabilities - self.functionDeclarations = functionDeclarations self.closeBracketToken = closeBracketToken + self.members = members } } @@ -73,6 +82,7 @@ public struct ContractBehaviorDeclaration: SourceEntity { public enum StructMember: Equatable { case variableDeclaration(VariableDeclaration) case functionDeclaration(FunctionDeclaration) + case initializerDeclaration(InitializerDeclaration) } /// The declaration of a struct. @@ -208,6 +218,54 @@ public struct FunctionDeclaration: SourceEntity { } } +public struct InitializerDeclaration: SourceEntity { + public var initToken: Token + + /// The attributes associated with the function, such as `@payable`. + public var attributes: [Attribute] + + /// The modifiers associted with the function, such as `public`. + public var modifiers: [Token] + public var parameters: [Parameter] + public var closeBracketToken: Token + public var body: [Statement] + public var closeBraceToken: Token + + public var sourceLocation: SourceLocation { + return initToken.sourceLocation + } + + /// The non-implicit parameters of the initializer. + public var explicitParameters: [Parameter] { + return asFunctionDeclaration.explicitParameters + } + + /// 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 + } + + public var isPublic: Bool { + return asFunctionDeclaration.isPublic + } + + public init(initToken: Token, attributes: [Attribute], modifiers: [Token], parameters: [Parameter], closeBracketToken: Token, body: [Statement], closeBraceToken: Token) { + self.initToken = initToken + self.attributes = attributes + self.modifiers = modifiers + self.parameters = parameters + self.closeBracketToken = closeBracketToken + self.body = body + self.closeBraceToken = closeBracketToken + } +} + /// A function attribute, such as `@payable`. public struct Attribute: SourceEntity { var kind: Kind diff --git a/Sources/AST/ASTDumper.swift b/Sources/AST/ASTDumper.swift index 8afcf81f..681449ca 100644 --- a/Sources/AST/ASTDumper.swift +++ b/Sources/AST/ASTDumper.swift @@ -78,8 +78,8 @@ public class ASTDumper { for callerCapability in contractBehaviorDeclaration.callerCapabilities { self.dump(callerCapability) } - for functionDeclaration in contractBehaviorDeclaration.functionDeclarations { - self.dump(functionDeclaration) + for member in contractBehaviorDeclaration.members { + self.dump(member) } self.dump(contractBehaviorDeclaration.closeBracketToken) } @@ -95,14 +95,25 @@ public class ASTDumper { } } - func dump(_ structMember: StructMember) { - switch structMember { - case .functionDeclaration(let functionDeclaration): - self.dump(functionDeclaration) - case .variableDeclaration(let variableDeclaration): - self.dump(variableDeclaration) - } + func dump(_ structMember: StructMember) { + switch structMember { + case .functionDeclaration(let functionDeclaration): + self.dump(functionDeclaration) + case .variableDeclaration(let variableDeclaration): + self.dump(variableDeclaration) + case .initializerDeclaration(let initializerDeclaration): + self.dump(initializerDeclaration) + } + } + + func dump(_ contractBehaviorMember: ContractBehaviorMember) { + switch contractBehaviorMember { + case .functionDeclaration(let functionDeclaration): + self.dump(functionDeclaration) + case .initializerDeclaration(let initializerDeclaration): + self.dump(initializerDeclaration) } + } func dump(_ variableDeclaration: VariableDeclaration) { writeNode("VariableDeclaration") { @@ -120,34 +131,45 @@ public class ASTDumper { func dump(_ functionDeclaration: FunctionDeclaration) { writeNode("FunctionDeclaration") { - for attribute in functionDeclaration.attributes { - self.dump(attribute) - } + self.dumpNodeContents(functionDeclaration) + } + } - for modifier in functionDeclaration.modifiers { - self.dump(modifier) - } - self.dump(functionDeclaration.funcToken) + func dump(_ initializerDeclaration: InitializerDeclaration) { + writeNode("InitializerDeclaration") { + self.dumpNodeContents(initializerDeclaration.asFunctionDeclaration) + } + } + + func dumpNodeContents(_ functionDeclaration: FunctionDeclaration) { + for attribute in functionDeclaration.attributes { + self.dump(attribute) + } - self.dump(functionDeclaration.identifier) + for modifier in functionDeclaration.modifiers { + self.dump(modifier) + } - for parameter in functionDeclaration.parameters { - self.dump(parameter) - } + self.dump(functionDeclaration.funcToken) - self.dump(functionDeclaration.closeBracketToken) + self.dump(functionDeclaration.identifier) - if let resultType = functionDeclaration.resultType { - self.writeNode("ResultType") { - self.dump(resultType) - } - } + for parameter in functionDeclaration.parameters { + self.dump(parameter) + } - for statement in functionDeclaration.body { - self.dump(statement) + self.dump(functionDeclaration.closeBracketToken) + + if let resultType = functionDeclaration.resultType { + self.writeNode("ResultType") { + self.dump(resultType) } } + + for statement in functionDeclaration.body { + self.dump(statement) + } } func dump(_ parameter: Parameter) { diff --git a/Sources/AST/ASTPass.swift b/Sources/AST/ASTPass.swift index 9da94a52..b5eaa35e 100644 --- a/Sources/AST/ASTPass.swift +++ b/Sources/AST/ASTPass.swift @@ -16,8 +16,10 @@ public protocol ASTPass { func process(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult func process(structMember: StructMember, passContext: ASTPassContext) -> ASTPassResult func process(contractBehaviorDeclaration: ContractBehaviorDeclaration, passContext: ASTPassContext) -> ASTPassResult + func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult func process(variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult func process(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult + func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult func process(parameter: Parameter, passContext: ASTPassContext) -> ASTPassResult func process(typeAnnotation: TypeAnnotation, passContext: ASTPassContext) -> ASTPassResult @@ -38,9 +40,11 @@ public protocol ASTPass { func postProcess(contractDeclaration: ContractDeclaration, passContext: ASTPassContext) -> ASTPassResult func postProcess(structMember: StructMember, passContext: ASTPassContext) -> ASTPassResult func postProcess(contractBehaviorDeclaration: ContractBehaviorDeclaration, passContext: ASTPassContext) -> ASTPassResult + func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult func postProcess(variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult func postProcess(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult + func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult func postProcess(parameter: Parameter, passContext: ASTPassContext) -> ASTPassResult func postProcess(typeAnnotation: TypeAnnotation, passContext: ASTPassContext) -> ASTPassResult @@ -88,6 +92,10 @@ public struct AnyASTPass: ASTPass { return base.process(contractBehaviorDeclaration: contractBehaviorDeclaration, passContext: passContext) } + public func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return base.process(contractBehaviorMember: contractBehaviorMember, passContext: passContext) + } + public func process(variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult { return base.process(variableDeclaration: variableDeclaration, passContext: passContext) } @@ -96,6 +104,10 @@ public struct AnyASTPass: ASTPass { return base.process(functionDeclaration: functionDeclaration, passContext: passContext) } + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return base.process(initializerDeclaration: initializerDeclaration, passContext: passContext) + } + public func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return base.process(attribute: attribute, passContext: passContext) } @@ -169,12 +181,16 @@ public struct AnyASTPass: ASTPass { return base.postProcess(contractBehaviorDeclaration: contractBehaviorDeclaration, passContext: passContext) } + public func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return base.postProcess(contractBehaviorMember: contractBehaviorMember, passContext: passContext) + } + public func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { - return base.process(structDeclaration: structDeclaration, passContext: passContext) + return base.postProcess(structDeclaration: structDeclaration, passContext: passContext) } public func postProcess(structMember: StructMember, passContext: ASTPassContext) -> ASTPassResult { - return base.process(structMember: structMember, passContext: passContext) + return base.postProcess(structMember: structMember, passContext: passContext) } public func postProcess(variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult { @@ -185,6 +201,10 @@ public struct AnyASTPass: ASTPass { return base.postProcess(functionDeclaration: functionDeclaration, passContext: passContext) } + public func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return base.postProcess(initializerDeclaration: initializerDeclaration, passContext: passContext) + } + public func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return base.postProcess(attribute: attribute, passContext: passContext) } diff --git a/Sources/AST/ASTVisitor.swift b/Sources/AST/ASTVisitor.swift index 63e7a203..4ee39423 100644 --- a/Sources/AST/ASTVisitor.swift +++ b/Sources/AST/ASTVisitor.swift @@ -93,9 +93,9 @@ public struct ASTVisitor { let typeScopeContext = processResult.passContext.scopeContext - processResult.element.functionDeclarations = processResult.element.functionDeclarations.map { functionDeclaration in + processResult.element.members = processResult.element.members.map { member in processResult.passContext.scopeContext = typeScopeContext - return processResult.combining(visit(functionDeclaration, passContext: processResult.passContext)) + return processResult.combining(visit(member, passContext: processResult.passContext)) } processResult.passContext.contractBehaviorDeclarationContext = nil @@ -137,12 +137,28 @@ public struct ASTVisitor { processResult.element = .functionDeclaration(processResult.combining(visit(functionDeclaration, passContext: processResult.passContext))) case .variableDeclaration(let variableDeclaration): processResult.element = .variableDeclaration(processResult.combining(visit(variableDeclaration, passContext: processResult.passContext))) + case .initializerDeclaration(let initializerDeclaration): + processResult.element = .initializerDeclaration(processResult.combining(visit(initializerDeclaration, passContext: processResult.passContext))) } let postProcessResult = pass.postProcess(structMember: processResult.element, passContext: processResult.passContext) return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext) } + func visit(_ contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + var processResult = pass.process(contractBehaviorMember: contractBehaviorMember, passContext: passContext) + + switch processResult.element { + case .functionDeclaration(let functionDeclaration): + processResult.element = .functionDeclaration(processResult.combining(visit(functionDeclaration, passContext: processResult.passContext))) + case .initializerDeclaration(let initializerDeclaration): + processResult.element = .initializerDeclaration(processResult.combining(visit(initializerDeclaration, passContext: processResult.passContext))) + } + + let postProcessResult = pass.postProcess(contractBehaviorMember: processResult.element, passContext: processResult.passContext) + return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext) + } + func visit(_ variableDeclaration: VariableDeclaration, passContext: ASTPassContext) -> ASTPassResult { var processResult = pass.process(variableDeclaration: variableDeclaration, passContext: passContext) @@ -186,6 +202,31 @@ public struct ASTVisitor { return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext) } + func visit(_ initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + var processResult = pass.process(initializerDeclaration: initializerDeclaration, passContext: passContext) + + processResult.element.attributes = processResult.element.attributes.map { attribute in + return processResult.combining(visit(attribute, passContext: processResult.passContext)) + } + + processResult.element.parameters = processResult.element.parameters.map { parameter in + return processResult.combining(visit(parameter, passContext: processResult.passContext)) + } + + let functionDeclaration = initializerDeclaration.asFunctionDeclaration + + processResult.passContext.scopeContext!.localVariables.append(contentsOf: functionDeclaration.parametersAsVariableDeclarations) + + processResult.element.body = processResult.element.body.map { statement in + return processResult.combining(visit(statement, passContext: processResult.passContext)) + } + + processResult.passContext.functionDeclarationContext = nil + + let postProcessResult = pass.postProcess(initializerDeclaration: processResult.element, passContext: processResult.passContext) + return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext) + } + func visit(_ attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { let processResult = pass.process(attribute: attribute, passContext: passContext) diff --git a/Sources/AST/ASTVisitorContext.swift b/Sources/AST/ASTVisitorContext.swift index 14c711a3..a68f070b 100644 --- a/Sources/AST/ASTVisitorContext.swift +++ b/Sources/AST/ASTVisitorContext.swift @@ -27,7 +27,7 @@ public struct StructDeclarationContext { } } -/// Contextual information used when visiting statements in a function, such as if it is mutating or note. +/// Contextual information used when visiting statements in a function, such as if it is mutating or not. public struct FunctionDeclarationContext { public var declaration: FunctionDeclaration diff --git a/Sources/AST/Environment.swift b/Sources/AST/Environment.swift index 5831896d..4cbd9617 100644 --- a/Sources/AST/Environment.swift +++ b/Sources/AST/Environment.swift @@ -234,6 +234,16 @@ public struct Environment { return .failure(candidates: candidates) } + /// Set the public initializer for the given contract. A contract should have at most one public initializer. + public mutating func setPublicInitializer(_ publicInitializer: InitializerDeclaration, forContract contract: RawTypeIdentifier) { + types[contract]!.publicInitializer = publicInitializer + } + + /// The public initializer for the given contract. A contract should have at most one public initializer. + public func publicInitializer(forContract contract: RawTypeIdentifier) -> InitializerDeclaration? { + return types[contract]!.publicInitializer + } + /// Whether two caller capability groups are compatible, i.e. whether a function with caller capabilities `source` is /// able to call a function which require caller capabilities `target`. func areCallerCapabilitiesCompatible(source: [CallerCapability], target: [CallerCapability]) -> Bool { @@ -309,6 +319,7 @@ public struct TypeInformation { var orderedProperties = [String]() var properties = [String: PropertyInformation]() var functions = [String: [FunctionInformation]]() + var publicInitializer: InitializerDeclaration? = nil } /// Information about a property defined in a type, such as its type and generic arguments. diff --git a/Sources/AST/Token.swift b/Sources/AST/Token.swift index 782afa72..1eebb504 100644 --- a/Sources/AST/Token.swift +++ b/Sources/AST/Token.swift @@ -133,6 +133,7 @@ extension Token { case `var` case `let` case `func` + case `init` case `mutating` case `return` case `public` @@ -165,6 +166,7 @@ extension Token.Kind: CustomStringConvertible { case .var: return "var" case .let: return "let" case .func: return "func" + case .init: return "init" case .self: return "self" case .implicit: return "implicit" case .inout: return "inout" diff --git a/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift b/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift new file mode 100644 index 00000000..2df7952f --- /dev/null +++ b/Sources/IRGen/Function/IULIACallerCapabilityChecks.swift @@ -0,0 +1,44 @@ +// +// IULIACallerCapabilityChecks.swift +// IRGen +// +// Created by Franklin Schrans on 4/28/18. +// + +import AST + +/// Checks whether the caller of a function has appropriate caller capabilities. +struct IULIACallerCapabilityChecks { + var callerCapabilities: [CallerCapability] + + func rendered(functionContext: FunctionContext) -> String { + let environment = functionContext.environment + + let checks = callerCapabilities.compactMap { callerCapability -> String? in + guard !callerCapability.isAny else { return nil } + + 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.. \(IULIAFunction.returnVariableName)" : "")" + let body = IULIAFunctionBody(functionDeclaration: functionDeclaration, typeIdentifier: typeIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: isContractFunction).rendered() + + return """ + function \(signature) { + \(body.indented(by: 2)) + } + """ + } + + /// The string representation of this function's signature, used for generating a IULIA interface. + func mangledSignature() -> String { + let name = functionDeclaration.identifier.name + let parametersString = parameterCanonicalTypes.map({ $0.rawValue }).joined(separator: ",") + + return "\(name)(\(parametersString))" + } +} + +struct IULIAFunctionBody { + var functionDeclaration: FunctionDeclaration + var typeIdentifier: Identifier + + var capabilityBinding: Identifier? + var callerCapabilities: [CallerCapability] + + var environment: Environment + + var isContractFunction = false + + /// The function's parameters and caller capability binding, as variable declarations in a `ScopeContext`. + var scopeContext: ScopeContext { + var localVariables = functionDeclaration.parametersAsVariableDeclarations + if let capabilityBinding = capabilityBinding { + localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .builtInType(.address), identifier: capabilityBinding))) + } + return ScopeContext(localVariables: localVariables) + } + + var functionContext: FunctionContext { + return FunctionContext(environment: environment, scopeContext: scopeContext, enclosingTypeName: typeIdentifier.name, isInContractFunction: isContractFunction) + } + + func rendered() -> String { // Dynamically check the caller has appropriate caller capabilities. let callerCapabilityChecks = IULIACallerCapabilityChecks(callerCapabilities: callerCapabilities).rendered(functionContext: functionContext) let body = renderBody(functionDeclaration.body, functionContext: functionContext) @@ -91,11 +133,7 @@ struct IULIAFunction { payableValueDeclaration = "" } - return """ - function \(signature) { - \(callerCapabilityChecks.indented(by: 2))\(payableValueDeclaration.indented(by: 2))\(capabilityBindingDeclaration.indented(by: 2))\(body.indented(by: 2)) - } - """ + return "\(callerCapabilityChecks)\(payableValueDeclaration)\(capabilityBindingDeclaration)\(body)" } func renderBody(_ statements: S, functionContext: FunctionContext) -> String where S.Element == AST.Statement, S.Index == Int { @@ -109,7 +147,7 @@ struct IULIAFunction { let defaultCode = """ default { - \(restCode.indented(by: 2)) + \(restCode.indented(by: 2)) } """ return firstCode + (restCode.isEmpty ? "" : defaultCode) @@ -117,48 +155,4 @@ struct IULIAFunction { return firstCode + (restCode.isEmpty ? "" : "\n" + restCode) } } - - /// The string representation of this function's signature, used for generating a IULIA interface. - func mangledSignature() -> String { - let name = functionDeclaration.identifier.name - let parametersString = parameterCanonicalTypes.map({ $0.rawValue }).joined(separator: ",") - - return "\(name)(\(parametersString))" - } -} - -/// Checks whether the caller of a function has appropriate caller capabilities. -struct IULIACallerCapabilityChecks { - var callerCapabilities: [CallerCapability] - - func rendered(functionContext: FunctionContext) -> String { - let environment = functionContext.environment - - let checks = callerCapabilities.compactMap { callerCapability -> String? in - guard !callerCapability.isAny else { return nil } - - 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.. String { + let parameterSizes = initializerDeclaration.explicitParameters.map { environment.size(of: $0.type.rawType) } + let offsetsAndSizes = zip(parameterSizes.reversed().reduce((0, [Int]())) { (acc, element) in + let (size, sizes) = acc + let nextSize = size + element * 32 + return (nextSize, sizes + [nextSize]) + }.1.reversed(), parameterSizes) + + let parameterBindings = zip(parameterNames, offsetsAndSizes).map { arg -> String in + let (parameter, (offset, size)) = arg + return """ + codecopy(0x0, sub(codesize, \(offset)), \(size * 32)) + let \(parameter) := mload(0) + """ + }.joined(separator: "\n") + + let defaultValuesAssignments = renderDefaultValuesAssignments() + + let body = IULIAFunctionBody(functionDeclaration: initializerDeclaration.asFunctionDeclaration, typeIdentifier: typeIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: isContractFunction).rendered() + + return """ + \(parameterBindings) + \(defaultValuesAssignments) + \(body) + """ + } + + func renderDefaultValuesAssignments() -> String { + let defaultValueAssignments = propertiesInEnclosingType.compactMap { declaration -> String? in + guard let assignedExpression = declaration.assignedExpression else { return nil } + guard case .literal(let literalToken) = assignedExpression else { + fatalError("Non-literal default values are not supported yet") + } + + var identifier = declaration.identifier + identifier.enclosingType = typeIdentifier.name + + return IULIAAssignment(lhs: .identifier(identifier), rhs: .literal(literalToken)).rendered(functionContext: functionContext) + } + + return defaultValueAssignments.joined(separator: "\n") + } +} diff --git a/Sources/IRGen/IULIAContract.swift b/Sources/IRGen/IULIAContract.swift index d836147b..d2f46e26 100644 --- a/Sources/IRGen/IULIAContract.swift +++ b/Sources/IRGen/IULIAContract.swift @@ -24,7 +24,10 @@ struct IULIAContract { func rendered() -> String { // Generate code for each function in the contract. let functions = contractBehaviorDeclarations.flatMap { contractBehaviorDeclaration in - return contractBehaviorDeclaration.functionDeclarations.map { functionDeclaration in + return contractBehaviorDeclaration.members.compactMap { member -> IULIAFunction? in + guard case .functionDeclaration(let functionDeclaration) = member else { + return nil + } return IULIAFunction(functionDeclaration: functionDeclaration, typeIdentifier: contractDeclaration.identifier, capabilityBinding: contractBehaviorDeclaration.capabilityBinding, callerCapabilities: contractBehaviorDeclaration.callerCapabilities, environment: environment) } } @@ -43,33 +46,17 @@ struct IULIAContract { } let structsFunctionsCode = structFunctions.map({ $0.rendered() }).joined(separator: "\n\n").indented(by: 6) - let initializerBody = renderInitializer() - - var index = 0 - var propertyDeclarations = [String]() - - for property in contractDeclaration.variableDeclarations where !property.type.rawType.isEventType { - let rawType = property.type.rawType - - for canonicalType in storageCanonicalTypes(for: rawType) { - propertyDeclarations.append("\(canonicalType) _flintStorage\(index);") - index += 1 - } - } - - let propertyDeclarationsCode = propertyDeclarations.joined(separator: "\n") + let initializerBody = renderPublicInitializer() // Generate runtime functions. let runtimeFunctionsDeclarations = IULIARuntimeFunction.all.map { $0.declaration }.joined(separator: "\n\n").indented(by: 6) // Main contract body. return """ - pragma solidity ^0.4.19; + pragma solidity ^0.4.21; contract \(contractDeclaration.identifier.name) { - \(propertyDeclarationsCode.indented(by: 2)) - \(initializerBody.indented(by: 2)) function () public payable { @@ -93,64 +80,53 @@ struct IULIAContract { """ } - func renderInitializer() -> String { - // Generate an initializer which takes in the 256-bit values in storage. - let initializerParameters = contractDeclaration.variableDeclarations.filter { $0.type.rawType.isBasicType && !$0.type.rawType.isEventType && $0.assignedExpression == nil } - let initializerParameterList = initializerParameters.map { "\(CanonicalType(from: $0.type.rawType)!.rawValue) \($0.identifier.name)" }.joined(separator: ", ") - var initializerBody = initializerParameters.map { parameter in - let offset = environment.propertyOffset(for: parameter.identifier.name, enclosingType: contractDeclaration.identifier.name)! - return "_flintStorage\(offset) = \(parameter.identifier.name);" - }.joined(separator: "\n") - - let defaultValueAssignments = contractDeclaration.variableDeclarations.compactMap { declaration -> String? in - guard let assignedExpression = declaration.assignedExpression else { return nil } - let offset = environment.propertyOffset(for: declaration.identifier.name, enclosingType: contractDeclaration.identifier.name)! - guard case .literal(let literalToken) = assignedExpression else { - fatalError("Non-literal default values are not supported yet") - } - return "_flintStorage\(offset) = \(IULIALiteralToken(literalToken: literalToken).rendered());" + func renderPublicInitializer() -> String { + let initializerDeclaration: InitializerDeclaration + + // The contract behavior declaration the initializer resides in. + let contractBehaviorDeclaration: ContractBehaviorDeclaration? + + if let publicInitializer = environment.publicInitializer(forContract: contractDeclaration.identifier.name) { + initializerDeclaration = publicInitializer + contractBehaviorDeclaration = findEnclosingContractBehaviorDeclaration(forInitializer: initializerDeclaration)! + } else { + // If there is not public initializer defined, synthesize one. + initializerDeclaration = synthesizeInitializer() + contractBehaviorDeclaration = nil } - initializerBody += "\n" + defaultValueAssignments.joined(separator: "\n") + let capabilityBinding = contractBehaviorDeclaration?.capabilityBinding + let callerCapabilities = contractBehaviorDeclaration?.callerCapabilities ?? [] + + let initializer = IULIAInitializer(initializerDeclaration: initializerDeclaration, typeIdentifier: contractDeclaration.identifier, propertiesInEnclosingType: contractDeclaration.variableDeclarations, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, environment: environment, isContractFunction: true).rendered() + + let parameters = initializerDeclaration.parameters.map { parameter in + let parameterName = Mangler.mangleName(parameter.identifier.name) + return "\(CanonicalType(from: parameter.type.rawType)!.rawValue) \(parameterName)" + }.joined(separator: ", ") + return """ - function \(contractDeclaration.identifier.name)(\(initializerParameterList)) public { - \(initializerBody.indented(by: 2)) + constructor(\(parameters)) public { + assembly { + \(initializer.indented(by: 4)) + } } """ } - /// The list of canonical types for the given type. Fixed-size arrays of size `n` will result in a list of `n` - /// canonical types. - public func storageCanonicalTypes(for type: Type.RawType) -> [String] { - switch type { - case .builtInType(_), .arrayType(_), .dictionaryType(_, _): - return [type.canonicalElementType!.rawValue] - case .fixedSizeArrayType(let rawType, let elementCount): - return [String](repeating: rawType.canonicalElementType!.rawValue, count: elementCount) - case .errorType: fatalError() - - case .userDefinedType(let identifier): - return environment.properties(in: identifier).flatMap { property -> [String] in - let type = environment.type(of: property, enclosingType: identifier)! - return storageCanonicalTypes(for: type) - } - case .inoutType(_): fatalError() - } + func synthesizeInitializer() -> InitializerDeclaration { + let sourceLocation = contractDeclaration.sourceLocation + + return InitializerDeclaration(initToken: Token(kind: .init, sourceLocation: sourceLocation), attributes: [], modifiers: [Token(kind: .public, sourceLocation: sourceLocation)], parameters: [], closeBracketToken: Token(kind: .punctuation(.closeBracket), sourceLocation: sourceLocation), body: [], closeBraceToken: Token(kind: .punctuation(.closeBrace), sourceLocation: sourceLocation)) } -} -fileprivate extension Type.RawType { - - /// The canonical type of self, or its element's canonical type in the case of arrays and dictionaries. - var canonicalElementType: CanonicalType? { - switch self { - case .builtInType(_): return CanonicalType(from: self) - case .errorType: return CanonicalType(from: self) - case .dictionaryType(_, _): return .uint256 // Nothing is stored in that property. - case .arrayType(_): return .uint256 // The number of elements is stored. - case .fixedSizeArrayType(let elementType, _): return CanonicalType(from: elementType) - case .userDefinedType(_): fatalError() - case .inoutType(_): fatalError() + /// Finds the contract behavior declaration associated with the given initializer. A contract should only have one + /// public initializer. + func findEnclosingContractBehaviorDeclaration(forInitializer initializer: InitializerDeclaration) -> ContractBehaviorDeclaration? { + return contractBehaviorDeclarations.first { contractBehaviorDeclaration -> Bool in + return contractBehaviorDeclaration.members.contains { member in + return member == .initializerDeclaration(initializer) + } } } } diff --git a/Sources/IRGen/IULIAInterface.swift b/Sources/IRGen/IULIAInterface.swift index 64727543..6ffcacd0 100644 --- a/Sources/IRGen/IULIAInterface.swift +++ b/Sources/IRGen/IULIAInterface.swift @@ -14,8 +14,14 @@ struct IULIAInterface { func rendered() -> String { let functionSignatures = contract.contractBehaviorDeclarations.flatMap { contractBehaviorDeclaration in - return contractBehaviorDeclaration.functionDeclarations.compactMap { functionDeclaration in - return render(functionDeclaration) + return contractBehaviorDeclaration.members.compactMap { member in + switch member { + case .functionDeclaration(let functionDeclaration): + return render(functionDeclaration) + case .initializerDeclaration(_): + return "" + // Rendering initializers is not supported yet. + } } }.joined(separator: "\n") diff --git a/Sources/IRGen/IULIAPreprocessor.swift b/Sources/IRGen/IULIAPreprocessor.swift index affe8ac4..9162cf19 100644 --- a/Sources/IRGen/IULIAPreprocessor.swift +++ b/Sources/IRGen/IULIAPreprocessor.swift @@ -30,6 +30,10 @@ public struct IULIAPreprocessor: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func process(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -57,6 +61,10 @@ public struct IULIAPreprocessor: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } + public func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } @@ -191,6 +199,10 @@ public struct IULIAPreprocessor: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -207,6 +219,10 @@ public struct IULIAPreprocessor: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } + public func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } diff --git a/Sources/Optimizer/Optimizer.swift b/Sources/Optimizer/Optimizer.swift index 9169b8c7..ae7516ee 100644 --- a/Sources/Optimizer/Optimizer.swift +++ b/Sources/Optimizer/Optimizer.swift @@ -27,6 +27,10 @@ public struct Optimizer: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func process(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -43,6 +47,10 @@ public struct Optimizer: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } + public func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } @@ -115,6 +123,10 @@ public struct Optimizer: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -131,6 +143,10 @@ public struct Optimizer: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } + public func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift index 6cf77e6a..3489a423 100644 --- a/Sources/Parser/Parser.swift +++ b/Sources/Parser/Parser.swift @@ -339,14 +339,16 @@ extension Parser { let (callerCapabilities, closeBracketToken) = try parseCallerCapabilityGroup() try consume(.punctuation(.openBrace)) - let functionDeclarations = try parseContractFunctionDeclarations(contractIdentifier: contractIdentifier) + let members = try parseContractBehaviorMembers() + try consume(.punctuation(.closeBrace)) - for functionDeclaration in functionDeclarations { + for case .functionDeclaration(let functionDeclaration) in members { + // Record all the function declarations. environment.addFunction(functionDeclaration, enclosingType: contractIdentifier.name, callerCapabilities: callerCapabilities) } - - return ContractBehaviorDeclaration(contractIdentifier: contractIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, closeBracketToken: closeBracketToken, functionDeclarations: functionDeclarations) + + return ContractBehaviorDeclaration(contractIdentifier: contractIdentifier, capabilityBinding: capabilityBinding, callerCapabilities: callerCapabilities, closeBracketToken: closeBracketToken, members: members) } func parseCapabilityBinding() throws -> Identifier { @@ -372,18 +374,24 @@ extension Parser { return callerCapabilities } - - func parseContractFunctionDeclarations(contractIdentifier: Identifier) throws -> [FunctionDeclaration] { - var functionDeclarations = [FunctionDeclaration]() - - while let functionDeclaration = attempt(try parseFunctionDeclaration(typeIdentifier: contractIdentifier)) { - functionDeclarations.append(functionDeclaration) + + func parseContractBehaviorMembers() throws -> [ContractBehaviorMember] { + var members = [ContractBehaviorMember]() + + while true { + if let functionDeclaration = attempt(task: parseFunctionDeclaration) { + members.append(.functionDeclaration(functionDeclaration)) + } else if let initializerDeclaration = attempt(task: parseInitializerDeclaration) { + members.append(.initializerDeclaration(initializerDeclaration)) + } else { + break + } } - - return functionDeclarations + + return members } - func parseFunctionDeclaration(typeIdentifier: Identifier) throws -> FunctionDeclaration { + func parseFunctionDeclaration() throws -> FunctionDeclaration { let (attributes, modifiers, funcToken) = try parseFunctionHead() let identifier = try parseIdentifier() let (parameters, closeBracketToken) = try parseParameters() @@ -392,8 +400,16 @@ extension Parser { return FunctionDeclaration(funcToken: funcToken, attributes: attributes, modifiers: modifiers, identifier: identifier, parameters: parameters, closeBracketToken: closeBracketToken, resultType: resultType, body: body, closeBraceToken: closeBraceToken) } - - func parseFunctionHead() throws -> (attributes: [Attribute], modifiers: [Token], funcToken: Token) { + + func parseInitializerDeclaration() throws -> InitializerDeclaration { + let (attributes, modifiers, initToken) = try parseInitializerHead() + let (parameters, closeBracketToken) = try parseParameters() + let (body, closeBraceToken) = try parseCodeBlock() + + return InitializerDeclaration(initToken: initToken, attributes: attributes, modifiers: modifiers, parameters: parameters, closeBracketToken: closeBracketToken, body: body, closeBraceToken: closeBraceToken) + } + + func parseAttributesAndModifiers() throws -> (attributes: [Attribute], modifiers: [Token]) { var attributes = [Attribute]() var modifiers = [Token]() @@ -412,10 +428,23 @@ extension Parser { break } } + + return (attributes, modifiers) + } + + func parseFunctionHead() throws -> (attributes: [Attribute], modifiers: [Token], funcToken: Token) { + let (attributes, modifiers) = try parseAttributesAndModifiers() let funcToken = try consume(.func) return (attributes, modifiers, funcToken) } + + func parseInitializerHead() throws -> (attributes: [Attribute], modifiers: [Token], initToken: Token) { + let (attributes, modifiers) = try parseAttributesAndModifiers() + + let initToken = try consume(.init) + return (attributes, modifiers, initToken) + } func parseParameters() throws -> ([Parameter], closeBracketToken: Token) { try consume(.punctuation(.openBracket)) @@ -632,9 +661,11 @@ extension Parser { while true { if let variableDeclaration = attempt(try parseVariableDeclaration(asTypeProperty: true)) { members.append(.variableDeclaration(variableDeclaration)) - } else if let functionDeclaration = attempt(try parseFunctionDeclaration(typeIdentifier: structIdentifier)) { + } else if let functionDeclaration = attempt(task: parseFunctionDeclaration) { members.append(.functionDeclaration(functionDeclaration)) environment.addFunction(functionDeclaration, enclosingType: structIdentifier.name) + } else if let initializerDeclaration = attempt(task: parseInitializerDeclaration) { + members.append(.initializerDeclaration(initializerDeclaration)) } else { break } diff --git a/Sources/Parser/Tokenizer.swift b/Sources/Parser/Tokenizer.swift index c9421aa3..62f12c1e 100644 --- a/Sources/Parser/Tokenizer.swift +++ b/Sources/Parser/Tokenizer.swift @@ -68,6 +68,7 @@ public struct Tokenizer { "var": .var, "let": .let, "func": .func, + "init": .init, "mutating": .mutating, "return": .return, "public": .public, diff --git a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift index d6660116..0df3a8a8 100644 --- a/Sources/SemanticAnalyzer/SemanticAnalyzer.swift +++ b/Sources/SemanticAnalyzer/SemanticAnalyzer.swift @@ -42,6 +42,10 @@ public struct SemanticAnalyzer: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: diagnostics, passContext: passContext) } + public func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func process(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -122,6 +126,10 @@ public struct SemanticAnalyzer: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: diagnostics, passContext: passContext) } + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } + public func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } @@ -289,6 +297,10 @@ public struct SemanticAnalyzer: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -317,6 +329,33 @@ public struct SemanticAnalyzer: ASTPass { return ASTPassResult(element: functionDeclaration, diagnostics: diagnostics, passContext: passContext) } + public func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + var diagnostics = [Diagnostic]() + var passContext = passContext + + var environmment = passContext.environment! + + // If we are in a contract behavior declaration, check there is only one public initializer. + if let context = passContext.contractBehaviorDeclarationContext, initializerDeclaration.isPublic { + let contractName = context.contractIdentifier.name + + // The caller capability block in which this initializer appears should be scoped by "any". + if !context.callerCapabilities.contains(where: { $0.isAny }) { + diagnostics.append(.contractInitializerNotDeclaredInAnyCallerCapabilityBlock(initializerDeclaration)) + } else { + if let publicInitializer = environmment.publicInitializer(forContract: contractName) { + diagnostics.append(.multiplePublicInitializersDefined(initializerDeclaration, originalInitializerLocation: publicInitializer.sourceLocation)) + } else { + // This is the first public initializer we encounter in this contract. + environmment.setPublicInitializer(initializerDeclaration, forContract: contractName) + } + } + } + + passContext.environment = environmment + return ASTPassResult(element: initializerDeclaration, diagnostics: diagnostics, passContext: passContext) + } + public func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) } @@ -423,6 +462,7 @@ public struct SemanticAnalyzer: ASTPass { } extension ASTPassContext { + /// The list of mutating expressions in a function. var mutatingExpressions: [Expression]? { get { return self[MutatingExpressionContextEntry.self] } set { self[MutatingExpressionContextEntry.self] = newValue } diff --git a/Sources/SemanticAnalyzer/SemanticError.swift b/Sources/SemanticAnalyzer/SemanticError.swift index ddca2a06..30321e26 100644 --- a/Sources/SemanticAnalyzer/SemanticError.swift +++ b/Sources/SemanticAnalyzer/SemanticError.swift @@ -60,6 +60,15 @@ extension Diagnostic { return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "'let' constant 'e' needs to be assigned a value") } + static func multiplePublicInitializersDefined(_ invalidAdditionalInitializer: InitializerDeclaration, originalInitializerLocation: SourceLocation) -> Diagnostic { + let note = Diagnostic(severity: .note, sourceLocation: originalInitializerLocation, message: "A public initializer is already declared here") + return Diagnostic(severity: .error, sourceLocation: invalidAdditionalInitializer.sourceLocation, message: "A public initializer has already been defined", notes: [note]) + } + + static func contractInitializerNotDeclaredInAnyCallerCapabilityBlock(_ initializerDeclaration: InitializerDeclaration) -> Diagnostic { + return Diagnostic(severity: .error, sourceLocation: initializerDeclaration.sourceLocation, message: "Public contract initializer should be callable using caller capability \"any\"") + } + static func renderCapabilityGroup(_ capabilities: [CallerCapability]) -> String { return "(\(capabilities.map({ $0.name }).joined(separator: ", ")))" } diff --git a/Sources/SemanticAnalyzer/TypeChecker.swift b/Sources/SemanticAnalyzer/TypeChecker.swift index fd075734..7245d4e5 100644 --- a/Sources/SemanticAnalyzer/TypeChecker.swift +++ b/Sources/SemanticAnalyzer/TypeChecker.swift @@ -27,6 +27,10 @@ public struct TypeChecker: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func process(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func process(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -65,6 +69,10 @@ public struct TypeChecker: ASTPass { public func process(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + + public func process(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } public func process(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) @@ -188,6 +196,10 @@ public struct TypeChecker: ASTPass { return ASTPassResult(element: contractBehaviorDeclaration, diagnostics: [], passContext: passContext) } + public func postProcess(contractBehaviorMember: ContractBehaviorMember, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: contractBehaviorMember, diagnostics: [], passContext: passContext) + } + public func postProcess(structDeclaration: StructDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: structDeclaration, diagnostics: [], passContext: passContext) } @@ -203,6 +215,10 @@ public struct TypeChecker: ASTPass { public func postProcess(functionDeclaration: FunctionDeclaration, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: functionDeclaration, diagnostics: [], passContext: passContext) } + + public func postProcess(initializerDeclaration: InitializerDeclaration, passContext: ASTPassContext) -> ASTPassResult { + return ASTPassResult(element: initializerDeclaration, diagnostics: [], passContext: passContext) + } public func postProcess(attribute: Attribute, passContext: ASTPassContext) -> ASTPassResult { return ASTPassResult(element: attribute, diagnostics: [], passContext: passContext) diff --git a/Sources/flintc/ASTPassRunner.swift b/Sources/flintc/ASTPassRunner.swift index 46c9e17e..05014d30 100644 --- a/Sources/flintc/ASTPassRunner.swift +++ b/Sources/flintc/ASTPassRunner.swift @@ -29,7 +29,7 @@ struct ASTPassRunner { diagnostics.append(contentsOf: result.diagnostics) } - return ASTPassRunResult(element: ast, diagnostics: diagnostics) + return ASTPassRunResult(element: ast, diagnostics: diagnostics, environment: environment) } } @@ -37,4 +37,5 @@ struct ASTPassRunner { struct ASTPassRunResult { var element: TopLevelModule var diagnostics: [Diagnostic] + var environment: Environment } diff --git a/Sources/flintc/Compiler.swift b/Sources/flintc/Compiler.swift index 6510381c..08cab61a 100644 --- a/Sources/flintc/Compiler.swift +++ b/Sources/flintc/Compiler.swift @@ -68,7 +68,7 @@ struct Compiler { } // Generate IULIA IR code. - let irCode = IULIACodeGenerator(topLevelModule: passRunnerOutcome.element, environment: environment).generateCode() + let irCode = IULIACodeGenerator(topLevelModule: passRunnerOutcome.element, environment: passRunnerOutcome.environment).generateCode() // Compile the IULIA IR code using solc. SolcCompiler(inputSource: irCode, outputDirectory: outputDirectory, emitBytecode: emitBytecode).compile() diff --git a/Tests/BehaviorTests/tests/array/test/migrations/1.js b/Tests/BehaviorTests/tests/array/test/migrations/1.js index 604a4b85..38a8b644 100644 --- a/Tests/BehaviorTests/tests/array/test/migrations/1.js +++ b/Tests/BehaviorTests/tests/array/test/migrations/1.js @@ -3,5 +3,5 @@ var config = require("../config.js") var Contract = artifacts.require("./" + config.contractName + ".sol"); module.exports = function(deployer) { - deployer.deploy(Contract, 0); + deployer.deploy(Contract); }; diff --git a/Tests/BehaviorTests/tests/bank/bank.flint b/Tests/BehaviorTests/tests/bank/bank.flint index afd08697..f429bd90 100644 --- a/Tests/BehaviorTests/tests/bank/bank.flint +++ b/Tests/BehaviorTests/tests/bank/bank.flint @@ -8,6 +8,10 @@ contract Bank { } Bank :: account <- (any) { + public init(manager: Address) { + self.manager = manager + } + public mutating func register() { accounts[lastIndex] = account lastIndex += 1 diff --git a/Tests/BehaviorTests/tests/bank/test/migrations/1.js b/Tests/BehaviorTests/tests/bank/test/migrations/1.js index 4e03200d..13f4a536 100644 --- a/Tests/BehaviorTests/tests/bank/test/migrations/1.js +++ b/Tests/BehaviorTests/tests/bank/test/migrations/1.js @@ -3,5 +3,5 @@ var config = require("../config.js") var Contract = artifacts.require("./" + config.contractName + ".sol"); module.exports = function(deployer, network, accounts) { - deployer.deploy(Contract, accounts[9], 0, 0); + deployer.deploy(Contract, accounts[9]); }; diff --git a/Tests/BehaviorTests/tests/dictionary/test/migrations/1.js b/Tests/BehaviorTests/tests/dictionary/test/migrations/1.js index d42bcd3c..38a8b644 100644 --- a/Tests/BehaviorTests/tests/dictionary/test/migrations/1.js +++ b/Tests/BehaviorTests/tests/dictionary/test/migrations/1.js @@ -3,5 +3,5 @@ var config = require("../config.js") var Contract = artifacts.require("./" + config.contractName + ".sol"); module.exports = function(deployer) { - deployer.deploy(Contract, 0, 0); + deployer.deploy(Contract); }; diff --git a/Tests/BehaviorTests/tests/factorial/test/migrations/1.js b/Tests/BehaviorTests/tests/factorial/test/migrations/1.js index 604a4b85..38a8b644 100644 --- a/Tests/BehaviorTests/tests/factorial/test/migrations/1.js +++ b/Tests/BehaviorTests/tests/factorial/test/migrations/1.js @@ -3,5 +3,5 @@ var config = require("../config.js") var Contract = artifacts.require("./" + config.contractName + ".sol"); module.exports = function(deployer) { - deployer.deploy(Contract, 0); + deployer.deploy(Contract); }; diff --git a/Tests/BehaviorTests/tests/inits/inits.flint b/Tests/BehaviorTests/tests/inits/inits.flint new file mode 100644 index 00000000..b38583aa --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/inits.flint @@ -0,0 +1,25 @@ +contract Inits { + var a: Int + var b: Address + var s: String +} + +Inits :: caller <- (any) { + public init(a: Int, s: String) { + self.a = a + self.b = caller + self.s = s + } + + public func getA() -> Int { + return a + } + + public func getB() -> Address { + return b + } + + public func getS() -> String { + return s + } +} diff --git a/Tests/BehaviorTests/tests/inits/test/config.js b/Tests/BehaviorTests/tests/inits/test/config.js new file mode 100644 index 00000000..7b06f323 --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/config.js @@ -0,0 +1 @@ +exports.contractName = "Inits" diff --git a/Tests/BehaviorTests/tests/inits/test/migrations/1.js b/Tests/BehaviorTests/tests/inits/test/migrations/1.js new file mode 100644 index 00000000..6bb6741d --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/migrations/1.js @@ -0,0 +1,7 @@ +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); + +module.exports = function(deployer) { + deployer.deploy(Contract, 4, "hello"); +}; diff --git a/Tests/BehaviorTests/tests/inits/test/test/.placeholder b/Tests/BehaviorTests/tests/inits/test/test/.placeholder new file mode 100644 index 00000000..3ed31e18 --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/test/.placeholder @@ -0,0 +1 @@ +This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove. diff --git a/Tests/BehaviorTests/tests/inits/test/test/test.js b/Tests/BehaviorTests/tests/inits/test/test/test.js new file mode 100644 index 00000000..1bf2c55d --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/test/test.js @@ -0,0 +1,23 @@ +var config = require("../config.js") + +var Contract = artifacts.require("./" + config.contractName + ".sol"); +var Interface = artifacts.require("./_Interface" + config.contractName + ".sol"); +Contract.abi = Interface.abi + +contract(config.contractName, function(accounts) { + it("should correctly set initializer arguments", async function() { + const instance = await Contract.deployed(); + let t; + + t = await instance.getX(); + assert.equal(t.valueOf(), 4); + + t = await instance.getB(); + assert.equal(t.valueOf(), accounts[0]); + + t = await instance.getS(); + assert.equal(web3.toUtf8(t.valueOf()), "hello"); + }); +}); + + diff --git a/Tests/BehaviorTests/tests/inits/test/truffle-config.js b/Tests/BehaviorTests/tests/inits/test/truffle-config.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/truffle-config.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/Tests/BehaviorTests/tests/inits/test/truffle.js b/Tests/BehaviorTests/tests/inits/test/truffle.js new file mode 100644 index 00000000..a6330d6d --- /dev/null +++ b/Tests/BehaviorTests/tests/inits/test/truffle.js @@ -0,0 +1,4 @@ +module.exports = { + // See + // to customize your Truffle configuration! +}; diff --git a/Tests/BehaviorTests/tests/string/string.flint b/Tests/BehaviorTests/tests/string/string.flint index 65e57f92..9932b8f0 100644 --- a/Tests/BehaviorTests/tests/string/string.flint +++ b/Tests/BehaviorTests/tests/string/string.flint @@ -3,6 +3,10 @@ contract StringContract { } StringContract :: (any) { + public init(s: String) { + self.s = s + } + mutating public func set(s: String) { self.s = s } diff --git a/Tests/BehaviorTests/tests/string/test/migrations/1.js b/Tests/BehaviorTests/tests/string/test/migrations/1.js index 604a4b85..f3a96e74 100644 --- a/Tests/BehaviorTests/tests/string/test/migrations/1.js +++ b/Tests/BehaviorTests/tests/string/test/migrations/1.js @@ -3,5 +3,5 @@ var config = require("../config.js") var Contract = artifacts.require("./" + config.contractName + ".sol"); module.exports = function(deployer) { - deployer.deploy(Contract, 0); + deployer.deploy(Contract, "hello"); }; diff --git a/Tests/BehaviorTests/tests/structs/structs.flint b/Tests/BehaviorTests/tests/structs/structs.flint index be34ea6f..6df44dba 100644 --- a/Tests/BehaviorTests/tests/structs/structs.flint +++ b/Tests/BehaviorTests/tests/structs/structs.flint @@ -34,6 +34,8 @@ contract C { } C :: (any) { + public init() {} + public func getAx() -> Int { return a.x } diff --git a/Tests/ParserTests/structs.flint b/Tests/ParserTests/structs.flint index 23fc05ba..34b6757b 100644 --- a/Tests/ParserTests/structs.flint +++ b/Tests/ParserTests/structs.flint @@ -17,6 +17,14 @@ struct MyStruct { // CHECK-AST: built-in type Int var a: Int +// CHECK-AST: InitializerDeclaration +// CHECK-AST: Parameter +// CHECK-AST: identifier "a" +// CHECK-AST: built-in type Int + init(a: Int) { + self.a = a + } + // CHECK-AST: FunctionDeclaration // CHECK-AST: identifier "foo" func foo() {} diff --git a/Tests/ParserTests/types.flint b/Tests/ParserTests/types.flint index d954794a..63da9d3a 100644 --- a/Tests/ParserTests/types.flint +++ b/Tests/ParserTests/types.flint @@ -37,3 +37,14 @@ contract Foo { // CHECK-AST: literal 2 var assignedValue: Int = 2 } + +Foo :: (any) { + +// CHECK-AST: InitializerDeclaration +// CHECK-AST: Parameter +// CHECK-AST: identifier "assignedValue" +// CHECK-AST: built-in type Int + init(assignedValue: Int) { + self.assignedValue = assignedValue + } +} diff --git a/Tests/SemanticTests/inits.flint b/Tests/SemanticTests/inits.flint new file mode 100644 index 00000000..e7072766 --- /dev/null +++ b/Tests/SemanticTests/inits.flint @@ -0,0 +1,23 @@ +// RUN: %flintc %s --verify + +contract Test { + var a: Address +} + +Test :: caller <- (any) { + public init(a: Address) { // expected-note {{A public initializer is already declared here}} + self.a = a + } + + public init() { // expected-error {{A public initializer has already been defined}} + self.a = caller + } + + init() { + } +} + +Test :: (a) { + public init(a: Address) { // expected-error {{Public contract initializer should be callable using caller capability "any"}} + } +} diff --git a/docs/grammar.txt b/docs/grammar.txt index 1ef6599f..db12c179 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -27,11 +27,13 @@ generic-parameter-list -> type | type [,] generic-argument-list struct-declaration -> [struct] identifier [{] (struct-members) [}] struct-members -> struct-member (struct-members) -struct-member -> variable-declaration | function-declaration +struct-member -> variable-declaration | function-declaration | initializer-declaration // Contract behavior declaration -contract-behavior-declaration -> identifier [::] (caller-capability-binding) caller-capability-group (function-declarations) +contract-behavior-declaration -> identifier [::] (caller-capability-binding) caller-capability-group (contract-behavior-members) +contract-behavior-members -> contract-behavior-member (contract-behavior-members) +contract-behavior-member -> function-declaration | initializer-declaration // Caller capability group @@ -44,12 +46,14 @@ caller-capability-binding -> identifier [<-] identifier -> [a-zA-Z] . [a-zA-Z0-9]* -// Function declarations +// Function and initializer declarations -function-declarations -> function-declaration (function-declarations) +initializer-declaration -> initializer-head parameter-clause code-block function-declaration -> function-head identifier parameter-clause (function-result) code-block +initializer-head -> (declaration-attributes) (declaration-modifiers) [init] function-head -> (declaration-attributes) (declaration-modifiers) [func] + declaration-modifier -> [public] | [mutating] declaration-modifiers -> declaration-modifier (declaration-modifiers)