Skip to content

Commit

Permalink
Require properties of basic types to be assigned
Browse files Browse the repository at this point in the history
  • Loading branch information
franklinsch committed Apr 30, 2018
1 parent 86dec9e commit 5fe83a5
Show file tree
Hide file tree
Showing 28 changed files with 144 additions and 73 deletions.
30 changes: 18 additions & 12 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 @@ -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
10 changes: 10 additions & 0 deletions 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 Down Expand Up @@ -114,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 Down
6 changes: 5 additions & 1 deletion 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
5 changes: 5 additions & 0 deletions 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 Down
20 changes: 10 additions & 10 deletions Sources/AST/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ public struct Environment {
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 @@ -145,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 @@ -223,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 @@ -333,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
4 changes: 2 additions & 2 deletions Sources/IRGen/Function/IULIAFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct IULIAFunction {
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)))
localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding)))
}
return ScopeContext(localVariables: localVariables)
}
Expand Down Expand Up @@ -103,7 +103,7 @@ struct IULIAFunctionBody {
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)))
localVariables.append(VariableDeclaration(declarationToken: nil, identifier: capabilityBinding, type: Type(inferredType: .basicType(.address), identifier: capabilityBinding)))
}
return ScopeContext(localVariables: localVariables)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/IRGen/Function/IULIAInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct IULIAInitializer {
var scopeContext: ScopeContext {
var localVariables = initializerDeclaration.parametersAsVariableDeclarations
if let capabilityBinding = 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)))
}
return ScopeContext(localVariables: localVariables)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/IRGen/IULIACanonicalType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum CanonicalType: String {

init?(from rawType: Type.RawType) {
switch rawType {
case .builtInType(let builtInType):
case .basicType(let builtInType):
switch builtInType {
case .address: self = .address
case .int, .bool, .wei: self = .uint256
Expand Down
10 changes: 8 additions & 2 deletions Sources/Parser/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ extension Parser {
let (callerCapabilities, closeBracketToken) = try parseCallerCapabilityGroup()
try consume(.punctuation(.openBrace))

let members = try parseContractBehaviorMembers()
let members = try parseContractBehaviorMembers(contractIdentifier: contractIdentifier.name)

try consume(.punctuation(.closeBrace))

Expand Down Expand Up @@ -447,14 +447,20 @@ extension Parser {
return callerCapabilities
}

func parseContractBehaviorMembers() throws -> [ContractBehaviorMember] {
func parseContractBehaviorMembers(contractIdentifier: RawTypeIdentifier) 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))

if initializerDeclaration.isPublic, environment.publicInitializer(forContract: contractIdentifier) == nil {
// Record the public initializer, we will need to know if one of was declared during semantic analysis of the
// contract's state properties.
environment.setPublicInitializer(initializerDeclaration, forContract: contractIdentifier)
}
} else {
break
}
Expand Down
24 changes: 19 additions & 5 deletions Sources/SemanticAnalyzer/SemanticAnalyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ public struct SemanticAnalyzer: ASTPass {
var passContext = passContext
var diagnostics = [Diagnostic]()

if let _ = passContext.functionDeclarationContext {
let inFunctionOrInitializer = passContext.functionDeclarationContext != nil || passContext.initializerDeclarationContext != nil

if inFunctionOrInitializer {
// We're in a function. Record the local variable declaration.
passContext.scopeContext?.localVariables += [variableDeclaration]
} else {
} else if let contractStateDeclarationContext = passContext.contractStateDeclarationContext {
// This is a state property declaration.

// If a default value is assigned, it should be a literal.
Expand All @@ -76,6 +78,12 @@ public struct SemanticAnalyzer: ASTPass {
if !assignedExpression.isLiteral {
diagnostics.append(.statePropertyDeclarationIsAssignedANonLiteralExpression(variableDeclaration))
}
} else {
if variableDeclaration.type.rawType.isBuiltInType && !variableDeclaration.type.rawType.isEventType && passContext.environment!.publicInitializer(forContract: contractStateDeclarationContext.contractIdentifier.name) == nil {
// The contract has no public initializer, so a default value must be provided.

diagnostics.append(.statePropertyIsNotAssignedAValue(variableDeclaration))
}
}
}

Expand Down Expand Up @@ -372,7 +380,8 @@ public struct SemanticAnalyzer: ASTPass {
if !context.callerCapabilities.contains(where: { $0.isAny }) {
diagnostics.append(.contractInitializerNotDeclaredInAnyCallerCapabilityBlock(initializerDeclaration))
} else {
if let publicInitializer = passContext.environment!.publicInitializer(forContract: contractName) {
if let publicInitializer = passContext.environment!.publicInitializer(forContract: contractName), publicInitializer.sourceLocation != initializerDeclaration.sourceLocation {
// There can be at most one public initializer.
diagnostics.append(.multiplePublicInitializersDefined(initializerDeclaration, originalInitializerLocation: publicInitializer.sourceLocation))
} else {
// This is the first public initializer we encounter in this contract.
Expand All @@ -382,8 +391,13 @@ public struct SemanticAnalyzer: ASTPass {
}

// Check all the properties in the type have been assigned.
if let unassignedProperties = passContext.unassignedProperties, unassignedProperties.count > 0 {
diagnostics.append(.returnFromInitializerWithoutInitializingAllProperties(initializerDeclaration, unassignedProperties: unassignedProperties))
if let unassignedProperties = passContext.unassignedProperties {
let propertiesWithBuiltInTypes = unassignedProperties.filter { $0.type.rawType.isBuiltInType && !$0.type.rawType.isEventType }

// For now, only non user-defined types need to be assigned a value.
if propertiesWithBuiltInTypes.count > 0 {
diagnostics.append(.returnFromInitializerWithoutInitializingAllProperties(initializerDeclaration, unassignedProperties: unassignedProperties))
}
}

return ASTPassResult(element: initializerDeclaration, diagnostics: diagnostics, passContext: passContext)
Expand Down
4 changes: 2 additions & 2 deletions Sources/SemanticAnalyzer/SemanticError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ extension Diagnostic {
return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "The default value assigned to a state property must be a literal")
}

static func constantStatePropertyIsNotAssignedAValue(_ variableDeclaration: VariableDeclaration) -> Diagnostic {
return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "'let' constant '\(variableDeclaration.identifier.name)' needs to be assigned a value")
static func statePropertyIsNotAssignedAValue(_ variableDeclaration: VariableDeclaration) -> Diagnostic {
return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "State property '\(variableDeclaration.identifier.name)' needs to be assigned a value")
}

static func returnFromInitializerWithoutInitializingAllProperties(_ initializerDeclaration: InitializerDeclaration, unassignedProperties: [VariableDeclaration]) -> Diagnostic {
Expand Down
4 changes: 3 additions & 1 deletion Sources/SemanticAnalyzer/TypeChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ public struct TypeChecker: ASTPass {
rhsType = nil
}

if let rhsType = rhsType, !lhsType.isCompatible(with: rhsType), ![lhsType, rhsType].contains(.errorType) {
// Numeric literals can be assigned to Wei properties (until we support struct initializers)
if let rhsType = rhsType, rhsType == .basicType(.int), lhsType == .basicType(.wei) {
} else if let rhsType = rhsType, !lhsType.isCompatible(with: rhsType), ![lhsType, rhsType].contains(.errorType) {
diagnostics.append(.incompatibleAssignment(lhsType: lhsType, rhsType: rhsType, expression: assignedExpression))
}
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/BehaviorTests/tests/array/array.flint
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
contract Array {
var arr: Int[4]
var arr2: Int[10]
var numWrites: Int
var arr: Int[4] = []
var arr2: Int[10] = []
var numWrites: Int = 0

var arr3: [S]
var arr3: [S] = []
}

Array :: (any) {
Expand Down
8 changes: 4 additions & 4 deletions Tests/BehaviorTests/tests/bank/bank.flint
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
contract Bank {
var manager: Address
var balances: [Address: Int]
var accounts: [Address]
var lastIndex: Int
var balances: [Address: Int] = [:]
var accounts: [Address] = []
var lastIndex: Int = 0

var totalDonations: Wei
var totalDonations: Wei = 0
}

Bank :: account <- (any) {
Expand Down
Loading

0 comments on commit 5fe83a5

Please sign in to comment.