Skip to content

Commit

Permalink
Merge pull request #179 from franklinsch/properties-require-assignment
Browse files Browse the repository at this point in the history
Ensure all contract properties are initialized before end of initializer
  • Loading branch information
franklinsch authored Apr 30, 2018
2 parents 19223ec + 5fe83a5 commit 05633a3
Show file tree
Hide file tree
Showing 28 changed files with 278 additions and 119 deletions.
32 changes: 19 additions & 13 deletions Sources/AST/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public struct FunctionDeclaration: SourceEntity {

/// The raw type of the function's return type.
public var rawType: Type.RawType {
return resultType?.rawType ?? .builtInType(.void)
return resultType?.rawType ?? .basicType(.void)
}

public var sourceLocation: SourceLocation {
Expand Down Expand Up @@ -262,7 +262,7 @@ public struct InitializerDeclaration: SourceEntity {
self.parameters = parameters
self.closeBracketToken = closeBracketToken
self.body = body
self.closeBraceToken = closeBracketToken
self.closeBraceToken = closeBraceToken
}
}

Expand Down Expand Up @@ -307,7 +307,7 @@ public struct Parameter: SourceEntity {

/// Whether the parameter is both `implicit` and has a currency type.
public var isPayableValueParameter: Bool {
if isImplicit, case .builtInType(let type) = type.rawType, type.isCurrencyType {
if isImplicit, case .basicType(let type) = type.rawType, type.isCurrencyType {
return true
}
return false
Expand Down Expand Up @@ -367,7 +367,7 @@ public struct Identifier: Hashable, SourceEntity {
public struct Type: SourceEntity {
/// A Flint raw type, without a source location.
public indirect enum RawType: Equatable {
case builtInType(BuiltInType)
case basicType(BasicType)
case arrayType(RawType)
case fixedSizeArrayType(RawType, size: Int)
case dictionaryType(key: RawType, value: RawType)
Expand All @@ -380,7 +380,7 @@ public struct Type: SourceEntity {
switch self {
case .fixedSizeArrayType(let rawType, size: let size): return "\(rawType.name)[\(size)]"
case .arrayType(let rawType): return "[\(rawType.name)]"
case .builtInType(let builtInType): return "\(builtInType.rawValue)"
case .basicType(let builtInType): return "\(builtInType.rawValue)"
case .dictionaryType(let keyType, let valueType): return "[\(keyType.name): \(valueType.name)]"
case .userDefinedType(let identifier): return identifier
case .inoutType(let rawType): return "&\(rawType)"
Expand All @@ -389,18 +389,24 @@ public struct Type: SourceEntity {
}
}

public var isBasicType: Bool {
if case .builtInType(_) = self { return true }
return false
public var isBuiltInType: Bool {
switch self {
case .basicType(_), .any, .errorType: return true
case .arrayType(let element): return element.isBuiltInType
case .fixedSizeArrayType(let element, _): return element.isBuiltInType
case .dictionaryType(let key, let value): return key.isBuiltInType && value.isBuiltInType
case .inoutType(let element): return element.isBuiltInType
case .userDefinedType(_): return false
}
}

public var isEventType: Bool {
return self == .builtInType(.event)
return self == .basicType(.event)
}

/// Whether the type is a dynamic type.
public var isDynamicType: Bool {
if case .builtInType(_) = self {
if case .basicType(_) = self {
return false
}

Expand All @@ -427,7 +433,7 @@ public struct Type: SourceEntity {
}
}

public enum BuiltInType: String {
public enum BasicType: String {
case address = "Address"
case int = "Int"
case string = "String"
Expand Down Expand Up @@ -463,8 +469,8 @@ public struct Type: SourceEntity {

public init(identifier: Identifier, genericArguments: [Type] = []) {
let name = identifier.name
if let builtInType = BuiltInType(rawValue: name) {
rawType = .builtInType(builtInType)
if let builtInType = BasicType(rawValue: name) {
rawType = .basicType(builtInType)
} else {
rawType = .userDefinedType(name)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/AST/ASTDumper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public class ASTDumper {
self.dump(keyType)
self.dump(valueType)
}
case .builtInType(let builtInType):
case .basicType(let builtInType):
writeNode("BuiltInType") {
self.dump(builtInType)
}
Expand All @@ -248,7 +248,7 @@ public class ASTDumper {
}
}

func dump(_ builtInType: Type.BuiltInType) {
func dump(_ builtInType: Type.BasicType) {
writeLine("built-in type \(builtInType.rawValue)")
}

Expand Down
22 changes: 21 additions & 1 deletion Sources/AST/ASTPassContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ extension ASTPassContext {
set { self[AsLValueContextEntry.self] = newValue }
}

/// Contextual information used when visiting the state properties declared in a contract declaration.
public var contractStateDeclarationContext: ContractStateDeclarationContext? {
get { return self[ContractStateDeclarationContextEntry.self] }
set { self[ContractStateDeclarationContextEntry.self] = newValue }
}

/// Contextual information used when visiting functions in a contract behavior declaration, such as the name of the
/// contract the functions are declared for, and the caller capability associated with them.
public var contractBehaviorDeclarationContext: ContractBehaviorDeclarationContext? {
Expand All @@ -69,12 +75,18 @@ extension ASTPassContext {
set { self[StructDeclarationContextEntry.self] = newValue }
}

/// 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 the function is mutating or note.
public var functionDeclarationContext: FunctionDeclarationContext? {
get { return self[FunctionDeclarationContextEntry.self] }
set { self[FunctionDeclarationContextEntry.self] = newValue }
}

/// Contextual information used when visiting statements in an initializer.
public var initializerDeclarationContext: InitializerDeclarationContext? {
get { return self[InitializerDeclarationContextEntry.self] }
set { self[InitializerDeclarationContextEntry.self] = newValue }
}

/// Contextual information used when visiting a scope, such as the local variables which are accessible in that
/// scope.
public var scopeContext: ScopeContext? {
Expand Down Expand Up @@ -108,6 +120,10 @@ private struct AsLValueContextEntry: PassContextEntry {
typealias Value = Bool
}

private struct ContractStateDeclarationContextEntry: PassContextEntry {
typealias Value = ContractStateDeclarationContext
}

private struct ContractBehaviorDeclarationContextEntry: PassContextEntry {
typealias Value = ContractBehaviorDeclarationContext
}
Expand All @@ -120,6 +136,10 @@ private struct FunctionDeclarationContextEntry: PassContextEntry {
typealias Value = FunctionDeclarationContext
}

private struct InitializerDeclarationContextEntry: PassContextEntry {
typealias Value = InitializerDeclarationContext
}

private struct ScopeContextContextEntry: PassContextEntry {
typealias Value = ScopeContext
}
Expand Down
12 changes: 9 additions & 3 deletions Sources/AST/ASTVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,14 @@ public struct ASTVisitor<Pass: ASTPass> {

processResult.element.identifier = processResult.combining(visit(processResult.element.identifier, passContext: processResult.passContext))

processResult.passContext.contractStateDeclarationContext = ContractStateDeclarationContext(contractIdentifier: contractDeclaration.identifier)

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

processResult.passContext.contractStateDeclarationContext = nil

let postProcessResult = pass.postProcess(contractDeclaration: processResult.element, passContext: processResult.passContext)
return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext)
}
Expand All @@ -70,7 +74,7 @@ public struct ASTVisitor<Pass: ASTPass> {

var localVariables = [VariableDeclaration]()
if let capabilityBinding = contractBehaviorDeclaration.capabilityBinding {
localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .builtInType(.address), identifier: capabilityBinding)))
localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding)))
}

let scopeContext = ScopeContext(localVariables: localVariables)
Expand Down Expand Up @@ -213,15 +217,17 @@ public struct ASTVisitor<Pass: ASTPass> {
return processResult.combining(visit(parameter, passContext: processResult.passContext))
}

let functionDeclaration = initializerDeclaration.asFunctionDeclaration
let initializerDeclarationContext = InitializerDeclarationContext(declaration: initializerDeclaration)
processResult.passContext.initializerDeclarationContext = initializerDeclarationContext

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
processResult.passContext.initializerDeclarationContext = nil

let postProcessResult = pass.postProcess(initializerDeclaration: processResult.element, passContext: processResult.passContext)
return ASTPassResult(element: postProcessResult.element, diagnostics: processResult.diagnostics + postProcessResult.diagnostics, passContext: postProcessResult.passContext)
Expand Down
16 changes: 15 additions & 1 deletion Sources/AST/ASTVisitorContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
// Created by Franklin Schrans on 1/11/18.
//

/// Contextual information used when visiting the state properties declared in a contract declaration.
public struct ContractStateDeclarationContext {
public var contractIdentifier: Identifier
}

/// Contextual information used when visiting functions in a contract behavior declaration, such as the name of the
/// contract the functions are declared for, and the caller capability associated with them.
public struct ContractBehaviorDeclarationContext {
Expand All @@ -27,7 +32,7 @@ public struct StructDeclarationContext {
}
}

/// Contextual information used when visiting statements in a function, such as if it is mutating or not.
/// Contextual information used when visiting statements in a function, such as if the function is mutating or not.
public struct FunctionDeclarationContext {
public var declaration: FunctionDeclaration

Expand All @@ -40,6 +45,15 @@ public struct FunctionDeclarationContext {
}
}

/// Contextual information used when visiting statements in an initializer.
public struct InitializerDeclarationContext {
public var declaration: InitializerDeclaration

public init(declaration: InitializerDeclaration) {
self.declaration = declaration
}
}

/// Contextual information used when visiting a scope, such as the local variables which are accessible in that
/// scope.
public struct ScopeContext {
Expand Down
65 changes: 41 additions & 24 deletions Sources/AST/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,18 @@ public struct Environment {
mutating func setProperties(_ variableDeclarations: [VariableDeclaration], enclosingType: RawTypeIdentifier) {
types[enclosingType]!.orderedProperties = variableDeclarations.map { $0.identifier.name }
for variableDeclaration in variableDeclarations {
addProperty(variableDeclaration.identifier.name, type: variableDeclaration.type, isConstant: variableDeclaration.isConstant, sourceLocation: variableDeclaration.sourceLocation, enclosingType: enclosingType)
addProperty(variableDeclaration, enclosingType: enclosingType)
}
}

/// Add a property to a type.
mutating func addProperty(_ property: String, type: Type, isConstant: Bool = false, sourceLocation: SourceLocation?, enclosingType: RawTypeIdentifier) {
types[enclosingType]!.properties[property] = PropertyInformation(type: type, isConstant: isConstant, sourceLocation: sourceLocation)
mutating func addProperty(_ variableDeclaration: VariableDeclaration, enclosingType: RawTypeIdentifier) {
types[enclosingType]!.properties[variableDeclaration.identifier.name] = PropertyInformation(variableDeclaration: variableDeclaration)
}

/// Add a use of an undefined variable.
public mutating func addUsedUndefinedVariable(_ variable: Identifier, enclosingType: RawTypeIdentifier) {
addProperty(variable.name, type: Type(inferredType: .errorType, identifier: variable), sourceLocation: nil, enclosingType: enclosingType)
addProperty(VariableDeclaration(declarationToken: nil, identifier: variable, type: Type(inferredType: .errorType, identifier: variable)), enclosingType: enclosingType)
}

/// Whether a contract has been declared in the program.
Expand Down Expand Up @@ -93,22 +93,32 @@ public struct Environment {
return types[enclosingType]!.properties[property]!.isConstant
}

public func isPropertyAssignedDefaultValue(_ property: String, enclosingType: RawTypeIdentifier) -> Bool {
return types[enclosingType]!.properties[property]!.isAssignedDefaultValue
}

/// The source location of a property declaration.
public func propertyDeclarationSourceLocation(_ property: String, enclosingType: RawTypeIdentifier) -> SourceLocation? {
return types[enclosingType]!.properties[property]!.sourceLocation
}

/// The list of properties declared in a type.
/// The names of the properties declared in a type.
public func properties(in enclosingType: RawTypeIdentifier) -> [String] {
return types[enclosingType]!.orderedProperties
}

/// The list of property declarations in a type.
public func propertyDeclarations(in enclosingType: RawTypeIdentifier) -> [VariableDeclaration] {
return types[enclosingType]!.properties.values.map { $0.variableDeclaration }
}

/// The list of properties declared in a type which can be used as caller capabilities.
func declaredCallerCapabilities(enclosingType: RawTypeIdentifier) -> [String] {
return types[enclosingType]!.properties.compactMap { key, value in
switch value.rawType {
case .builtInType(.address): return key
case .fixedSizeArrayType(.builtInType(.address), _): return key
case .arrayType(.builtInType(.address)): return key
case .basicType(.address): return key
case .fixedSizeArrayType(.basicType(.address), _): return key
case .arrayType(.basicType(.address)): return key
default: return nil
}
}
Expand All @@ -135,13 +145,13 @@ public struct Environment {
return matchingFunction.resultType
}

/// The type of a literal token.
/// The types a literal token can be.
public func type(ofLiteralToken literalToken: Token) -> Type.RawType {
guard case .literal(let literal) = literalToken.kind else { fatalError() }
switch literal {
case .boolean(_): return .builtInType(.bool)
case .decimal(.integer(_)): return .builtInType(.int)
case .string(_): return .builtInType(.string)
case .boolean(_): return .basicType(.bool)
case .decimal(.integer(_)): return .basicType(.int)
case .string(_): return .basicType(.string)
default: fatalError()
}
}
Expand Down Expand Up @@ -213,7 +223,7 @@ public struct Environment {
return .inoutType(type(of: inoutExpression.expression, enclosingType: enclosingType, callerCapabilities: callerCapabilities, scopeContext: scopeContext))
case .binaryExpression(let binaryExpression):
if binaryExpression.opToken.isBooleanOperator {
return .builtInType(.bool)
return .basicType(.bool)
}
return type(of: binaryExpression.rhs, enclosingType: enclosingType, callerCapabilities: callerCapabilities, scopeContext: scopeContext)

Expand Down Expand Up @@ -323,8 +333,8 @@ public struct Environment {
/// The memory size of a type, in terms of number of memory slots it occupies.
public func size(of type: Type.RawType) -> Int {
switch type {
case .builtInType(.event): return 0 // Events do not use memory.
case .builtInType(_): return 1
case .basicType(.event): return 0 // Events do not use memory.
case .basicType(_): return 1
case .fixedSizeArrayType(let rawType, let elementCount): return size(of: rawType) * elementCount
case .arrayType(_): return 1
case .dictionaryType(_, _): return 1
Expand Down Expand Up @@ -381,23 +391,30 @@ public struct TypeInformation {

/// Information about a property defined in a type, such as its type and generic arguments.
public struct PropertyInformation {
private var type: Type
public var variableDeclaration: VariableDeclaration

public var isConstant: Bool
public var sourceLocation: SourceLocation?
public var isConstant: Bool {
return variableDeclaration.isConstant
}

public var isAssignedDefaultValue: Bool {
return variableDeclaration.assignedExpression != nil
}

public var sourceLocation: SourceLocation? {
return variableDeclaration.sourceLocation
}

init(type: Type, isConstant: Bool = false, sourceLocation: SourceLocation?) {
self.type = type
self.isConstant = isConstant
self.sourceLocation = sourceLocation
init(variableDeclaration: VariableDeclaration) {
self.variableDeclaration = variableDeclaration
}

public var rawType: Type.RawType {
return type.rawType
return variableDeclaration.type.rawType
}

public var typeGenericArguments: [Type.RawType] {
return type.genericArguments.map { $0.rawType }
return variableDeclaration.type.genericArguments.map { $0.rawType }
}
}

Expand Down
Loading

0 comments on commit 05633a3

Please sign in to comment.