diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f6a914b01..a0a59e057 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,7 +18,7 @@ name: Build and test # branch. # 2) Use https://github.com/nektos/act to run your CI steps locally. Note this will only work with # steps run on Linux platforms, as `act` is implemented with Docker containers. - +# on: push: branches: [main] @@ -151,7 +151,7 @@ jobs: - name: Upload coverage reports to Codecov if: ${{ contains(matrix.more-spm-test-options, '--enable-code-coverage') }} - uses: codecov/codecov-action@v4.4.1 + uses: codecov/codecov-action@v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true @@ -238,6 +238,7 @@ jobs: uses: SwiftyLab/setup-swift@latest with: swift-version: ${{ env.swift-version }} + cache-snapshot: false # Workaround for https://github.com/SwiftyLab/setup-swift/issues/315 - uses: compnerd/gha-setup-vsdevenv@main - name: Set up swift (Windows) diff --git a/.github/workflows/doc-extraction.yml b/.github/workflows/doc-extraction.yml index 243f1a3d5..99466304c 100644 --- a/.github/workflows/doc-extraction.yml +++ b/.github/workflows/doc-extraction.yml @@ -46,6 +46,7 @@ jobs: uses: SwiftyLab/setup-swift@latest with: swift-version: ${{ env.swift-version }} + cache-snapshot: false # Workaround for https://github.com/SwiftyLab/setup-swift/issues/315 - name: Install LLVM # 7z doesn't support decompressing from a stream or we'd do this all as one statement. Maybe diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 2ebbedb1b..326c98409 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -18,4 +18,4 @@ jobs: show-progress: false - name: Check spelling of file.txt - uses: crate-ci/typos@v1.22.3 + uses: crate-ci/typos@v1.24.6 diff --git a/.typos.toml b/.typos.toml index 77ae59d8d..238ad4fdf 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,3 +1,6 @@ [default.extend-words] inout = "inout" # Mutable projection keyword olt = "olt" # Abbreviation for "ordered less than" + +[files] +extend-exclude = ["Tests/ManglingTests/MangledStrings.swift"] diff --git a/CMakeLists.txt b/CMakeLists.txt index d3925c626..ee25f7fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,7 @@ # # Standard project boilerplate # -cmake_minimum_required(VERSION 3.29) - -# Workaround for -# https://gitlab.kitware.com/cmake/cmake/-/issues/25869#note_1506487 -cmake_policy(SET CMP0157 OLD) +cmake_minimum_required(VERSION 3.30) project(Hylo VERSION 0.1.0 @@ -16,18 +12,14 @@ project(Hylo enable_testing() include(CTest) -set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") +set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0") list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) set(CMAKE_Swift_LANGUAGE_VERSION 5) set(CMAKE_POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}) # Needed? set(CMAKE_Swift_COMPILE_OPTIONS - -warnings-as-errors - # Workaround for https://github.com/apple/swift/issues/68947 that is - # compatible with the workaround above for - # https://gitlab.kitware.com/cmake/cmake/-/issues/25869 - -driver-filelist-threshold=500) + -warnings-as-errors) if(BUILD_TESTING) list(APPEND CMAKE_Swift_COMPILE_OPTIONS -enable-testing) endif() diff --git a/Package.swift b/Package.swift index 434e64fe5..12f994afb 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( name: "Hylo", platforms: [ - .macOS(.v12) + .macOS(.v13) ], products: [ @@ -135,7 +135,7 @@ let package = Package( name: "StandardLibrary", dependencies: ["FrontEnd", "Utils"], path: "StandardLibrary", - exclude: ["Sources"], + resources: [.copy("Sources")], swiftSettings: allTargetsSwiftSettings), .plugin( diff --git a/Plugins/TestGeneratorPlugin/TestGeneratorPlugin.swift b/Plugins/TestGeneratorPlugin/TestGeneratorPlugin.swift index bb9f6cd71..038397517 100644 --- a/Plugins/TestGeneratorPlugin/TestGeneratorPlugin.swift +++ b/Plugins/TestGeneratorPlugin/TestGeneratorPlugin.swift @@ -8,27 +8,27 @@ struct TestGeneratorPlugin: SPMBuildToolPlugin { func buildCommands( context: PackagePlugin.PluginContext, target: PackagePlugin.Target - ) throws -> [SPMBuildCommand] - { + ) throws -> [SPMBuildCommand] { guard let target = target as? SourceModuleTarget else { return [] } // Using target.sourceFiles(withSuffix: ".hylo").map(\.path) as inputFiles creates noisy - // warnings about unused sources. Instead, find the input files relative to the target + // warnings about unused sources. Instead, find the input files relative to the target // directory and exclude them as sources in Package.swift. let testCases = target.directory.url / "TestCases" let inputPaths = try FileManager.default.subpathsOfDirectory(atPath: testCases.platformString) - .filter { $0.hasSuffix(".hylo") }.map { (testCases/$0).spmPath } + .compactMap { (p) in p.hasSuffix(".hylo") ? .some((testCases / p).spmPath) : nil } let outputPath = context.pluginWorkDirectory.appending("HyloFileTests.swift") + let options = ["-o", outputPath.platformString, "-n", target.moduleName] return [ .buildCommand( - displayName: "Generating XCTestCases into \(outputPath)", - executable: .targetInThisPackage("GenerateHyloFileTests"), - arguments: ["-o", outputPath.platformString, "-n", target.moduleName] - + inputPaths.map(\.platformString), - inputFiles: inputPaths, - outputFiles: [outputPath])] + displayName: "Generating XCTestCases into \(outputPath)", + executable: .targetInThisPackage("GenerateHyloFileTests"), + arguments: options + inputPaths.map(\.platformString), + inputFiles: inputPaths, + outputFiles: [outputPath]) + ] } } diff --git a/Sources/CodeGen/LLVM/LLVMProgram.swift b/Sources/CodeGen/LLVM/LLVMProgram.swift index 54b90ee93..fd7425fcd 100644 --- a/Sources/CodeGen/LLVM/LLVMProgram.swift +++ b/Sources/CodeGen/LLVM/LLVMProgram.swift @@ -24,7 +24,8 @@ public struct LLVMProgram { ) throws { self.target = try target ?? SwiftyLLVM.TargetMachine(for: .host()) for m in ir.modules.keys { - let transpilation = SwiftyLLVM.Module(transpiling: m, from: ir) + var context = CodeGenerationContext(forCompiling: m, of: ir) + let transpilation = SwiftyLLVM.Module(transpiling: m, in: &context) do { try transpilation.verify() } catch { @@ -55,7 +56,9 @@ public struct LLVMProgram { /// to `directory`, returning the URL of each written file. /// /// - Returns: The URL of each written product, one for each module in `self`. - public func write(_ type: SwiftyLLVM.CodeGenerationResultType, to directory: URL) throws -> [URL] { + public func write( + _ type: SwiftyLLVM.CodeGenerationResultType, to directory: URL + ) throws -> [URL] { precondition(directory.hasDirectoryPath) var result: [URL] = [] for m in llvmModules.values { diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index 0b36603cc..d75d90e5e 100644 --- a/Sources/CodeGen/LLVM/Transpilation.swift +++ b/Sources/CodeGen/LLVM/Transpilation.swift @@ -4,42 +4,67 @@ import IR import SwiftyLLVM import Utils +/// The state of a compilation from Hylo IR to LLVM IR. +struct CodeGenerationContext { + + /// The program containing the `module`. + let ir: IR.Program + + /// The Hylo module being compiled. + let module: ModuleDecl.ID + + /// A table from string constant to its representation in LLVM. + var strings = Trie() + + /// Creates an instance for compiling `m`, which is a module of `p`. + init(forCompiling m: ModuleDecl.ID, of p: IR.Program) { + self.ir = p + self.module = m + } + + /// Projects the contents of the Hylo module being compiled. + var source: IR.Module { + _read { yield ir.modules[module]! } + } + +} + extension SwiftyLLVM.Module { /// Creates the LLVM transpilation of the Hylo IR module `m` in `ir`. - init(transpiling m: ModuleDecl.ID, from ir: IR.Program) { - let source = ir.modules[m]! + init(transpiling m: ModuleDecl.ID, in context: inout CodeGenerationContext) { + let source = context.ir.modules[m]! self.init(source.name) for t in source.productTypes { - _ = demandMetatype(of: ^t, usedIn: source, from: ir) + _ = demandMetatype(of: ^t, in: &context) } for t in source.traits { - _ = demandTrait(t, usedIn: source, from: ir) + _ = demandTrait(t, in: &context) } for a in source.allocations { - incorporate(a, of: source, from: ir) + incorporate(a, in: &context) } for f in source.functions.keys { - incorporate(f, of: source, from: ir) + incorporate(f, in: &context) } } /// Transpiles and incorporates `f`, which is a function or subscript of `m` in `ir`. - mutating func incorporate(_ f: IR.Function.ID, of m: IR.Module, from ir: IR.Program) { + mutating func incorporate(_ f: IR.Function.ID, in context: inout CodeGenerationContext) { // Don't transpile generic functions. - if m[f].isGeneric { + if context.source[f].isGeneric { return } - if m[f].isSubscript { - let d = declareSubscript(transpiledFrom: f, of: m, from: ir) - transpile(contentsOf: f, of: m, from: ir, into: d) + if context.source[f].isSubscript { + let d = declareSubscript(transpiledFrom: f, in: &context) + transpile(contentsOf: f, into: d, inContext: &context) } else { - let d = declareFunction(transpiledFrom: f, of: m, from: ir) - transpile(contentsOf: f, of: m, from: ir, into: d) - if f == m.entryFunction { - defineMain(calling: f, of: m, from: ir) + let d = declareFunction(transpiledFrom: f, in: &context) + transpile(contentsOf: f, into: d, inContext: &context) + if f == context.source.entryFunction { + defineMain(calling: f, in: &context) } } } @@ -49,19 +74,19 @@ extension SwiftyLLVM.Module { /// The storage is allocated along with a flag keeping track of its initialization state. Access /// is provided by an addressor that initializes the storage the first time it is called using /// `s.initializer`. - private mutating func incorporate(_ s: StaticStorage, of m: IR.Module, from ir: IR.Program) { - let prefix = ir.base.mangled(s.id) + private mutating func incorporate(_ s: StaticStorage, in context: inout CodeGenerationContext) { + let prefix = context.ir.base.mangled(s.id) // Define the static allocation. - let t = ir.llvm(s.pointee, in: &self) + let t = context.ir.llvm(s.pointee, in: &self) let u = SwiftyLLVM.StructType(named: prefix + ".T", [t, self.i1], in: &self) let storage = addGlobalVariable(prefix + ".S", u) setInitializer(u.null, for: storage) setLinkage(.private, for: storage) // Define the addressor projecting the allocated access. - incorporate(s.initializer, of: m, from: ir) - let initializer = function(named: ir.llvmName(of: s.initializer))! + incorporate(s.initializer, in: &context) + let initializer = function(named: context.ir.llvmName(of: s.initializer))! let addressor = declareFunction(prefix, .init(from: [], to: ptr, in: &self)) let entry = appendBlock(to: addressor) @@ -86,7 +111,7 @@ extension SwiftyLLVM.Module { // store true, %1 // br b0 insertionPoint = endOf(b1) - let x3 = insertAlloca(ir.llvm(AnyType.void, in: &self), at: insertionPoint) + let x3 = insertAlloca(context.ir.llvm(AnyType.void, in: &self), at: insertionPoint) _ = insertCall(initializer, on: [x0, x3], at: insertionPoint) _ = insertStore(i1.constant(1), to: x1, at: insertionPoint) insertBr(to: b0, at: insertionPoint) @@ -103,22 +128,20 @@ extension SwiftyLLVM.Module { /// function named "main", taking no parameter and returning either `Void` or `Int32`. `f` will /// be linked privately in `m`. private mutating func defineMain( - calling f: IR.Function.ID, - of m: IR.Module, - from ir: IR.Program + calling f: IR.Function.ID, in context: inout CodeGenerationContext ) { let main = declareFunction("main", FunctionType(from: [], to: i32, in: &self)) let b = appendBlock(to: main) let p = endOf(b) - let transpilation = function(named: ir.llvmName(of: f))! + let transpilation = function(named: context.ir.llvmName(of: f))! setLinkage(.private, for: transpilation) - let int32 = ir.ast.coreType("Int32")! - switch m[f].output { + let int32 = context.ir.ast.coreType("Int32")! + switch context.source[f].output { case int32: - let t = StructType(ir.llvm(int32, in: &self))! + let t = StructType(context.ir.llvm(int32, in: &self))! let s = insertAlloca(t, at: p) _ = insertCall(transpilation, on: [s], at: p) @@ -127,7 +150,7 @@ extension SwiftyLLVM.Module { insertReturn(status, at: p) default: - let t = ir.llvm(AnyType.void, in: &self) + let t = context.ir.llvm(AnyType.void, in: &self) let s = insertAlloca(t, at: p) _ = insertCall(transpilation, on: [s], at: p) insertReturn(i32.zero, at: p) @@ -218,23 +241,21 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value corresponding to the Hylo IR constant `c` when used in `m` in `ir`. private mutating func transpiledConstant( - _ c: any IR.Constant, - usedIn m: IR.Module, - from ir: IR.Program + _ c: any IR.Constant, in context: inout CodeGenerationContext ) -> SwiftyLLVM.IRValue { switch c { case let v as IR.WordConstant: - return transpiledConstant(v, usedIn: m, from: ir) + return transpiledConstant(v, in: &context) case let v as IR.IntegerConstant: - return transpiledConstant(v, usedIn: m, from: ir) + return transpiledConstant(v, in: &context) case let v as IR.FloatingPointConstant: - return transpiledConstant(v, usedIn: m, from: ir) + return transpiledConstant(v, in: &context) case let v as IR.WitnessTable: - return transpiledWitnessTable(v, usedIn: m, from: ir) + return transpiledWitnessTable(v, in: &context) case let v as IR.FunctionReference: - return declare(v, from: ir) + return declare(v, from: context.ir) case let v as MetatypeType: - return demandMetatype(of: v.instance, usedIn: m, from: ir) + return demandMetatype(of: v.instance, in: &context) case is IR.VoidConstant: return SwiftyLLVM.StructConstant(aggregating: [], in: &self) default: @@ -244,14 +265,14 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value corresponding to the Hylo IR constant `c` when used in `m` in `ir`. private mutating func transpiledConstant( - _ c: IR.WordConstant, usedIn m: IR.Module, from ir: IR.Program + _ c: IR.WordConstant, in context: inout CodeGenerationContext ) -> SwiftyLLVM.IRValue { word().constant(c.value) } /// Returns the LLVM IR value corresponding to the Hylo IR constant `c` when used in `m` in `ir`. private mutating func transpiledConstant( - _ c: IR.IntegerConstant, usedIn m: IR.Module, from ir: IR.Program + _ c: IR.IntegerConstant, in context: inout CodeGenerationContext ) -> SwiftyLLVM.IRValue { guard c.value.bitWidth <= 64 else { UNIMPLEMENTED() } let t = SwiftyLLVM.IntegerType(c.value.bitWidth, in: &self) @@ -260,17 +281,15 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value corresponding to the Hylo IR constant `c` when used in `m` in `ir`. private mutating func transpiledConstant( - _ c: IR.FloatingPointConstant, usedIn m: IR.Module, from ir: IR.Program + _ c: IR.FloatingPointConstant, in context: inout CodeGenerationContext ) -> SwiftyLLVM.IRValue { - let t = SwiftyLLVM.FloatingPointType(ir.llvm(c.type.ast, in: &self))! + let t = SwiftyLLVM.FloatingPointType(context.ir.llvm(c.type.ast, in: &self))! return t.constant(parsing: c.value) } /// Returns the LLVM IR value of the witness table `t` used in `m` in `ir`. private mutating func transpiledWitnessTable( - _ t: WitnessTable, - usedIn m: IR.Module, - from ir: IR.Program + _ t: WitnessTable, in context: inout CodeGenerationContext ) -> SwiftyLLVM.IRValue { // A witness table is composed of a header, a trait map, and a (possibly empty) sequence of // implementation maps. All parts are laid out inline without any padding. @@ -286,7 +305,7 @@ extension SwiftyLLVM.Module { // Encode the table's header. var tableContents: [SwiftyLLVM.IRValue] = [ - demandMetatype(of: t.witness, usedIn: m, from: ir), + demandMetatype(of: t.witness, in: &context), word().constant(t.conformances.count), ] @@ -295,15 +314,15 @@ extension SwiftyLLVM.Module { var implementations: [SwiftyLLVM.IRValue] = [] for c in t.conformances { let entry: [SwiftyLLVM.IRValue] = [ - demandTrait(c.concept, usedIn: m, from: ir), + demandTrait(c.concept, in: &context), word().constant(implementations.count), ] entries.append(SwiftyLLVM.StructConstant(aggregating: entry, in: &self)) for (r, d) in c.implementations { let requirement: [SwiftyLLVM.IRValue] = [ - word().constant(r.rawValue), - transpiledRequirementImplementation(d, from: ir), + word().constant(r.rawValue.bits), + transpiledRequirementImplementation(d, from: context.ir), ] implementations.append(SwiftyLLVM.StructConstant(aggregating: requirement, in: &self)) } @@ -322,12 +341,13 @@ extension SwiftyLLVM.Module { if !implementations.isEmpty { tableContents.append( SwiftyLLVM.ArrayConstant( - of: SwiftyLLVM.StructType([word(), ptr], in: &self), containing: implementations, in: &self)) + of: SwiftyLLVM.StructType([word(), ptr], in: &self), containing: implementations, + in: &self)) } let table = SwiftyLLVM.StructConstant(aggregating: tableContents, in: &self) - let g = declareGlobalVariable(ir.base.mangled(t), table.type) + let g = declareGlobalVariable(context.ir.base.mangled(t), table.type) setInitializer(table, for: g) setLinkage(.linkOnce, for: g) setGlobalConstant(true, for: g) @@ -348,13 +368,13 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value of the metatype `t` used in `m` in `ir`. private mutating func demandMetatype( - of t: AnyType, usedIn m: IR.Module, from ir: IR.Program + of t: AnyType, in context: inout CodeGenerationContext ) -> SwiftyLLVM.GlobalVariable { - demandMetatype(of: t, usedIn: m, from: ir) { (me, v) in + demandMetatype(of: t, in: &context) { (me, c, v) in if let u = ProductType(t) { - me.initializeTranspiledProductTypeMetatype(v, of: u, usedIn: m, from: ir) + me.initializeTranspiledProductTypeMetatype(v, of: u, in: &c) } else { - me.initializeTranspiledMetatype(v, of: t, usedIn: m, from: ir) + me.initializeTranspiledMetatype(v, of: t, in: &c) } } } @@ -362,11 +382,11 @@ extension SwiftyLLVM.Module { /// Initializes `instance` with the value of the metatype of `t` used in `m` in `ir`. private mutating func initializeTranspiledMetatype( _ instance: SwiftyLLVM.GlobalVariable, - of t: T, usedIn m: IR.Module, from ir: IR.Program + of t: T, in context: inout CodeGenerationContext ) { setLinkage(.linkOnce, for: instance) - let layout = ConcreteTypeLayout(of: ^t, definedIn: ir, forUseIn: &self) + let layout = ConcreteTypeLayout(of: ^t, definedIn: context.ir, forUseIn: &self) let v = SwiftyLLVM.StructType(instance.valueType)!.constant( aggregating: [ word().constant(layout.size), @@ -383,18 +403,18 @@ extension SwiftyLLVM.Module { /// Initializes `instance` with the value of the metatype of `t` used in `m` in `ir`. private mutating func initializeTranspiledProductTypeMetatype( _ instance: SwiftyLLVM.GlobalVariable, - of t: ProductType, usedIn m: IR.Module, from ir: IR.Program + of t: ProductType, in context: inout CodeGenerationContext ) { // Initialize the instance if it's being used in the module defining `t`. Otherwise, simply let // the symbol be linked to its definition later. - if m.id != ir.base.module(containing: t.decl) { return } + if context.module != context.ir.base.module(containing: t.decl) { return } // If `t` is generic, its metatype is only a stub. let layout: ConcreteTypeLayout - if !ir.base[t.decl].genericParameters.isEmpty { + if !context.ir.base[t.decl].genericParameters.isEmpty { layout = ConcreteTypeLayout(size: 0, alignment: 0) } else { - layout = ConcreteTypeLayout(of: ^t, definedIn: ir, forUseIn: &self) + layout = ConcreteTypeLayout(of: ^t, definedIn: context.ir, forUseIn: &self) } let v = SwiftyLLVM.StructType(instance.valueType)!.constant( @@ -413,24 +433,26 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value of `t` used in `m` in `ir`, calling `initializeInstance` to /// initialize it. private mutating func demandMetatype( - of t: T, usedIn m: IR.Module, from ir: IR.Program, - initializedWith initializeInstance: (inout Self, SwiftyLLVM.GlobalVariable) -> Void + of t: T, in context: inout CodeGenerationContext, + initializedWith initializeInstance: ( + inout Self, inout CodeGenerationContext, SwiftyLLVM.GlobalVariable + ) -> Void ) -> SwiftyLLVM.GlobalVariable { - let globalName = ir.base.mangled(t) + let globalName = context.ir.base.mangled(t) if let g = global(named: globalName) { return g } let metatype = metatypeType() let instance = declareGlobalVariable(globalName, metatype) - initializeInstance(&self, instance) + initializeInstance(&self, &context, instance) return instance } /// Returns the LLVM IR value of `t` used in `m` in `ir`. private mutating func demandTrait( - _ t: TraitType, usedIn m: IR.Module, from ir: IR.Program + _ t: TraitType, in context: inout CodeGenerationContext ) -> SwiftyLLVM.GlobalVariable { // Check if we already created the trait's instance. - let globalName = ir.base.mangled(t) + let globalName = context.ir.base.mangled(t) if let g = global(named: globalName) { return g } @@ -438,7 +460,7 @@ extension SwiftyLLVM.Module { // Initialize the instance if it's being used in the module defining `t`. Otherwise, simply // declare the symbol and let it be linked later. let instance = declareGlobalVariable(globalName, ptr) - if m.id != ir.base.module(containing: t.decl) { + if context.module != context.ir.base.module(containing: t.decl) { return instance } @@ -463,36 +485,41 @@ extension SwiftyLLVM.Module { /// Inserts and returns the transpiled declaration of `f`, which is a function of `m` in `ir`. private mutating func declareFunction( - transpiledFrom f: IR.Function.ID, of m: IR.Module, from ir: IR.Program + transpiledFrom f: IR.Function.ID, in context: inout CodeGenerationContext ) -> SwiftyLLVM.Function { - precondition(!m[f].isSubscript) + precondition(!context.source[f].isSubscript) // Parameters and return values are passed by reference. - let parameters = Array(repeating: ptr as SwiftyLLVM.IRType, count: m[f].inputs.count + 1) - let transpilation = declareFunction(ir.llvmName(of: f), .init(from: parameters, in: &self)) + let parameters = Array( + repeating: ptr as SwiftyLLVM.IRType, count: context.source[f].inputs.count + 1) + let transpilation = declareFunction( + context.ir.llvmName(of: f), .init(from: parameters, in: &self)) - configureAttributes(transpilation, transpiledFrom: f, of: m) - configureInputAttributes(transpilation.parameters.dropLast(), transpiledFrom: f, in: m) + configureAttributes(transpilation, transpiledFrom: f, of: context.source) + configureInputAttributes( + transpilation.parameters.dropLast(), transpiledFrom: f, in: context.source) return transpilation } /// Inserts and returns the transpiled declaration of `f`, which is a subscript of `m` in `ir`. private mutating func declareSubscript( - transpiledFrom f: IR.Function.ID, of m: IR.Module, from ir: IR.Program + transpiledFrom f: IR.Function.ID, in context: inout CodeGenerationContext ) -> SwiftyLLVM.Function { - precondition(m[f].isSubscript) + precondition(context.source[f].isSubscript) // Parameters are a buffer for the subscript frame followed by its declared parameters. Return // type is a pair `(c, p)` where `c` points to a subscript slide and `p` is the address of the // projected value. let r = SwiftyLLVM.StructType([ptr, ptr], in: &self) - let parameters = Array(repeating: ptr as SwiftyLLVM.IRType, count: m[f].inputs.count + 1) + let parameters = Array( + repeating: ptr as SwiftyLLVM.IRType, count: context.source[f].inputs.count + 1) let transpilation = declareFunction( - ir.llvmName(of: f), .init(from: parameters, to: r, in: &self)) + context.ir.llvmName(of: f), .init(from: parameters, to: r, in: &self)) - configureAttributes(transpilation, transpiledFrom: f, of: m) - configureInputAttributes(transpilation.parameters.dropFirst(), transpiledFrom: f, in: m) + configureAttributes(transpilation, transpiledFrom: f, of: context.source) + configureInputAttributes( + transpilation.parameters.dropFirst(), transpiledFrom: f, in: context.source) return transpilation } @@ -550,12 +577,11 @@ extension SwiftyLLVM.Module { /// of `m` in `ir`. private mutating func transpile( contentsOf f: IR.Function.ID, - of m: IR.Module, - from ir: IR.Program, - into transpilation: SwiftyLLVM.Function + into transpilation: SwiftyLLVM.Function, + inContext context: inout CodeGenerationContext ) { /// The function's entry. - guard let entry = m[f].entry else { return } + guard let entry = context.source[f].entry else { return } /// Where new LLVM IR instruction are inserted. var insertionPoint: SwiftyLLVM.InsertionPoint! @@ -581,7 +607,7 @@ extension SwiftyLLVM.Module { // In subscripts, parameters are laid out after the frame buffer. let parameterOffset: Int - if m[f].isSubscript { + if context.source[f].isSubscript { parameterOffset = 1 frame = insertSubscriptPrologue(into: transpilation) } else { @@ -589,19 +615,19 @@ extension SwiftyLLVM.Module { frame = nil } - for i in m[m.entry(of: f)!].inputs.indices { + for i in context.source[context.source.entry(of: f)!].inputs.indices { let o = Operand.parameter(.init(f, entry), i) let s = transpilation.parameters[parameterOffset + i] register[o] = s } - for b in m.blocks(in: f) { + for b in context.source.blocks(in: f) { block[b] = appendBlock(named: b.description, to: transpilation) } - for b in m.blocks(in: f) { + for b in context.source.blocks(in: f) { insertionPoint = endOf(block[b]!) - for i in m.instructions(in: b) { + for i in context.source.instructions(in: b) { insert(i) } } @@ -610,7 +636,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(_ i: IR.InstructionID) { - switch m[i] { + switch context.source[i] { case is IR.AddressToPointer: insert(addressToPointer: i) case is IR.AdvancedByBytes: @@ -673,6 +699,8 @@ extension SwiftyLLVM.Module { insert(switch: i) case is IR.UnionDiscriminator: insert(unionDiscriminator: i) + case is IR.UnionSwitch: + insert(unionSwitch: i) case is IR.Unreachable: insert(unreachable: i) case is IR.WrapExistentialAddr: @@ -686,13 +714,13 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(addressToPointer i: IR.InstructionID) { - let s = m[i] as! AddressToPointer + let s = context.source[i] as! AddressToPointer register[.register(i)] = llvm(s.source) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(advancedByBytes i: IR.InstructionID) { - let s = m[i] as! AdvancedByBytes + let s = context.source[i] as! AdvancedByBytes let base = llvm(s.base) let v = insertGetElementPointerInBounds( @@ -702,10 +730,10 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(advancedByStrides i: IR.InstructionID) { - let s = m[i] as! AdvancedByStrides + let s = context.source[i] as! AdvancedByStrides let base = llvm(s.base) - let baseType = ir.llvm(m.type(of: s.base).ast, in: &self) + let baseType = context.ir.llvm(context.source.type(of: s.base).ast, in: &self) let indices = [i32.constant(0), i32.constant(s.offset)] let v = insertGetElementPointerInBounds( of: base, typed: baseType, indices: indices, at: insertionPoint) @@ -714,8 +742,8 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(allocStack i: IR.InstructionID) { - let s = m[i] as! AllocStack - let t = ir.llvm(s.allocatedType, in: &self) + let s = context.source[i] as! AllocStack + let t = context.ir.llvm(s.allocatedType, in: &self) if layout.storageSize(of: t) == 0 { register[.register(i)] = ptr.null } else { @@ -725,19 +753,19 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(access i: IR.InstructionID) { - let s = m[i] as! Access + let s = context.source[i] as! Access register[.register(i)] = llvm(s.source) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(branch i: IR.InstructionID) { - let s = m[i] as! Branch + let s = context.source[i] as! Branch insertBr(to: block[s.target]!, at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(call i: IR.InstructionID) { - let s = m[i] as! Call + let s = context.source[i] as! Call var arguments: [SwiftyLLVM.IRValue] = [] // Callee is evaluated first; environment is passed before explicit arguments. @@ -752,14 +780,16 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(callFFI i: IR.InstructionID) { - let s = m[i] as! CallFFI - let parameters = s.operands.map({ ir.llvm(m.type(of: $0).ast, in: &self) }) + let s = context.source[i] as! CallFFI + let parameters = s.operands.map { (o) in + context.ir.llvm(context.source.type(of: o).ast, in: &self) + } let returnType: SwiftyLLVM.IRType if s.returnType.ast.isVoidOrNever { returnType = void } else { - returnType = ir.llvm(s.returnType.ast, in: &self) + returnType = context.ir.llvm(s.returnType.ast, in: &self) } let callee = declareFunction(s.callee, .init(from: parameters, to: returnType, in: &self)) @@ -769,21 +799,21 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(captureIn i: IR.InstructionID) { - let s = m[i] as! CaptureIn + let s = context.source[i] as! CaptureIn insertStore(llvm(s.source), to: llvm(s.target), at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(closeUnion i: IR.InstructionID) { - let s = m[i] as! CloseUnion - let open = m[s.start.instruction!] as! OpenUnion + let s = context.source[i] as! CloseUnion + let open = context.source[s.start.instruction!] as! OpenUnion // TODO: Memoize somehow - let t = UnionType(m.type(of: open.container).ast)! - let e = m.program.discriminatorToElement(in: t) + let t = UnionType(context.source.type(of: open.container).ast)! + let e = context.ir.base.discriminatorToElement(in: t) let n = e.firstIndex(of: open.payloadType)! - let baseType = ir.llvm(unionType: t, in: &self) + let baseType = context.ir.llvm(unionType: t, in: &self) let container = llvm(open.container) let indices = [i32.constant(0), i32.constant(1)] let discriminator = insertGetElementPointerInBounds( @@ -793,7 +823,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(constantString i: IR.InstructionID) { - let s = m[i] as! ConstantString + let s = context.source[i] as! ConstantString let count = s.value.count // Contents fit inline storage. @@ -807,7 +837,14 @@ extension SwiftyLLVM.Module { register[.register(i)] = i64.constant(units) } - // Contents require out-of-line storage. + // Contents has already been incorporated in the module. + else if let storage = context.strings[s.value] { + let x0 = insertPtrToInt(storage, to: i64, at: insertionPoint) + let x1 = insertBitwiseOr(x0, i64(0b11), at: insertionPoint) + register[.register(i)] = x1 + } + + // Contents require new out-of-line storage. else { let w = word() @@ -817,10 +854,11 @@ extension SwiftyLLVM.Module { aggregating: [w(count), w(count), payload] as [IRValue], in: &self) - let storage = declareGlobalVariable(UUID().uuidString, storageType) + let storage = declareGlobalVariable("_" + UUID().uuidString, storageType) setInitializer(storageValue, for: storage) setLinkage(.private, for: storage) setGlobalConstant(true, for: storage) + context.strings[s.value] = storage let x0 = insertPtrToInt(storage, to: i64, at: insertionPoint) let x1 = insertBitwiseOr(x0, i64(0b11), at: insertionPoint) @@ -830,7 +868,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(condBranch i: IR.InstructionID) { - let s = m[i] as! CondBranch + let s = context.source[i] as! CondBranch let c = llvm(s.condition) insertCondBr( if: c, then: block[s.targetIfTrue]!, else: block[s.targetIfFalse]!, @@ -839,9 +877,9 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(endProjection i: IR.InstructionID) { - let s = m[i] as! EndProject + let s = context.source[i] as! EndProject let start = s.start.instruction! - assert(m[start] is Project) + assert(context.source[start] is Project) let t = SwiftyLLVM.FunctionType(from: [ptr, i1], to: void, in: &self) let p = byproduct[start]! @@ -850,18 +888,18 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(globalAddr i: IR.InstructionID) { - let s = m[i] as! IR.GlobalAddr - let n = ir.base.mangled(s.binding) + let s = context.source[i] as! IR.GlobalAddr + let n = context.ir.base.mangled(s.binding) let a = declareFunction(n, .init(from: [], to: ptr, in: &self)) register[.register(i)] = insertCall(a, on: [], at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(subfieldView i: IR.InstructionID) { - let s = m[i] as! SubfieldView + let s = context.source[i] as! SubfieldView let base = llvm(s.recordAddress) - let baseType = ir.llvm(m.type(of: s.recordAddress).ast, in: &self) + let baseType = context.ir.llvm(context.source.type(of: s.recordAddress).ast, in: &self) let indices = [i32.constant(0)] + s.subfield.map({ i32.constant(UInt64($0)) }) let v = insertGetElementPointerInBounds( of: base, typed: baseType, indices: indices, at: insertionPoint) @@ -870,7 +908,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(llvm i: IR.InstructionID) { - let s = m[i] as! IR.LLVMInstruction + let s = context.source[i] as! IR.LLVMInstruction switch s.instruction { case .add(let p, _): let l = llvm(s.operands[0]) @@ -926,43 +964,55 @@ extension SwiftyLLVM.Module { let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.sadd.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.sadd.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedAdditionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.uadd.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.uadd.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .signedSubtractionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.ssub.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.ssub.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedSubtractionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.usub.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.usub.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .signedMultiplicationWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.smul.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.smul.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedMultiplicationWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( - named: Intrinsic.llvm.umul.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + named: Intrinsic.llvm.umul.with.overflow, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .icmp(let p, _): let l = llvm(s.operands[0]) @@ -985,17 +1035,17 @@ extension SwiftyLLVM.Module { register[.register(i)] = insertBitwiseXor(l, r, at: insertionPoint) case .trunc(_, let t): - let target = ir.llvm(builtinType: t, in: &self) + let target = context.ir.llvm(builtinType: t, in: &self) let source = llvm(s.operands[0]) register[.register(i)] = insertTrunc(source, to: target, at: insertionPoint) case .zext(_, let t): - let target = ir.llvm(builtinType: t, in: &self) + let target = context.ir.llvm(builtinType: t, in: &self) let source = llvm(s.operands[0]) register[.register(i)] = insertZeroExtend(source, to: target, at: insertionPoint) case .sext(_, let t): - let target = ir.llvm(builtinType: t, in: &self) + let target = context.ir.llvm(builtinType: t, in: &self) let source = llvm(s.operands[0]) register[.register(i)] = insertSignExtend(source, to: target, at: insertionPoint) @@ -1004,7 +1054,7 @@ extension SwiftyLLVM.Module { register[.register(i)] = insertIntToPtr(source, at: insertionPoint) case .ptrtoint(let t): - let target = ir.llvm(builtinType: t, in: &self) + let target = context.ir.llvm(builtinType: t, in: &self) let source = llvm(s.operands[0]) register[.register(i)] = insertPtrToInt(source, to: target, at: insertionPoint) @@ -1039,29 +1089,36 @@ extension SwiftyLLVM.Module { register[.register(i)] = insertFloatingPointComparison(p, l, r, at: insertionPoint) case .fptrunc(_, let t): - let target = ir.llvm(builtinType: t, in: &self) + let target = context.ir.llvm(builtinType: t, in: &self) let source = llvm(s.operands[0]) register[.register(i)] = insertFPTrunc(source, to: target, at: insertionPoint) case .ctpop(let t): let source = llvm(s.operands[0]) - let f = intrinsic(named: Intrinsic.llvm.ctpop, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [source], at: insertionPoint) + let f = intrinsic( + named: Intrinsic.llvm.ctpop, + for: [context.ir.llvm(builtinType: t, in: &self)])! + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [source], at: insertionPoint) case .ctlz(let t): let source = llvm(s.operands[0]) - let f = intrinsic(named: Intrinsic.llvm.ctlz, for: [ir.llvm(builtinType: t, in: &self)])! + let f = intrinsic( + named: Intrinsic.llvm.ctlz, + for: [context.ir.llvm(builtinType: t, in: &self)])! register[.register(i)] = insertCall( SwiftyLLVM.Function(f)!, on: [source, i1.zero], at: insertionPoint) case .cttz(let t): let source = llvm(s.operands[0]) - let f = intrinsic(named: Intrinsic.llvm.cttz, for: [ir.llvm(builtinType: t, in: &self)])! + let f = intrinsic( + named: Intrinsic.llvm.cttz, + for: [context.ir.llvm(builtinType: t, in: &self)])! register[.register(i)] = insertCall( SwiftyLLVM.Function(f)!, on: [source, i1.zero], at: insertionPoint) case .zeroinitializer(let t): - register[.register(i)] = ir.llvm(builtinType: t, in: &self).null + register[.register(i)] = context.ir.llvm(builtinType: t, in: &self).null case .advancedByBytes: let base = llvm(s.operands[0]) @@ -1076,38 +1133,39 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(load i: IR.InstructionID) { - let s = m[i] as! Load - let t = ir.llvm(s.objectType.ast, in: &self) + let s = context.source[i] as! Load + let t = context.ir.llvm(s.objectType.ast, in: &self) let source = llvm(s.source) register[.register(i)] = insertLoad(t, from: source, at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(memoryCopy i: IR.InstructionID) { - let s = m[i] as! MemoryCopy + let s = context.source[i] as! MemoryCopy let memcpy = SwiftyLLVM.Function( intrinsic(named: Intrinsic.llvm.memcpy, for: [ptr, ptr, i32])!)! let source = llvm(s.source) let target = llvm(s.target) - let l = ConcreteTypeLayout(of: m.type(of: s.source).ast, definedIn: ir, forUseIn: &self) + let l = ConcreteTypeLayout( + of: context.source.type(of: s.source).ast, definedIn: context.ir, forUseIn: &self) let byteCount = i32.constant(l.size) _ = insertCall(memcpy, on: [target, source, byteCount, i1.zero], at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(openCapture i: IR.InstructionID) { - let s = m[i] as! OpenCapture + let s = context.source[i] as! OpenCapture register[.register(i)] = insertLoad(ptr, from: llvm(s.source), at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(openUnion i: IR.InstructionID) { - let s = m[i] as! OpenUnion - let t = UnionType(m.type(of: s.container).ast)! + let s = context.source[i] as! OpenUnion + let t = UnionType(context.source.type(of: s.container).ast)! - let baseType = ir.llvm(unionType: t, in: &self) + let baseType = context.ir.llvm(unionType: t, in: &self) let container = llvm(s.container) let indices = [i32.constant(0), i32.constant(0)] register[.register(i)] = insertGetElementPointerInBounds( @@ -1116,13 +1174,13 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(pointerToAddress i: IR.InstructionID) { - let s = m[i] as! IR.PointerToAddress + let s = context.source[i] as! IR.PointerToAddress register[.register(i)] = llvm(s.source) } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(project i: IR.InstructionID) { - let s = m[i] as! IR.Project + let s = context.source[i] as! IR.Project // %0 = alloca [8 x i8], align 8 let buffer = SwiftyLLVM.ArrayType(8, i8, in: &self) @@ -1132,8 +1190,8 @@ extension SwiftyLLVM.Module { // All arguments are passed by reference. var arguments: [SwiftyLLVM.IRValue] = [x0] for a in s.operands { - if m.type(of: a).isObject { - let t = ir.llvm(s.result!.ast, in: &self) + if context.source.type(of: a).isObject { + let t = context.ir.llvm(s.result!.ast, in: &self) let l = insertAlloca(t, atEntryOf: transpilation) insertStore(llvm(a), to: l, at: insertionPoint) arguments.append(l) @@ -1143,7 +1201,7 @@ extension SwiftyLLVM.Module { } // %1 = call ptr @llvm.coro.prepare.retcon(ptr @s) - let f = declareSubscript(transpiledFrom: s.callee, of: m, from: ir) + let f = declareSubscript(transpiledFrom: s.callee, in: &context) let prepare = intrinsic(named: Intrinsic.llvm.coro.prepare.retcon)! let x1 = insertCall(SwiftyLLVM.Function(prepare)!, on: [f], at: insertionPoint) @@ -1156,7 +1214,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(return i: IR.InstructionID) { - if m[f].isSubscript { + if context.source[f].isSubscript { _ = insertCall( SwiftyLLVM.Function(intrinsic(named: Intrinsic.llvm.coro.end)!)!, on: [frame!, i1.zero], @@ -1169,13 +1227,16 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(store i: IR.InstructionID) { - let s = m[i] as! IR.Store - insertStore(llvm(s.object), to: llvm(s.target), at: insertionPoint) + let s = context.source[i] as! IR.Store + let v = llvm(s.object) + if layout.storageSize(of: v.type) > 0 { + insertStore(llvm(s.object), to: llvm(s.target), at: insertionPoint) + } } /// Inserts the transpilation of `i` at `insertionPoint`. func insert(switch i: IR.InstructionID) { - let s = m[i] as! Switch + let s = context.source[i] as! Switch let branches = s.successors.enumerated().map { (value, destination) in (word().constant(UInt64(value)), block[destination]!) @@ -1190,15 +1251,28 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(unionDiscriminator i: IR.InstructionID) { - let s = m[i] as! UnionDiscriminator - let t = UnionType(m.type(of: s.container).ast)! + let s = context.source[i] as! UnionDiscriminator + register[.register(i)] = discriminator(s.container) + } - let baseType = ir.llvm(unionType: t, in: &self) - let container = llvm(s.container) - let indices = [i32.constant(0), i32.constant(1)] - let discriminator = insertGetElementPointerInBounds( - of: container, typed: baseType, indices: indices, at: insertionPoint) - register[.register(i)] = insertLoad(word(), from: discriminator, at: insertionPoint) + /// Inserts the transpilation of `i` at `insertionPoint`. + func insert(unionSwitch i: IR.InstructionID) { + let s = context.source[i] as! UnionSwitch + + if let (_, b) = s.targets.elements.uniqueElement { + insertBr(to: block[b]!, at: insertionPoint) + } else { + let e = context.ir.base.discriminatorToElement(in: s.union) + let branches = s.targets.map { (t, b) in + (word().constant(e.firstIndex(of: t)!), block[b]!) + } + + // The last branch is the "default". + let d = llvm(s.discriminator) + insertSwitch( + on: d, cases: branches.dropLast(), default: branches.last!.1, + at: insertionPoint) + } } /// Inserts the transpilation of `i` at `insertionPoint`. @@ -1208,7 +1282,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(wrapAddr i: IR.InstructionID) { - let s = m[i] as! IR.WrapExistentialAddr + let s = context.source[i] as! IR.WrapExistentialAddr let t = containerType() let a = insertAlloca(t, atEntryOf: transpilation) insertStore(container(witness: s.witness, table: s.table), to: a, at: insertionPoint) @@ -1217,7 +1291,7 @@ extension SwiftyLLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(yield i: IR.InstructionID) { - let s = m[i] as! IR.Yield + let s = context.source[i] as! IR.Yield let p = llvm(s.projection) // The intrinsic will return a non-zero result if the subscript should resume abnormally. @@ -1230,7 +1304,7 @@ extension SwiftyLLVM.Module { /// Returns the LLVM IR value corresponding to the Hylo IR operand `o`. func llvm(_ o: IR.Operand) -> SwiftyLLVM.IRValue { if case .constant(let c) = o { - return transpiledConstant(c, usedIn: m, from: ir) + return transpiledConstant(c, in: &context) } else { return register[o]! } @@ -1239,14 +1313,14 @@ extension SwiftyLLVM.Module { /// Returns the callee of `s`. func unpackCallee(of s: Operand) -> ArrowContents { if case .constant(let f) = s { - let f = transpiledConstant(f, usedIn: m, from: ir) + let f = transpiledConstant(f, in: &context) let t = SwiftyLLVM.Function(f)!.valueType return .init(function: f, type: t, environment: []) } // `s` is an arrow. - let hyloType = ArrowType(m.type(of: s).ast)! - let llvmType = StructType(ir.llvm(hyloType, in: &self))! + let hyloType = ArrowType(context.source.type(of: s).ast)! + let llvmType = StructType(context.ir.llvm(hyloType, in: &self))! let lambda = llvm(s) // The first element of the representation is the function pointer. @@ -1256,7 +1330,7 @@ extension SwiftyLLVM.Module { let e = insertGetStructElementPointer( of: lambda, typed: llvmType, index: 1, at: insertionPoint) - let captures = StructType(ir.llvm(hyloType.environment, in: &self))! + let captures = StructType(context.ir.llvm(hyloType.environment, in: &self))! // Following elements constitute the environment. var environment: [SwiftyLLVM.IRValue] = [] @@ -1284,6 +1358,17 @@ extension SwiftyLLVM.Module { v = insertInsertValue(llvm(table), at: 1, into: v, at: insertionPoint) return v } + + /// Returns the value of `container`'s discriminator. + func discriminator(_ container: IR.Operand) -> SwiftyLLVM.Instruction { + let union = UnionType(context.source.type(of: container).ast)! + let baseType = context.ir.llvm(unionType: union, in: &self) + let container = llvm(container) + let indices = [i32.constant(0), i32.constant(1)] + let discriminator = insertGetElementPointerInBounds( + of: container, typed: baseType, indices: indices, at: insertionPoint) + return insertLoad(word(), from: discriminator, at: insertionPoint) + } } /// Inserts the prologue of the subscript `transpilation` at the end of its entry and returns diff --git a/Sources/CodeGen/LLVM/TypeLowering.swift b/Sources/CodeGen/LLVM/TypeLowering.swift index 4c640c389..2af3bacf4 100644 --- a/Sources/CodeGen/LLVM/TypeLowering.swift +++ b/Sources/CodeGen/LLVM/TypeLowering.swift @@ -5,33 +5,33 @@ import Utils extension IR.Program { - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(_ val: T, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - switch val { - case let t as AnyType: - return llvm(t.base, in: &module) - case let t as ArrowType: - return llvm(arrowType: t, in: &module) - case let t as BufferType: - return llvm(bufferType: t, in: &module) - case let t as BuiltinType: - return llvm(builtinType: t, in: &module) - case let t as BoundGenericType: - return llvm(boundGenericType: t, in: &module) + /// - Requires: `t` is representable in LLVM. + func llvm(_ t: T, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + switch t { + case let u as AnyType: + return llvm(u.base, in: &module) + case let u as ArrowType: + return llvm(arrowType: u, in: &module) + case let u as BufferType: + return llvm(bufferType: u, in: &module) + case let u as BuiltinType: + return llvm(builtinType: u, in: &module) + case let u as BoundGenericType: + return llvm(boundGenericType: u, in: &module) case is MetatypeType: return module.ptr - case let t as ProductType: - return llvm(productType: t, in: &module) + case let u as ProductType: + return llvm(productType: u, in: &module) case is RemoteType: return module.ptr - case let t as TupleType: - return llvm(tupleType: t, in: &module) - case let t as UnionType: - return llvm(unionType: t, in: &module) + case let u as TupleType: + return llvm(tupleType: u, in: &module) + case let u as UnionType: + return llvm(unionType: u, in: &module) default: - notLLVMRepresentable(val) + notLLVMRepresentable(t) } } @@ -39,27 +39,27 @@ extension IR.Program { /// /// - Requires: `t` is representable in LLVM. func llvm(arrowType t: ArrowType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - precondition(t[.isCanonical]) + precondition(t.isCanonical) let e = llvm(t.environment, in: &module) return SwiftyLLVM.StructType([module.ptr, e], in: &module) } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(bufferType val: BufferType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - let e = llvm(val.element, in: &module) - guard let n = val.count.asCompilerKnown(Int.self) else { - notLLVMRepresentable(val) + /// - Requires: `t` is representable in LLVM. + func llvm(bufferType t: BufferType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + let e = llvm(t.element, in: &module) + guard let n = ConcreteTerm(t.count)?.value as? Int else { + notLLVMRepresentable(t) } return SwiftyLLVM.ArrayType(n, e, in: &module) } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(builtinType val: BuiltinType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - switch val { + /// - Requires: `t` is representable in LLVM. + func llvm(builtinType t: BuiltinType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + switch t { case .i(let width): return SwiftyLLVM.IntegerType(width, in: &module) case .word: @@ -75,85 +75,81 @@ extension IR.Program { case .ptr: return module.ptr case .module: - notLLVMRepresentable(val) + notLLVMRepresentable(t) } } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(boundGenericType val: BoundGenericType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - precondition(val[.isCanonical]) - - let fields = base.storage(of: val.base).map { (part) in - let z = GenericArguments(val) - let u = base.specialize(part.type, for: z, in: AnyScopeID(base.ast.coreLibrary!)) - return llvm(u, in: &module) - } - - switch val.base.base { - case let u as ProductType: - return SwiftyLLVM.StructType(named: u.name.value, fields, in: &module) - case is TupleType: - return SwiftyLLVM.StructType(fields, in: &module) - default: - unreachable() + /// - Requires: `t` is representable in LLVM. + func llvm( + boundGenericType t: BoundGenericType, in module: inout SwiftyLLVM.Module + ) -> SwiftyLLVM.IRType { + precondition(t.isCanonical) + precondition(t.base.base is ProductType) + return demandStruct(named: base.mangled(t), in: &module) { (m) in + llvm(fields: base.storage(of: t), in: &m) } } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(productType val: ProductType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - precondition(val[.isCanonical]) - - let n = base.mangled(val) - if let t = module.type(named: n) { - assert(SwiftyLLVM.StructType(t) != nil) - return t - } - - let l = AbstractTypeLayout(of: val, definedIn: base) - var fields: [SwiftyLLVM.IRType] = [] - for p in l.properties { - fields.append(llvm(p.type, in: &module)) + /// - Requires: `t` is representable in LLVM. + func llvm(productType t: ProductType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + precondition(t.isCanonical) + return demandStruct(named: base.mangled(t), in: &module) { (m) in + llvm(fields: AbstractTypeLayout(of: t, definedIn: base).properties, in: &m) } - - return SwiftyLLVM.StructType(named: n, fields, in: &module) } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(tupleType val: TupleType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - precondition(val[.isCanonical]) - - var fields: [SwiftyLLVM.IRType] = [] - for e in val.elements { - fields.append(llvm(e.type, in: &module)) - } + /// - Requires: `t` is representable in LLVM. + func llvm(tupleType t: TupleType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + precondition(t.isCanonical) + let fs = llvm(fields: t.elements, in: &module) + return SwiftyLLVM.StructType(fs, in: &module) + } - return SwiftyLLVM.StructType(fields, in: &module) + /// Rethrns the LLVM forms of `fields` in `module`. + private func llvm( + fields: [TupleType.Element], in module: inout SwiftyLLVM.Module + ) -> [SwiftyLLVM.IRType] { + fields.map({ (p) in llvm(p.type, in: &module) }) } - /// Returns the LLVM form of `val` in `module`. + /// Returns the LLVM form of `t` in `module`. /// - /// - Requires: `val` is representable in LLVM. - func llvm(unionType val: UnionType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { - precondition(val[.isCanonical]) + /// - Requires: `t` is representable in LLVM. + func llvm(unionType t: UnionType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + precondition(t.isCanonical) var payload: SwiftyLLVM.IRType = SwiftyLLVM.StructType([], in: &module) - if val.isNever { + if t.isNever { return payload } - for e in val.elements { - let t = llvm(e, in: &module) - if module.layout.storageSize(of: t) > module.layout.storageSize(of: payload) { - payload = t + for e in t.elements { + let u = llvm(e, in: &module) + if module.layout.storageSize(of: u) > module.layout.storageSize(of: payload) { + payload = u } } return StructType([payload, module.word()], in: &module) } + /// Returns a LLVM struct named `n` and having the fields returned by `fields`, declaring it in + /// `module` if it does not already exist. + private func demandStruct( + named n: String, in module: inout SwiftyLLVM.Module, + fields: (inout SwiftyLLVM.Module) -> [SwiftyLLVM.IRType] + ) -> SwiftyLLVM.StructType { + if let u = module.type(named: n) { + return SwiftyLLVM.StructType(u) ?? fatalError("'\(n)' is not a struct") + } else { + let fs = fields(&module) + return SwiftyLLVM.StructType(named: n, fs, in: &module) + } + } + } diff --git a/Sources/Driver/Driver.swift b/Sources/Driver/Driver.swift index 170626db8..0d8ccccca 100644 --- a/Sources/Driver/Driver.swift +++ b/Sources/Driver/Driver.swift @@ -3,14 +3,15 @@ import CodeGenLLVM import Foundation import FrontEnd import IR -import SwiftyLLVM import StandardLibrary +import SwiftyLLVM import Utils public struct Driver: ParsableCommand { /// A validation error that includes the command's full help message. - struct ValidationErrorWithHelp: Error, CustomStringConvertible { + private struct ValidationErrorWithHelp: Error, CustomStringConvertible { + var message: String init(_ message: String) { @@ -24,6 +25,7 @@ public struct Driver: ParsableCommand { \(Driver.helpMessage()) """ } + } /// The type of the output files to generate. @@ -48,6 +50,20 @@ public struct Driver: ParsableCommand { case binary = "binary" } + /// The result of a compiler invocation. + public struct CompilationResult { + + /// The exit status of the compiler. + public let status: ExitCode + + /// The URL of the output file. + public let output: URL + + /// The generated diagnostics. + public let diagnostics: DiagnosticSet + + } + public static let configuration = CommandConfiguration(commandName: "hc") @Flag( @@ -63,8 +79,10 @@ public struct Driver: ParsableCommand { @Flag( name: [.customLong("freestanding")], help: - "Import only the freestanding core of the standard library, omitting any definitions that depend on having an operating system." - ) + """ + Import only the freestanding core of the standard library, omitting any definitions that \ + depend on having an operating system. + """) private var freestanding: Bool = false @Flag( @@ -84,6 +102,13 @@ public struct Driver: ParsableCommand { valueName: "file:line")) private var inferenceTracingSite: SourceLine? + @Option( + name: [.customLong("show-requirements")], + help: ArgumentHelp( + "Log the requirement system of the generic declaration at the given line.", + valueName: "file:line")) + private var showRequirementsSite: SourceLine? + @Option( name: [.customLong("emit")], help: ArgumentHelp( @@ -137,6 +162,7 @@ public struct Driver: ParsableCommand { transform: URL.init(fileURLWithPath:)) private var inputs: [URL] = [] + /// Creates a new instance with default options. public init() {} /// The URL of the current working directory. @@ -144,36 +170,62 @@ public struct Driver: ParsableCommand { URL(fileURLWithPath: FileManager.default.currentDirectoryPath, isDirectory: true) } + /// Compiles `input` with the given command line arguments and returns the compiler's exit + /// status, the URL of the output file, and any diagnostics. + /// + /// - Parameters: + /// - input: The URL of a single Hylo source file or the root directory of a Hylo module. + /// - options: The compiler's options sans input and output arguments. + /// - baseProgram: A program with some or all of the dependencies of the compiler's input, + /// which have been compiled with options compatible with `options`. All dependencies are + /// loaded from disk if this argument is `nil` + public static func compileToTemporary( + _ input: URL, withOptions options: [String], extending baseProgram: TypedProgram? = nil + ) throws -> CompilationResult { + // Prepare the driver. + let output = FileManager.default.makeTemporaryFileURL() + let cli = try Driver.parse(options + ["-o", output.relativePath, input.relativePath]) + + // Execute the command. + var log = DiagnosticSet() + try log.capturingErrors { (ds) in + try cli.executeCommand(extending: baseProgram, reportingDiagnosticsTo: &ds) + } + let status = log.containsError ? ExitCode.failure : ExitCode.success + return .init(status: status, output: output, diagnostics: log) + } + + /// Executes the command. public func run() throws { + var log = DiagnosticSet() + let status: ExitCode + do { - let (exitCode, diagnostics) = try execute() - diagnostics.render( - into: &standardError, style: ProcessInfo.ansiTerminalIsConnected ? .styled : .unstyled) - Driver.exit(withError: exitCode) + try log.capturingErrors { (ds) in + try executeCommand(reportingDiagnosticsTo: &ds) + } + status = log.containsError ? ExitCode.failure : ExitCode.success } catch let e { print("Unexpected error\n") Driver.exit(withError: e) } - } - /// Executes the command, returning its exit status and any generated diagnostics. - /// - /// Propagates any thrown errors that are not Hylo diagnostics, - public func execute() throws -> (ExitCode, DiagnosticSet) { - var diagnostics = DiagnosticSet() - do { - try executeCommand(diagnostics: &diagnostics) - } catch let d as DiagnosticSet { - assert(d.containsError, "Diagnostics containing no errors were thrown") - diagnostics.formUnion(d) - return (ExitCode.failure, diagnostics) - } - return (ExitCode.success, diagnostics) + log.render( + into: &standardError, style: ProcessInfo.ansiTerminalIsConnected ? .styled : .unstyled) + Driver.exit(withError: status) } - /// Executes the command, accumulating diagnostics in `diagnostics`. - private func executeCommand(diagnostics: inout DiagnosticSet) throws { - + /// Executes the command, loading modules into `baseProgram` and reporting diagnostics to `log`. + /// + /// - Parameters: + /// - baseProgram: A program with some or all of the dependencies of the compiler's input, + /// which have been compiled with options compatible with the driver's current configuration. + /// All dependencies are loaded from disk if this argument is `nil` + /// - log: A set of diagnostics that doesn't contain any error. + public func executeCommand( + extending baseProgram: TypedProgram? = nil, + reportingDiagnosticsTo log: inout DiagnosticSet + ) throws { if version { standardError.write("\(hcVersion)\n") return @@ -189,28 +241,50 @@ public struct Driver: ParsableCommand { let productName = makeProductName(inputs) - /// An instance that includes just the standard library. - var ast = try (freestanding ? Host.freestandingLibraryAST : Host.hostedLibraryAST).get() - - // The module whose Hylo files were given on the command-line - let sourceModule = try ast.makeModule( - productName, sourceCode: sourceFiles(in: inputs), - builtinModuleAccess: importBuiltinModule, diagnostics: &diagnostics) - + // There's no need to load the standard library under `--emit raw-ast`. if outputType == .rawAST { - try write(ast, to: astFile(productName)) + var a = AST() + _ = try a.loadModule( + productName, parsing: sourceFiles(in: inputs), + withBuiltinModuleAccess: importBuiltinModule, + reportingDiagnosticsTo: &log) + try write(a, to: astFile(productName)) return } - let program = try TypedProgram( - annotating: ScopedProgram(ast), inParallel: experimentalParallelTypeChecking, - reportingDiagnosticsTo: &diagnostics, - tracingInferenceIf: shouldTraceInference) + // Type checking + + let dependencies: TypedProgram + if let p = baseProgram { + precondition( + p.ast.compilationConditions.freestanding == freestanding, + "program to extend has incompatible compilation factors") + dependencies = p + } else { + let a = try (freestanding ? Host.freestandingLibraryAST : Host.hostedLibraryAST).get() + dependencies = try TypedProgram( + annotating: ScopedProgram(a), inParallel: experimentalParallelTypeChecking, + reportingDiagnosticsTo: &log, + tracingInferenceIf: shouldTraceInference, + loggingRequirementSystemIf: shouldLogRequirements) + } + + let (program, sourceModule) = try dependencies.loadModule( + reportingDiagnosticsTo: &log, + tracingInferenceIf: shouldTraceInference, + loggingRequirementSystemIf: shouldLogRequirements + ) { (ast, log, space) in + try ast.loadModule( + productName, parsing: sourceFiles(in: inputs), inNodeSpace: space, + withBuiltinModuleAccess: importBuiltinModule, + reportingDiagnosticsTo: &log) + } + if typeCheckOnly { return } // IR - var ir = try lower(program: program, reportingDiagnosticsTo: &diagnostics) + var ir = try lower(program: program, reportingDiagnosticsTo: &log) if outputType == .ir || outputType == .rawIR { let m = ir.modules[sourceModule]! @@ -266,15 +340,13 @@ public struct Driver: ParsableCommand { let binaryPath = executableOutputPath(default: productName) #if os(macOS) - try makeMacOSExecutable(at: binaryPath, linking: objectFiles, diagnostics: &diagnostics) + try makeMacOSExecutable(at: binaryPath, linking: objectFiles, diagnostics: &log) #elseif os(Linux) - try makeLinuxExecutable(at: binaryPath, linking: objectFiles, diagnostics: &diagnostics) + try makeLinuxExecutable(at: binaryPath, linking: objectFiles, diagnostics: &log) #elseif os(Windows) - try makeWindowsExecutable(at: binaryPath, linking: objectFiles, diagnostics: &diagnostics) - + try makeWindowsExecutable(at: binaryPath, linking: objectFiles, diagnostics: &log) #else - _ = objectFiles - _ = binaryPath + _ = (objectFiles, binaryPath) UNIMPLEMENTED() #endif } @@ -293,6 +365,15 @@ public struct Driver: ParsableCommand { } } + /// Returns `true` if the requirement system of `n`, which is in `p`, should be logged. + private func shouldLogRequirements(_ n: AnyDeclID, _ p: TypedProgram) -> Bool { + if let s = showRequirementsSite { + return (n.kind.value is GenericDecl.Type) && s.bounds.contains(p[n].site.start) + } else { + return false + } + } + /// Returns `program` lowered to Hylo IR, accumulating diagnostics in `log` and throwing if an /// error occurred. /// @@ -437,8 +518,8 @@ public struct Driver: ParsableCommand { throw EnvironmentError("not found: executable invoked as \(invocationName)") } - /// Runs the executable at `path`, passing `arguments` on the command line, and returns - /// its standard output sans any leading or trailing whitespace. + /// Runs the executable at `path`, passing `arguments` on the command line, and returns its + /// standard output sans any leading or trailing whitespace. @discardableResult private func runCommandLine( _ programPath: String, @@ -477,8 +558,8 @@ public struct Driver: ParsableCommand { outputURL ?? URL(fileURLWithPath: productName + ".ll") } - /// Given the desired name of the compiler's product, returns the file to write when "intel-asm" is - /// selected as the output type. + /// Given the desired name of the compiler's product, returns the file to write when "intel-asm" + /// is selected as the output type. private func intelASMFile(_ productName: String) -> URL { outputURL ?? URL(fileURLWithPath: productName + ".s") } diff --git a/Sources/FrontEnd/AST/AST+Walk.swift b/Sources/FrontEnd/AST/AST+Walk.swift index f764af16a..10ca5a6ae 100644 --- a/Sources/FrontEnd/AST/AST+Walk.swift +++ b/Sources/FrontEnd/AST/AST+Walk.swift @@ -160,6 +160,8 @@ extension AST { traverse(self[n] as! ExprPattern, notifying: &o) case NamePattern.self: traverse(self[n] as! NamePattern, notifying: &o) + case OptionPattern.self: + traverse(self[n] as! OptionPattern, notifying: &o) case TuplePattern.self: traverse(self[n] as! TuplePattern, notifying: &o) case WildcardPattern.self: @@ -171,8 +173,8 @@ extension AST { traverse(self[n] as! BraceStmt, notifying: &o) case BreakStmt.self: traverse(self[n] as! BreakStmt, notifying: &o) - case CondBindingStmt.self: - traverse(self[n] as! CondBindingStmt, notifying: &o) + case ConditionalBindingStmt.self: + traverse(self[n] as! ConditionalBindingStmt, notifying: &o) case ConditionalCompilationStmt.self: traverse(self[n] as! ConditionalCompilationStmt, notifying: &o) case ConditionalStmt.self: @@ -637,6 +639,13 @@ extension AST { walk(n.decl, notifying: &o) } + /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. + public func traverse( + _ n: OptionPattern, notifying o: inout O + ) { + walk(n.name, notifying: &o) + } + /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. public func traverse( _ n: TuplePattern, notifying o: inout O @@ -673,9 +682,10 @@ extension AST { /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. public func traverse( - _ n: CondBindingStmt, notifying o: inout O + _ n: ConditionalBindingStmt, notifying o: inout O ) { walk(n.binding, notifying: &o) + walk(n.fallback, notifying: &o) } /// Visits the children of `n` in pre-order, notifying `o` when a node is entered or left. diff --git a/Sources/FrontEnd/AST/AST.swift b/Sources/FrontEnd/AST/AST.swift index 3609cce56..8cb8c80bd 100644 --- a/Sources/FrontEnd/AST/AST.swift +++ b/Sources/FrontEnd/AST/AST.swift @@ -3,16 +3,22 @@ import Utils /// An abstract syntax tree. public struct AST { + /// A function inserting the contents of a module in `ast`, registering the identities of newly + /// formed ASTs in `nodeSpace` and reporting diagnostics to `log`. + public typealias ModuleLoader = ( + _ ast: inout AST, _ log: inout DiagnosticSet, _ nodeSpace: Int + ) throws -> ModuleDecl.ID + /// The stored representation of an AST; distinguished for encoding/decoding purposes. private struct Storage: Codable { /// The nodes in `self`. - public var nodes: [AnyNode] = [] + public var nodes: [[AnyNode]] = [] /// The indices of the modules in the AST. /// - /// Indices are ordered by module dependency. If the module identified by the index at position - /// `i` depends on the one identified by the index at position `j`, then `i` precedes `j`. + /// Indices are ordered by module dependency. If `i` and `j` are the indices of two modules `A` + /// and `B`, respectively, and `A` depends on `B`, then `i` precedes `j` in this property. /// /// - Invariant: All referred modules have a different name. public var modules: [ModuleDecl.ID] = [] @@ -26,7 +32,8 @@ public struct AST { /// Conditions for selecting conditional compilation branches. public let compilationConditions: ConditionalCompilationFactors - /// Creates an empty instance, using `compilationConditions` as conditions for selecting conditional compilation branches. + /// Creates an empty instance, using `compilationConditions` as conditions for selecting + /// conditional compilation branches. public init(_ compilationConditions: ConditionalCompilationFactors) { self.compilationConditions = compilationConditions } @@ -43,13 +50,6 @@ public struct AST { _modify { yield &storage.coreTraits } } - /// The nodes in `self`. - private var nodes: [AnyNode] { - get { storage.nodes } - set { storage.nodes = newValue } - _modify { yield &storage.nodes } - } - /// The indices of the modules. /// /// - Invariant: All referred modules have a different name. @@ -70,57 +70,107 @@ public struct AST { storage.compilationConditions } - /// Creates an empty AST, using using `compilationConditions` as conditions for selecting conditional compilation branches. + /// Creates an empty AST, using using `compilationConditions` as conditions for selecting + /// conditional compilation branches. public init( _ compilationConditions: ConditionalCompilationFactors = ConditionalCompilationFactors() ) { self.storage = Storage(compilationConditions) } - /// Inserts `n` into `self`, updating `diagnostics` if `n` is ill-formed. - public mutating func insert(_ n: T, diagnostics: inout DiagnosticSet) -> T.ID { - n.validateForm(in: self, reportingDiagnosticsTo: &diagnostics) + /// Creates a new node space and returns its identifier. + public mutating func createNodeSpace() -> Int { + storage.nodes.append([]) + return storage.nodes.count - 1 + } + + /// Loads a new module in `self`, calling `make` to parse its contents and reporting + /// diagnostics to `log`. + public mutating func loadModule( + inNodeSpace space: Int? = nil, + reportingDiagnosticsTo log: inout DiagnosticSet, + creatingContentsWith parseSources: ModuleLoader + ) rethrows -> ModuleDecl.ID { + try parseSources(&self, &log, space ?? createNodeSpace()) + } + + /// Loads a new module in `self`, parsing its contents from `sourceCode`, registering the + /// identities of newly formed ASTs in space `space`, and reporting diagnostics to `log`. + /// + /// - Parameters: + /// - name: The name of the loaded module. + /// - sourceCode: The URL of a single source file or the root directory of the module. + /// - space: The space in which the module's ASTs are registered. This argument should be `nil` + /// unless this method is called in a module loader. + /// - builtinModuleAccess: Whether the module is allowed to access the builtin module. + /// - log: A set extended with the diagnostics reported by this method. + public mutating func loadModule( + _ name: String, parsing sourceCode: S, inNodeSpace space: Int? = nil, + withBuiltinModuleAccess builtinModuleAccess: Bool = false, + reportingDiagnosticsTo log: inout DiagnosticSet + ) throws -> ModuleDecl.ID where S.Element == SourceFile { + try loadModule(reportingDiagnosticsTo: &log) { (me, log, k) in + // Suppress thrown diagnostics until all files are parsed. + let translations = sourceCode.compactMap { (f) in + try? Parser.parse(f, inNodeSpace: k, in: &me, diagnostics: &log) + } + + let m = me.insert( + ModuleDecl(name, sources: translations, builtinModuleAccess: builtinModuleAccess), + inNodeSpace: k, + reportingDiagnosticsTo: &log) + try log.throwOnError() + return m + } + } + + /// Inserts `n` into `self`, registering its identity in space `k` and reporting well-formedness + /// issues to `log`. + public mutating func insert( + _ n: T, inNodeSpace k: Int, reportingDiagnosticsTo log: inout DiagnosticSet + ) -> T.ID { + n.validateForm(in: self, reportingDiagnosticsTo: &log) - let i = T.ID(rawValue: nodes.count) + let i = T.ID(rawValue: .init(base: k, offset: storage.nodes[k].count)) if let n = n as? ModuleDecl { precondition( !modules.contains(where: { self[$0].baseName == n.baseName }), "duplicate module") modules.append(i as! ModuleDecl.ID) } - nodes.append(AnyNode(n)) + storage.nodes[k].append(AnyNode(n)) return i } - /// Inserts `n` into `self`. + /// Inserts `n` into `self`, registering its identity in space `k`. /// /// - Precondition: `n` is well formed. - public mutating func insert(synthesized n: T) -> T.ID { + public mutating func insert(synthesized n: T, inNodeSpace k: Int) -> T.ID { var d = DiagnosticSet() - let r = insert(n, diagnostics: &d) + let r = insert(n, inNodeSpace: k, reportingDiagnosticsTo: &d) precondition(d.elements.isEmpty, "ill-formed synthesized node \(n)\n\(d)") return r } // MARK: Node access - /// Accesses the node at `position`. - public subscript(position: T) -> T.Subject { - nodes[position.rawValue].node as! T.Subject + /// Accesses the node identified by `i`. + public subscript(i: T) -> T.Subject { + storage.nodes[i.rawValue.base][i.rawValue.offset].node as! T.Subject } - /// Accesses the node at `position`. - public subscript(position: T?) -> T.Subject? { - position.map({ nodes[$0.rawValue].node as! T.Subject }) + /// Accesses the node identified by `i`. + public subscript(i: T?) -> T.Subject? { + i.map({ (j) in storage.nodes[j.rawValue.base][j.rawValue.offset].node as! T.Subject }) } - /// Accesses the node at `position`. - public subscript(position: T) -> Node { - nodes[position.rawValue].node + /// Accesses the node identified by `i`. + public subscript(i: T) -> Node { + storage.nodes[i.rawValue.base][i.rawValue.offset].node } - /// Accesses the node at `position`. - public subscript(position: T?) -> Node? { - position.map({ nodes[$0.rawValue].node }) + /// Accesses the node identified by `i`. + public subscript(i: T?) -> Node? { + i.map({ (j) in storage.nodes[j.rawValue.base][j.rawValue.offset].node }) } /// A sequence of concrete nodes projected from an AST. @@ -139,6 +189,25 @@ public struct AST { /// Indicates whether the Core library has been loaded. public var coreModuleIsLoaded: Bool { coreLibrary != nil } + /// Returns the Hylo type of an optional `t`. + /// + /// - Requires: The Core library must have been loaded. + public func optional(_ t: AnyType) -> UnionType { + let none = coreType("None")! + let p = self[none.decl].genericParameters[0] + let u = BoundGenericType(none, arguments: [p: .type(t)]) + return UnionType([t, ^u]) + } + + /// Returns the Hylo type of an array of `t`. + /// + /// - Requires: The Core library must have been loaded. + public func array(_ t: AnyType) -> BoundGenericType { + let b = coreType("Array")! + let e = self[b.decl].genericParameters[0] + return BoundGenericType(b, arguments: [e: .type(t)]) + } + /// Returns the type named `name` defined in the core library or `nil` it does not exist. /// /// - Requires: The Core library must have been loaded. @@ -253,7 +322,6 @@ public struct AST { /// Returns the kind identifying synthesized declarations of `requirement`, or `nil` if /// `requirement` is not synthesizable. public func synthesizedKind(of requirement: T) -> SynthesizedFunctionDecl.Kind? { - // If the requirement is defined in `Deinitializable`, it must be the deinitialization method. switch requirement.rawValue { case core.deinitializable.deinitialize.rawValue: return .deinitialize @@ -263,6 +331,8 @@ public struct AST { return .moveAssignment case core.copyable.copy.rawValue: return .copy + case core.equatable.equal.rawValue: + return .equal default: return nil } @@ -274,7 +344,7 @@ public struct AST { runtimeParameters(of: d)?.map({ self[$0].defaultValue }) } - /// Returns the name of entity defining the implementation of the declared function if it is external. + /// Returns the name of entity defining the implementation of `d` if it is external. public func externalName(of d: FunctionDecl.ID) -> String? { self[d].attributes.first(where: { $0.value.name.value == "@external" }).map { (a) in if a.value.arguments[0].value.kind.value is StringLiteralExpr.Type { @@ -361,9 +431,8 @@ public struct AST { ) { switch pattern.kind { case BindingPattern.self: - visit( - pattern: self[BindingPattern.ID(pattern)!].subpattern, subfield: subfield, result: &result - ) + let s = self[BindingPattern.ID(pattern)!].subpattern + visit(pattern: s, subfield: subfield, result: &result) case ExprPattern.self: break @@ -371,6 +440,10 @@ public struct AST { case NamePattern.self: result.append((subfield: subfield, pattern: NamePattern.ID(pattern)!)) + case OptionPattern.self: + let n = self[OptionPattern.ID(pattern)!].name + result.append((subfield: subfield, pattern: n)) + case TuplePattern.self: let x = TuplePattern.ID(pattern)! for i in 0 ..< self[x].elements.count { diff --git a/Sources/FrontEnd/AST/Decl/FunctionDecl.swift b/Sources/FrontEnd/AST/Decl/FunctionDecl.swift index aa2bd686f..0e50cb0fb 100644 --- a/Sources/FrontEnd/AST/Decl/FunctionDecl.swift +++ b/Sources/FrontEnd/AST/Decl/FunctionDecl.swift @@ -104,6 +104,9 @@ public struct FunctionDecl: CapturingDecl, ExposableDecl, GenericDecl { /// `true` iff `self` denotes a `sink` member function. public var isSink: Bool { receiverEffect?.value == .sink } + /// `true` iff `self` denotes a deinitializer. + public var isDeinit: Bool { (identifier?.value == "deinit") && isSink && parameters.isEmpty } + /// `true` iff `self` is a foreign function interface. public var isForeignInterface: Bool { api.contains(.isForeignInterface) diff --git a/Sources/FrontEnd/AST/Decl/GenericParameterDecl.swift b/Sources/FrontEnd/AST/Decl/GenericParameterDecl.swift index 001666201..847a3f209 100644 --- a/Sources/FrontEnd/AST/Decl/GenericParameterDecl.swift +++ b/Sources/FrontEnd/AST/Decl/GenericParameterDecl.swift @@ -3,8 +3,22 @@ public struct GenericParameterDecl: SingleEntityDecl, ConstrainedGenericTypeDecl public static let constructDescription = "generic parameter declaration" + /// The introducer of a generic parameter declaration. + public enum Introducer: Codable { + + /// The type introducer, `type`. + case type + + /// The value introducer, `value` + case value + + } + public let site: SourceRange + /// The parameter's introducer, if any. + public let introducer: SourceRepresentable? + /// The identifier of the parameter. public let identifier: SourceRepresentable @@ -19,12 +33,14 @@ public struct GenericParameterDecl: SingleEntityDecl, ConstrainedGenericTypeDecl public let defaultValue: AnyExprID? public init( + introducer: SourceRepresentable?, identifier: SourceRepresentable, conformances: [NameExpr.ID] = [], defaultValue: AnyExprID? = nil, site: SourceRange ) { self.site = site + self.introducer = introducer self.identifier = identifier self.conformances = conformances self.defaultValue = defaultValue @@ -32,4 +48,9 @@ public struct GenericParameterDecl: SingleEntityDecl, ConstrainedGenericTypeDecl public var baseName: String { identifier.value } + /// `true` iff `self` is denotes a variable that ranges over types. + public var isTypeKinded: Bool { + introducer?.value != .value + } + } diff --git a/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift b/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift index d0c785930..5987d4c53 100644 --- a/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift +++ b/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift @@ -18,6 +18,9 @@ public struct SynthesizedFunctionDecl: Hashable { /// A copy method. case copy + /// An equality method. + case equal + /// A global initializer for a binding declaration. case globalInitialization(BindingDecl.ID) @@ -47,6 +50,7 @@ public struct SynthesizedFunctionDecl: Hashable { parameterizedBy genericParameters: [GenericParameterDecl.ID], in scope: AnyScopeID ) { + precondition(type.isCanonical) precondition(type.environment.base is TupleType) self.type = type self.genericParameters = genericParameters diff --git a/Sources/FrontEnd/AST/Decl/WhereClause.swift b/Sources/FrontEnd/AST/Decl/WhereClause.swift index bd002cf02..cca84ac94 100644 --- a/Sources/FrontEnd/AST/Decl/WhereClause.swift +++ b/Sources/FrontEnd/AST/Decl/WhereClause.swift @@ -13,7 +13,7 @@ public struct WhereClause: Codable { case equality(l: NameExpr.ID, r: AnyExprID) /// A conformance or instance constraint on a skolem. - case bound(l: NameExpr.ID, r: [AnyExprID]) + case bound(l: NameExpr.ID, r: [NameExpr.ID]) /// A constraint on a value parameter. case value(AnyExprID) diff --git a/Sources/FrontEnd/AST/NodeIDs/AnyNodeID.swift b/Sources/FrontEnd/AST/NodeIDs/AnyNodeID.swift index 67f9e5131..29924fa31 100644 --- a/Sources/FrontEnd/AST/NodeIDs/AnyNodeID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/AnyNodeID.swift @@ -1,7 +1,7 @@ /// The type-erased ID of a node. public struct AnyNodeID: NodeIDProtocol { - public let rawValue: NodeID.RawValue + public let rawValue: NodeRawIdentity public let kind: NodeKind diff --git a/Sources/FrontEnd/AST/NodeIDs/DeclID.swift b/Sources/FrontEnd/AST/NodeIDs/DeclID.swift index f9b9c9be4..05d20a677 100644 --- a/Sources/FrontEnd/AST/NodeIDs/DeclID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/DeclID.swift @@ -64,7 +64,7 @@ public struct AnyDeclID: DeclID { } } - public var rawValue: Int { base.rawValue } + public var rawValue: NodeRawIdentity { base.rawValue } public var kind: NodeKind { base.kind } diff --git a/Sources/FrontEnd/AST/NodeIDs/ExprID.swift b/Sources/FrontEnd/AST/NodeIDs/ExprID.swift index 4d9b53618..6ef092a12 100644 --- a/Sources/FrontEnd/AST/NodeIDs/ExprID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/ExprID.swift @@ -53,7 +53,7 @@ public struct AnyExprID: ExprID { } } - public var rawValue: Int { base.rawValue } + public var rawValue: NodeRawIdentity { base.rawValue } public var kind: NodeKind { base.kind } diff --git a/Sources/FrontEnd/AST/NodeIDs/NodeID.swift b/Sources/FrontEnd/AST/NodeIDs/NodeID.swift index 3caecf848..699759f72 100644 --- a/Sources/FrontEnd/AST/NodeIDs/NodeID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/NodeID.swift @@ -2,7 +2,7 @@ public protocol NodeIDProtocol: Hashable, Codable, CustomStringConvertible { /// The raw value of the ID. - var rawValue: NodeID.RawValue { get } + var rawValue: NodeRawIdentity { get } /// The identifier of type of the referred node. var kind: NodeKind { get } @@ -30,13 +30,48 @@ public protocol ConcreteNodeID: NodeIDProtocol { } +/// The type of a node ID's raw value. +public struct NodeRawIdentity: Hashable, Codable { + + public let bits: UInt64 + + public init(base: Int, offset: Int) { + precondition(base < (1 << 16)) + self.bits = UInt64(base) + (UInt64(offset) << 16) + } + + public var base: Int { Int(self.bits & ((1 << 16) - 1)) } + + public var offset: Int { Int(self.bits >> 16) } + +} + +extension NodeRawIdentity: Comparable { + + public static func < (l: Self, r: Self) -> Bool { + l.bits < r.bits + } + +} + +extension NodeRawIdentity: ExpressibleByIntegerLiteral { + + public init(integerLiteral value: Int) { + self.bits = UInt64(value) + } + +} + +extension NodeRawIdentity: CustomStringConvertible { + + public var description: String { bits.description } + +} + /// The ID of a node in an AST. public struct NodeID: ConcreteNodeID { - /// The type of a node ID's raw value. - public typealias RawValue = Int - - public let rawValue: RawValue + public let rawValue: NodeRawIdentity /// The dynamic type of node being referred to. public var kind: NodeKind { NodeKind(Subject.self) } @@ -53,7 +88,7 @@ public struct NodeID: ConcreteNodeID { /// Creates an instance having the given raw value. /// /// The result can only be used correctly in an AST where the identified node has type `Subject`. - init(rawValue: RawValue) { + init(rawValue: NodeRawIdentity) { self.rawValue = rawValue } diff --git a/Sources/FrontEnd/AST/NodeIDs/NodeKind.swift b/Sources/FrontEnd/AST/NodeIDs/NodeKind.swift index 492546ddb..9873fd396 100644 --- a/Sources/FrontEnd/AST/NodeIDs/NodeKind.swift +++ b/Sources/FrontEnd/AST/NodeIDs/NodeKind.swift @@ -132,13 +132,14 @@ extension NodeKind { BindingPattern.self, ExprPattern.self, NamePattern.self, + OptionPattern.self, TuplePattern.self, WildcardPattern.self, AssignStmt.self, BraceStmt.self, BreakStmt.self, - CondBindingStmt.self, + ConditionalBindingStmt.self, ConditionalCompilationStmt.self, ConditionalStmt.self, ContinueStmt.self, diff --git a/Sources/FrontEnd/AST/NodeIDs/PatternID.swift b/Sources/FrontEnd/AST/NodeIDs/PatternID.swift index f87d632c9..20d55b709 100644 --- a/Sources/FrontEnd/AST/NodeIDs/PatternID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/PatternID.swift @@ -24,7 +24,7 @@ public struct AnyPatternID: PatternID { } } - public var rawValue: Int { base.rawValue } + public var rawValue: NodeRawIdentity { base.rawValue } public var kind: NodeKind { base.kind } diff --git a/Sources/FrontEnd/AST/NodeIDs/ScopeID.swift b/Sources/FrontEnd/AST/NodeIDs/ScopeID.swift index 357904e2a..ba4ba26e4 100644 --- a/Sources/FrontEnd/AST/NodeIDs/ScopeID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/ScopeID.swift @@ -26,7 +26,7 @@ public struct AnyScopeID: ScopeID { } } - public var rawValue: Int { base.rawValue } + public var rawValue: NodeRawIdentity { base.rawValue } public var kind: NodeKind { base.kind } diff --git a/Sources/FrontEnd/AST/NodeIDs/StmtID.swift b/Sources/FrontEnd/AST/NodeIDs/StmtID.swift index 94d585451..5fa5f5b05 100644 --- a/Sources/FrontEnd/AST/NodeIDs/StmtID.swift +++ b/Sources/FrontEnd/AST/NodeIDs/StmtID.swift @@ -25,7 +25,7 @@ public struct AnyStmtID: StmtID { } } - public var rawValue: Int { base.rawValue } + public var rawValue: NodeRawIdentity { base.rawValue } public var kind: NodeKind { base.kind } diff --git a/Sources/FrontEnd/AST/Pattern/OptionPattern.swift b/Sources/FrontEnd/AST/Pattern/OptionPattern.swift new file mode 100644 index 000000000..f9234fe8f --- /dev/null +++ b/Sources/FrontEnd/AST/Pattern/OptionPattern.swift @@ -0,0 +1,14 @@ +/// A pattern that matches the presence of a value in an optional. +public struct OptionPattern: Pattern { + + public let site: SourceRange + + /// The expression of the pattern. + public let name: NamePattern.ID + + public init(name: NamePattern.ID, site: SourceRange) { + self.site = site + self.name = name + } + +} diff --git a/Sources/FrontEnd/AST/Stmt/CondBindingStmt.swift b/Sources/FrontEnd/AST/Stmt/CondBindingStmt.swift deleted file mode 100644 index 19af352c2..000000000 --- a/Sources/FrontEnd/AST/Stmt/CondBindingStmt.swift +++ /dev/null @@ -1,26 +0,0 @@ -/// A break statement. -public struct CondBindingStmt: Stmt { - - public enum Fallback: Codable { - - case expr(AnyExprID) - - case exit(AnyStmtID) - - } - - public let site: SourceRange - - /// The conditional binding. - public let binding: BindingDecl.ID - - /// The fallback expression or statement. - public let fallback: Fallback - - public init(binding: BindingDecl.ID, fallback: Fallback, site: SourceRange) { - self.site = site - self.binding = binding - self.fallback = fallback - } - -} diff --git a/Sources/FrontEnd/AST/Stmt/ConditionalBindingStmt.swift b/Sources/FrontEnd/AST/Stmt/ConditionalBindingStmt.swift new file mode 100644 index 000000000..dfa1b943d --- /dev/null +++ b/Sources/FrontEnd/AST/Stmt/ConditionalBindingStmt.swift @@ -0,0 +1,19 @@ +/// A binding to a pattern that breaks control flow if the scrutinee doesn't match. +public struct ConditionalBindingStmt: Stmt { + + public let site: SourceRange + + /// The conditional binding. + public let binding: BindingDecl.ID + + /// The branch that to which control flow jumps if the binding fails. + public let fallback: BraceStmt.ID + + /// Creates an instance with the given properties. + public init(binding: BindingDecl.ID, fallback: BraceStmt.ID, site: SourceRange) { + self.site = site + self.binding = binding + self.fallback = fallback + } + +} diff --git a/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift b/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift index b186ed1ae..e74ec3620 100644 --- a/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift +++ b/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift @@ -52,6 +52,12 @@ public struct ConditionalCompilationStmt: Stmt { /// Holds iff the payload doesn't. case not(Condition) + /// Holds iff both conditions in the payload do. + case and(Condition, Condition) + + /// Holds iff either condition in the payload does. + case or(Condition, Condition) + /// `true` iff the body of the conditional-compilation shouldn't be parsed. public var mayNotNeedParsing: Bool { switch self { @@ -63,6 +69,10 @@ public struct ConditionalCompilationStmt: Stmt { return true case .not(let c): return c.mayNotNeedParsing + case .and(let l, let r): + return l.mayNotNeedParsing && r.mayNotNeedParsing + case .or(let l, let r): + return l.mayNotNeedParsing || r.mayNotNeedParsing default: return false } @@ -89,6 +99,10 @@ public struct ConditionalCompilationStmt: Stmt { return comparison.evaluate(for: factors.hyloVersion) case .not(let c): return !c.holds(for: factors) + case .and(let l, let r): + return l.holds(for: factors) && r.holds(for: factors) + case .or(let l, let r): + return l.holds(for: factors) || r.holds(for: factors) } } diff --git a/Sources/FrontEnd/AccessEffectSet.swift b/Sources/FrontEnd/AccessEffectSet.swift index f83891432..9690c98be 100644 --- a/Sources/FrontEnd/AccessEffectSet.swift +++ b/Sources/FrontEnd/AccessEffectSet.swift @@ -66,7 +66,8 @@ public struct AccessEffectSet: OptionSet, Hashable { } public mutating func update(with newMember: AccessEffect) -> AccessEffect? { - insert(newMember).memberAfterInsert + let (i, k) = insert(newMember) + return i ? nil : k } /// A set with `set` and `inout`. @@ -103,8 +104,12 @@ extension AccessEffectSet { self.base = s } + public var isEmpty: Bool { + base.rawValue == 0 + } + public var startIndex: UInt8 { - base.rawValue & (~base.rawValue + 1) + isEmpty ? endIndex : (base.rawValue & (~base.rawValue + 1)) } public var endIndex: UInt8 { diff --git a/Sources/FrontEnd/BuiltinFunction.swift b/Sources/FrontEnd/BuiltinFunction.swift index cf5896ecc..976d3e4f6 100644 --- a/Sources/FrontEnd/BuiltinFunction.swift +++ b/Sources/FrontEnd/BuiltinFunction.swift @@ -14,6 +14,10 @@ public struct BuiltinFunction: Hashable { let p = ParameterType(.let, ^freshVariable()) return .init(inputs: [.init(label: "of", type: ^p)], output: .builtin(.ptr)) + case .markUninitialized: + let p = ParameterType(.let, ^freshVariable()) + return .init(inputs: [.init(label: nil, type: ^p)], output: .void) + case .llvm(let s): return s.type } @@ -37,6 +41,11 @@ extension BuiltinFunction { /// measures may be needed to keep the argument alive during the pointer's use. case addressOf + /// `Builtin.mark_uninitialized(_ v: sink T) -> Void` + /// + /// Marks `v` as being uninitialized. + case markUninitialized + } } @@ -49,6 +58,8 @@ extension BuiltinFunction.Name: CustomStringConvertible { return n.description case .addressOf: return "address(of:)" + case .markUninitialized: + return "mark_uninitialized(_:)" } } @@ -63,6 +74,8 @@ extension BuiltinFunction { switch n { case "address": self.init(name: .addressOf) + case "mark_uninitialized": + self.init(name: .markUninitialized) default: self.init(native: n) } @@ -339,7 +352,9 @@ private func mathFlags(_ stream: inout ArraySlice) -> NativeInstructi } /// Returns an overflow behavior parsed from `stream` or `.ignore` if none can be parsed. -private func overflowBehavior(_ stream: inout ArraySlice) -> SwiftyLLVM.OverflowBehavior { +private func overflowBehavior( + _ stream: inout ArraySlice +) -> SwiftyLLVM.OverflowBehavior { switch stream.first { case "nuw": stream.removeFirst() diff --git a/Sources/FrontEnd/CompileTimeValues/CompileTimeValue.swift b/Sources/FrontEnd/CompileTimeValues/CompileTimeValue.swift index 3999ee4d4..928aa8462 100644 --- a/Sources/FrontEnd/CompileTimeValues/CompileTimeValue.swift +++ b/Sources/FrontEnd/CompileTimeValues/CompileTimeValue.swift @@ -4,30 +4,40 @@ public enum CompileTimeValue: Hashable { /// A type. case type(AnyType) - /// An instance of a type known by the compiler (e.g. `Int`). - case compilerKnown(AnyHashable) + /// A term. + case term(AnyTerm) + + /// Properties about the representation of self. + public var flags: ValueFlags { + switch self { + case .type(let t): return t.flags + case .term(let t): return t.flags + } + } /// The payload of `.type`. public var asType: AnyType? { - if case .type(let t) = self { - return t - } else { - return nil - } + if case .type(let v) = self { v } else { nil } + } + + /// The payload of `.term`. + public var asTerm: AnyTerm? { + if case .term(let v) = self { v } else { nil } + } + + /// `true` iff self is in canonical form. + public var isCanonical: Bool { + if let t = asType { t.isCanonical } else { true } } /// `true` if `self` is a `TypeVariable`. public var isTypeVariable: Bool { - asType?.base is TypeVariable + self.asType?.base is TypeVariable } /// The payload of `.compilerKnown` as an instance of `T`. public func asCompilerKnown(_: T.Type) -> T? { - if case .compilerKnown(let v) = self { - return v as? T - } else { - return nil - } + ConcreteTerm(self.asTerm)?.value as? T } } @@ -38,7 +48,7 @@ extension CompileTimeValue: CustomStringConvertible { switch self { case .type(let v): return "\(v)" - case .compilerKnown(let v): + case .term(let v): return "\(v)" } } diff --git a/Sources/FrontEnd/CompileTimeValues/ValueFlags.swift b/Sources/FrontEnd/CompileTimeValues/ValueFlags.swift new file mode 100644 index 000000000..051a1e15a --- /dev/null +++ b/Sources/FrontEnd/CompileTimeValues/ValueFlags.swift @@ -0,0 +1,29 @@ +/// Properties about the representation of a type or term. +public struct ValueFlags: Hashable, OptionSet { + + public typealias RawValue = UInt8 + + public let rawValue: UInt8 + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + /// Returns the union of `l` with `r`. + public static func | (l: Self, r: Self) -> Self { + l.union(r) + } + + /// The type contains one or more error types. + public static let hasError = ValueFlags(rawValue: 1 << 0) + + /// The type contains open type variables. + public static let hasVariable = ValueFlags(rawValue: 1 << 1) + + /// The type contains skolemized variables. + public static let hasSkolem = ValueFlags(rawValue: 1 << 2) + + /// The type is not canonical. + public static let hasNonCanonical = ValueFlags(rawValue: 1 << 3) + +} diff --git a/Sources/FrontEnd/ConditionalCompilationFactors.swift b/Sources/FrontEnd/ConditionalCompilationFactors.swift index 73feccd98..cb94be81f 100644 --- a/Sources/FrontEnd/ConditionalCompilationFactors.swift +++ b/Sources/FrontEnd/ConditionalCompilationFactors.swift @@ -4,19 +4,19 @@ import Utils public struct ConditionalCompilationFactors: Codable, Equatable, Hashable { /// The target operating system. - let operatingSystem: Platform.OperatingSystem + public let operatingSystem: Platform.OperatingSystem /// The target architecture. - let architecture: Platform.Architecture + public let architecture: Platform.Architecture /// The version of the compiler. - let compilerVersion: SemanticVersion + public let compilerVersion: SemanticVersion /// The version of the Hylo language recognized by the compiler. - let hyloVersion: SemanticVersion + public let hyloVersion: SemanticVersion /// `true` if the standard library exposes only non-OS-dependent parts. - let freestanding: Bool + public let freestanding: Bool public init( operatingSystem os: Platform.OperatingSystem = Platform.hostOperatingSystem, diff --git a/Sources/FrontEnd/DiagnosticSet.swift b/Sources/FrontEnd/DiagnosticSet.swift index d08ce0625..e3be21621 100644 --- a/Sources/FrontEnd/DiagnosticSet.swift +++ b/Sources/FrontEnd/DiagnosticSet.swift @@ -33,11 +33,30 @@ public struct DiagnosticSet: Error { containsError = containsError || other.containsError } + /// Removes from `self` the elements that are not also contained in `other`. + public mutating func formIntersection(_ other: Self) { + self = .init(elements.filter(other.elements.contains(_:))) + } + /// Throws `self` if any errors were reported. public func throwOnError() throws { if containsError { throw self } } + /// Returns the result of calling `action` or captures the Hylo diagnostics it has thrown and + /// returns `nil`, rethrowing any other errors. + public mutating func capturingErrors( + thrownBy action: (inout Self) throws -> T + ) rethrows -> T? { + do { + return try action(&self) + } catch let d as DiagnosticSet { + assert(d.containsError, "non-error diagnostics were thrown") + self.formUnion(d) + return nil + } + } + /// Whether `self` contains no elements. public var isEmpty: Bool { elements.isEmpty } diff --git a/Sources/FrontEnd/Parse/Parser.swift b/Sources/FrontEnd/Parse/Parser.swift index 6b33703f1..a53b7b197 100644 --- a/Sources/FrontEnd/Parse/Parser.swift +++ b/Sources/FrontEnd/Parse/Parser.swift @@ -19,17 +19,19 @@ import Utils /// A namespace for the routines of Hylo's parser. public enum Parser { - /// Adds a parse of `input` to `ast` and returns its identity, reporting errors and warnings to - /// `diagnostics`. + /// Parses the contents of `input` as a translation unit, registering the identities of newly + /// formed ASTs in space `k` and reporting errors to `diagnostics`. /// /// - Throws: Diagnostics if syntax errors were encountered. public static func parse( _ input: SourceFile, + inNodeSpace k: Int, in ast: inout AST, diagnostics: inout DiagnosticSet ) throws -> TranslationUnit.ID { // Temporarily stash the AST and diagnostics in the parser state, avoiding CoW costs - var state = ParserState(ast: ast, lexer: Lexer(tokenizing: input), diagnostics: diagnostics) + var state = ParserState( + ast: ast, space: k, lexer: Lexer(tokenizing: input), reportingDiagnosticsTo: diagnostics) defer { diagnostics = state.diagnostics } diagnostics = DiagnosticSet() @@ -971,6 +973,7 @@ public enum Parser { // Synthesize the `Self` parameter of the trait. let selfParameterDecl = state.insert( GenericParameterDecl( + introducer: SourceRepresentable(value: .type, range: introducer.site), identifier: SourceRepresentable(value: "Self", range: name.site), site: name.site)) members.append(AnyDeclID(selfParameterDecl)) @@ -1221,13 +1224,7 @@ public enum Parser { static func parseParameterDecl(in state: inout ParserState) throws -> ParameterDecl.ID? { guard let interface = try parseParameterInterface(in: &state) else { return nil } - let annotation: ParameterTypeExpr.ID? - if state.take(.colon) != nil { - annotation = try state.expect("type expression", using: parseParameterTypeExpr(in:)) - } else { - annotation = nil - } - + let annotation = try parseAscription(in: &state, parseParameterTypeExpr(in:)) let defaultValue = try parseDefaultValue(in: &state) let isImplicit = interface.implicitMarker != nil @@ -1329,19 +1326,44 @@ public enum Parser { (genericParameter.and(zeroOrMany(take(.comma).and(genericParameter).second)) .map({ (_, tree) -> [GenericParameterDecl.ID] in [tree.0] + tree.1 })) - static let genericParameter = - (maybe(typeAttribute).andCollapsingSoftFailures(take(.name)) - .and(maybe(take(.colon).and(traitComposition))) - .and(maybe(take(.assign).and(expr))) - .map({ (state, tree) -> GenericParameterDecl.ID in - state.insert( - GenericParameterDecl( - identifier: state.token(tree.0.0.1), - conformances: tree.0.1?.1 ?? [], - defaultValue: tree.1?.1, - site: state.range( - from: tree.0.0.0?.site.startIndex ?? tree.0.0.1.site.startIndex))) - })) + static let genericParameter = Apply(parseGenericParameterDecl(in:)) + + private static func parseGenericParameterDecl( + in state: inout ParserState + ) throws -> GenericParameterDecl.ID? { + let i = parseGenericParameterIntroducer(in: &state) + + guard let n = state.take(.name) else { + if i == nil { + return nil + } else { + try fail(.error(expected: "identifier", at: state.currentLocation)) + } + } + + let a = try parseAscription(in: &state, boundComposition.parse(_:)) + let v = try parseDefaultValue(in: &state) + + return state.insert( + GenericParameterDecl( + introducer: i, + identifier: state.token(n), + conformances: a ?? [], + defaultValue: v, + site: (i?.site ?? n.site).extended(upTo: state.currentIndex))) + } + + private static func parseGenericParameterIntroducer( + in state: inout ParserState + ) -> SourceRepresentable? { + (state.take(.type) ?? state.take(nameTokenWithValue: "value")).map { (t) in + if t.kind == .type { + return .init(value: .type, range: t.site) + } else { + return .init(value: .value, range: t.site) + } + } + } static let conformanceList = (take(.colon).and(nameTypeExpr).and(zeroOrMany(take(.comma).and(nameTypeExpr).second)) @@ -1356,6 +1378,17 @@ public enum Parser { } } + /// Parses a colon and returns the result of `ascription` applied on `state`. + private static func parseAscription( + in state: inout ParserState, _ ascription: (inout ParserState) throws -> T? + ) throws -> T? { + if state.take(.colon) != nil { + return try state.expect("type expression", using: ascription) + } else { + return nil + } + } + // MARK: Expressions static let expr = Apply(parseExpr(in:)) @@ -1762,7 +1795,7 @@ public enum Parser { guard let introducer = state.take(.any) else { return nil } // Parse the parts of the expression. - let traits = try state.expect("trait composition", using: traitComposition) + let traits = try state.expect("trait composition", using: boundComposition) let clause = try whereClause.parse(&state) return state.insert( @@ -2577,10 +2610,16 @@ public enum Parser { return AnyPatternID(p) } - // Attempt to parse a name pattern if we're in the context of a binding pattern. + // Attempt to parse a name or option pattern if we're in the context of a binding pattern. if state.contexts.last == .bindingPattern { if let p = try namePattern.parse(&state) { - return AnyPatternID(p) + if let mark = state.takePostfixQuestionMark() { + let s = state.ast[p].site.extended(toCover: mark.site) + let o = state.insert(OptionPattern(name: p, site: s)) + return AnyPatternID(o) + } else { + return AnyPatternID(p) + } } } @@ -2601,16 +2640,9 @@ public enum Parser { state.contexts.append(.bindingPattern) defer { state.contexts.removeLast() } - // Parse the subpattern. + // Parse the subpattern and its optional ascription. let subpattern = try state.expect("pattern", using: parsePattern(in:)) - - // Parse the type annotation, if any. - let annotation: AnyExprID? - if state.take(.colon) != nil { - annotation = try state.expect("type expression", using: parseExpr(in:)) - } else { - annotation = nil - } + let annotation = try parseAscription(in: &state, parseExpr(in:)) return state.insert( BindingPattern( @@ -2926,30 +2958,15 @@ public enum Parser { try fail(.error("conditional binding requires an initializer", at: state.ast[d].site)) } - let fallback = try state.expect("fallback", using: conditionalBindingFallback) + let fallback = try state.expect("fallback", using: braceStmt.parse) let s = state.insert( - CondBindingStmt( + ConditionalBindingStmt( binding: d, fallback: fallback, site: state.ast[d].site.extended(upTo: state.currentIndex))) return AnyStmtID(s) } - static let conditionalBindingFallback = - (conditionalBindingFallbackStmt.or(conditionalBindingFallbackExpr)) - - static let conditionalBindingFallbackExpr = - (expr.map({ (_, id) -> CondBindingStmt.Fallback in .expr(id) })) - - static let conditionalBindingFallbackStmt = - (oneOf([ - anyStmt(breakStmt), - anyStmt(continueStmt), - anyStmt(returnStmt), - anyStmt(braceStmt), - ]) - .map({ (_, id) -> CondBindingStmt.Fallback in .exit(id) })) - static let declStmt = (Apply(parseDecl) .map({ (state, decl) -> DeclStmt.ID in @@ -2984,9 +3001,74 @@ public enum Parser { static let compilerConditionStmt = Apply(parseCompilerConditionStmt(in:)) - private static func parseCompilerConditionStmt(in state: inout ParserState) throws -> AnyStmtID? { - let head = state.take(.poundIf) - return head != nil ? try parseCompilerConditionTail(head: head!, in: &state) : nil + private static func parseCompilerConditionStmt( + in state: inout ParserState + ) throws -> AnyStmtID? { + try state.take(.poundIf).map { (head) in + try parseCompilerConditionTail(head: head, in: &state) + } + } + + /// Parses a logical connective from `state`. + private static func parseConnective(in state: inout ParserState) -> Connective? { + // Next token must be an operator. + guard let t = state.peek(), t.kind == .oper else { return nil } + + // The value of the token must be either `||` or `&&`. + var r: Connective + switch t.site.text { + case "||": + r = .disjunction + case "&&": + r = .conjunction + default: + return nil + } + + // Consume the token and "succeed". + _ = state.take() + return r + } + + /// Parses a `Condition` for a `ConditionalCompilationStmt`. + private static func parseCondition( + in state: inout ParserState + ) throws -> ConditionalCompilationStmt.Condition { + return try condition(withInfixConnectiveStrongerOrEqualTo: .disjunction, in: &state) + + /// Parses a condition as a proposition, a negation, or an infix sentence whose operator has + /// a precedence at least as strong as `p`. + func condition( + withInfixConnectiveStrongerOrEqualTo p: Connective, + in state: inout ParserState + ) throws -> ConditionalCompilationStmt.Condition { + var lhs = try parseCompilerCondition(in: &state) + + while true { + // Tentatively parse a connective. + let backup = state.backup() + guard let c = parseConnective(in: &state) else { return lhs } + + // Backtrack if the connective we got hasn't strong enough precedence. + if (c.rawValue < p.rawValue) { + state.restore(from: backup) + return lhs + } + + // If we parsed `||` the RHS must be a conjunction. Otherwise it must be a proposition or + // negation. In either case we'll come back here to parse the remainder of the expression. + switch c { + case .disjunction: + let rhs = try condition(withInfixConnectiveStrongerOrEqualTo: .conjunction, in: &state) + lhs = .or(lhs, rhs) + case .conjunction: + let rhs = try parseCompilerCondition(in: &state) + lhs = .and(lhs, rhs) + } + } + + return lhs + } } /// Parses a compiler condition structure, after the initial token (#if or #elseif). @@ -2994,7 +3076,7 @@ public enum Parser { head: Token, in state: inout ParserState ) throws -> AnyStmtID { // Parse the condition. - let condition = try parseCompilerCondition(in: &state) + let condition = try parseCondition(in: &state) // Parse the body of the compiler condition. let stmts: [AnyStmtID] @@ -3005,28 +3087,28 @@ public enum Parser { stmts = try parseConditionalCompilationBranch(in: &state) } - // The next token may be #endif, #else or #elseif. + // Parse the other branch(es) of the condition. let fallback: [AnyStmtID] - if state.take(.poundEndif) != nil { + + switch state.peek()?.kind { + case .poundEndif: fallback = [] - } else if state.take(.poundElse) != nil { - if condition.mayNotNeedParsing && condition.holds(for: state.ast.compilationConditions) { - try skipConditionalCompilationBranch(in: &state, stoppingAtElse: false) - fallback = [] - } else { - fallback = try parseConditionalCompilationBranch(in: &state) - } - // Expect #endif. _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) - } else if let head2 = state.take(.poundElseif) { + + case .poundElse, .poundElseif: + let h = state.take()! if condition.mayNotNeedParsing && condition.holds(for: state.ast.compilationConditions) { try skipConditionalCompilationBranch(in: &state, stoppingAtElse: false) fallback = [] - } else { - // We continue with another conditional compilation statement. - fallback = [try parseCompilerConditionTail(head: head2, in: &state)] + _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) + } else if h.kind == .poundElse { + fallback = try parseConditionalCompilationBranch(in: &state) + _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) + } else{ + fallback = [try parseCompilerConditionTail(head: h, in: &state)] } - } else { + + default: try fail(.error(expected: "statement, #endif, #else or #elseif", at: state.currentLocation)) } @@ -3289,7 +3371,7 @@ public enum Parser { // bound-constraint if state.take(.colon) != nil { - let rhs = try state.expect("type expression", using: boundList) + let rhs = try state.expect("type expression", using: boundComposition) return SourceRepresentable( value: .bound(l: lhs, r: rhs), range: state.ast[lhs].site.extended(upTo: state.currentIndex)) @@ -3306,13 +3388,9 @@ public enum Parser { range: tree.0.site.extended(upTo: state.currentIndex)) })) - static let traitComposition = + static let boundComposition = (nameTypeExpr.and(zeroOrMany(take(.ampersand).and(nameTypeExpr).second)) - .map({ (state, tree) -> TraitComposition in [tree.0] + tree.1 })) - - private static let boundList = - (expr.and(zeroOrMany(take(.ampersand).and(expr).second)) - .map({ (state, tree) -> [AnyExprID] in [tree.0] + tree.1 })) + .map({ (state, tree) -> [NameExpr.ID] in [tree.0] + tree.1 })) // MARK: Attributes @@ -3349,8 +3427,6 @@ public enum Parser { return nil } - static let typeAttribute = attribute("@type") - static let valueAttribute = attribute("@value") } @@ -3490,6 +3566,17 @@ struct ParameterInterface { } +/// A conjunction (`&&`) or disjunction (`||`) operator. +enum Connective: Int { + + /// The logical disjunction. + case disjunction + + /// The logical conjunction. + case conjunction + +} + /// A combinator that parses tokens with a specific kind. struct TakeKind: Combinator { @@ -3726,32 +3813,3 @@ extension ParserState { } } - -extension AST { - - /// Imports and returns a new module with the given `name` from `sourceCode`, writing diagnostics - /// to `diagnostics`. - /// - /// - Parameter builtinModuleAccess: whether the module is allowed to access the builtin module. - public mutating func makeModule( - _ name: String, sourceCode: S, - builtinModuleAccess: Bool = false, - diagnostics: inout DiagnosticSet - ) throws -> ModuleDecl.ID where S.Element == SourceFile { - var translations: [TranslationUnit.ID] = [] - for f in sourceCode { - do { - try translations.append(Parser.parse(f, in: &self, diagnostics: &diagnostics)) - } catch _ as DiagnosticSet { - // Suppress the error until all files are parsed. - } - } - - let m = insert( - ModuleDecl(name, sources: translations, builtinModuleAccess: builtinModuleAccess), - diagnostics: &diagnostics) - try diagnostics.throwOnError() - return m - } - -} diff --git a/Sources/FrontEnd/Parse/ParserState.swift b/Sources/FrontEnd/Parse/ParserState.swift index 9c5b837e6..64e6ff36b 100644 --- a/Sources/FrontEnd/Parse/ParserState.swift +++ b/Sources/FrontEnd/Parse/ParserState.swift @@ -29,9 +29,12 @@ struct ParserState { } - /// The AST being parsed. + /// The AST being constructed by the parser. var ast: AST + /// The node space into which new node identities must be registered. + private let space: Int + /// The lexer generating the tokens to parse. private(set) var lexer: Lexer @@ -47,12 +50,14 @@ struct ParserState { /// A stack describing the parsing context. var contexts: [Context] = [] - /// Creates a new context, using `lexer` to generate tokens. - init(ast: AST, lexer: Lexer, diagnostics: DiagnosticSet? = nil) { + /// Creates a new context for parsing parts of `ast`, registering the identities of newly + /// formed ASTs in space `k`, using `lexer` to generate tokens and reporting errors to `log`. + init(ast: AST, space: Int, lexer: Lexer, reportingDiagnosticsTo log: DiagnosticSet? = nil) { self.ast = ast + self.space = space self.lexer = lexer self.currentIndex = lexer.sourceCode.text.startIndex - self.diagnostics = diagnostics ?? DiagnosticSet() + self.diagnostics = log ?? DiagnosticSet() } /// Indicates whether the parser is at global scope. @@ -164,15 +169,12 @@ struct ParserState { /// Consumes and returns the next token, if any. mutating func take() -> Token? { // Return the token in the lookahead buffer, if available. - if let token = lookahead.popFirst() { + if let token = lookahead.popFirst() ?? lexer.next() { currentIndex = token.site.endIndex return token + } else { + return nil } - - // Attempt to pull a new element from the lexer. - guard let token = lexer.next() else { return nil } - currentIndex = token.site.endIndex - return token } /// Consumes and returns the next token if it has the specified kind. @@ -314,14 +316,14 @@ struct ParserState { /// Inserts `n` into `self.ast`, accumulating any diagnostics in `self.diagnostics`. mutating func insert(_ n: T) -> T.ID { - ast.insert(n, diagnostics: &diagnostics) + ast.insert(n, inNodeSpace: space, reportingDiagnosticsTo: &diagnostics) } /// Inserts `n` into `self.ast`. /// /// - Precondition: `n` is well-formed. mutating func insert(synthesized n: T) -> T.ID { - ast.insert(synthesized: n) + ast.insert(synthesized: n, inNodeSpace: space) } } diff --git a/Sources/FrontEnd/Program.swift b/Sources/FrontEnd/Program.swift index ae5a8b9d4..490cacbcd 100644 --- a/Sources/FrontEnd/Program.swift +++ b/Sources/FrontEnd/Program.swift @@ -450,8 +450,8 @@ extension Program { scopes(from: scope).first(TranslationUnit.self)! } - /// Returns the name of `d` if it introduces a single entity. - public func name(of d: AnyDeclID) -> Name? { + /// Returns the name of `d`. + public func name(of d: T) -> Name? { if let e = self.ast[d] as? SingleEntityDecl { return Name(stem: e.baseName) } switch d.kind { diff --git a/Sources/FrontEnd/ScopedProgram.swift b/Sources/FrontEnd/ScopedProgram.swift index 403ed435c..23539ab9c 100644 --- a/Sources/FrontEnd/ScopedProgram.swift +++ b/Sources/FrontEnd/ScopedProgram.swift @@ -11,6 +11,19 @@ public struct ScopedProgram: Program { public let varToBinding: [VarDecl.ID: BindingDecl.ID] + /// Creates an instance with the given properties. + private init( + ast: AST, + nodeToScope: ASTProperty, + scopeToDecls: ASTProperty, + varToBinding: [VarDecl.ID: BindingDecl.ID] + ) { + self.ast = ast + self.nodeToScope = nodeToScope + self.scopeToDecls = scopeToDecls + self.varToBinding = varToBinding + } + /// Creates a scoped program from an AST. public init(_ ast: AST) { // Establish the scope relationships. @@ -25,26 +38,65 @@ public struct ScopedProgram: Program { self.varToBinding = s.varToBinding } + /// Returns a copy of `self` in which a new module has been loaded, calling `make` to form its + /// contents and reporting diagnostics to `log`. + public func loadModule( + reportingDiagnosticsTo log: inout DiagnosticSet, + creatingContentsWith make: AST.ModuleLoader + ) throws -> (ScopedProgram, ModuleDecl.ID) { + var extended = ast + let m = try extended.loadModule(reportingDiagnosticsTo: &log, creatingContentsWith: make) + + var s = ScopeVisitor(extending: self) + extended.walk(m, notifying: &s) + let p = ScopedProgram( + ast: extended, + nodeToScope: s.nodeToScope, + scopeToDecls: s.scopeToDecls, + varToBinding: s.varToBinding) + + try log.throwOnError() + return (p, m) + } + } /// The state of the visitor building scope relationships. private struct ScopeVisitor: ASTWalkObserver { /// A map from node to the innermost scope that contains it. - var nodeToScope = ASTProperty() + var nodeToScope: ASTProperty /// A map from scope to the declarations that it contains. - var scopeToDecls = ASTProperty() + var scopeToDecls: ASTProperty /// A map from variable declaration its containing binding declaration. - var varToBinding: [VarDecl.ID: BindingDecl.ID] = [:] + var varToBinding: [VarDecl.ID: BindingDecl.ID] /// A stack containing the bindind declarations currently visited. - var bindingDecls: [BindingDecl.ID] = [] + var bindingDecls: [BindingDecl.ID] /// The innermost lexical scope currently visited. var innermost: AnyScopeID? + /// Creates a new instance. + init() { + self.nodeToScope = [:] + self.scopeToDecls = [:] + self.varToBinding = [:] + self.bindingDecls = [] + self.innermost = nil + } + + /// Creates an instance storing its result in a copy of `p`'s properties. + init(extending p: ScopedProgram) { + self.nodeToScope = p.nodeToScope + self.scopeToDecls = p.scopeToDecls + self.varToBinding = p.varToBinding + self.bindingDecls = [] + self.innermost = nil + } + /// Inserts `child` into `scope`. private mutating func insert(child: AnyNodeID, into scope: AnyScopeID) { assert(child.kind != ModuleDecl.self) diff --git a/Sources/FrontEnd/SourceFile.swift b/Sources/FrontEnd/SourceFile.swift index 7af3acbb0..966b9e67f 100644 --- a/Sources/FrontEnd/SourceFile.swift +++ b/Sources/FrontEnd/SourceFile.swift @@ -61,7 +61,8 @@ public struct SourceFile { } /// Creates a synthetic source file with the specified contents and base name. - public init(synthesizedText text: String, named baseName: String = UUID().uuidString) { + public init(synthesizedText text: String) { + let baseName = UUID().uuidString let storage = Storage(URL(string: "synthesized://\(baseName)")!) { text[...] } self.storage = storage } diff --git a/Sources/FrontEnd/Terms/AnyTerm.swift b/Sources/FrontEnd/Terms/AnyTerm.swift new file mode 100644 index 000000000..f040896d1 --- /dev/null +++ b/Sources/FrontEnd/Terms/AnyTerm.swift @@ -0,0 +1,107 @@ +import Utils + +/// A box wrapping a term. +private protocol TermBox { + + /// Hashes the salient parts of the wrapped value into `hasher`. + func hash(into hasher: inout Hasher) + + /// Returns whether the value wrapped inside `self` is equal to that wrapped inside `other`. + func equals(_ other: Other) -> Bool + + /// Returns the value wrapped inside `self` with its type erased. + func unwrap() -> any TermProtocol + + /// Returns the value wrapped inside `self` as an instance of `T` or `nil` if that value has a + /// different type. + func unwrap(as: T.Type) -> T? + +} + +/// A box wrapping an instance of `Base`. +private struct ConcreteTermBox: TermBox { + + /// The value wrapped by this instance. + let base: Base + + func hash(into hasher: inout Hasher) { + base.hash(into: &hasher) + } + + func equals(_ other: Other) -> Bool { + base == other.unwrap(as: Base.self) + } + + func unwrap() -> any TermProtocol { + base + } + + func unwrap(as: T.Type) -> T? { + base as? T + } + +} + +/// The compile-time representation of the value of an expression. +public struct AnyTerm { + + /// A shorthand for `^ErrorTerm()`. + public static let error = ^ErrorTerm() + + /// The value wrapped by this instance. + private var wrapped: TermBox + + /// Creates a type-erased container wrapping the given instance. + /// + /// - Parameter base: A type to wrap. + public init(_ base: T) { + if let t = base as? AnyTerm { + self.wrapped = t.wrapped + } else { + self.wrapped = ConcreteTermBox(base: base) + } + } + + /// Accesses value wrapped by this instance. + /// + /// The `base` property can be cast back to its original type using one of the type casting + /// operators (`as?`, `as!`, or `as`). + public var base: any TermProtocol { + wrapped.unwrap() + } + +} + +extension AnyTerm: TermProtocol { + + public var flags: ValueFlags { base.flags } + +} + +extension AnyTerm: Equatable { + + /// Returns whether `l` is syntactically equal to `r`. + public static func == (l: Self, r: Self) -> Bool { + l.wrapped.equals(r.wrapped) + } + +} + +extension AnyTerm: Hashable { + + public func hash(into hasher: inout Hasher) { + wrapped.hash(into: &hasher) + } + +} + +extension AnyTerm: CustomStringConvertible { + + public var description: String { String(describing: base) } + +} + +/// Creates a type-erased container wrapping the given instance. +public prefix func ^ (_ base: T) -> AnyTerm { + AnyTerm(base) +} diff --git a/Sources/FrontEnd/Terms/ConcreteTerm.swift b/Sources/FrontEnd/Terms/ConcreteTerm.swift new file mode 100644 index 000000000..3b0e49629 --- /dev/null +++ b/Sources/FrontEnd/Terms/ConcreteTerm.swift @@ -0,0 +1,20 @@ +/// A box wrapping a concrete compile-time value. +public struct ConcreteTerm: TermProtocol { + + /// The value of the term. + public let value: AnyHashable + + /// Creates an instance with the given value. + public init(wrapping value: AnyHashable) { + self.value = value + } + + public var flags: ValueFlags { .init() } + +} + +extension ConcreteTerm: CustomStringConvertible { + + public var description: String { "\(value)" } + +} diff --git a/Sources/FrontEnd/Terms/ErrorTerm.swift b/Sources/FrontEnd/Terms/ErrorTerm.swift new file mode 100644 index 000000000..cc2d86340 --- /dev/null +++ b/Sources/FrontEnd/Terms/ErrorTerm.swift @@ -0,0 +1,12 @@ +/// A term denoting a type checking error. +public struct ErrorTerm: TermProtocol { + + public var flags: ValueFlags { .hasError } + +} + +extension ErrorTerm: CustomStringConvertible { + + public var description: String { "_" } + +} diff --git a/Sources/FrontEnd/Terms/GenericTermParameter.swift b/Sources/FrontEnd/Terms/GenericTermParameter.swift new file mode 100644 index 000000000..8bd521a79 --- /dev/null +++ b/Sources/FrontEnd/Terms/GenericTermParameter.swift @@ -0,0 +1,26 @@ +import Utils + +/// A generic type parameter. +public struct GenericTermParameter: TermProtocol { + + /// The declaration that introduces the parameter. + public let decl: GenericParameterDecl.ID + + /// The name of the parameter. + public let name: Incidental + + /// Creates an instance denoting the generic type parameter declared by `decl`. + public init(_ decl: GenericParameterDecl.ID, ast: AST) { + self.decl = decl + self.name = Incidental(ast[decl].baseName) + } + + public var flags: ValueFlags { .hasSkolem } + +} + +extension GenericTermParameter: CustomStringConvertible { + + public var description: String { name.value } + +} diff --git a/Sources/FrontEnd/Terms/TermProtocol.swift b/Sources/FrontEnd/Terms/TermProtocol.swift new file mode 100644 index 000000000..036e34b07 --- /dev/null +++ b/Sources/FrontEnd/Terms/TermProtocol.swift @@ -0,0 +1,34 @@ +/// A protocol describing the API of a Hylo term. +public protocol TermProtocol: Hashable { + + /// Properties about the representation of `self`. + var flags: ValueFlags { get } + +} + +extension TermProtocol { + + /// Creates an instance with the value of `container.base` or returns `nil` if that value has + /// a different type. + public init?(_ container: AnyTerm) { + if let t = container.base as? Self { + self = t + } else { + return nil + } + } + + /// Creates an instance with the value of `container.base` or returns `nil` if either that value + /// has a different type or `container` is `nil`. + public init?(_ container: AnyTerm?) { + if let t = container.flatMap(Self.init(_:)) { + self = t + } else { + return nil + } + } + + /// Returns whether the specified flags are raised on this term. + public subscript(fs: ValueFlags) -> Bool { flags.contains(fs) } + +} diff --git a/Sources/FrontEnd/Terms/TermVariable.swift b/Sources/FrontEnd/Terms/TermVariable.swift new file mode 100644 index 000000000..206786855 --- /dev/null +++ b/Sources/FrontEnd/Terms/TermVariable.swift @@ -0,0 +1,28 @@ +import Utils + +/// A term variable. +public struct TermVariable: TermProtocol { + + /// The identifier of the variable. + public let rawValue: UInt64 + + /// Creates an instance with given `rawValue`. + public init(_ rawValue: UInt64) { + self.rawValue = rawValue + } + + public var flags: ValueFlags { .hasVariable } + + /// The context in which this instance was created. + var context: UInt8 { UInt8(rawValue >> 56) } + + /// The identifier of this instance. + var identifier: UInt64 { ~(255 << 56) & rawValue } + +} + +extension TermVariable: CustomStringConvertible { + + public var description: String { "%\(context).\(identifier)*" } + +} diff --git a/Sources/FrontEnd/TypeChecking/ConstraintSystem.swift b/Sources/FrontEnd/TypeChecking/ConstraintSystem.swift index c40e422e5..3e1079d8a 100644 --- a/Sources/FrontEnd/TypeChecking/ConstraintSystem.swift +++ b/Sources/FrontEnd/TypeChecking/ConstraintSystem.swift @@ -27,19 +27,19 @@ struct ConstraintSystem { /// The root goals that could not be solved. private var failureRoots: [GoalIdentity] = [] - /// A map from open type variable to its assignment. + /// A map from open type or term variable to its assignment. /// - /// This map is monotonically extended during constraint solving to assign a type to each open - /// variable in the constraint system. A system is complete if it can be used to derive a - /// complete substitution map w.r.t. its open type variables. - private var typeAssumptions = SubstitutionMap() + /// This map is monotonically extended during constraint solving to assign a type or term to each + /// open variable in the constraint system. A system is complete if it can be used to derive a + /// complete substitution map w.r.t. its open variables. + private var substitutions = SubstitutionMap() /// A map from name expression to its referred declaration. /// /// This map is monotonically extended during constraint solving to assign a declaration to each /// unresolved name expression in the constraint system. A system is complete if it can be used /// to derive a complete name binding map w.r.t. its unresolved name expressions. - private var bindingAssumptions: [NameExpr.ID: DeclReference] + private var bindings: [NameExpr.ID: DeclReference] /// A map from call expression to its operands after desugaring and implicit resolution. private var callOperands: [CallID: [ArgumentResolutionResult]] = [:] @@ -60,7 +60,7 @@ struct ConstraintSystem { /// deduction process if `loggingIsEnabled` is `true`. init(_ obligations: ProofObligations, logging loggingIsEnabled: Bool) { self.scope = obligations.scope - self.bindingAssumptions = obligations.referredDecl + self.bindings = obligations.referredDecl self.loggingIsEnabled = loggingIsEnabled _ = insert(fresh: obligations.constraints) } @@ -73,8 +73,8 @@ struct ConstraintSystem { self.fresh = other.fresh self.stale = other.stale self.failureRoots = other.failureRoots - self.typeAssumptions = other.typeAssumptions - self.bindingAssumptions = other.bindingAssumptions + self.substitutions = other.substitutions + self.bindings = other.bindings self.callOperands = other.callOperands self.penalties = other.penalties self.loggingIsEnabled = other.loggingIsEnabled @@ -106,7 +106,10 @@ struct ConstraintSystem { return nil } - goals[g].modifyTypes({ typeAssumptions[$0] }) + goals[g].modifyTypes { (t) in + substitutions.reify(t, withVariables: .kept) + } + log("- solve: \"\(goals[g])\"") indentation += 1 log("actions:") @@ -181,22 +184,28 @@ struct ConstraintSystem { setOutcome(.failure({ (_, _, _) in () }), for: g) } - let m = typeAssumptions.optimized() + let m = substitutions.optimized() var d = DiagnosticSet() for (k, v) in zip(goals.indices, outcomes) where isFailureRoot(k) { v!.diagnoseFailure!(&d, m, outcomes) } return Solution( - substitutions: m, bindings: bindingAssumptions, callOperands: callOperands, + substitutions: m, bindings: bindings, callOperands: callOperands, penalties: penalties, diagnostics: d, stale: stale.map({ goals[$0] })) } - /// Creates an ambiguous solution. + /// Creates an ambiguous solution, reporting the ambiguity with `d`. + /// + /// The return value is the intersection of the choices and diagnostics that are identical in + /// each result along with an additional diagnostic describing the ambiguity. + /// + /// - Requires: `result` contains at least two elements. private func formAmbiguousSolution( _ results: Explorations, diagnosedBy d: Diagnostic ) -> Solution { - var s = results.elements.reduce(into: Solution(), { (s, r) in s.formIntersection(r.solution) }) + let (first, others) = results.elements.headAndTail! + var s = others.reduce(into: first.solution, { (s, r) in s.formIntersection(r.solution) }) s.incorporate(d) return s } @@ -221,49 +230,6 @@ struct ConstraintSystem { } } - /// If `goal.model` is a structural type, creates and returns sub-goals checking that its parts - /// conform to `goal.concept`; returns `.failure` otherwise. - /// - /// - Requires: `goal.concept` is a trait supporting structural conformances and `goal.model` is - /// not a type variable. - private mutating func solve(structuralConformance goal: ConformanceConstraint) -> Outcome { - let model = checker.canonical(goal.model, in: scope) - assert(!(model.base is TypeVariable)) - - switch model.base { - case let t as ArrowType: - return delegate(structuralConformance: goal, for: [t.environment]) - case let t as TupleType: - return delegate(structuralConformance: goal, for: t.elements.lazy.map(\.type)) - case let t as UnionType: - return delegate(structuralConformance: goal, for: t.elements) - default: - return .failure(failureToSolve(goal)) - } - } - - /// Returns the outcome of breaking down `goal`, which is a structural conformance constraint, - /// into a set containing a conformance constraint for each type in `elements`. - private mutating func delegate>( - structuralConformance goal: ConformanceConstraint, for elements: S - ) -> Outcome { - if elements.isEmpty { - return .success - } - - let subordinateOrigin = goal.origin.subordinate() - var subordinates: [GoalIdentity] = [] - for e in elements { - let c = ConformanceConstraint(e, conformsTo: goal.concept, origin: subordinateOrigin) - subordinates.append(schedule(c)) - } - - return .product(subordinates) { (d, m, _) in - let t = m.reify(goal.model) - d.insert(.error(t, doesNotConformTo: goal.concept, at: goal.origin.site)) - } - } - /// Returns a closure diagnosing a failure to solve `goal`. private mutating func failureToSolve(_ goal: ConformanceConstraint) -> DiagnoseFailure { return { (d, m, _) in @@ -275,7 +241,7 @@ struct ConstraintSystem { /// Returns eiteher `.success` if `g.left` is unifiable with `g.right` or `.failure` otherwise. private mutating func solve(equality g: GoalIdentity) -> Outcome { let goal = goals[g] as! EqualityConstraint - if unify(goal.left, goal.right) { + if solve(goal.left, equals: goal.right) { return .success } else { return .failure(failureToSolve(goal)) @@ -301,6 +267,7 @@ struct ConstraintSystem { /// If the constraint is strict, then `g.left` must be different than `g.right`. private mutating func solve(subtyping g: GoalIdentity) -> Outcome? { let goal = goals[g] as! SubtypingConstraint + lazy var o = goal.origin.subordinate() // Note: we're not using canonical equality here since we'll be dealing with aliases and // structural equivalences during unification anyway. @@ -327,11 +294,10 @@ struct ConstraintSystem { case (_, let r as UnionType): // If `R` is an empty union, so must be `L`. if r.elements.isEmpty { - return unify(goal.left, goal.right) ? .success : .failure(failureToSolve(goal)) + return solve(goal.left, equals: goal.right) ? .success : .failure(failureToSolve(goal)) } // If `R` has a single element, it must be above (the canonical form of) `L`. - let o = goal.origin.subordinate() if let e = r.elements.uniqueElement { let s = schedule(SubtypingConstraint(goal.left, e, origin: o)) return delegate(to: [s]) @@ -369,7 +335,6 @@ struct ConstraintSystem { postpone(g) return nil } else { - let o = goal.origin.subordinate() let s = schedule(inferenceConstraint(goal.left, isSubtypeOf: goal.right, origin: o)) return delegate(to: [s]) } @@ -379,18 +344,16 @@ struct ConstraintSystem { // coercible to `R` and that are above `L`, but that set is unbounded unless `R` is a leaf. // If it isn't, we have no choice but to postpone the goal. if goal.right.isLeaf { - return unify(goal.left, goal.right) ? .success : .failure(failureToSolve(goal)) + return solve(goal.left, equals: goal.right) ? .success : .failure(failureToSolve(goal)) } else if goal.isStrict { postpone(g) return nil } else { - let o = goal.origin.subordinate() let s = schedule(inferenceConstraint(goal.left, isSubtypeOf: goal.right, origin: o)) return delegate(to: [s]) } case (let l as RemoteType, _): - let o = goal.origin.subordinate() let s = schedule( SubtypingConstraint(l.bareType, goal.right, strictly: goal.isStrict, origin: o)) return delegate(to: [s]) @@ -408,7 +371,6 @@ struct ConstraintSystem { return .success } else { var subordinates: [GoalIdentity] = [] - let o = goal.origin.subordinate() for t in traits { subordinates.append( schedule(ConformanceConstraint(goal.left, conformsTo: t, origin: o))) @@ -428,28 +390,33 @@ struct ConstraintSystem { } let r = checker.openForUnification(d) - let s = schedule(EqualityConstraint(goal.left, ^r, origin: goal.origin.subordinate())) + let s = schedule(EqualityConstraint(goal.left, ^r, origin: o)) return delegate(to: [s]) case .metatype: let r = MetatypeType(of: checker.freshVariable()) - let s = schedule(EqualityConstraint(goal.left, ^r, origin: goal.origin.subordinate())) + let s = schedule(EqualityConstraint(goal.left, ^r, origin: o)) return delegate(to: [s]) } + case (let l as AssociatedTypeType, _) where l.root.base is TypeVariable: + postpone(g) + return nil + + case (_, let r as AssociatedTypeType) where r.root.base is TypeVariable: + postpone(g) + return nil + default: - if !goal.left[.isCanonical] || !goal.right[.isCanonical] { + if !goal.left.isCanonical || !goal.right.isCanonical { let l = checker.canonical(goal.left, in: scope) let r = checker.canonical(goal.right, in: scope) - let s = schedule( - SubtypingConstraint(l, r, strictly: goal.isStrict, origin: goal.origin.subordinate())) + let s = schedule(SubtypingConstraint(l, r, strictly: goal.isStrict, origin: o)) return delegate(to: [s]) - } - - if goal.isStrict { + } else if goal.isStrict { return .failure(failureToSolve(goal)) } else { - return unify(goal.left, goal.right) ? .success : .failure(failureToSolve(goal)) + return solve(goal.left, equals: goal.right) ? .success : .failure(failureToSolve(goal)) } } } @@ -609,7 +576,7 @@ struct ConstraintSystem { if let i = candidates.viable.uniqueElement { let c = candidates.elements[i] - bindingAssumptions[goal.memberExpr] = c.reference + bindings[goal.memberExpr] = c.reference var subordinates = insert(fresh: c.constraints) subordinates.append( @@ -801,7 +768,7 @@ struct ConstraintSystem { let results: Explorations = explore(g) { (solver, choice) in solver.penalties += choice.penalties - solver.bindingAssumptions[goal.overloadedExpr] = choice.reference + solver.bindings[goal.overloadedExpr] = choice.reference return solver.insert(fresh: choice.constraints) } @@ -910,21 +877,19 @@ struct ConstraintSystem { stale.append(g) } - /// Returns `true` iff `lhs` and `rhs` can be unified, updating the type substitution table. + /// Returns `true` iff `lhs` and `rhs` can be unified, updating the substitution table. /// /// Type unification consists of finding substitutions that makes `lhs` and `rhs` equal. Both - /// types are visited in lockstep, updating `self.typeAssumptions` every time either side is a - /// type variable for which no substitution has been made yet. - private mutating func unify(_ lhs: AnyType, _ rhs: AnyType) -> Bool { - lhs.matches(rhs, mutating: &self) { (this, a, b) in - this.unifySyntacticallyInequal(a, b) - } + /// types are visited in lockstep, updating `self.subscritutions` every time either side is a + /// variable for which no substitution has been made yet. + private mutating func solve(_ lhs: AnyType, equals rhs: AnyType) -> Bool { + matches(lhs, rhs) } /// Returns `true` iff `lhs` and `rhs` can be unified. - private mutating func unifySyntacticallyInequal(_ lhs: AnyType, _ rhs: AnyType) -> Bool { - let t = typeAssumptions[lhs] - let u = typeAssumptions[rhs] + private mutating func unify(_ lhs: AnyType, _ rhs: AnyType) -> Bool { + let t = substitutions[lhs] + let u = substitutions[rhs] switch (t.base, u.base) { case (let l as TypeVariable, _): @@ -936,24 +901,23 @@ struct ConstraintSystem { return true case (let l as UnionType, let r as UnionType): - return unifySyntacticallyInequal(l, r) + return unify(l, r) - case _ where !t[.isCanonical] || !u[.isCanonical]: - return unify(checker.canonical(t, in: scope), checker.canonical(u, in: scope)) + case _ where !t.isCanonical || !u.isCanonical: + return solve(checker.canonical(t, in: scope), equals: checker.canonical(u, in: scope)) default: - // TODO: Use semantic equality - return t == u + return checker.areEquivalent(t, u, in: scope) } } /// Returns `true` iff `lhs` and `rhs` can be unified. - private mutating func unifySyntacticallyInequal( + private mutating func unify( _ lhs: UnionType, _ rhs: UnionType ) -> Bool { for a in lhs.elements { var success = false - for b in rhs.elements where unify(a, b) { + for b in rhs.elements where solve(a, equals: b) { success = true } if !success { return false } @@ -961,19 +925,132 @@ struct ConstraintSystem { return true } + /// Returns `true` iff `lhs` and `rhs` can be unified. + private mutating func unify(_ lhs: AnyTerm, _ rhs: AnyTerm) -> Bool { + let t = substitutions[lhs] + let u = substitutions[rhs] + + switch (t.base, u.base) { + case (let l as TermVariable, _): + assume(l, equals: u) + return true + + case (_, let r as TermVariable): + assume(r, equals: t) + return true + + default: + return t == u + } + } + + /// Returns `true` iff `t` and `u` are equal under some substitution of their variables. + private mutating func matches(_ t: AnyType, _ u: AnyType) -> Bool { + switch (t.base, u.base) { + case (let lhs as BoundGenericType, let rhs as BoundGenericType): + if lhs.arguments.count != rhs.arguments.count { return false } + var result = matches(lhs.base, rhs.base) + for (a, b) in zip(lhs.arguments, rhs.arguments) { + result = matches(a.value, b.value) && result + } + return result + + case (let lhs as MetatypeType, let rhs as MetatypeType): + return matches(lhs.instance, rhs.instance) + + case (let lhs as TupleType, let rhs as TupleType): + if !lhs.labels.elementsEqual(rhs.labels) { return false } + return matches(lhs.elements, rhs.elements, at: \.type) + + case (let lhs as ArrowType, let rhs as ArrowType): + if !lhs.labels.elementsEqual(rhs.labels) { return false } + var result = matches(lhs.inputs, rhs.inputs, at: \.type) + result = matches(lhs.output, rhs.output) && result + result = matches(lhs.environment, rhs.environment) && result + return result + + case (let lhs as BufferType, let rhs as BufferType): + return matches(lhs.element, rhs.element) && matches(lhs.count, rhs.count) + + case (let lhs as MethodType, let rhs as MethodType): + if !lhs.labels.elementsEqual(rhs.labels) || (lhs.capabilities != rhs.capabilities) { + return false + } + + var result = matches(lhs.inputs, rhs.inputs, at: \.type) + result = matches(lhs.output, rhs.output) && result + result = matches(lhs.receiver, rhs.receiver) && result + return result + + case (let lhs as ParameterType, let rhs as ParameterType): + if lhs.access != rhs.access { return false } + return matches(lhs.bareType, rhs.bareType) + + case (let lhs as RemoteType, let rhs as RemoteType): + if lhs.access != rhs.access { return false } + return matches(lhs.bareType, rhs.bareType) + + default: + return (t == u) || unify(t, u) + } + } + + /// Returns `true` iff the result of `matches(_:_:)` applied on all elements from `ts` and `us` + /// pairwise is `true`. + private mutating func matches( + _ ts: T, _ us: T, at p: KeyPath + ) -> Bool { + var result = true + for (a, b) in zip(ts, us) { + result = matches(a[keyPath: p], b[keyPath: p]) && result + } + return result + } + + /// Returns `true` iff `t` and `u` are equal under some substitution of their variables. + private mutating func matches(_ t: AnyTerm, _ u: AnyTerm) -> Bool { + (t == u) || unify(t, u) + } + + /// Returns `true` iff `t` and `u` are equal under some substitution of their variables. + private mutating func matches(_ t: CompileTimeValue, _ u: CompileTimeValue) -> Bool { + switch (t, u) { + case (.type(let lhs), .type(let rhs)): + return matches(lhs, rhs) + case (.term(let lhs), .term(let rhs)): + return matches(lhs, rhs) + default: + return false + } + } + /// Extends the type substution table to map `tau` to `substitute`. private mutating func assume(_ tau: TypeVariable, equals substitute: AnyType) { log("- assume: \"\(tau) = \(substitute)\"") - typeAssumptions.assign(substitute, to: tau) + substitutions.assign(substitute, to: tau) + refresh() + } - // Refresh stale constraints. + /// Extends the term substution table to map `tau` to `substitute`. + private mutating func assume(_ tau: TermVariable, equals substitute: AnyTerm) { + log("- assume: \"\(tau) = \(substitute)\"") + substitutions.assign(substitute, to: tau) + refresh() + } + + /// Refresh stale constraints containing variables that have been assigned. + private mutating func refresh() { for i in (0 ..< stale.count).reversed() { var changed = false - goals[stale[i]].modifyTypes({ (type) in - let u = typeAssumptions.reify(type, withVariables: .kept) - changed = changed || (type != u) - return u - }) + goals[stale[i]].modifyTypes { (t) in + if t[.hasVariable] { + let u = substitutions.reify(t, withVariables: .kept) + changed = changed || (t != u) + return u + } else { + return t + } + } if changed { log("- refresh \(goals[stale[i]])") diff --git a/Sources/FrontEnd/TypeChecking/Rewriting/RequirementPrinter.swift b/Sources/FrontEnd/TypeChecking/Rewriting/RequirementPrinter.swift new file mode 100644 index 000000000..f3350fda6 --- /dev/null +++ b/Sources/FrontEnd/TypeChecking/Rewriting/RequirementPrinter.swift @@ -0,0 +1,75 @@ +import Utils + +/// An object creating textual descriptions of requirement rewriting systems. +struct RequirementPrinter { + + /// The program defining the symbols represented by the terms. + let program: TypedProgram + + /// A factory for making unique type names. + private var disambiguator = DistinctNameGenerator() + + /// Creates an instance for creating textual descriptions of symbols defined in `program`. + init(program: TypedProgram) { + self.program = program + } + + /// Returns a textual description of `rule`. + mutating func describe(_ rule: RequirementRule) -> String { + let l = describe(rule.lhs) + let r = describe(rule.rhs) + return "\(l) => \(r)" + } + + /// Returns a textual description of `term`. + mutating func describe(_ term: RequirementTerm) -> String { + var result: [String] = [] + for s in term.symbols { + switch s { + case .associatedType(let d): + let t = program.traitDeclaring(d)!.name + let a = program[d].baseName + result.append("[::\(t.value).\(a)]") + + case .parameterType(let d): + let p = disambiguator.name(program[d].baseName, keyedBy: AnyDeclID(d)) + result.append(p) + + case .trait(let d): + let p = program[d].baseName + result.append("[\(p)]") + + case .concrete(let t): + result.append("[concrete: \(t)]") + } + } + return result.isEmpty ? "ε" : result.joined(separator: ".") + } + +} + +extension TypedProgram { + + /// Returns a debug description of `m`. + func describe(_ m: RequirementSystem) -> String { + var o = "" + var p = RequirementPrinter(program: self) + for r in m.rules.indices where !m.rules[r].isSimplified { + o += "\(r): \(p.describe(m.rules[r]))\n" + } + return o + } + + /// Returns a debug description of `r`. + func describe(_ r: RequirementRule) -> String { + var p = RequirementPrinter(program: self) + return p.describe(r) + } + + /// Returns a debug description of `t`. + func describe(_ t: RequirementTerm) -> String { + var p = RequirementPrinter(program: self) + return p.describe(t) + } + +} diff --git a/Sources/FrontEnd/TypeChecking/Rewriting/RequirementTerm.swift b/Sources/FrontEnd/TypeChecking/Rewriting/RequirementTerm.swift new file mode 100644 index 000000000..7681dcef6 --- /dev/null +++ b/Sources/FrontEnd/TypeChecking/Rewriting/RequirementTerm.swift @@ -0,0 +1,111 @@ +import Utils + +/// A set of rewriting rules describing the requirements of a generic signature. +typealias RequirementSystem = RewritingSystem + +/// A rule in a requirement rewriting system. +typealias RequirementRule = RewritingRule + +/// A term corresponding to a type or trait in a generic requirement. +struct RequirementTerm: RewritingTerm { + + /// The parts of the term. + let symbols: [RequirementSymbol] + + /// Creates a term with the given symbols. + init>(_ s: S) { + self.symbols = Array(s) + } + + /// The generic parameter at the root of this term, if any. + var rootParameter: GenericParameterDecl.ID? { + symbols.first.flatMap { (s) in + if case .parameterType(let p) = s { + return p + } else { + return nil + } + } + } + + /// Returns `self` suffixed by `s`. + func appending(_ s: RequirementSymbol) -> Self { + .init(symbols + [s]) + } + + /// Returns a copy of `self` in which occurrences of `s` have been replaced by `t`. + func substituting(_ s: RequirementTerm, for t: RequirementTerm) -> Self { + .init(symbols.replacing(s.symbols, with: t.symbols)) + } + + /// Returns `u` concatenated with `v`. + static func + (u: Self, v: Self) -> Self { + .init(u.symbols + v.symbols) + } + + /// Returns `u` concatenated with `v`. + static func + >(u: Self, v: S) -> Self { + .init(u.symbols + v) + } + + /// Returns `u` concatenated with `v`. + static func + >(u: S, v: Self) -> Self { + .init(u + v.symbols) + } + +} + +extension RequirementTerm: ExpressibleByArrayLiteral { + + init(arrayLiteral elements: RequirementSymbol...) { + self.init(elements) + } + +} + +extension RequirementTerm: Collection { + + typealias Element = RequirementSymbol + + typealias Index = Int + + var startIndex: Int { 0 } + + var endIndex: Int { symbols.count } + + func index(after p: Int) -> Int { p + 1 } + + subscript(p: Int) -> RequirementSymbol { symbols[p] } + +} + +/// A part of a term in a requirement rewriting system. +enum RequirementSymbol: Hashable { + + /// An associated type. + case associatedType(AssociatedTypeDecl.ID) + + /// A generic parameter type. + case parameterType(GenericParameterDecl.ID) + + /// A trait. + case trait(TraitDecl.ID) + + /// A concrete type constraint. + case concrete(AnyType) + + /// The kind of this symbol. + var kind: Int { + switch self { + case .associatedType: + return 0 + case .parameterType: + return 1 + case .trait: + return 2 + case .concrete: + return 3 + } + } + +} diff --git a/Sources/FrontEnd/TypeChecking/Rewriting/RewritingRule.swift b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingRule.swift new file mode 100644 index 000000000..864124ecd --- /dev/null +++ b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingRule.swift @@ -0,0 +1,66 @@ +/// A rule in a rewriting system. +struct RewritingRule: Equatable { + + /// The left-hand side of the rule. + let lhs: Term + + /// The right-hand side of the rule. + let rhs: Term + + /// `true` if `self` has been simplified. + private(set) var flags: RequirementRuleFlags + + /// Creates an instance rewriting `lhs` to `rhs`. + init(_ lhs: Term, _ rhs: Term) { + self.lhs = lhs + self.rhs = rhs + self.flags = [] + } + + /// `self` as a pair `(source, target)`. + var deconstructed: (Term, Term) { (lhs, rhs) } + + /// `true` if `self` has been simplified. + var isSimplified: Bool { + flags.contains(.isLeftSimplified) || flags.contains(.isRightSimplified) + } + + /// Returns a copy of `self` in which occurrences of `s` have been replaced by `t`. + func substituting(_ s: Term, for t: Term) -> Self { + .init(lhs.substituting(s, for: t), rhs.substituting(s, for: t)) + } + + /// Raises the flags `fs` in the rule. + mutating func raiseFlags(_ fs: RequirementRuleFlags) { + flags.insert(fs) + } + +} + +extension RewritingRule where Term == RequirementTerm { + + /// Returns `true` if the generic parameters mentioned by this rule are contained in `ps`. + func parametersAreContained(in ps: [GenericParameterDecl.ID]) -> Bool { + if let a = lhs.rootParameter, !ps.contains(a) { + return false + } else { + return rhs.rootParameter.map(ps.contains(_:)) ?? true + } + } + +} + +/// A set of flags associated with a rewriting rule. +struct RequirementRuleFlags: OptionSet { + + typealias RawValue = UInt8 + + var rawValue: UInt8 + + /// Indicates that a rule has been removed by left-simplification. + static let isLeftSimplified = RequirementRuleFlags(rawValue: 1) + + /// Indicates that a rule has been removed by right-simplification. + static let isRightSimplified = RequirementRuleFlags(rawValue: 2) + +} diff --git a/Sources/FrontEnd/TypeChecking/Rewriting/RewritingSystem.swift b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingSystem.swift new file mode 100644 index 000000000..0a5a9ed89 --- /dev/null +++ b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingSystem.swift @@ -0,0 +1,300 @@ +import Utils + +/// A set of rewriting rules describing some equational equivalences. +struct RewritingSystem { + + /// The identifier of a rule in a rewriting system. + typealias RuleID = Int + + /// The rules in the system, including those that may have been simplified. + private(set) var rules: [RewritingRule] + + /// A map from terms to the rule of which they are the left-hand side. + private var termToRule: Trie + + /// Creates an empty system. + init() { + self.rules = [] + self.termToRule = Trie() + } + + /// Rewrites `u` with the rules in `self` until a normal form is reached. + /// + /// The rewriting process is notionally nondeterministic unless `self` is confluent. + func reduce(_ u: Term) -> Term { + for p in u.indices { + let (n, q) = termToRule.longestPrefix(startingWith: u[p...]) + if p != q, let r = n[[]] { + let x = Term(u[.. v* is notionally contained in a rewriting system if that system contains a set + /// of rules capable of rewriting *u* to *v*. This set may not contain *u => v* itself. + /// + /// The return value is `(true, new)` if `r` is not already notionally contained in the system, + /// where `new` is the identifier of a newly inserted rule that is encoding `r`. Otherwise, the + /// return value is `(false, old)` where `old` is the identifier of a rule encoding `r`. + /// + /// - Precondition: The source of the rule is ordered after its target. + @discardableResult + mutating func insert( + _ r: RewritingRule, + orderingTermsWith compareOrder: (Term, Term) -> StrictOrdering + ) -> (inserted: Bool, ruleAfterInsertion: RuleID) { + precondition(compareOrder(r.lhs, r.rhs) == .descending, "invalid rewriting rule") + precondition(!r.isSimplified) + + // If the source of the rule isn't associated with any other rule yet, inserts the rule and + // return `(true, i)` where `i` is the position of the rule in `rules`. Otherwise, return + // `(false, j)` where `j` is the position of a rule sharing the same source. + let result = modify(&termToRule[r.lhs]) { (q) in + if let old = q { + return (inserted: false, ruleAfterInsertion: old) + } else { + q = rules.count + rules.append(r) + return (inserted: true, ruleAfterInsertion: q!) + } + } + + // Nothing more to do if the rule was inserted. + if result.inserted { + return result + } + + // Otherwise, update the system to notionally contain the rule. + let old = result.ruleAfterInsertion + switch compareOrder(r.rhs, rules[old].rhs) { + case .equal: + return result + + case .descending: + return insert(.init(r.rhs, rules[old].rhs), orderingTermsWith: compareOrder) + + case .ascending: + // Let the u1 => v1 and u2 => v2 be the old and new rules, respectively. Remove u1 => v1 from + // the system and add v1 => v2. + rules[old].raiseFlags(.isRightSimplified) + insert(.init(rules[old].rhs, r.rhs), orderingTermsWith: compareOrder) + + // Add u2 => v2. + let q = rules.count + termToRule[r.lhs] = q + rules.append(r) + return (inserted: true, ruleAfterInsertion: q) + } + } + + /// Turns `self` into a terminating and confluent rewriting system, using `compareOrder` to order + /// pairs of terms. + /// + /// This method uses Knuth-Bendix completion algorithm to transform `self` into a terminating + /// confluent system. The completion procedure is semi-decidable: it returns `true` if it + /// succeeds or `false` if it suspects that it won't terminate. + mutating func complete(orderingTermsWith compareOrder: (Term, Term) -> StrictOrdering) -> Bool{ + var visitedOverlaps = Set() + var pairs: [CriticalPair] = [] + var changed = true + var insertions = 0 + + while changed { + changed = false + + forEachOverlap { (i, j, k) in + if visitedOverlaps.insert(.init(i, j, at: k)).inserted { + pairs.append(formCriticalPair(i, j, overlappingAt: k)) + } + } + + while let p = pairs.popLast() { + if let q = resolveCriticalPair(p, orderingTermsWith: compareOrder) { + if insert(q, orderingTermsWith: compareOrder).inserted { + changed = true + insertions += 1 + if insertions > 100 { return false } + } + } + } + } + + leftSimplify() + return true + } + + + /// Calls `action` on each overlap between two rules of the system. + private func forEachOverlap(do action: (RuleID, RuleID, Term.Index) -> Void) { + for i in rules.indices where !rules[i].isSimplified { + forEachOverlap(involving: i, do: { (j, p) in action(i, j, p) }) + } + } + + /// Calls `action` on each overlap involving `i`. + private func forEachOverlap(involving i: RuleID, do action: (RuleID, Term.Index) -> Void) { + let u = rules[i].lhs + for p in u.indices { + forEachOverlap(of: u[p...], in: termToRule[prefix: []]!) { (j) in + // Ignore the overlap of a rule with itself at position 0. + if (i == j) && (p == u.startIndex) { return } + action(j, p) + } + } + } + + /// Calls `action` on each identifier in `terms` denoting a rule having an overlap between its + /// left-hand side and `suffix`. + /// + /// If the key/value pair `(t, i)` is contained in `terms`, then `t` is the suffix of some term + /// `l` and `i` identifies a rewriting rule `l => r`. + private func forEachOverlap( + of suffix: Term.SubSequence, in terms: SubTrie, + do action: (RuleID) -> Void + ) { + var t = suffix + var n = terms + + while let (head, tail) = t.headAndTail { + if let m = n[prefix: [head]] { + if let i = m[[]] { action(i) } + t = tail + n = m + } else { + return + } + } + + for e in n.elements { + action(e.value) + } + } + + /// Returns the critical pair formed by the rules `lhs` and `rhs`, which overlap at the `i`-th + /// position of `lhs`'s source. + private func formCriticalPair( + _ lhs: RuleID, _ rhs: RuleID, overlappingAt i: Int + ) -> CriticalPair { + // Let `lhs` and `rhs` denote rewriting rules u1 => v1 and u2 => v2, respectively. + let (u1, v1) = rules[lhs].deconstructed + let (u2, v2) = rules[rhs].deconstructed + + // If i + |u2| ≤ |u1|, then u1 = x·u2·z for some x and z. + if i + u2.count <= u1.count { + let x = u1[.. StrictOrdering + ) -> RewritingRule? { + // Fast path: critical pair is trivial without any reduction. + if p.first == p.second { return nil } + + // Reduce both sides of the pair. + let b1 = reduce(p.first) + let b2 = reduce(p.second) + + // There are only three cases to consider because we assume a total order on the terms. That is + // unlike traditional implementations of Knuth-Bendix, which must fail on incomparable terms. + switch compareOrder(b1, b2) { + case .equal: + // b1 = b2: the pair is trivial and there's nothing more to do. + return nil + + case .ascending: + // b1 < b2: create a new rule b2 => b1. + return .init(b2, b1) + + case .descending: + // b2 < b1: create a new rule b1 => b2. + return .init(b1, b2) + } + } + + /// Removes the rules in `self` whose left hand side can be reduced by a simpler rule. + private mutating func leftSimplify() { + for i in 0 ..< rules.count where !rules[i].isSimplified { + for p in rules[i].lhs.indices { + if simplify(i, lookingForTermsInSuffixFrom: p) { break } + } + } + } + + /// Removes `i` from `self` if the suffix of its left-hand side starting from `p` contains a + /// subterm that can be reduced by another rule. + private mutating func simplify(_ i: RuleID, lookingForTermsInSuffixFrom p: Term.Index) -> Bool { + let w = rules[i].lhs + var n = termToRule[prefix: []]! + + for q in w[p...].indices { + if let m = n[prefix: w[q ..< w.index(after: q)]] { + if let j = m[[]], i != j { + rules[i].raiseFlags(.isLeftSimplified) + termToRule[rules[i].lhs] = nil + return true + } else { + n = m + } + } else { + return false + } + } + + return false + } + + /// The rewritings of a term by two different rules or the same rule at two different positions. + private struct CriticalPair { + + /// The first term of the pair. + let first: Term + + /// The first term of the pair. + let second: Term + + /// Creates an instance with the given terms. + init(_ u: Term, _ v: Term) { + self.first = u + self.second = v + } + + } + + /// The identifier of an overlap between rewriting rules. + private struct OverlapIdentifier: Hashable { + + /// The raw value of this identifier. + private let rawValue: UInt64 + + /// Creates an instance identifying an overlap between `lhs` and `rhs` at the `i`-th position + /// of `lhs`'s source. + init( + _ lhs: RewritingSystem.RuleID, _ rhs: RewritingSystem.RuleID, + at i: Term.Index + ) { + precondition((i | lhs | rhs) & ~((1 << 16) - 1) == 0) + self.rawValue = UInt64(truncatingIfNeeded: i | (lhs << 16) | (rhs << 32)) + } + + } + +} diff --git a/Sources/FrontEnd/TypeChecking/Rewriting/RewritingTerm.swift b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingTerm.swift new file mode 100644 index 000000000..82b420d6d --- /dev/null +++ b/Sources/FrontEnd/TypeChecking/Rewriting/RewritingTerm.swift @@ -0,0 +1,19 @@ +/// A term in a rewriting system. +protocol RewritingTerm: Equatable, Collection where Index == Int, Element: Hashable { + + /// Creates a term with the given symbols. + init>(_ s: S) + + /// Returns a copy of `self` in which occurrences of `s` have been replaced by `t`. + func substituting(_ s: Self, for t: Self) -> Self + + /// Returns `u` concatenated with `v`. + static func + (u: Self, v: Self) -> Self + + /// Returns `u` concatenated with `v`. + static func + >(u: Self, v: S) -> Self + + /// Returns `u` concatenated with `v`. + static func + >(u: S, v: Self) -> Self + +} diff --git a/Sources/FrontEnd/TypeChecking/Solution.swift b/Sources/FrontEnd/TypeChecking/Solution.swift index 2a5febeaa..52eac38b1 100644 --- a/Sources/FrontEnd/TypeChecking/Solution.swift +++ b/Sources/FrontEnd/TypeChecking/Solution.swift @@ -21,11 +21,11 @@ struct Solution { } - /// The type assumptions made by the solver. - private(set) var typeAssumptions: SubstitutionMap + /// The type and term substitutions made by the solver. + private(set) var substitutions: SubstitutionMap /// The name binding assumptions made by the solver. - private(set) var bindingAssumptions: BindingMap + private(set) var bindings: BindingMap /// A map from call expression to its operands after desugaring and implicit resolution. private(set) var callOperands: [CallID: [ArgumentResolutionResult]] @@ -42,21 +42,21 @@ struct Solution { /// Creates an empty solution. init() { self.init( - substitutions: [:], bindings: [:], callOperands: [:], + substitutions: .init(), bindings: [:], callOperands: [:], penalties: 0, diagnostics: [], stale: []) } /// Creates an instance with the given properties. init( - substitutions typeAssumptions: SubstitutionMap, - bindings bindingAssumptions: [NameExpr.ID: DeclReference], + substitutions: SubstitutionMap, + bindings: [NameExpr.ID: DeclReference], callOperands: [CallID: [ArgumentResolutionResult]], penalties: Int, diagnostics: DiagnosticSet, stale: [Constraint] ) { - self.typeAssumptions = typeAssumptions - self.bindingAssumptions = bindingAssumptions + self.substitutions = substitutions + self.bindings = bindings self.callOperands = callOperands self.penalties = penalties self.diagnostics = diagnostics @@ -81,8 +81,9 @@ struct Solution { /// Removes the type and binding assumptions that aren't in `other` and incorporate the /// penalties and diagnostics of `other` into `self`. mutating func formIntersection(_ other: Self) { - typeAssumptions.formIntersection(other.typeAssumptions) - bindingAssumptions.formIntersection(other.bindingAssumptions) + substitutions.formIntersection(other.substitutions) + bindings.formIntersection(other.bindings) + diagnostics.formIntersection(other.diagnostics) penalties = max(penalties, other.penalties) } @@ -93,13 +94,13 @@ extension Solution: CustomStringConvertible { public var description: String { var result = "" - result.append("typeAssumptions:\n") - for (k, v) in typeAssumptions.storage { + result.append("substitutions:\n") + for (k, v) in substitutions.types { result.append(" \(k) : \(v)\n") } - result.append("bindingAssumptions:\n") - for (k, v) in bindingAssumptions { + result.append("bindings:\n") + for (k, v) in bindings { result.append(" \(k) : \(v)\n") } diff --git a/Sources/FrontEnd/TypeChecking/SubstitutionMap.swift b/Sources/FrontEnd/TypeChecking/SubstitutionMap.swift index 276ac86e5..ed55329ee 100644 --- a/Sources/FrontEnd/TypeChecking/SubstitutionMap.swift +++ b/Sources/FrontEnd/TypeChecking/SubstitutionMap.swift @@ -1,7 +1,7 @@ -/// A substitution table mapping type variables to assumptions during type inference. +/// A substitution table mapping type and term variables to assumptions during inference. struct SubstitutionMap { - /// A policy for substituting type variales during reification. + /// A policy for substituting variables during reification. enum SubstitutionPolicy { /// Free variables are substituted by errors. @@ -10,35 +10,80 @@ struct SubstitutionMap { /// Free variables are kept. case kept + /// Returns the application of this policy to `t`. + fileprivate func callAsFunction(_ t: TypeVariable) -> AnyType { + switch self { + case .substitutedByError: + return .error + case .kept: + return ^t + } + } + + /// Returns the application of this policy to `t`. + fileprivate func callAsFunction(_ t: TermVariable) -> AnyTerm { + switch self { + case .substitutedByError: + return .error + case .kept: + return ^t + } + } + } - /// The internal storage of a substitution table. - typealias Storage = [TypeVariable: AnyType] + /// A map from type variable to its assignment. + private(set) var types: [TypeVariable: AnyType] - /// The internal storage of the map. - private(set) var storage: Storage = [:] + /// A map from term variable to its assignment. + private(set) var terms: [TermVariable: AnyTerm] /// Creates an empty substitution map. - init() {} + init() { + self.init(types: [:], terms: [:]) + } + + /// Creates an instance with the given properties. + private init(types: [TypeVariable: AnyType], terms: [TermVariable: AnyTerm]) { + self.types = types + self.terms = terms + } /// Returns a copy of this instance with its internal representation optimized. func optimized() -> Self { - var result = SubstitutionMap() - result.storage = storage.mapValues({ self[$0] }) - return result + .init( + types: types.mapValues({ self[$0] }), + terms: terms.mapValues({ self[$0] })) } - /// Returns the substitution for `variable`, if any. - subscript(variable: TypeVariable) -> AnyType? { - storage[walk(variable)] + /// Returns the substitution for `v`, if any. + subscript(v: TypeVariable) -> AnyType? { + types[walk(v)] } - /// Returns the substitution for `type` if it is a variable to which a type is assigned in this - /// map; returns `type` otherwise. - subscript(type: AnyType) -> AnyType { - var walked = type + /// Returns the substitution of `t` in this map or `t` is no such substitution exists. + subscript(t: AnyType) -> AnyType { + var walked = t while let a = TypeVariable(walked) { - if let b = storage[a] { + if let b = types[a] { + walked = b + } else { + break + } + } + return walked + } + + /// Returns the substitution for `v`, if any. + subscript(v: TermVariable) -> AnyTerm? { + terms[walk(v)] + } + + /// Returns the substitution of `t` in this map or `t` is no such substitution exists. + subscript(t: AnyTerm) -> AnyTerm { + var walked = t + while let a = TermVariable(walked) { + if let b = terms[a] { walked = b } else { break @@ -50,7 +95,7 @@ struct SubstitutionMap { /// Assigns `substitution` to `variable`. mutating func assign(_ substitution: AnyType, to variable: TypeVariable) { var walked = variable - while let a = storage[walked] { + while let a = types[walked] { guard let b = TypeVariable(a) else { precondition(a == substitution, "'\(variable)' already bound to '\(a)'") return @@ -61,13 +106,33 @@ struct SubstitutionMap { precondition( !occurCheck(walked, substitution), "illegal substitution: '\(walked)' for '\(substitution)'") - storage[walked] = substitution + types[walked] = substitution + } + + /// Assigns `substitution` to `variable`. + mutating func assign(_ substitution: AnyTerm, to variable: TermVariable) { + var walked = variable + while let a = terms[walked] { + guard let b = TermVariable(a) else { + precondition(a == substitution, "'\(variable)' already bound to '\(a)'") + return + } + walked = b + } + terms[walked] = substitution } /// Returns the type variable representing the equivalence class of `v` in `self`. private func walk(_ v: TypeVariable) -> TypeVariable { var w = v - while let a = TypeVariable(storage[w]) { w = a } + while let a = TypeVariable(types[w]) { w = a } + return w + } + + /// Returns the term variable representing the equivalence class of `v` in `self`. + private func walk(_ v: TermVariable) -> TermVariable { + var w = v + while let a = TermVariable(terms[w]) { w = a } return w } @@ -81,8 +146,8 @@ struct SubstitutionMap { return occurs } - /// Substitutes each type variable occurring in `type` by its corresponding substitution in `self`, - /// apply `substitutionPolicy` to deal with free variables. + /// Returns a copy of `type` where each variable is replaced by its substitution in `self` or the + /// application of `substitutionPolicy` is no such substitution exists. /// /// The default substitution policy is `substituteByError` because we typically use `reify` after /// having built a complete solution and therefore don't expect its result to still contain open @@ -90,100 +155,101 @@ struct SubstitutionMap { func reify( _ type: AnyType, withVariables substitutionPolicy: SubstitutionPolicy = .substitutedByError ) -> AnyType { - return type.transform(transform(type:)) - - func transform(type: AnyType) -> TypeTransformAction { - if type.base is TypeVariable { - let walked = self[type] - - // Substitute `walked` for `type`. - if walked.base is TypeVariable { - switch substitutionPolicy { - case .substitutedByError: - return .stepOver(.error) - case .kept: - return .stepOver(type) - } + type.transform { (t: AnyType) -> TypeTransformAction in + switch t.base { + case let u as BufferType: + let n = reify(u.count, withVariables: substitutionPolicy) + return .stepInto(^BufferType(u.element, n)) + + case _ where t[.hasVariable]: + let walked = self[t] + if let w = TypeVariable(walked) { + return .stepOver(substitutionPolicy(w)) } else { return .stepInto(walked) } - } else if !type[.hasVariable] { + + default: // Nothing to do if the type doesn't contain any variable. - return .stepOver(type) - } else { - // Recursively visit other types. - return .stepInto(type) + return .stepOver(t) } } } - /// Returns `r` where each type variable occurring in its generic arguments of `r` are replaced by - /// their corresponding value in `self`, applying `substitutionPolicy` to handle free variables. + /// Returns a copy of `term` where each variable is replaced by its substitution in `self` or the + /// application of `substitutionPolicy` is no such substitution exists. func reify( - _ r: DeclReference, withVariables substitutionPolicy: SubstitutionPolicy + _ term: AnyTerm, withVariables substitutionPolicy: SubstitutionPolicy + ) -> AnyTerm { + let walked = self[term] + return TermVariable(walked).map({ (w) in substitutionPolicy(w) }) ?? walked + } + + /// Returns a copy of `r` where each generic argument is replaced by the result of applying + /// `reify(withVariables:)` on it. + func reify( + reference r: DeclReference, withVariables substitutionPolicy: SubstitutionPolicy ) -> DeclReference { switch r { case .direct(let d, let a): - return .direct(d, reify(a, withVariables: substitutionPolicy)) + return .direct(d, reify(argument: a, withVariables: substitutionPolicy)) case .member(let d, let a, let r): - return .member(d, reify(a, withVariables: substitutionPolicy), r) + return .member(d, reify(argument: a, withVariables: substitutionPolicy), r) case .constructor(let d, let a): - return .constructor(d, reify(a, withVariables: substitutionPolicy)) + return .constructor(d, reify(argument: a, withVariables: substitutionPolicy)) case .builtinModule, .builtinType, .builtinFunction, .compilerKnownType: return r } } - /// Returns `a` with its type variables replaced by their their corresponding value in `self`, - /// applying `substitutionPolicy` to handle free variables. + /// Returns a copy of `a` where each variable is replaced by its substitution value in `self` or + /// the application `substitutionPolicy` is no such substitution exists. private func reify( - _ a: GenericArguments, withVariables substitutionPolicy: SubstitutionPolicy + argument a: GenericArguments, withVariables substitutionPolicy: SubstitutionPolicy ) -> GenericArguments { a.mapValues({ reify(value: $0, withVariables: substitutionPolicy) }) } - /// Returns `v` with its type variables replaced by their their corresponding value in `self`, - /// applying `substitutionPolicy` to handle free variables. + /// Returns a copy of `v` where each variable is replaced by its substitution value in `self` or + /// the application `substitutionPolicy` is no such substitution exists. private func reify( value v: CompileTimeValue, withVariables substitutionPolicy: SubstitutionPolicy ) -> CompileTimeValue { - if case .type(let t) = v { + switch v { + case .type(let t): return .type(reify(t, withVariables: substitutionPolicy)) - } else { - return v + case .term(let t): + return .term(reify(t, withVariables: substitutionPolicy)) } } /// Removes the key/value pairs in `self` that are not also in `other`. mutating func formIntersection(_ other: Self) { - for (key, lhs) in storage { - storage[key] = (lhs == other[key]) ? lhs : nil - } + self = self.intersection(other) } /// Returns a new substitution map containing the key/value pairs common to `self` and `other`. func intersection(_ other: Self) -> Self { var result = SubstitutionMap() - for (key, lhs) in storage { - result.storage[key] = (lhs == other[key]) ? lhs : nil + result.types.reserveCapacity(types.capacity) + result.terms.reserveCapacity(terms.capacity) + for (key, lhs) in types { + result.types[key] = (lhs == other[key]) ? lhs : nil } - return result - } - -} - -extension SubstitutionMap: ExpressibleByDictionaryLiteral { - - init(dictionaryLiteral elements: (TypeVariable, AnyType)...) { - for (k, v) in elements { - self.assign(v, to: k) + for (key, lhs) in terms { + result.terms[key] = (lhs == other[key]) ? lhs : nil } + return result } } extension SubstitutionMap: CustomStringConvertible { - var description: String { String(describing: storage) } + var description: String { + let ts = types.map({ "\($0.key): \($0.value)" }) + let us = terms.map({ "\($0.key): \($0.value)" }) + return "[\(list: ts + us)]" + } } diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift b/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift index e4f55ba56..1762de29c 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift @@ -19,6 +19,10 @@ extension Diagnostic { .error("declaration of \(a) binding requires an initializer", at: site) } + static func error(invalidOptionPattern p: OptionPattern.ID, in ast: AST) -> Diagnostic { + .error("optional pattern may only be used as a condition", at: ast[p].site) + } + static func error(circularRefinementAt site: SourceRange) -> Diagnostic { .error("circular trait refinement", at: site) } @@ -27,6 +31,10 @@ extension Diagnostic { .error("circular dependency", at: site) } + static func error(cannotConstructRequirementSystem d: AnyDeclID, in ast: AST) -> Diagnostic { + .error("requirement system too complex to build", at: ast.siteForDiagnostics(about: d)) + } + static func error(cannotConstructTrait t: TraitType, at site: SourceRange) -> Diagnostic { .error("cannot construct an instance of trait '\(t)'; did you mean 'any \(t)'?", at: site) } @@ -94,6 +102,12 @@ extension Diagnostic { .error("incompatible types '\(l)' and '\(r)'", at: site) } + static func error( + expected: AnyType, found: AnyType, at site: SourceRange + ) -> Diagnostic { + .error("expected type '\(expected)' but found '\(found)'", at: site) + } + static func error(invalidDestructuringOfType type: AnyType, at site: SourceRange) -> Diagnostic { .error("invalid destructuring of type '\(type)'", at: site) } @@ -234,10 +248,6 @@ extension Diagnostic { .error("non-generic type '\(type)' has no generic parameters", at: site) } - static func error(tooManyAnnotationsOnGenericValueParametersAt site: SourceRange) -> Diagnostic { - .error("only one annotation is allowed on generic value parameter declarations", at: site) - } - static func error( invalidBufferTypeExprArgumentCount e: SubscriptCallExpr.ID, in ast: AST ) -> Diagnostic { @@ -359,6 +369,10 @@ extension Diagnostic { } } + static func error(fallbackBranchCannotFallThroughAt site: SourceRange) -> Diagnostic { + .error("fallback branch of conditional binding cannot fall through", at: site) + } + static func error( referenceTo d: SourceRepresentable, requires t: AnyType, conformsTo u: TraitType, dueToConstraintAt constraintSite: SourceRange diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 587e66260..71ae9d744 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -20,10 +20,14 @@ struct TypeChecker { /// The representation under construction. private var cache: Cache - /// A closure that takes a node and its containing program, and returns `true` if a trace of type - /// inference should be logged on the console for that node. + /// A closure that accepts a node with its containing program and returns `true` if a trace of + /// type inference should be logged on the console for that node. private let shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? + /// A closure that accepts a generic declaration with its containing program and returns `true` + /// if the requirement system of its environment should be logged on the console. + private let shouldLogRequirementSystem: ((AnyDeclID, TypedProgram) -> Bool)? + /// The local copy of the program being type checked. var program: TypedProgram { cache.local @@ -34,16 +38,19 @@ struct TypeChecker { self.identifier = 0 self.cache = Cache(local: p) self.shouldTraceInference = nil + self.shouldLogRequirementSystem = nil } /// Creates an instance for constructing `instanceUnderConstruction`. init( constructing instanceUnderConstruction: TypedProgram, - tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? + tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)?, + loggingRequirementSystemIf shouldLogRequirements: ((AnyDeclID, TypedProgram) -> Bool)? ) { self.identifier = 0 self.cache = Cache(local: instanceUnderConstruction) self.shouldTraceInference = shouldTraceInference + self.shouldLogRequirementSystem = shouldLogRequirements } /// Creates an instance with given `identifier` for constructing `instanceUnderConstruction` @@ -53,13 +60,15 @@ struct TypeChecker { init( _ identifier: UInt8, collaborativelyConstructing instanceUnderConstruction: SharedMutable, - tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? + tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)?, + loggingRequirementSystemIf shouldLogRequirements: ((AnyDeclID, TypedProgram) -> Bool)? ) { self.identifier = identifier self.nextFreshVariableIdentifier = UInt64(identifier) << 56 self.cache = instanceUnderConstruction.read( applying: { Cache(local: $0, shared: instanceUnderConstruction) }) self.shouldTraceInference = shouldTraceInference + self.shouldLogRequirementSystem = shouldLogRequirements } /// Reports the given diagnostic. @@ -76,23 +85,38 @@ struct TypeChecker { /// Returns the canonical form of `t` in `scopeOfUse`. mutating func canonical(_ t: AnyType, in scopeOfUse: AnyScopeID) -> AnyType { - if t[.isCanonical] { return t } + if t.isCanonical { return t } + let r: AnyType switch t.base { + case let u as AssociatedTypeType: + r = canonical(u, in: scopeOfUse) case let u as BoundGenericType: - return canonical(u, in: scopeOfUse) + r = canonical(u, in: scopeOfUse) case let u as TypeAliasType: - return canonical(u.aliasee.value, in: scopeOfUse) + r = canonical(u.aliasee.value, in: scopeOfUse) case let u as UnionType: - return canonical(u, in: scopeOfUse) + r = canonical(u, in: scopeOfUse) default: - return t.transformParts(mutating: &self, { .stepOver($0.canonical($1, in: scopeOfUse)) }) + r = t.transformParts(mutating: &self, { .stepOver($0.canonical($1, in: scopeOfUse)) }) + } + + assert(r.isCanonical) + return r + } + + /// Returns the canonical form of `t` in `scopeOfUse`. + private mutating func canonical(_ t: AssociatedTypeType, in scopeOfUse: AnyScopeID) -> AnyType { + if let u = demandImplementation(of: t.decl, for: t.domain, in: scopeOfUse) { + return canonical(u, in: scopeOfUse) + } else { + return .error } } /// Returns the canonical form of `t` in `scopeOfUse`. private mutating func canonical(_ t: BoundGenericType, in scopeOfUse: AnyScopeID) -> AnyType { - if t[.isCanonical] { return ^t } + if t.isCanonical { return ^t } let b = canonical(t.base, in: scopeOfUse) let a = canonical(GenericArguments(t), in: scopeOfUse) @@ -102,7 +126,7 @@ struct TypeChecker { /// Returns the canonical form of `t` in `scopeOfUse`. private mutating func canonical(_ t: UnionType, in scopeOfUse: AnyScopeID) -> AnyType { - if t[.isCanonical] { return ^t } + if t.isCanonical { return ^t } var elements = Set() for e in t.elements { @@ -131,24 +155,18 @@ struct TypeChecker { /// Returns `true` iff `t` and `u` are semantically equivalent in `scopeOfUse`. mutating func areEquivalent(_ t: AnyType, _ u: AnyType, in scopeOfUse: AnyScopeID) -> Bool { - if let a = GenericTypeParameterType(t) { - return areEquivalent(a, u, in: scopeOfUse) - } else if !t[.isCanonical] { - return areEquivalent(u, canonical(t, in: scopeOfUse), in: scopeOfUse) + let lhs = canonical(t, in: scopeOfUse) + let rhs = canonical(u, in: scopeOfUse) + + if lhs == rhs { + return true + } else if let s = program.scopes(from: scopeOfUse).first(where: \.isGenericScope) { + return environment(of: s)!.areEquivalent(lhs, rhs, querying: &self) } else { - return t == canonical(u, in: scopeOfUse) + return false } } - /// Returns `true` iff `t` and `u` are semantically equivalent in `scopeOfUse`. - mutating func areEquivalent( - _ t: GenericTypeParameterType, _ u: AnyType, in scopeOfUse: AnyScopeID - ) -> Bool { - let b = canonical(u, in: scopeOfUse) - let e = environment(of: program[t.decl].scope)! - return e.areEquivalent(^t, b) || (t == u) - } - /// Returns `true` iff `t` is a refinement of `u` and `t != u`. mutating func isStrictRefinement(_ t: TraitType, of u: TraitType) -> Bool { (t != u) && bases(of: t).contains(u) @@ -252,7 +270,7 @@ struct TypeChecker { let d = TraitDecl.ID(program[base.decl].scope)! let e = possiblyPartiallyFormedEnvironment(of: AnyDeclID(d))! - accumulator.formUnion(e.conformedTraits(of: definition)) + accumulator.formUnion(e.conformedTraits(of: ^base, querying: &self)) // Gather the conformances declared as associated type constraints in the traits defining the // domain of `t`. @@ -297,7 +315,7 @@ struct TypeChecker { result = bases(of: TraitType(d, ast: program.ast)).unordered } else if !isNotionallyContained(scopeOfUse, in: scopeOfDeclaration) { let e = possiblyPartiallyFormedEnvironment(of: AnyDeclID(scopeOfDeclaration)!)! - result = e.conformedTraits(of: ^t) + result = Set(e.conformedTraits(of: ^t, querying: &self)) } else { result = [] } @@ -359,18 +377,29 @@ struct TypeChecker { ) -> Set { if let s = program.scopes(from: scopeOfUse).first(where: \.isGenericScope) { let e = possiblyPartiallyFormedEnvironment(of: AnyDeclID(s)!)! - return e.conformedTraits(of: ^t) + return Set(e.conformedTraits(of: ^t, querying: &self)) } else { return [] } } - /// Returns `true` if `model` conforms to `trait` explicitly or structurally in `scopeOfUse`. + /// Returns `true` if `m` conforms to `c` explicitly or structurally in `scopeOfUse`. + /// + /// See also `explicitlyConforms` and `structurallyConforms`. mutating func conforms( + _ m: AnyType, to c: TraitType, in scopeOfUse: AnyScopeID + ) -> Bool { + explicitlyConforms(m, to: c, in: scopeOfUse) || structurallyConforms(m, to: c, in: scopeOfUse) + } + + /// Returns `true` if `model` conforms to `trait` explicitly in `scopeOfUse`. + /// + /// A conformance is explicit if it has been declared in sources, or if it is the consequence of + /// generic requirements written in sources. + private mutating func explicitlyConforms( _ model: AnyType, to trait: TraitType, in scopeOfUse: AnyScopeID ) -> Bool { conformedTraits(of: model, in: scopeOfUse).contains(trait) - || structurallyConforms(model, to: trait, in: scopeOfUse) } /// Returns `true` if `model` structurally conforms to `trait` in `scopeOfUse`. @@ -405,49 +434,51 @@ struct TypeChecker { } } - /// Returns `true` if a conformance of `model` to `trait` is synthesizable in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. /// /// A conformance *M: T* is synthesizable iff *M* structurally conforms to *T*. private mutating func canSynthesizeConformance( - _ model: AnyType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: AnyType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { switch model.base { case let m as BoundGenericType: - return canSynthesizeConformance(m, to: trait, in: scopeOfUse) + return canSynthesizeConformance(m, to: trait, in: scopeOfImplementation) case let m as ProductType: - return canSynthesizeConformance(m, to: trait, in: scopeOfUse) + return canSynthesizeConformance(m, to: trait, in: scopeOfImplementation) default: - return structurallyConforms(model, to: trait, in: scopeOfUse) + return structurallyConforms(model, to: trait, in: scopeOfImplementation) } } - /// Returns `true` if `model` structurally conforms to `trait` in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. private mutating func canSynthesizeConformance( - _ model: BoundGenericType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: BoundGenericType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { - let base = canonical(model.base, in: scopeOfUse) + let base = canonical(model.base, in: scopeOfImplementation) let z = GenericArguments(model) // If the base is a product type, we specialize each stored part individually to check whether // the conformance holds for a specialized whole. Othwrwise, we specialize the base directly. if let b = ProductType(base) { - let s = AnyScopeID(b.decl) return program.storedParts(of: b.decl).allSatisfy { (p) in - let t = specialize(uncheckedType(of: p), for: z, in: s) - return conforms(t, to: trait, in: s) + let t = specialize(uncheckedType(of: p), for: z, in: scopeOfImplementation) + return conforms(t, to: trait, in: scopeOfImplementation) } } else { - let t = specialize(base, for: z, in: scopeOfUse) - return canSynthesizeConformance(t, to: trait, in: scopeOfUse) + let t = specialize(base, for: z, in: scopeOfImplementation) + return canSynthesizeConformance(t, to: trait, in: scopeOfImplementation) } } - /// Returns `true` if `model` structurally conforms to `trait` in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. private mutating func canSynthesizeConformance( - _ model: ProductType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: ProductType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { program.storedParts(of: model.decl).allSatisfy { (p) in - conforms(uncheckedType(of: p), to: trait, in: scopeOfUse) + conforms(uncheckedType(of: p), to: trait, in: scopeOfImplementation) } } @@ -481,7 +512,7 @@ struct TypeChecker { func cachedConformance( of model: AnyType, to trait: TraitType, exposedTo scopeOfUse: AnyScopeID ) -> Conformance? { - assert(model[.isCanonical]) + assert(model.isCanonical) // `A: T` iff `A: T`. if let t = BoundGenericType(model) { @@ -569,7 +600,7 @@ struct TypeChecker { var result: [ConformanceOrigin] = [] let s = evalTraitComposition(program[d].conformances) for (n, t) in s { - if conformedTraits(of: ^t, in: scopeOfUse).contains(trait) { + if explicitlyConforms(^t, to: trait, in: scopeOfUse) { result.append(.init(d, at: program[n].site)) } } @@ -590,15 +621,18 @@ struct TypeChecker { return result } - /// Returns the type implementing requirement `r` for the model `m` in `scopeOfUse`, or `nil` if - /// `m` does not implement `r`. + /// Returns the type implementing `requirement` for `model` in `scopeOfUse`, or `nil` if `model` + /// does not implement `requirement`. private mutating func demandImplementation( - of r: AssociatedTypeDecl.ID, for m: AnyType, in scopeOfUse: AnyScopeID + of requirement: AssociatedTypeDecl.ID, for model: AnyType, in scopeOfUse: AnyScopeID ) -> AnyType? { - if let c = demandConformance(of: m, to: traitDeclaring(r)!, exposedTo: scopeOfUse) { - return demandImplementation(of: r, in: c) + let p = traitDeclaring(requirement)! + let m = canonical(model, in: scopeOfUse) + + if let c = demandConformance(of: m, to: p, exposedTo: scopeOfUse) { + return demandImplementation(of: requirement, in: c) } else if m.base is GenericTypeParameterType { - return ^AssociatedTypeType(r, domain: m, ast: program.ast) + return ^AssociatedTypeType(requirement, domain: m, ast: program.ast) } else { return nil } @@ -787,6 +821,11 @@ struct TypeChecker { check(program.ast.modules) } + /// Type checks `m`. + mutating func checkModule(_ m: ModuleDecl.ID) { + check(m) + } + /// Type checks the sources in `batch`. mutating func check>(_ batch: S) { for u in batch { check(u) } @@ -913,11 +952,10 @@ struct TypeChecker { checkEnvironment(of: d) checkParameters(program[d].parameters, of: d) - let funcAttr = FunctionAttributes( + let a = FunctionAttributes( externalName: program[d].isExternal ? program.ast.externalName(of: d) : nil, - foreignName: program[d].isForeignInterface ? program.ast.foreignName(of: d) : nil - ) - cache.write(funcAttr, at: \.functionAttributes[d], ignoringSharedCache: ignoreSharedCache) + foreignName: program[d].isForeignInterface ? program.ast.foreignName(of: d) : nil) + cache.write(a, at: \.functionAttributes[d], ignoringSharedCache: ignoreSharedCache) switch program[d].body { case .block(let b): @@ -1148,7 +1186,7 @@ struct TypeChecker { // Note: in theory, we should make sure this update is monotonic. In practice, such a test // would require prohibitively expensive structural comparisons. modify(&cache.uncheckedType[d]!) { (u) in - let v = solution.typeAssumptions.reify(u.computed!) + let v = solution.substitutions.reify(u.computed!) u = .computed(v) return v } @@ -1197,6 +1235,8 @@ struct TypeChecker { check(BraceStmt.ID(s)!) case BreakStmt.self: break + case ConditionalBindingStmt.self: + check(ConditionalBindingStmt.ID(s)!) case ConditionalCompilationStmt.self: check(ConditionalCompilationStmt.ID(s)!) case ConditionalStmt.self: @@ -1247,6 +1287,19 @@ struct TypeChecker { discharge(obligations, relatedTo: s) } + /// Type checks `s`. + private mutating func check(_ s: ConditionalBindingStmt.ID) { + checkedType(of: program[s].binding, usedAs: .condition, ignoringSharedCache: true) + + let f = program.ast[s].fallback + check(f) + + if canFallThrough(f) { + let site = program[f].stmts.last.map({ (l) in program[l].site }) ?? program[f].site + report(.error(fallbackBranchCannotFallThroughAt: site)) + } + } + /// Type checks `s`. private mutating func check(_ s: ConditionalCompilationStmt.ID) { for t in program.ast[s].expansion(for: program.ast.compilationConditions) { check(t) } @@ -1346,14 +1399,16 @@ struct TypeChecker { } /// Returns the modules visible as imports in `u`. + /// + /// The returned set contains the modules declared as explicit imports at the top of `u` along + /// with the standard library and the module containing `u`. private mutating func imports(exposedTo u: TranslationUnit.ID) -> Set { if let result = cache.read(\.imports[u]) { return result } // The core library and the containing module are always implicitly imported. - var result = Set() - result.insert(ModuleDecl.ID(program[u].scope)!) + var result: Set = [ModuleDecl.ID(program[u].scope)!] if let m = program.ast.coreLibrary { result.insert(m) } @@ -1383,21 +1438,30 @@ struct TypeChecker { /// Builds and type checks the generic environment of `d`. private mutating func checkEnvironment(of d: T.ID) { // TODO: Type check default values - _ = environment(of: d) + let e = environment(of: d) check(program[d].genericParameters) + if let f = shouldLogRequirementSystem, f(AnyDeclID(d), program) { + print(program.describe(e.requirements)) + } } /// Builds and type checks the generic environment of `d`. private mutating func checkEnvironment(of d: TraitDecl.ID) { // TODO: Type check default values - _ = environment(of: d) + let e = environment(of: d) check(program[d].genericParameters) + if let f = shouldLogRequirementSystem, f(AnyDeclID(d), program) { + print(program.describe(e.requirements)) + } } /// Builds and type checks the generic environment of `d`. private mutating func checkEnvironment(of d: T.ID) { // TODO: Type check default values - _ = environment(of: d) + let e = environment(of: d) + if let f = shouldLogRequirementSystem, f(AnyDeclID(d), program) { + print(program.describe(e.requirements)) + } } /// Type checks the conformances declared by `d`. @@ -1546,9 +1610,10 @@ struct TypeChecker { /// Checks whether the constraints on the requirements of `trait` are satisfied by `model` in /// `scopeOfuse`, reporting diagnostics in `conformanceDiagnostics`. func checkRequirementConstraints() -> Bool { - var obligations = ProofObligations(scope: scopeOfDefinition) - let e = environment(of: trait.decl) + var obligations = ProofObligations( + scope: AnyScopeID(origin.source) ?? program[origin.source].scope) + for g in e.constraints { let c = specialize( g, for: traitReceiverToModel, in: scopeOfDefinition, @@ -1619,16 +1684,19 @@ struct TypeChecker { func syntheticImplementation( of requirement: AnyDeclID, withAPI expectedAPI: API ) -> SynthesizedFunctionDecl? { - guard + let scopeOfImplementation = AnyScopeID(origin.source)! + if let k = program.ast.synthesizedKind(of: requirement), - canSynthesizeConformance(model, to: trait, in: scopeOfDefinition) - else { return nil } - - let t = ArrowType(expectedAPI.type)! - let h = Array(t.environment.skolems) + canSynthesizeConformance(model, to: trait, in: scopeOfImplementation) + { + let t = ArrowType(expectedAPI.type)! + let h = Array(t.environment.skolems) - // Note: compiler-known requirement is assumed to be well-typed. - return .init(k, typed: t, parameterizedBy: h, in: AnyScopeID(origin.source)!) + // Note: compiler-known requirement is assumed to be well-typed. + return .init(k, typed: t, parameterizedBy: h, in: scopeOfImplementation) + } else { + return nil + } } /// Returns a concrete implementation of `requirement` for `model` with given `expectedAPI`, @@ -1755,7 +1823,7 @@ struct TypeChecker { return } - assert(expectedType[.isCanonical] && b.type[.isCanonical]) + assert(expectedType.isCanonical && b.type.isCanonical) // TODO: Use semantic equality if program[d].isDefinition && (b.type == expectedType) { s.append(AnyDeclID(d)) @@ -1828,7 +1896,7 @@ struct TypeChecker { var obligations = ProofObligations(scope: program[d].scope) let t = inferredType(of: d, usedAs: purpose, updating: &obligations) let s = discharge(obligations, relatedTo: d) - let u = s.typeAssumptions.reify(t) + let u = s.substitutions.reify(t) cache.write(s.isSound ? u : .error, at: \.declType[d], ignoringSharedCache: ignoreSharedCache) return u } @@ -1837,7 +1905,7 @@ struct TypeChecker { private mutating func checkedType(of e: AnyExprID, withHint hint: AnyType? = nil) -> AnyType { let (incompleteType, obligations) = partiallyCheckedType(of: e, withHint: hint) let s = discharge(obligations, relatedTo: e) - return s.typeAssumptions.reify(incompleteType) + return s.substitutions.reify(incompleteType) } /// Type checks `e` as an argument to `p` and returns its type. @@ -1848,7 +1916,7 @@ struct TypeChecker { obligations.insert( ParameterConstraint(incompleteType, ^p, origin: .init(.argument, at: program[e].site))) let s = discharge(obligations, relatedTo: e) - return s.typeAssumptions.reify(incompleteType) + return s.substitutions.reify(incompleteType) } /// Returns the inferred type of `e`, along with a set of goals to solve to check that type. @@ -1864,7 +1932,6 @@ struct TypeChecker { /// /// A scope `s` inherits from a generic environment `g` if: /// - `s` is notionally contained in `g`; and/or - /// - `s` declares a trait `t` and `g` is the environment of a trait refined by `t`; and/or /// - `s` is an extension and `g` is the environment of the type extended by `s`. private mutating func forEachGenericParentScope( inheritedBy s: AnyScopeID, @@ -1877,11 +1944,6 @@ struct TypeChecker { if let e = scopeExtended(by: ConformanceDecl.ID(s)!) { action(&self, e) } case ExtensionDecl.self: if let e = scopeExtended(by: ExtensionDecl.ID(s)!) { action(&self, e) } - case TraitDecl.self: - let t = TraitType(TraitDecl.ID(s)!, ast: program.ast) - for u in bases(of: t).unordered where u != t { - action(&self, AnyScopeID(u.decl)) - } default: break } @@ -1938,8 +2000,9 @@ struct TypeChecker { guard !bounds.isEmpty, let t = MetatypeType(lhs) else { continue } for (n, u) in evalTraitComposition(bounds) { - let c = GenericConstraint(.conformance(t.instance, u), at: program[n].site) - insertConstraint(c, in: &partialResult) + partialResult.registerConstraint(.init(.conformance(t.instance, u), at: program[n].site)) + insertConformanceRequirement(t.instance, u, in: &partialResult) + cache.partiallyFormedEnvironment[d] = partialResult } } @@ -1954,31 +2017,81 @@ struct TypeChecker { /// Returns the generic environment introduced by `d`. private mutating func environment(of d: TraitDecl.ID) -> GenericEnvironment { + // Environment already created? if let e = cache.read(\.environment[d]) { return e } + let t = TraitType(uncheckedType(of: d))! - let r = program[d].receiver.id - var partialResult = initialEnvironment(of: d, introducing: [r]) + // Create a partially formed environment to break cycles in general name resolution. + var partialResult = GenericEnvironment(of: AnyDeclID(d), introducing: [program[d].receiver.id]) + cache.partiallyFormedEnvironment[d] = partialResult - // Synthesize `Self: T`. - let s = GenericTypeParameterType(selfParameterOf: d, in: program.ast) - cache.write(^MetatypeType(of: s), at: \.declType[r], ignoringSharedCache: true) + // Recursive constraints involving more than a single trait are not supported yet. + let c = traitComponent(containing: t) + if cache.traitDependencyComponents.vertices(in: c).count > 1 { + report(.error(circularRefinementAt: program[d].introducerSite)) + return commit(partialResult) + } - let t = TraitType(uncheckedType(of: d))! - let c = GenericConstraint(.conformance(^s, t), at: program[d].identifier.site) - insertConstraint(c, in: &partialResult) + // Gather the requirements defined by trait dependencies, ignoring those on generic parameters + // not introduced nor captured by the environment. + partialResult.registerDependencies(dependencies(of: d)) + for u in partialResult.dependencies where u != t { + for r in environment(of: u.decl).publicRules { + if !r.parametersAreContained(in: partialResult.parameters) { continue } + insertRequirement(r, in: &partialResult) + } + } + + // Requirements gathered so far are not inherited by dependent environments. + partialResult.markRequirementsAsInherited() + + generatePublicRequirements(of: t, in: &partialResult) + return commit(partialResult) + } - for m in program[d].members { + /// Inserts the public requirements introduced by the generic signature of `t` in its partially + /// formed environment. + private mutating func generatePublicRequirements( + of t: TraitType, + in partiallyFormedEnvironment: inout GenericEnvironment + ) { + // Synthesize `Self: T`. + let s = GenericTypeParameterType(selfParameterOf: t.decl, in: program.ast) + partiallyFormedEnvironment.registerConstraint( + .init(.conformance(^s, t), at: program[t.decl].identifier.site)) + insertRequirement( + RequirementRule([.trait(t.decl), .trait(t.decl)], [.trait(t.decl)]), + in: &partiallyFormedEnvironment) + insertRequirement( + RequirementRule([.parameterType(s.decl), .trait(t.decl)], [.parameterType(s.decl)]), + in: &partiallyFormedEnvironment) + + // Traits are never nested in generic scopes. Their dependencies and parameters are defined by + // the traits that they refine. + for u in bases(of: t).unordered where u != t { + let e = environment(of: u.decl) + partiallyFormedEnvironment.registerConstraints(e.constraints) + let v = RequirementTerm([.parameterType(program[u.decl].receiver.id)]) + for r in e.publicRules { + insertRequirement( + r.substituting(v, for: [.parameterType(s.decl)]), + in: &partiallyFormedEnvironment) + } + } + + // Name resolution can use `Self: T` and its consequences to form additional constraints. + cache.partiallyFormedEnvironment[t.decl] = partiallyFormedEnvironment + + for m in program[t.decl].members { switch m.kind { case AssociatedTypeDecl.self: - insertConstraints(of: AssociatedTypeDecl.ID(m)!, in: &partialResult) + insertConstraints(of: AssociatedTypeDecl.ID(m)!, in: &partiallyFormedEnvironment) case AssociatedValueDecl.self: - insertConstraints(of: AssociatedValueDecl.ID(m)!, in: &partialResult) + insertConstraints(of: AssociatedValueDecl.ID(m)!, in: &partiallyFormedEnvironment) default: continue } } - - return commit(partialResult) } /// Returns the generic environment introduced by `d`. @@ -1993,20 +2106,6 @@ struct TypeChecker { return commit(partialResult) } - /// Returns the generic environment introduced by the declaration of `t`, if any. - private mutating func environment(introducedByDeclOf t: AnyType) -> GenericEnvironment? { - switch t.base { - case let u as GenericTypeParameterType: - return environment(of: program[u.decl].scope) - case let u as ProductType: - return environment(of: u.decl) - case let u as TypeAliasType: - return environment(of: u.decl) - default: - return nil - } - } - /// Creates the partially formed generic environment of `d`, which introduces `parameters`, /// initialized with constraints inherited from the enclosing generic environment. private mutating func initialEnvironment( @@ -2016,9 +2115,28 @@ struct TypeChecker { return e } + // Create a partially formed environment to break cycles in general name resolution. var e = GenericEnvironment(of: AnyDeclID(d), introducing: parameters) + cache.partiallyFormedEnvironment[d] = e + e.registerDependencies(dependencies(of: d)) + e.markRequirementsAsInherited() + + // Gather the parameters and requirements defined by generic parent scopes. forEachGenericParentScope(inheritedBy: AnyScopeID(d)!) { (me, p) in - e.extend(me.environment(of: p)!, in: me.program.ast) + let inheritedEnvironment = me.environment(of: p)! + e.registerInheritance(inheritedEnvironment) + for r in inheritedEnvironment.publicRules { + me.insertRequirement(r, in: &e) + } + } + + // Gather the requirements defined by trait dependencies, ignoring those on generic parameters + // not introduced nor captured by the environment. + for u in e.dependencies where u.decl.rawValue != d.rawValue { + for r in environment(of: u.decl).publicRules { + if !r.parametersAreContained(in: e.parameters) { continue } + insertRequirement(r, in: &e) + } } cache.partiallyFormedEnvironment[d] = e @@ -2038,23 +2156,16 @@ struct TypeChecker { } } - /// Ensures `e` is consistent and writes it to the cache. - private mutating func commit(_ finalResult: GenericEnvironment) -> GenericEnvironment { - cache.partiallyFormedEnvironment[finalResult.decl] = nil - cache.write(finalResult, at: \.environment[finalResult.decl]) - return finalResult - } - - /// Returns the generic environment introduced by `m` or its immediate parent if `m` is a variant - /// in a bundled declaration. - private mutating func memberEnvironment(of m: AnyDeclID) -> GenericEnvironment? { - if (m.kind == MethodImpl.self) || (m.kind == SubscriptImpl.self) { - return environment(of: program[m].scope) - } else if let s = AnyScopeID(m) { - return environment(of: s) - } else { - return nil + /// Writes `finalResult` to the cache. + private mutating func commit(_ finalResult: consuming GenericEnvironment) -> GenericEnvironment { + var e = finalResult + if !e.requirements.complete(orderingTermsWith: { (a, b) in compareOrder(a, b) }) { + report(.error(cannotConstructRequirementSystem: e.decl, in: program.ast)) } + + cache.partiallyFormedEnvironment[e.decl] = nil + cache.write(e, at: \.environment[e.decl]) + return e } /// Inserts `d`'s constraints in `e`. @@ -2063,7 +2174,15 @@ struct TypeChecker { private mutating func insertConstraints( of d: AssociatedTypeDecl.ID, in e: inout GenericEnvironment ) { - insertAnnotatedConstraints(on: d, in: &e) + // Synthesize sugared conformance constraint, if any. + if let lhs = MetatypeType(uncheckedType(of: d))?.instance { + for (n, t) in evalTraitComposition(program[d].conformances) { + e.registerConstraint(.init(.conformance(lhs, t), at: program[n].site)) + insertConformanceRequirement(lhs, t, in: &e) + cache.partiallyFormedEnvironment[e.decl] = e + } + } + for c in (program[d].whereClause?.value.constraints ?? []) { insertConstraint(c, in: &e) } @@ -2080,24 +2199,6 @@ struct TypeChecker { } } - /// Inserts the constraints declared as `p`'s annotations in `e`. - /// - /// `p` is a generic parameter, associated type, or associated value declaration. `e` is the - /// environment in which `p` is introduced. - private mutating func insertAnnotatedConstraints( - on p: T.ID, in e: inout GenericEnvironment - ) { - // TODO: Constraints on generic value parameters - let t = uncheckedType(of: p) - guard let lhs = MetatypeType(t)?.instance else { return } - - // Synthesize sugared conformance constraint, if any. - for (n, t) in evalTraitComposition(program[p].conformances) { - let c = GenericConstraint(.conformance(lhs, t), at: program[n].site) - insertConstraint(c, in: &e) - } - } - /// Evaluates `c` as a generic constraint and inserts it in `e`. private mutating func insertConstraint( _ c: SourceRepresentable, in e: inout GenericEnvironment @@ -2110,7 +2211,9 @@ struct TypeChecker { else { return } if lhs.isTypeParameter || rhs.isTypeParameter { - insertConstraint(.init(.equality(lhs, rhs), at: c.site), in: &e) + e.registerConstraint(.init(.equality(lhs, rhs), at: c.site)) + insertEqualityRequirement(lhs, rhs, in: &e) + cache.partiallyFormedEnvironment[e.decl] = e } else { report(.error(invalidEqualityConstraintBetween: lhs, and: rhs, at: c.site)) } @@ -2122,97 +2225,168 @@ struct TypeChecker { return } - let r2 = r.map({ (e) in NameExpr.ID.init(e)! }) - for (_, rhs) in evalTraitComposition(r2) { - insertConstraint(.init(.conformance(lhs, rhs), at: c.site), in: &e) + for (_, rhs) in evalTraitComposition(r) { + e.registerConstraint(.init(.conformance(lhs, rhs), at: c.site)) + insertConformanceRequirement(lhs, rhs, in: &e) + cache.partiallyFormedEnvironment[e.decl] = e } - case .value(let p): - // TODO: Symbolic execution - insertConstraint(.init(.predicate(p), at: c.site), in: &e) + case .value: + UNIMPLEMENTED("generic value constraints") } } - /// Inserts `c` in `e` and registers the conformances and equalities that `c` implies. - private mutating func insertConstraint( - _ c: GenericConstraint, in e: inout GenericEnvironment + /// Inserts a rule in `e`'s requirement system specifying that `l` conforms to `r`. + private mutating func insertConformanceRequirement( + _ l: AnyType, _ r: TraitType, in e: inout GenericEnvironment ) { - switch c.value { - case .conformance(let lhs, let rhs): - for c in bases(of: rhs).unordered { - e.establishConformance(lhs, to: c) - } + let f = possiblyPartiallyFormedEnvironment(of: AnyDeclID(r.decl))! + let u = RequirementTerm([.parameterType(program[r.decl].receiver.id)]) + var v = buildTerm(l) - case .equality(let lhs, let rhs): - e.establishEquivalence(lhs, rhs) + if let t = TraitDecl.ID(e.decl), l.base is AssociatedTypeType { + assert(v.first == .parameterType(program[t].receiver)) + v = .init(v.dropFirst()) + } - case .instance: - break + for rule in f.publicRules { + insertRequirement(rule.substituting(u, for: v), in: &e) + } + } - case .predicate: - UNIMPLEMENTED("generic value constraints") + /// Inserts a rule in `e`'s requirement system specifying that `l` is equal to `r`. + private mutating func insertEqualityRequirement( + _ l: AnyType, _ r: AnyType, in e: inout GenericEnvironment + ) { + // Make sure `a` is not concrete. + let (a, b) = l.isTypeParameter ? (l, r) : (r, l) + assert(a.isTypeParameter) + + // Rule orientation depends on ordering. + var v = buildTerm(a) + var u = buildTerm(b) + + if let t = TraitDecl.ID(e.decl) { + v = v.substituting([.parameterType(program[t].receiver)], for: [.trait(t)]) + u = u.substituting([.parameterType(program[t].receiver)], for: [.trait(t)]) } - e.insertConstraint(c) - cache.partiallyFormedEnvironment[e.decl] = e + if compareOrder(u, v) == .ascending { swap(&v, &u) } + insertRequirement(.init(u, v), in: &e) } - /// Returns `(trait, hasError)` where `trait` is the trait to which `bound` resolves or `nil` if - /// it resolves to another entity, and `hasError` is `true` iff name resolution failed. - /// - /// This method does not run standard name resolution on `bound`, as it may require information - /// in which `bound` occurs. Instead, it processes the components of `bound` one by one, bailing - /// out as soon as it find one that denotes a type or expression since trait declarations can - /// only occur at global scope. - /// - /// - Parameters: - /// - bound: The expression of a bound on a generic parameter declaration or the RHS of a - /// conformance constraint in a where clause. - /// - scopeOfUse: The declaration defining the environment in which `bound` occurs. - private mutating func resolveTrait( - expressedBy bound: NameExpr.ID, - inEnvironmentOf scopeOfUse: AnyScopeID - ) -> (trait: TraitType?, hasError: Bool) { - let (n, d) = program.ast.splitNominalComponents(of: bound) + /// Inserts the requirement `r` into `e`. + @discardableResult + private mutating func insertRequirement( + _ r: RequirementRule, in e: inout GenericEnvironment + ) -> (inserted: Bool, ruleAfterInsertion: RequirementSystem.RuleID) { + e.requirements.insert(r, orderingTermsWith: { (a, b) in compareOrder(a, b) }) + } - // `bound` can't denote a trait if it contains a non-nominal component. - if d != .none { - return (nil, false) - } + /// Returns the traits to which a conformance can be derived in the environment of `d`. + private mutating func dependencies(of d: T.ID) -> [TraitType] { + var work = traitMentions(in: d) + var seen: [TraitType] = [] - // `bound` can't denote a trait if it refers to a generic parameter. - if let ns = names(introducedIn: scopeOfUse)[program[n.last!].name.value.stem] { - if ns.contains(where: { $0.kind == GenericParameterDecl.self }) { - return (nil, false) + while let u = work.popLast() { + if !seen.contains(u) { + work.append(contentsOf: traitMentions(in: u.decl)) + seen.append(u) } } - // We can apply name resolution on the components of `bound` as long as they don't have - // generic arguments. A name expression with arguments can't denote a trait. - var parent: NameResolutionContext? = nil - for c in n.reversed() { - if !program[c].arguments.isEmpty { - return (nil, false) - } + return seen + } - let candidates = resolve(c, in: parent, usedAs: .unapplied) - if candidates.isEmpty { - return (nil, true) + /// Returns the traits to which a conformance can be derived given the constraints in `d`. + private mutating func traitMentions(in d: T.ID) -> [TraitType] { + switch d.kind { + case ConformanceDecl.self: + return traitMentions(in: ConformanceDecl.ID(d)!) + case ExtensionDecl.self: + return traitMentions(in: ExtensionDecl.ID(d)!) + case TraitDecl.self: + return traitMentions(in: TraitDecl.ID(d)!) + default: + if let g = (program.ast[d] as? GenericDecl)?.genericClause?.value { + return traitMentions(in: g) + } else { + return [] } + } + } + + /// Returns the traits to which a conformance can be derived given the constraints in `d`. + private mutating func traitMentions(in d: ConformanceDecl.ID) -> [TraitType] { + var result = evalTraitComposition(program[d].conformances).map(\.trait) + if let w = program[d].whereClause { + result.append(contentsOf: traitMentions(in: w.value)) + } + return result + } + + /// Returns the traits to which a conformance can be derived given the constraints in `d`. + private mutating func traitMentions(in d: ExtensionDecl.ID) -> [TraitType] { + if let w = program[d].whereClause { + return traitMentions(in: w.value) + } else { + return [] + } + } - guard let pick = candidates.uniqueElement else { - return (nil, false) + /// Returns the traits to which a conformance can be derived given the constraints in `d`. + private mutating func traitMentions(in d: TraitDecl.ID) -> [TraitType] { + if let s = cache.traitToSuccessors[d] { return s } + + // Enumerate base traits. + var result = evalTraitComposition(program[d].bounds).map(\.trait) + + // Enumerate traits occurring in associated type declarations. + for m in program[d].members.filter(AssociatedTypeDecl.self) { + result.append(contentsOf: evalTraitComposition(program[m].conformances).map(\.trait)) + if let w = program[m].whereClause { + result.append(contentsOf: traitMentions(in: w.value)) } + } - switch pick.type.base { - case is ModuleType, is NamespaceType, is TraitType: - parent = .init(type: pick.type, arguments: .empty, receiver: .explicit(AnyExprID(c))) - default: - return (nil, false) + cache.traitToSuccessors[d] = result + return result + } + + /// Returns the traits to which a conformance can be derived given the constraints in `g`. + private mutating func traitMentions(in g: GenericClause) -> [TraitType] { + var result: [TraitType] = [] + for m in g.parameters where uncheckedType(of: m).base is MetatypeType { + result.append(contentsOf: evalTraitComposition(program[m].conformances).map(\.trait)) + } + if let w = g.whereClause { + result.append(contentsOf: traitMentions(in: w.value)) + } + return result + } + + /// Returns the traits to which a conformance can be derived given the constraints in `w`. + private mutating func traitMentions(in w: WhereClause) -> [TraitType] { + var result: [TraitType] = [] + for c in w.constraints { + if case .bound(_, let r) = c.value { + for n in r { + if let u = TraitType(evalTypeAnnotation(AnyExprID(n))) { result.append(u) } + } } } + return result + } - return (TraitType(parent?.type), false) + /// Returns the strongly connected component that contains `t` in the trait dependency graph. + private mutating func traitComponent(containing t: TraitType) -> Int { + var s = StronglyConnectedComponents() + swap(&cache.traitDependencyComponents, &s) + let i = s.component( + containing: t, + enumeratingSuccessorsWith: { (v) in traitMentions(in: v.decl) }) + swap(&cache.traitDependencyComponents, &s) + return i } /// Returns the type of `d`, computing it if necessary, without type checking `d`. @@ -2240,65 +2414,68 @@ struct TypeChecker { // Check if work has to be done. if let t = cache.read(\.declType[d], ignoringSharedCache: ignoreSharedCache) { - return commit(t) + return commitUncheckedType(t, for: AnyDeclID(d)) } // Do the work. + var result: AnyType switch d.kind { case AssociatedTypeDecl.self: - return commit(_uncheckedType(of: AssociatedTypeDecl.ID(d)!)) + result = _uncheckedType(of: AssociatedTypeDecl.ID(d)!) case AssociatedValueDecl.self: - return commit(_uncheckedType(of: AssociatedValueDecl.ID(d)!)) + result = _uncheckedType(of: AssociatedValueDecl.ID(d)!) case BindingDecl.self: - return commit(_uncheckedType(of: BindingDecl.ID(d)!)) + result = _uncheckedType(of: BindingDecl.ID(d)!) case ConformanceDecl.self: - return commit(_uncheckedType(of: ConformanceDecl.ID(d)!)) + result = _uncheckedType(of: ConformanceDecl.ID(d)!) case ExtensionDecl.self: - return commit(_uncheckedType(of: ExtensionDecl.ID(d)!)) + result = _uncheckedType(of: ExtensionDecl.ID(d)!) case FunctionDecl.self: - return commit(_uncheckedType(of: FunctionDecl.ID(d)!)) + result = _uncheckedType(of: FunctionDecl.ID(d)!) case GenericParameterDecl.self: - return commit(_uncheckedType(of: GenericParameterDecl.ID(d)!)) + result = _uncheckedType(of: GenericParameterDecl.ID(d)!) case ImportDecl.self: - return commit(_uncheckedType(of: ImportDecl.ID(d)!)) + result = _uncheckedType(of: ImportDecl.ID(d)!) case InitializerDecl.self: - return commit(_uncheckedType(of: InitializerDecl.ID(d)!)) + result = _uncheckedType(of: InitializerDecl.ID(d)!) case MethodDecl.self: - return commit(_uncheckedType(of: MethodDecl.ID(d)!)) + result = _uncheckedType(of: MethodDecl.ID(d)!) case MethodImpl.self: - return commit(_uncheckedType(of: MethodImpl.ID(d)!)) + result = _uncheckedType(of: MethodImpl.ID(d)!) case ModuleDecl.self: - return commit(_uncheckedType(of: ModuleDecl.ID(d)!)) + result = _uncheckedType(of: ModuleDecl.ID(d)!) case NamespaceDecl.self: - return commit(_uncheckedType(of: NamespaceDecl.ID(d)!)) + result = _uncheckedType(of: NamespaceDecl.ID(d)!) case OperatorDecl.self: - return commit(_uncheckedType(of: OperatorDecl.ID(d)!)) + result = _uncheckedType(of: OperatorDecl.ID(d)!) case ParameterDecl.self: - return commit(_uncheckedType(of: ParameterDecl.ID(d)!)) + result = _uncheckedType(of: ParameterDecl.ID(d)!) case ProductTypeDecl.self: - return commit(_uncheckedType(of: ProductTypeDecl.ID(d)!)) + result = _uncheckedType(of: ProductTypeDecl.ID(d)!) case SubscriptDecl.self: - return commit(_uncheckedType(of: SubscriptDecl.ID(d)!)) + result = _uncheckedType(of: SubscriptDecl.ID(d)!) case SubscriptImpl.self: - return commit(_uncheckedType(of: SubscriptImpl.ID(d)!)) + result = _uncheckedType(of: SubscriptImpl.ID(d)!) case TraitDecl.self: - return commit(_uncheckedType(of: TraitDecl.ID(d)!)) + result = _uncheckedType(of: TraitDecl.ID(d)!) case TypeAliasDecl.self: - return commit(_uncheckedType(of: TypeAliasDecl.ID(d)!)) + result = _uncheckedType(of: TypeAliasDecl.ID(d)!) case VarDecl.self: - return commit(_uncheckedType(of: VarDecl.ID(d)!)) + result = _uncheckedType(of: VarDecl.ID(d)!) default: unexpected(d, in: program.ast) } - /// Commits `t` as the unchecked type of `d` to the local cache. - func commit(_ t: AnyType) -> AnyType { - modify(&cache.uncheckedType[d]!) { (old) in - if let u = old.computed { assert(u == t, "non-monotonic update") } - old = .computed(t) - } - return t + return commitUncheckedType(result, for: AnyDeclID(d)) + } + + /// Commits `t` as the unchecked type of `d` to the local cache. + private mutating func commitUncheckedType(_ t: AnyType, for d: AnyDeclID) -> AnyType { + modify(&cache.uncheckedType[d]!) { (old) in + if let u = old.computed { assert(u == t, "non-monotonic update") } + old = .computed(t) } + return t } /// Computes and returns the type of `d`. @@ -2367,32 +2544,11 @@ struct TypeChecker { /// Computes and returns the type of `d`. private mutating func _uncheckedType(of d: GenericParameterDecl.ID) -> AnyType { - // If `p` is introduced without any bounds, it is assumed to be type-kinded. - guard let bounds = program[d].conformances.headAndTail else { + if program[d].isTypeKinded { return ^MetatypeType(of: GenericTypeParameterType(d, ast: program.ast)) - } - - // Otherwise, the first bound of `p` determines its kind. - let (firstBoundAsTrait, nameResolutionFailed) = resolveTrait( - expressedBy: bounds.head, inEnvironmentOf: program[d].scope) - - // Register failures so we can fail fast. - if nameResolutionFailed { - return ^MetatypeType(of: GenericTypeParameterType(d, ast: program.ast)) - } - - // If the first bound is a trait, then `p` is type-kinded. - if firstBoundAsTrait != nil { - return ^MetatypeType(of: GenericTypeParameterType(d, ast: program.ast)) - } - - // Otherwise, `p` is value-kinded. - else { - let rhs = evalTypeAnnotation(AnyExprID(bounds.head)) - if let n = bounds.tail.first { - report(.error(tooManyAnnotationsOnGenericValueParametersAt: program[n].site)) - } - return rhs + } else { + let bound = program[d].conformances.uniqueElement! + return evalTypeAnnotation(AnyExprID(bound)) } } @@ -2567,17 +2723,6 @@ struct TypeChecker { return (e, i) } - /// Computes and returns the type of `d`. - /// - /// - Requires: `d` has a type annotation. - private mutating func uncheckedInputType(of d: ParameterDecl.ID) -> CallableTypeParameter { - .init( - label: program[d].label?.value, - type: uncheckedType(of: d, ignoringSharedCache: true), - hasDefault: program[d].defaultValue != nil, - isImplicit: program[d].isImplicit) - } - /// Computes and returns the types of the inputs `ps` of `d`. /// /// - Requires: The parameters in `ps` have type annotations. @@ -2858,6 +3003,25 @@ struct TypeChecker { } } + /// Evaluates `e` as a buffer type expression, having inferred that the type of the buffer's + /// elements is `t`. + private mutating func evalBufferTypeExpression( + _ e: SubscriptCallExpr.ID, havingInferredElementType t: AnyType + ) -> MetatypeType { + let n = program[e].arguments[0].value + let i = program.ast.coreType("Int")! + let j = checkedType(of: n, withHint: ^i) + + // Bail out if the type of the argument isn't `Hylo.Int`. + if j != i { + report(.error(expected: ^i, found: j, at: program[n].site)) + return MetatypeType(of: BufferType(t, ^ConcreteTerm(wrapping: 0))) + } else { + let v = denotation(of: n).asTerm! + return MetatypeType(of: BufferType(t, v)) + } + } + /// Ensures that `t` is a valid type for an annotation and returns its meaning iff it is; /// otherwise, returns `nil`. private mutating func ensureValidTypeAnnotation( @@ -2898,7 +3062,7 @@ struct TypeChecker { /// Returns the value expressed by `e` private func denotation(of e: IntegerLiteralExpr.ID) -> CompileTimeValue { if cache.local.exprType[e]! == program.ast.coreType("Int")! { - return .compilerKnown(Int(program[e].value)!) + return .term(^ConcreteTerm(wrapping: Int(program[e].value)!)) } else { UNIMPLEMENTED("arbitrary compile-time literals") } @@ -2906,7 +3070,15 @@ struct TypeChecker { /// Returns the value expressed by `e` private func denotation(of e: NameExpr.ID) -> CompileTimeValue { - UNIMPLEMENTED() + guard let d = cache.local.referredDecl[e] else { + return .term(^ErrorTerm()) + } + + if let p = d.decl.flatMap(GenericParameterDecl.ID.init(_:)) { + return .term(^GenericTermParameter(p, ast: program.ast)) + } else { + UNIMPLEMENTED("denotation of \(e.kind)") + } } /// Evaluates and returns the qualification of `e`, which is a type annotation. @@ -4224,10 +4396,12 @@ struct TypeChecker { } // If the match is a trait member, specialize its receiver. - if let t = traitDeclaring(d) { - if let r = context?.type ?? resolveReceiverMetatype(in: scopeOfUse)?.instance { - specialization[program[t.decl].receiver] = .type(r) - } + if + d.kind != AssociatedTypeDecl.self, + let t = traitDeclaring(d), + let r = context?.type ?? resolveReceiverMetatype(in: scopeOfUse)?.instance + { + specialization[program[t.decl].receiver] = .type(r) } // If the name resolves to an initializer, determine if it is used as a constructor. @@ -4251,9 +4425,16 @@ struct TypeChecker { // `X` from the resolution of the qualification and `Y` from the resolution of the candidate. entityType = specialize(entityType, for: specialization, in: scopeOfUse) + // If `d` is a trait requirement, we have to remember the generic arguments that are part of + // its qualification in case its implementation is synthesized. Otherwise, we should only + // remember arguments to parameters that are in `d`'s scope. var capturedArguments = GenericArguments.empty - for p in capturedGenericParameter(of: d) { - capturedArguments[p] = specialization[p] + if program.isRequirement(d) { + capturedArguments = specialization + } else { + for p in capturedGenericParameter(of: d) { + capturedArguments[p] = specialization[p] + } } return (entityType, capturedArguments, isConstructor) @@ -4463,8 +4644,8 @@ struct TypeChecker { // Capture-less functions are not captured. if d.kind == FunctionDecl.self { - return areEquivalent( - ArrowType(uncheckedType(of: d))?.environment ?? .error, .void, in: scopeOfUse) + let captures = ArrowType(uncheckedType(of: d))?.environment ?? .error + return areEquivalent(captures, .void, in: scopeOfUse) } else { return true } @@ -4472,7 +4653,7 @@ struct TypeChecker { /// Returns the generic arguments passed to `d`, which has type `t` and is being referred to by /// `name`, reporting diagnostics to `log`. - private func genericArguments( + private mutating func genericArguments( passedTo d: AnyDeclID, typed t: AnyType, referredToBy name: SourceRepresentable, specializedBy arguments: [CompileTimeValue], reportingDiagnosticsTo log: inout DiagnosticSet @@ -4505,10 +4686,10 @@ struct TypeChecker { } } - /// Associates `parameters`, which are introduced by `name`'s declaration, to corresponding - /// values in `arguments` if the two arrays have the same length; returns `nil` otherwise, - /// reporting diagnostics to `log`. - private func associateGenericParameters( + /// Returns a table mapping the elements of `parameters`, which are introduced by `name`'s + /// declaration, to their corresponding values in `arguments` if the two arrays have the same + /// length. Otherwise, returns `nil` and reports diagnostics to `log`. + private mutating func associateGenericParameters( _ parameters: [GenericParameterDecl.ID], of name: SourceRepresentable, to arguments: [CompileTimeValue], reportingDiagnosticsTo log: inout DiagnosticSet @@ -4518,7 +4699,11 @@ struct TypeChecker { result[p] = a } for p in parameters.dropFirst(arguments.count) { - result[p] = .type(^GenericTypeParameterType(p, ast: program.ast)) + if program[p].isTypeKinded { + result[p] = .type(^GenericTypeParameterType(p, ast: program.ast)) + } else { + result[p] = .term(^GenericTermParameter(p, ast: program.ast)) + } } if !arguments.isEmpty && (parameters.count != arguments.count) { @@ -4747,9 +4932,9 @@ struct TypeChecker { /// Replaces the generic parameters in `candidate` by fresh variables if their environments don't /// contain `scopeOfUse`, updating `substitutions` with opened generic parameters and anchoring /// instantiation constraints at `cause`. - mutating func instantiate( + private mutating func instantiate( _ candidate: NameResolutionResult.Candidate, in scopeOfUse: AnyScopeID, - updating substitutions: inout [GenericParameterDecl.ID: AnyType], + updating substitutions: inout GenericArguments, anchoringInstantiationConstraintsAt cause: ConstraintOrigin ) -> NameResolutionResult.Candidate { let ctx = instantiationContext(candidate.reference, in: scopeOfUse) @@ -4764,8 +4949,8 @@ struct TypeChecker { cs.formUnion(x.constraints) return .type(x.shape) - case .compilerKnown: - return v + case .term(let w): + return .term(instantiate(w, in: ctx, updating: &s)) } } @@ -4782,7 +4967,7 @@ struct TypeChecker { _ subject: AnyType, in scopeOfUse: AnyScopeID, cause: ConstraintOrigin ) -> InstantiatedType { let ctx = instantiationContext(in: scopeOfUse) - var substitutions: [GenericParameterDecl.ID: AnyType] = [:] + var substitutions = GenericArguments() return instantiate(subject, in: ctx, cause: cause, updating: &substitutions) } @@ -4790,22 +4975,24 @@ struct TypeChecker { /// contain `contextOfUse`, assigning `cause` to instantiation constraints. private mutating func instantiate( _ subject: AnyType, in contextOfUse: InstantiationContext, cause: ConstraintOrigin, - updating substitutions: inout [GenericParameterDecl.ID: AnyType] + updating substitutions: inout GenericArguments ) -> InstantiatedType { let shape = subject.transform(mutating: &self, transform) return InstantiatedType(shape: shape, constraints: []) func transform(mutating me: inout Self, _ t: AnyType) -> TypeTransformAction { // Nothing to do if `t` doesn't contain any generic parameter. - if !t[.hasGenericTypeParameter] && !t[.hasGenericValueParameter] { + if !t[.hasSkolem] { return .stepOver(t) } switch t.base { case let u as AssociatedTypeType: return transform(mutating: &me, u) - case let u as GenericTypeParameterType: + case let u as BufferType: return transform(mutating: &me, u) + case let u as GenericTypeParameterType: + return .stepInto(me.instantiate(u, in: contextOfUse, updating: &substitutions)) default: return .stepInto(t) } @@ -4819,16 +5006,51 @@ struct TypeChecker { } func transform( - mutating me: inout Self, _ t: GenericTypeParameterType + mutating me: inout Self, _ t: BufferType ) -> TypeTransformAction { - if let t = substitutions[t.decl] { - return .stepOver(t) - } else if me.shouldOpen(t, in: contextOfUse) { - // TODO: Collect constraints - return .stepOver(substitutions[t.decl].setIfNil(^me.freshVariable())) - } else { - return .stepOver(substitutions[t.decl].setIfNil(^t)) - } + let n = me.instantiate(t.count, in: contextOfUse, updating: &substitutions) + return .stepInto(^BufferType(t.element, n)) + } + } + + /// Replaces the generic parameters in `subject` by fresh variables if their environments don't + /// contain `contextOfUse`. + private mutating func instantiate( + _ subject: AnyTerm, in contextOfUse: InstantiationContext, + updating substitutions: inout GenericArguments + ) -> AnyTerm { + if let p = GenericTermParameter(subject) { + return instantiate(p, in: contextOfUse, updating: &substitutions) + } else { + return subject + } + } + + /// Returns a fresh variables if `p`' environment doesn't contain `contextOfUse`. + private mutating func instantiate( + _ p: GenericTypeParameterType, in contextOfUse: InstantiationContext, + updating substitutions: inout GenericArguments + ) -> AnyType { + if let v = substitutions[p.decl]?.asType { + return v + } else { + let v = shouldOpen(p.decl, in: contextOfUse) ? ^freshVariable() : ^p + substitutions[p.decl] = .type(v) + return v + } + } + + /// Returns a fresh variables if `p`' environment doesn't contain `contextOfUse`. + private mutating func instantiate( + _ p: GenericTermParameter, in contextOfUse: InstantiationContext, + updating substitutions: inout GenericArguments + ) -> AnyTerm { + if let v = substitutions[p.decl]?.asTerm { + return v + } else { + let v = shouldOpen(p.decl, in: contextOfUse) ? ^freshTermVariable() : ^p + substitutions[p.decl] = .term(v) + return v } } @@ -4836,7 +5058,7 @@ struct TypeChecker { /// opened generic parameters and anchoring instantiation constraints at `cause`. private mutating func instantiate( constraints: inout ConstraintSet, in contextOfUse: InstantiationContext, - updating substitutions: inout [GenericParameterDecl.ID: AnyType], + updating substitutions: inout GenericArguments, anchoringInstantiationConstraintsAt cause: ConstraintOrigin ) { var work = ConstraintSet() @@ -4854,10 +5076,10 @@ struct TypeChecker { /// Returns `true` iff `contextOfUse` is not contained in `p`'s environment. private func shouldOpen( - _ p: GenericTypeParameterType, in contextOfUse: InstantiationContext + _ p: GenericParameterDecl.ID, in contextOfUse: InstantiationContext ) -> Bool { // Generic parameters introduced by a trait can't be referenced outside of their environment. - let introductionScope = program[p.decl].scope + let introductionScope = program[p].scope if introductionScope.kind == TraitDecl.self { return false } @@ -4892,9 +5114,8 @@ struct TypeChecker { updating obligations: inout ProofObligations ) -> AnyType { let p = program[d].pattern - let pattern = inferredType( - of: p.id, withHint: purpose.filteredType, updating: &obligations) - if pattern[.hasError] { return pattern } + let lhs = inferredType(of: p.id, withHint: purpose.filteredType, updating: &obligations) + if lhs[.hasError] { return lhs } // If `d` has no initializer, its type is that of its annotation, if present. Otherwise, its // pattern must be used as a filter and `d` is inferred to have the same type. @@ -4906,40 +5127,60 @@ struct TypeChecker { report(.error(binding: p.introducer.value, requiresInitializerAt: p.introducer.site)) return .error } else { - return pattern + return lhs } case .condition: assert(p.annotation != nil, "expected type annotation") - return pattern + return lhs case .filter: - return pattern + return lhs } } // Note: `i` should not have been assigned a type if before `d` is checked. assert(cache.local.exprType[i] == nil) - let initializer = constrain(i, to: ^freshVariable(), in: &obligations) + let rhs = constrain(i, to: ^freshVariable(), in: &obligations) + + // If `p` is an option pattern, the initializer must be an option of the pattern's type. + // E.g. `if let x? = y { ... }` + if let q = OptionPattern.ID(p.subpattern.id) { + if purpose == .irrefutable { + report(.error(invalidOptionPattern: q, in: program.ast)) + return .error + } else { + let u = program.ast.optional(lhs) + let o = ConstraintOrigin(.optionalBinding, at: program[i].site) + obligations.insert(EqualityConstraint(^u, rhs, origin: o)) + } + } - // If `d` has no annotation, its type is inferred as that of its initializer. Otherwise, the - // type of its initializer must be subtype or supertype of its pattern if the latter is used - // irrefutably or as a condition/filter, respectively. - if p.annotation == nil { + // If `d` has no annotation, its type is inferred as that of its initializer. + // E.g. `let x = 42` + else if p.annotation == nil { let o = ConstraintOrigin(.initializationWithPattern, at: program[i].site) - obligations.insert(EqualityConstraint(initializer, pattern, origin: o)) - } else if purpose == .irrefutable { + obligations.insert(EqualityConstraint(rhs, lhs, origin: o)) + } + + // If `d` is used irrefutably, the initializer's type must be subtype of the pattern's. + // E.g. `let x: Int = 42` + else if purpose == .irrefutable { let o = ConstraintOrigin(.initializationWithHint, at: program[i].site) - obligations.insert(SubtypingConstraint(initializer, pattern, origin: o)) - } else { + obligations.insert(SubtypingConstraint(rhs, lhs, origin: o)) + } + + // If `d` is used as a condition, the initializer's type must be supertype of the pattern's. + // E.g. `if let x: Int = y { ... }` + else { let o = ConstraintOrigin(.optionalBinding, at: program[i].site) - obligations.insert(SubtypingConstraint(pattern, initializer, origin: o)) + obligations.insert(SubtypingConstraint(lhs, rhs, origin: o)) } // Collect the proof obligations for the initializer. The resulting type can be discarded; the // type of `i` will be assigned after the all proof obligations are discharged. - _ = inferredType(of: i, withHint: pattern, updating: &obligations) - return pattern + _ = inferredType(of: i, withHint: lhs, updating: &obligations) + return lhs } /// Returns the inferred type of `e`, updating `obligations` and gathering contextual information @@ -5058,14 +5299,17 @@ struct TypeChecker { let head = inferredType(of: elements.head, withHint: elementHint, updating: &obligations) for x in elements.tail { let t = inferredType(of: x, withHint: head, updating: &obligations) - let o = ConstraintOrigin(.structural, at: program[x].site) - obligations.insert(EqualityConstraint(head, t, origin: o)) + if !areEquivalent(t, head, in: program[e].scope) { + let o = ConstraintOrigin(.structural, at: program[x].site) + obligations.insert(EqualityConstraint(head, t, origin: o)) + } } - let n = program[e].elements.count - return constrain(e, to: ^BufferType(head, .compilerKnown(n)), in: &obligations) + let n = ConcreteTerm(wrapping: program[e].elements.count) + return constrain(e, to: ^BufferType(head, ^n), in: &obligations) } else { - return constrain(e, to: ^BufferType(elementHint, .compilerKnown(0)), in: &obligations) + let n = ConcreteTerm(wrapping: 0) + return constrain(e, to: ^BufferType(elementHint, ^n), in: &obligations) } } @@ -5154,7 +5398,7 @@ struct TypeChecker { return constrain(e, to: .error, in: &obligations) } - guard conformedTraits(of: s, in: program[e].scope).contains(lens) else { + guard conforms(s, to: lens, in: program[e].scope) else { report(.error(s, doesNotConformTo: lens, at: program[e].lens.site)) return constrain(e, to: .error, in: &obligations) } @@ -5464,21 +5708,20 @@ struct TypeChecker { } // The callee has a metatype and is a name expression bound to a nominal type declaration, - // meaning that the call is actually a sugared buffer type expression. + // meaning that the call is actually a sugared array or buffer type expression. if isBoundToNominalTypeDecl(program[e].callee, in: obligations) { - let n = program[e].arguments.count - if n != 1 { + let t = MetatypeType(callee)!.instance + switch program[e].arguments.count { + case 0: + let u = MetatypeType(of: program.ast.array(t)) + return constrain(e, to: ^u, in: &obligations) + case 1: + let u = evalBufferTypeExpression(e, havingInferredElementType: t) + return constrain(e, to: ^u, in: &obligations) + case let n: report(.error(invalidBufferTypeArgumentCount: n, at: program[e].callee.site)) return constrain(e, to: .error, in: &obligations) } - - if let a = IntegerLiteralExpr.ID(program[e].arguments[0].value) { - let t = MetatypeType(callee)!.instance - let r = BufferType(t, .compilerKnown(Int(program[a].value)!)) - return constrain(e, to: ^MetatypeType(of: r), in: &obligations) - } else { - UNIMPLEMENTED("arbitrary buffer type argument expression") - } } // The callee has a callable type or we need inference to determine its type. Either way, @@ -5619,9 +5862,8 @@ struct TypeChecker { ) -> AnyType { switch callee.kind { case InoutExpr.self: - let e = InoutExpr.ID(callee)! - let t = inferredType( - ofCallee: program[e].subject, usedAs: purpose, implicitlyIn: q, updating: &obligations) + let e = program[InoutExpr.ID(callee)!].subject.id + let t = inferredType(ofCallee: e, usedAs: purpose, implicitlyIn: q, updating: &obligations) return constrain(callee, to: t, in: &obligations) case NameExpr.self: @@ -5639,44 +5881,47 @@ struct TypeChecker { /// /// If `hint` is not `nil`, it is constrained to be conforming to the `ExpressibleBy***Literal` /// corresponding to `defaultType` and the type of `e` if inferred as `hint`. Otherwise, the - /// type of `e` is inferred as `defaultType`. + /// type of `literal` is inferred as `defaultType`. /// - /// - Requires: `e` is a literal expression. + /// - Requires: `literal` is a literal expression. private mutating func _inferredType( ofLiteral literal: T.ID, withHint hint: AnyType? = nil, defaultingTo defaultType: AnyType, updating obligations: inout ProofObligations ) -> AnyType { + // Use the default type in the absence of any hint. guard let h = hint else { return constrain(literal, to: defaultType, in: &obligations) } - // Fast path if `e` is the default type. + // Trivial if `h` is the default type. if areEquivalent(defaultType, h, in: program[literal].scope) { return constrain(literal, to: h, in: &obligations) } - let cause = ConstraintOrigin(.literal, at: program[literal].site) - let t = ^freshVariable() + // Trivial if `h` conforms to `ExpressibleBy***Literal`. let p = program.ast.coreTrait(forTypesExpressibleBy: T.self)! + if conforms(h, to: p, in: program[literal].scope) { + return constrain(literal, to: h, in: &obligations) + } - let preferred: ConstraintSet = [ - EqualityConstraint(t, defaultType, origin: cause), - EqualityConstraint(defaultType, h, origin: cause), - ] - let alternative: ConstraintSet = [ - EqualityConstraint(t, h, origin: cause), - ConformanceConstraint(h, conformsTo: p, origin: cause), - ] + // Trivial if `h` provably doesn't conform to `ExpressibleBy***Literal`. + if !h[.hasVariable] { + report(.error(h, doesNotConformTo: p, at: program[literal].site)) + return .error + } + // In other cases, we infer a type conforming to `ExpressibleBy***Literal`, preferring the + // default type if the hint doesn't give us any better information. + let cause = ConstraintOrigin(.literal, at: program[literal].site) + let a: ConstraintSet = [EqualityConstraint(defaultType, h, origin: cause)] + let b: ConstraintSet = [ConformanceConstraint(h, conformsTo: p, origin: cause)] obligations.insert( DisjunctionConstraint( - between: [ - .init(constraints: preferred, penalties: 0), - .init(constraints: alternative, penalties: 1), - ], + between: [.init(constraints: a, penalties: 0), .init(constraints: b, penalties: 1)], origin: cause)) - return constrain(literal, to: t, in: &obligations) + // return constrain(literal, to: t, in: &obligations) + return constrain(literal, to: h, in: &obligations) } /// Returns the inferred type of `e`'s output, updating `obligations` and gathering contextual @@ -5708,6 +5953,8 @@ struct TypeChecker { return inferredType(of: ExprPattern.ID(p)!, withHint: hint, updating: &obligations) case NamePattern.self: return inferredType(of: NamePattern.ID(p)!, withHint: hint, updating: &obligations) + case OptionPattern.self: + return inferredType(of: OptionPattern.ID(p)!, withHint: hint, updating: &obligations) case TuplePattern.self: return inferredType(of: TuplePattern.ID(p)!, withHint: hint, updating: &obligations) case WildcardPattern.self: @@ -5761,6 +6008,15 @@ struct TypeChecker { return t } + /// Returns the inferred type of `p`, updating `obligations` and gathering contextual information + /// from `hint`. + private mutating func inferredType( + of p: OptionPattern.ID, withHint hint: AnyType? = nil, + updating obligations: inout ProofObligations + ) -> AnyType { + inferredType(of: program[p].name, updating: &obligations) + } + /// Returns the inferred type of `p`, updating `obligations` and gathering contextual information /// from `hint`. private mutating func inferredType( @@ -5837,19 +6093,17 @@ struct TypeChecker { guard let domain = checkedType(of: program[s].domain.value).errorFree else { return nil } let isConsuming = program.ast.isConsuming(s) - let domainTraits = conformedTraits(of: domain, in: program[s].scope) let collection = program.ast.core.collection.type + let iterator = program.ast.core.iterator.type // By default, non-consuming loops use `Collection`. - if !isConsuming && domainTraits.contains(collection) { + if !isConsuming && conforms(domain, to: collection, in: program[s].scope) { let r = AssociatedTypeDecl.ID(program.ast.requirements("Element", in: collection.decl)[0])! return demandImplementation(of: r, for: domain, in: program[s].scope) } - let iterator = program.ast.core.iterator.type - // Any kind of for loop can consume an iterator. - if domainTraits.contains(iterator) { + if conforms(domain, to: iterator, in: program[s].scope) { let r = AssociatedTypeDecl.ID(program.ast.requirements("Element", in: iterator.decl)[0])! return demandImplementation(of: r, for: domain, in: program[s].scope) } @@ -5868,7 +6122,7 @@ struct TypeChecker { _ components: [NameResolutionResult.ResolvedComponent], in obligations: inout ProofObligations ) -> AnyType { var last: AnyType? - var substitutions: [GenericParameterDecl.ID: AnyType] = [:] + var substitutions = GenericArguments() for p in components { last = constrain(p.component, to: p.candidates, in: &obligations, extending: &substitutions) } @@ -5882,7 +6136,7 @@ struct TypeChecker { private mutating func constrain( _ name: NameExpr.ID, to candidates: [NameResolutionResult.Candidate], in obligations: inout ProofObligations, - extending substitutions: inout [GenericParameterDecl.ID: AnyType] + extending substitutions: inout GenericArguments ) -> AnyType { precondition(!candidates.isEmpty) let site = program[name].site @@ -5965,13 +6219,13 @@ struct TypeChecker { _ solution: Solution, satisfying obligations: ProofObligations, ignoringSharedCache ignoreSharedCache: Bool ) { - for (n, r) in solution.bindingAssumptions { - var s = solution.typeAssumptions.reify(r, withVariables: .kept) + for (n, r) in solution.bindings { + var s = solution.substitutions.reify(reference: r, withVariables: .kept) - let t = solution.typeAssumptions.reify(obligations.exprType[n]!, withVariables: .kept) + let t = solution.substitutions.reify(obligations.exprType[n]!, withVariables: .kept) if t[.hasVariable] || s.arguments.values.contains(where: { $0.isTypeVariable }) { report(.error(notEnoughContextToInferArgumentsAt: program[n].site)) - s = solution.typeAssumptions.reify(s, withVariables: .substitutedByError) + s = solution.substitutions.reify(reference: s, withVariables: .substitutedByError) } cache.write(s, at: \.referredDecl[n], ignoringSharedCache: ignoreSharedCache) @@ -5982,7 +6236,7 @@ struct TypeChecker { } for (e, t) in obligations.exprType { - let u = solution.typeAssumptions.reify(t, withVariables: .substitutedByError) + let u = solution.substitutions.reify(t, withVariables: .substitutedByError) cache.write(u, at: \.exprType[e], ignoringSharedCache: ignoreSharedCache) } @@ -5990,7 +6244,7 @@ struct TypeChecker { // Note: Post-inference checks run after other choices have been committed. for (d, t) in obligations.declType { - let u = solution.typeAssumptions.reify(t, withVariables: .substitutedByError) + let u = solution.substitutions.reify(t, withVariables: .substitutedByError) cache.uncheckedType[d].updateMonotonically(.computed(u)) checkPostInference(d, solution: solution, ignoringSharedCache: ignoreSharedCache) } @@ -5999,26 +6253,6 @@ struct TypeChecker { assert(solution.isSound || diagnostics.containsError, "inference failed without diagnostics") } - /// Commits `r` in the program, where `r` is the name resolution result for a name component - /// used in a type expression, returning the type of that component. - /// - /// - Precondition: `r` has a single candidate. - private mutating func bindTypeAnnotation( - _ r: NameResolutionResult.ResolvedComponent - ) -> AnyType { - let c = r.candidates.uniqueElement! - cache.write(c.reference, at: \.referredDecl[r.component], ignoringSharedCache: true) - - let t: AnyType - if isBoundToNominalTypeDecl(c.reference) { - t = MetatypeType(c.type)!.instance - } else { - t = c.type - } - cache.write(t, at: \.exprType[r.component], ignoringSharedCache: true) - return t - } - /// Calls `action` on `self`, logging a trace of constraint solving iff `shouldTraceInference(n)` /// returns `true`. private mutating func tracingInference( @@ -6041,8 +6275,8 @@ struct TypeChecker { var ranking: StrictPartialOrdering = .equal var namesInCommon = 0 - for (n, lhs) in lhs.bindingAssumptions { - guard let rhs = rhs.bindingAssumptions[n] else { continue } + for (n, lhs) in lhs.bindings { + guard let rhs = rhs.bindings[n] else { continue } namesInCommon += 1 // Nothing to do if both functions have the same binding. @@ -6062,23 +6296,23 @@ struct TypeChecker { } } - if lhs.bindingAssumptions.count < rhs.bindingAssumptions.count { - if namesInCommon == lhs.bindingAssumptions.count { + if lhs.bindings.count < rhs.bindings.count { + if namesInCommon == lhs.bindings.count { return ranking != .ascending ? .descending : nil } else { return nil } } - if lhs.bindingAssumptions.count > rhs.bindingAssumptions.count { - if namesInCommon == rhs.bindingAssumptions.count { + if lhs.bindings.count > rhs.bindings.count { + if namesInCommon == rhs.bindings.count { return ranking != .descending ? .ascending : nil } else { return nil } } - return namesInCommon == lhs.bindingAssumptions.count ? ranking : nil + return namesInCommon == lhs.bindings.count ? ranking : nil } /// Compares `lhs` and `rhs` in `scopeOfUse` and returns whether one has either shadows or is @@ -6236,6 +6470,14 @@ struct TypeChecker { return .init(nextFreshVariableIdentifier) } + /// Creates a fresh term variable. + /// + /// The returned instance is unique access concurrent type checker instances. + mutating func freshTermVariable() -> TermVariable { + defer { nextFreshVariableIdentifier += 1 } + return .init(nextFreshVariableIdentifier) + } + /// Returns `true` iff `t` is known as an arrow type. private func isArrow(_ t: AnyType) -> Bool { (t.base as? CallableType)?.isArrow ?? false @@ -6303,6 +6545,112 @@ struct TypeChecker { !(program.isRequirement(d) || program[d].isForeignInterface || program[d].isExternal) } + /// Returns the trait whose `g` is the receiver, if any. + private func traitIntroducing(_ g: GenericParameterDecl.ID) -> TraitType? { + TraitDecl.ID(program[g].scope).map({ (d) in TraitType(d, ast: program.ast) }) + } + + /// Returns `true` iff control flow may jump to the statement after `s`. + private mutating func canFallThrough(_ s: T) -> Bool { + switch s.kind { + case BreakStmt.kind, ContinueStmt.kind, ReturnStmt.kind: + return false + case BraceStmt.kind: + return canFallThrough(BraceStmt.ID(s)!) + case ExprStmt.kind: + return canFallThrough(ExprStmt.ID(s)!) + default: + return true + } + } + + /// Returns `true` iff control flow may jump to the statement after `s`. + private mutating func canFallThrough(_ s: BraceStmt.ID) -> Bool { + if let last = program[s].stmts.last { + return canFallThrough(last) + } else { + return true + } + } + + /// Returns `false` iff executing `s` always terminates the program. + private mutating func canFallThrough(_ s: ExprStmt.ID) -> Bool { + let e = program.ast[s].expr + let t = checkedType(of: e) + let u = canonical(t, in: program[e].scope) + return !u.isNever + } + + /// Returns how `a` and `b` are ordered. + private mutating func compareOrder( + _ a: TraitType, _ b: TraitType + ) -> StrictOrdering { + let ab = bases(of: a) + let bb = bases(of: b) + + if ab.unordered.count > bb.unordered.count { + return .descending // !!! .ascending in Slava's work + } else if ab.unordered.count == bb.unordered.count { + return .init(between: a.decl.rawValue, and: b.decl.rawValue) + } else { + return .descending + } + } + + /// Returns how `a` and `b` are ordered. + private mutating func compareOrder( + _ a: RequirementSymbol, _ b: RequirementSymbol + ) -> StrictOrdering { + switch (a, b) { + case (.associatedType(let l), .associatedType(let r)): + if program[l].baseName == program[r].baseName { + return compareOrder(program.traitDeclaring(l)!, program.traitDeclaring(r)!) + } else { + return .init(between: program[l].baseName, and: program[r].baseName) + } + + case (.parameterType(let l), .parameterType(let r)): + if let t = traitIntroducing(l), let u = traitIntroducing(r) { + return compareOrder(t, u) + } else { + return .init(between: l.rawValue, and: r.rawValue) + } + + case (.trait(let l), .trait(let r)): + return compareOrder(TraitType(l, ast: program.ast), TraitType(r, ast: program.ast)) + + default: + return .init(between: a.kind, and: b.kind) + } + } + + /// Returns how `a` and `b` are ordered. + private mutating func compareOrder( + _ a: RequirementTerm, _ b: RequirementTerm + ) -> StrictOrdering { + if a.symbols.count == b.symbols.count { + for i in a.symbols.indices { + let c = compareOrder(a.symbols[i], b.symbols[i]) + if c != .equal { return c } + } + return .equal + } else { + return .init(between: a.symbols.count, and: b.symbols.count) + } + } + + /// Returns the requirement term corresponding to `t`. + func buildTerm(_ t: AnyType) -> RequirementTerm { + switch t.base { + case let u as GenericTypeParameterType: + return [.parameterType(u.decl)] + case let u as AssociatedTypeType: + return buildTerm(u.domain).appending(.associatedType(u.decl)) + default: + return [.concrete(t)] + } + } + // MARK: Caching /// A possibly shared instance of a typed program. @@ -6380,12 +6728,20 @@ struct TypeChecker { /// A map from trait to its bases (i.e., the traits that it refines). /// - /// This map serves as cache for `bases(of:)` + /// This map serves as cache for `bases(of:)`. var traitToBases: [TraitType: RefinementClosure] = [:] + /// A map from trait to its direct successors in the trait dependency graph. + /// + /// This map serves as cache for `successors(of:)`. + var traitToSuccessors: [TraitDecl.ID: [TraitType]] = [:] + /// A map from generic declarations to their (partially formed) environment. var partiallyFormedEnvironment: [AnyDeclID: GenericEnvironment] = [:] + /// The strongly connected components of the trait dependency graph. + var traitDependencyComponents = StronglyConnectedComponents() + /// Creates an instance for memoizing type checking results in `local` and comminicating them /// to concurrent type checkers using `shared`. init(local: TypedProgram, shared: SharedMutable? = nil) { diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 8d9ad8ef7..ef669bd18 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -11,7 +11,7 @@ public struct TypedProgram { public typealias ConformanceTable = [TraitType: Set] /// The program annotated by the properties of `self`. - private let base: ScopedProgram + private var base: ScopedProgram /// A map from translation unit to its imports. public internal(set) var imports: [TranslationUnit.ID: Set] = [:] @@ -55,7 +55,7 @@ public struct TypedProgram { /// - Parameters: /// - typeCheckingIsParallel: if `true`, the program is partitioned into chucks that are type /// checked separately. Otherwise, type checking is performed sequentially. Either way, the - /// order in which declarations are being checked is undeterministic. + /// order in which declarations are being checked is nondeterministic. /// - shouldTraceInference: A closure accepting a node and its containing program, returning /// `true` if a trace of type inference should be logged on the console for that node. The /// closure is not called if `typeCheckingIsParallel` is `true`. @@ -63,7 +63,8 @@ public struct TypedProgram { annotating base: ScopedProgram, inParallel typeCheckingIsParallel: Bool = false, reportingDiagnosticsTo log: inout DiagnosticSet, - tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? = nil + tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? = nil, + loggingRequirementSystemIf shouldLogRequirements: ((AnyDeclID, TypedProgram) -> Bool)? = nil ) throws { let instanceUnderConstruction = SharedMutable(TypedProgram(partiallyFormedFrom: base)) @@ -75,7 +76,8 @@ public struct TypedProgram { let t = TypeCheckTask( Array(chunk), withCheckerIdentifiedBy: UInt8(i), collaborativelyConstructing: instanceUnderConstruction, - tracingInferenceIf: nil) + tracingInferenceIf: nil, + loggingRequirementSystemIf: nil) tasks.append(t) } @@ -89,7 +91,8 @@ public struct TypedProgram { self = try instanceUnderConstruction.read { var checker = TypeChecker( constructing: $0, - tracingInferenceIf: typeCheckingIsParallel ? nil : shouldTraceInference) + tracingInferenceIf: typeCheckingIsParallel ? nil : shouldTraceInference, + loggingRequirementSystemIf: typeCheckingIsParallel ? nil : shouldLogRequirements) checker.checkAllDeclarations() log.formUnion(checker.diagnostics) @@ -98,6 +101,33 @@ public struct TypedProgram { } } + /// Returns a copy of `self` in which a new module has been loaded, calling `make` to form its + /// contents and reporting diagnostics to `log`. + /// + /// - Parameters: + /// - shouldTraceInference: A closure accepting a node and its containing program, returning + /// `true` if a trace of type inference should be logged on the console for that node. + public func loadModule( + reportingDiagnosticsTo log: inout DiagnosticSet, + tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? = nil, + loggingRequirementSystemIf shouldLogRequirements: ((AnyDeclID, TypedProgram) -> Bool)? = nil, + creatingContentsWith make: AST.ModuleLoader + ) throws -> (Self, ModuleDecl.ID) { + let (p, m) = try base.loadModule(reportingDiagnosticsTo: &log, creatingContentsWith: make) + var extended = self + extended.base = consume p + + var checker = TypeChecker( + constructing: extended, + tracingInferenceIf: shouldTraceInference, + loggingRequirementSystemIf: shouldLogRequirements) + checker.checkModule(m) + + log.formUnion(checker.diagnostics) + try log.throwOnError() + return (checker.program, m) + } + /// The type checking of a collection of source files. private final class TypeCheckTask: Operation { @@ -113,12 +143,15 @@ public struct TypedProgram { _ sources: [TranslationUnit.ID], withCheckerIdentifiedBy checkerIdentifier: UInt8, collaborativelyConstructing instanceUnderConstruction: SharedMutable, - tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)? + tracingInferenceIf shouldTraceInference: ((AnyNodeID, TypedProgram) -> Bool)?, + loggingRequirementSystemIf shouldLogRequirements: ((AnyDeclID, TypedProgram) -> Bool)? ) { self.sources = sources self.checker = TypeChecker( - checkerIdentifier, collaborativelyConstructing: instanceUnderConstruction, - tracingInferenceIf: shouldTraceInference) + checkerIdentifier, + collaborativelyConstructing: instanceUnderConstruction, + tracingInferenceIf: shouldTraceInference, + loggingRequirementSystemIf: shouldLogRequirements) } /// Executes the operation. @@ -139,9 +172,19 @@ public struct TypedProgram { self.base = base } + /// Returns the canonical form of `d`'s type. + public func canonical(typeOf d: T) -> AnyType { + canonical(declType[d]!, in: nodeToScope[d]!) + } + + /// Returns the canonical form of `e`'s type. + public func canonical(typeOf e: T) -> AnyType { + canonical(exprType[e]!, in: nodeToScope[e]!) + } + /// Returns the canonical form of `t` in `scopeOfUse`. public func canonical(_ t: AnyType, in scopeOfUse: AnyScopeID) -> AnyType { - if t[.isCanonical] { return t } + if t.isCanonical { return t } var checker = TypeChecker(asContextFor: self) return checker.canonical(t, in: scopeOfUse) } @@ -293,15 +336,18 @@ public struct TypedProgram { /// Returns the names and types of `t`'s stored properties. public func storage(of t: BoundGenericType) -> [TupleType.Element] { storage(of: t.base).map { (p) in + // FIXME: Probably wrong to specialize/canonicalize in any random scope. + let arbitraryScope = AnyScopeID(base.ast.coreLibrary!) let z = GenericArguments(t) - let t = specialize(p.type, for: z, in: AnyScopeID(base.ast.coreLibrary!)) - return .init(label: p.label, type: t) + let u = specialize(p.type, for: z, in: arbitraryScope) + let v = canonical(u, in: arbitraryScope) + return .init(label: p.label, type: v) } } /// Returns the names and types of `t`'s stored properties. public func storage(of t: BufferType) -> [TupleType.Element] { - if let w = t.count.asCompilerKnown(Int.self) { + if let w = ConcreteTerm(t.count)?.value as? Int { return Array(repeating: .init(label: nil, type: t.element), count: w) } else { return [] @@ -321,23 +367,6 @@ public struct TypedProgram { return result } - /// Returns the generic parameters captured in the scope of `d` if `d` is callable; returns an - /// empty collection otherwise. - public func liftedGenericParameters(of d: AnyDeclID) -> [GenericParameterDecl.ID] { - switch d.kind { - case FunctionDecl.self: - return Array(accumulatedGenericParameters(in: FunctionDecl.ID(d)!)) - case InitializerDecl.self: - return Array(accumulatedGenericParameters(in: InitializerDecl.ID(d)!)) - case SubscriptImpl.self: - return Array(accumulatedGenericParameters(in: SubscriptImpl.ID(d)!)) - case MethodImpl.self: - return Array(accumulatedGenericParameters(in: MethodImpl.ID(d)!)) - default: - return [] - } - } - /// Returns generic parameters captured by `s` and the scopes semantically containing `s`. public func accumulatedGenericParameters( in s: T @@ -427,8 +456,7 @@ public struct TypedProgram { if !model.isSkolem { return nil } var checker = TypeChecker(asContextFor: self) - let bounds = checker.conformedTraits(of: model, in: scopeOfUse) - if !bounds.contains(concept) { return nil } + if !checker.conforms(model, to: concept, in: scopeOfUse) { return nil } // An abstract conformance maps each requirement to itself. var implementations = Conformance.ImplementationMap() @@ -446,7 +474,7 @@ public struct TypedProgram { private func structuralConformance( of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID ) -> Conformance? { - assert(model[.isCanonical]) + assert(model.isCanonical) switch model.base { case let m as BufferType: @@ -494,7 +522,8 @@ public struct TypedProgram { /// - Requires: `n` is declared by the trait for which `c` has been established. public func associatedType(_ n: AssociatedTypeDecl.ID, for c: Conformance) -> AnyType { let d = c.implementations[n]!.decl! - let t = specialize(MetatypeType(declType[d]!)!.instance, for: c.arguments, in: c.scope) + let s: AnyScopeID = c.origin.flatMap({ (o) in AnyScopeID(o.source) }) ?? c.scope + let t = specialize(MetatypeType(declType[d]!)!.instance, for: c.arguments, in: s) return canonical(t, in: c.scope) } diff --git a/Sources/FrontEnd/Types/AnyType.swift b/Sources/FrontEnd/Types/AnyType.swift index 334e6c230..85f47756a 100644 --- a/Sources/FrontEnd/Types/AnyType.swift +++ b/Sources/FrontEnd/Types/AnyType.swift @@ -92,8 +92,7 @@ public struct AnyType { /// The `base` property can be cast back to its original type using one of the type casting /// operators (`as?`, `as!`, or `as`). public var base: any TypeProtocol { - get { wrapped.unwrap() } - set { wrapped = AnyType(newValue).wrapped } + wrapped.unwrap() } /// `self` if `!self[.hasError]`; otherwise, `nil`. @@ -146,7 +145,7 @@ public struct AnyType { /// /// - Requires: `self` is canonical. public var isBuiltin: Bool { - precondition(self[.isCanonical]) + precondition(isCanonical) return base is BuiltinType } @@ -154,7 +153,7 @@ public struct AnyType { /// /// - Requires: `self` is canonical. public var isBuiltinInteger: Bool { - precondition(self[.isCanonical]) + precondition(isCanonical) if let b = BuiltinType(self) { return b.isInteger } else { @@ -166,7 +165,7 @@ public struct AnyType { /// /// - Requires: `self` is canonical. public var isBuiltinOrRawTuple: Bool { - precondition(self[.isCanonical]) + precondition(isCanonical) if let b = TupleType(self) { return b.elements.allSatisfy(\.type.isBuiltin) } else { @@ -289,90 +288,16 @@ public struct AnyType { return .stepOver(t) default: - return t[.hasGenericTypeParameter] ? .stepInto(t) : .stepOver(t) + return t[.hasSkolem] ? .stepInto(t) : .stepOver(t) } } } - /// Returns `true` if `self` matches `other`, calling `unify` on `unifier` to attempt unifying - /// syntactically different parts. - /// - /// The method visits `self` and `other` are visited "side-by-side", calling `unify` on inequal - /// pairs of non-structural type terms. `unify` returns `true` if these terms can be "unified", - /// i.e., considered equivalent under some substitution. - public func matches( - _ other: AnyType, mutating unifier: inout U, - _ unify: (inout U, _ lhs: AnyType, _ rhs: AnyType) -> Bool - ) -> Bool { - switch (self.base, other.base) { - case (let lhs as BoundGenericType, let rhs as BoundGenericType): - if lhs.arguments.count != rhs.arguments.count { return false } - - var result = lhs.base.matches(rhs.base, mutating: &unifier, unify) - for (a, b) in zip(lhs.arguments, rhs.arguments) { - switch (a.value, b.value) { - case (.type(let vl), .type(let vr)): - result = vl.matches(vr, mutating: &unifier, unify) && result - default: - result = a.value == b.value && result - } - } - return result - - case (let lhs as MetatypeType, let rhs as MetatypeType): - return lhs.instance.matches(rhs.instance, mutating: &unifier, unify) - - case (let lhs as TupleType, let rhs as TupleType): - if !lhs.labels.elementsEqual(rhs.labels) { return false } - - var result = true - for (a, b) in zip(lhs.elements, rhs.elements) { - result = a.type.matches(b.type, mutating: &unifier, unify) && result - } - return result - - case (let lhs as ArrowType, let rhs as ArrowType): - if !lhs.labels.elementsEqual(rhs.labels) { return false } - - var result = true - for (a, b) in zip(lhs.inputs, rhs.inputs) { - result = a.type.matches(b.type, mutating: &unifier, unify) && result - } - result = lhs.output.matches(rhs.output, mutating: &unifier, unify) && result - result = lhs.environment.matches(rhs.environment, mutating: &unifier, unify) && result - return result - - case (let lhs as MethodType, let rhs as MethodType): - if !lhs.labels.elementsEqual(rhs.labels) || (lhs.capabilities != rhs.capabilities) { - return false - } - - var result = true - for (a, b) in zip(lhs.inputs, rhs.inputs) { - result = a.type.matches(b.type, mutating: &unifier, unify) && result - } - result = lhs.output.matches(rhs.output, mutating: &unifier, unify) && result - result = lhs.receiver.matches(rhs.receiver, mutating: &unifier, unify) && result - return result - - case (let lhs as ParameterType, let rhs as ParameterType): - if lhs.access != rhs.access { return false } - return lhs.bareType.matches(rhs.bareType, mutating: &unifier, unify) - - case (let lhs as RemoteType, let rhs as RemoteType): - if lhs.access != rhs.access { return false } - return lhs.bareType.matches(rhs.bareType, mutating: &unifier, unify) - - default: - return (self == other) || unify(&unifier, self, other) - } - } - } extension AnyType: TypeProtocol { - public var flags: TypeFlags { base.flags } + public var flags: ValueFlags { base.flags } public func transformParts( mutating m: inout M, _ transformer: (inout M, AnyType) -> TypeTransformAction diff --git a/Sources/FrontEnd/Types/ArrowType.swift b/Sources/FrontEnd/Types/ArrowType.swift index bfc73c6df..e60f9f5bb 100644 --- a/Sources/FrontEnd/Types/ArrowType.swift +++ b/Sources/FrontEnd/Types/ArrowType.swift @@ -15,7 +15,7 @@ public struct ArrowType: TypeProtocol { /// The output type of the arrow. public let output: AnyType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init( @@ -28,11 +28,7 @@ public struct ArrowType: TypeProtocol { self.environment = environment self.inputs = inputs self.output = output - - var fs = environment.flags - for i in inputs { fs.merge(i.type.flags) } - fs.merge(output.flags) - flags = fs + self.flags = inputs.reduce(output.flags | environment.flags, { (fs, p) in fs | p.type.flags }) } /// Creates the type of a function accepting `inputs` as `let` parameters and returning `output`. diff --git a/Sources/FrontEnd/Types/AssociatedTypeType.swift b/Sources/FrontEnd/Types/AssociatedTypeType.swift index 1bc9377fe..ae33b5dc5 100644 --- a/Sources/FrontEnd/Types/AssociatedTypeType.swift +++ b/Sources/FrontEnd/Types/AssociatedTypeType.swift @@ -15,7 +15,7 @@ public struct AssociatedTypeType: TypeProtocol { public let name: Incidental /// A set of flags describing recursive properties. - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance denoting the associated type declared by `decl` as a member of `domain`. public init(_ decl: AssociatedTypeDecl.ID, domain: AnyType, ast: AST) { @@ -25,8 +25,10 @@ public struct AssociatedTypeType: TypeProtocol { /// Creates an instance with the given properties. init(decl: AssociatedTypeDecl.ID, domain: AnyType, name: String) { var fs = domain.flags - if !domain.isSkolem && !(domain.base is TypeVariable) { - fs.remove(.isCanonical) + + let d = AssociatedTypeType(domain)?.root ?? domain + if !(d.isSkolem || d.base is TypeVariable) { + fs.insert(.hasNonCanonical) } self.decl = decl @@ -35,6 +37,11 @@ public struct AssociatedTypeType: TypeProtocol { self.flags = fs } + /// Returns the root of `self`'s qualification. + public var root: AnyType { + AssociatedTypeType(domain)?.root ?? domain + } + public func transformParts( mutating m: inout M, _ transformer: (inout M, AnyType) -> TypeTransformAction ) -> Self { diff --git a/Sources/FrontEnd/Types/AssociatedValueType.swift b/Sources/FrontEnd/Types/AssociatedValueType.swift index 5097e9e75..5d20ff5b3 100644 --- a/Sources/FrontEnd/Types/AssociatedValueType.swift +++ b/Sources/FrontEnd/Types/AssociatedValueType.swift @@ -29,7 +29,7 @@ public struct AssociatedValueType: TypeProtocol { self.name = Incidental(ast[decl].baseName) } - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } } diff --git a/Sources/FrontEnd/Types/BoundGenericType.swift b/Sources/FrontEnd/Types/BoundGenericType.swift index 5e76f0fc4..7ced297ca 100644 --- a/Sources/FrontEnd/Types/BoundGenericType.swift +++ b/Sources/FrontEnd/Types/BoundGenericType.swift @@ -12,7 +12,7 @@ public struct BoundGenericType: TypeProtocol { /// The type and value arguments of the base type. public let arguments: Arguments - public let flags: TypeFlags + public let flags: ValueFlags /// Creates a bound generic type binding `base` to the given `arguments`. /// @@ -22,13 +22,8 @@ public struct BoundGenericType: TypeProtocol { self.base = ^base self.arguments = arguments - var flags: TypeFlags = base.flags - for (_, a) in arguments { - if case .type(let t) = a { - flags.merge(t.flags) - } else { - UNIMPLEMENTED() - } + var flags = arguments.reduce(base.flags) { (fs, a) in + fs | a.value.flags } // The type isn't canonical if `base` is structural. @@ -38,7 +33,7 @@ public struct BoundGenericType: TypeProtocol { case is ProductType: break default: - flags.remove(.isCanonical) + flags.insert(.hasNonCanonical) } self.flags = flags diff --git a/Sources/FrontEnd/Types/BufferType.swift b/Sources/FrontEnd/Types/BufferType.swift index 3220c8e1d..46b3464d3 100644 --- a/Sources/FrontEnd/Types/BufferType.swift +++ b/Sources/FrontEnd/Types/BufferType.swift @@ -5,16 +5,16 @@ public struct BufferType: TypeProtocol { public let element: AnyType /// The number of elements in the buffer. - public let count: CompileTimeValue + public let count: AnyTerm /// The structural properties of the type. - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. - public init(_ element: AnyType, _ count: CompileTimeValue) { + public init(_ element: AnyType, _ count: AnyTerm) { self.element = element self.count = count - self.flags = element.flags + self.flags = element.flags | count.flags } public func transformParts( diff --git a/Sources/FrontEnd/Types/BuiltinType.swift b/Sources/FrontEnd/Types/BuiltinType.swift index b229da345..95581f214 100644 --- a/Sources/FrontEnd/Types/BuiltinType.swift +++ b/Sources/FrontEnd/Types/BuiltinType.swift @@ -28,6 +28,9 @@ public enum BuiltinType: TypeProtocol { /// The type of the built-in module. case module + /// The type of a union discriminator. + public static let discriminator = word + /// `true` iff `self` is `.i` or `.word`. public var isInteger: Bool { switch self { @@ -38,7 +41,7 @@ public enum BuiltinType: TypeProtocol { } } - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } } diff --git a/Sources/FrontEnd/Types/ConformanceLensType.swift b/Sources/FrontEnd/Types/ConformanceLensType.swift index fed1d7249..ae311a34c 100644 --- a/Sources/FrontEnd/Types/ConformanceLensType.swift +++ b/Sources/FrontEnd/Types/ConformanceLensType.swift @@ -7,7 +7,7 @@ public struct ConformanceLensType: TypeProtocol { /// The trait through which the subject is viewed. public let lens: TraitType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates a new conformance lens. public init(viewing subject: AnyType, through lens: TraitType) { diff --git a/Sources/FrontEnd/Types/ErrorType.swift b/Sources/FrontEnd/Types/ErrorType.swift index c43d3f7fb..51fde710a 100644 --- a/Sources/FrontEnd/Types/ErrorType.swift +++ b/Sources/FrontEnd/Types/ErrorType.swift @@ -1,7 +1,7 @@ /// A type denoting a type checking error. public struct ErrorType: TypeProtocol { - public var flags: TypeFlags { [.isCanonical, .hasError] } + public var flags: ValueFlags { .hasError } } diff --git a/Sources/FrontEnd/Types/ExistentialType.swift b/Sources/FrontEnd/Types/ExistentialType.swift index 6e12804bf..f2f82044c 100644 --- a/Sources/FrontEnd/Types/ExistentialType.swift +++ b/Sources/FrontEnd/Types/ExistentialType.swift @@ -16,14 +16,14 @@ public struct ExistentialType: TypeProtocol { case metatype /// A set of flags describing recursive properties. - public var flags: TypeFlags { + public var flags: ValueFlags { switch self { case .traits(let ts): - return .init(merging: ts.map(\.flags)) + return .init(ts.map(\.flags)) case .generic(let t): return t.flags case .metatype: - return .isCanonical + return .init() } } @@ -37,7 +37,7 @@ public struct ExistentialType: TypeProtocol { /// - Note: This set shall only contain equality and conformance constraints. public let constraints: Set - public let flags: TypeFlags + public let flags: ValueFlags /// Creates a new existential type bound by the given interface and constraints. public init(_ interface: Interface, constraints: Set) { diff --git a/Sources/FrontEnd/Types/GenericEnvironment.swift b/Sources/FrontEnd/Types/GenericEnvironment.swift index 395311a37..8eec3aa60 100644 --- a/Sources/FrontEnd/Types/GenericEnvironment.swift +++ b/Sources/FrontEnd/Types/GenericEnvironment.swift @@ -1,163 +1,88 @@ -import Algorithms import Utils /// Context to interpret the generic parameters of a declaration. public struct GenericEnvironment { - /// An equivalence class and its associated conformances. - private struct EquivalenceClass: Equatable { - - /// A set of types known to be equivalent to each others. - var equivalences: Set = [] - - /// The set of traits to which types in this class are known to conform. - var conformances: Set = [] - - } - /// The declaration associated with the environment. public let decl: AnyDeclID /// The generic parameters introduced in the environment, in the order there declaration appears /// in Hylo sources. - public private(set) var parameters: [GenericParameterDecl.ID] + private(set) var parameters: [GenericParameterDecl.ID] + + /// The traits for which a conformance can be derived given the environment. + private(set) var dependencies: [TraitType] = [] /// The uninstantiated type constraints. - public private(set) var constraints: [GenericConstraint] = [] + private(set) var constraints: [GenericConstraint] = [] - /// A table from types to their entry. - private var ledger: [AnyType: Int] = [:] + /// The properties of the generic parameters visible in the environment. + var requirements = RequirementSystem() - /// The equivalence classes and their associated conformance sets. - private var entries: [EquivalenceClass] = [] + /// The index of the first public rule. + private(set) var publicStart = 0 - /// Creates an environment that introduces `parameters` in a declaration space. - public init(of id: AnyDeclID, introducing parameters: [GenericParameterDecl.ID]) { - self.decl = id + /// Creates an empty environment associated with `d`, which introduces `parameters`. + public init(of d: AnyDeclID, introducing parameters: [GenericParameterDecl.ID]) { + self.decl = d self.parameters = parameters } - /// `true` if this environment doesn't introduce any generic parameter or constraint. - public var isEmpty: Bool { - parameters.isEmpty && constraints.isEmpty + /// The non-inherited requirement rules in the environment. + var publicRules: some Collection { + requirements.rules[publicStart...].lazy.filter({ (r) in !r.isSimplified }) } /// Returns the set of traits to which `type` conforms in t`self`. - public func conformedTraits(of type: AnyType) -> Set { - if let i = ledger[type] { - return entries[i].conformances - } else { - return [] + func conformedTraits(of t: AnyType, querying checker: inout TypeChecker) -> [TraitType] { + var result: [TraitType] = [] + let v = checker.buildTerm(^t) + let w = requirements.reduce(v) + for c in dependencies { + let u = v.appending(.trait(c.decl)) + if requirements.reduce(u) == w { + result.append(c) + } } + return result } /// Returns `true` iff `t` is known to be semantically equivalent to `u` in this environment. - public func areEquivalent(_ t: AnyType, _ u: AnyType) -> Bool { - if let i = ledger[t] { - return entries[i].equivalences.contains(u) - } else { - return t == u - } + func areEquivalent(_ t: AnyType, _ u: AnyType, querying checker: inout TypeChecker) -> Bool { + let a = checker.buildTerm(t) + let b = checker.buildTerm(u) + return requirements.reduce(a) == requirements.reduce(b) } - /// Inserts constraint `c` to the environment, updating equivalence classes. - public mutating func insertConstraint(_ c: GenericConstraint) { - // TODO: Eliminate duplicates - constraints.append(c) - switch c.value { - case .equality(let lhs, let rhs): - establishEquivalence(lhs, rhs) - case .conformance(let lhs, let rhs): - establishConformance(lhs, to: rhs) - default: - break - } - } - - /// Registers the fact that `l` is equivalent to `r` in the context of this environment. - public mutating func establishEquivalence(_ l: AnyType, _ r: AnyType) { - if let i = ledger[l] { - // `l` is part of a class. - if let j = ledger[r] { - // `r` is part of a class too; merge the entries. - entries[i].equivalences.formUnion(entries[j].equivalences) - entries[i].conformances.formUnion(entries[j].conformances) - entries[j] = .init() - } else { - // `r` isn't part of a class. - entries[i].equivalences.insert(r) - } + /// Add the parameters, dependencies, and constraints of `base` to the environment. + mutating func registerInheritance(_ base: GenericEnvironment) { + // Parameters of the inherited environment occur first. + var ps = base.parameters + ps.append(disjointContentsOf: parameters) + self.parameters = ps - // Update the ledger for `r`. - ledger[r] = i - } else if let j = ledger[r] { - // `l` isn't part of a class, but `r` is. - ledger[l] = j - entries[j].equivalences.insert(l) - } else { - // Neither `l` nor `r` are part of a class. - ledger[l] = entries.count - ledger[l] = entries.count - entries.append(.init(equivalences: [l, r], conformances: [])) - } + registerDependencies(base.dependencies) + registerConstraints(base.constraints) } - /// Registers the fact that `l` conforms to `r` in the context of this environment. - public mutating func establishConformance(_ l: AnyType, to r: TraitType) { - if let i = ledger[l] { - // `l` is part of a class. - entries[i].conformances.insert(r) - } else { - // `l` isn't part of a class. - ledger[l] = entries.count - entries.append(.init(equivalences: [l], conformances: [r])) - } + /// Adds the contents of `ds` to the trait dependencies of the environment. + mutating func registerDependencies(_ ds: [TraitType]) { + dependencies.append(disjointContentsOf: ds) } - /// Inserts in this environment the parameters and constraints introduced by `parent`. - /// - /// If both `self` and `parent` are associated with traits, the receiver parameter of the latter - /// is added to the equivalence class of the former in `self` rather than inserted as a parameter - /// introduced by `self`. - /// - /// - Parameters: - /// - parent A generic environment from which `self` inherits, either because `self` is - /// notionally contained in `parent` or because `self` and `parent` are trait environments - /// and `self` refines `parent`. - mutating func extend(_ parent: Self, in ast: AST) { - // Fast path: `parent` is empty. - if parent.isEmpty { - return - } - - if let t = TraitDecl.ID(decl), let u = TraitDecl.ID(parent.decl) { - establishEquivalence( - ^GenericTypeParameterType(selfParameterOf: t, in: ast), - ^GenericTypeParameterType(selfParameterOf: u, in: ast)) - } else { - // Order parameters introduced by `parent` first. - parameters = Array( - chain(parent.parameters, parameters.lazy.filter({ !parent.parameters.contains($0) }))) - } + /// Adds the contents of `cs` to the constraints of the environment. + mutating func registerConstraints(_ cs: [GenericConstraint]) { + constraints.append(contentsOf: cs) + } - // Merge equivalence classes. - if ledger.isEmpty { - ledger = parent.ledger - entries = parent.entries - } else { - for (t, i) in parent.ledger { - if let j = ledger[t] { - entries[j].equivalences.formUnion(parent.entries[i].equivalences) - entries[j].conformances.formUnion(parent.entries[i].conformances) - } else { - ledger[t] = entries.count - entries.append(parent.entries[i]) - } - } - } + /// Adds `c` to the constraints of the environment. + mutating func registerConstraint(_ c: GenericConstraint) { + constraints.append(c) + } - // Merge user constraints. - constraints.append(contentsOf: parent.constraints) + /// Marks all the requirement rules currently in the environment as inherited. + mutating func markRequirementsAsInherited() { + publicStart = requirements.rules.count } } @@ -165,16 +90,23 @@ public struct GenericEnvironment { extension GenericEnvironment: Equatable { public static func == (l: Self, r: Self) -> Bool { - guard l.parameters == r.parameters else { return false } + // TODO: What kind of equivalence can we reasonably implement? + l.decl == r.decl + } - var s = Set(0 ..< r.entries.count) - for (t, i) in l.ledger { - guard let j = r.ledger[t] else { return false } - guard l.entries[i] == r.entries[j] else { return false } - s.remove(j) - } +} - return s.isEmpty +extension Array where Element: Equatable { + + /// Appends to `self` the elements in `s` that not contained in `self`. + /// + /// - Complexity: O(*m* * *n*) where *m* is the length of `s` and *n* is the length of `self`. + fileprivate mutating func append>(disjointContentsOf s: S) { + let r = self[...] + reserveCapacity(r.count + s.underestimatedCount) + for e in s where !r.contains(e) { + append(e) + } } } diff --git a/Sources/FrontEnd/Types/GenericTypeParameterType.swift b/Sources/FrontEnd/Types/GenericTypeParameterType.swift index b3f1a1ab1..215d83750 100644 --- a/Sources/FrontEnd/Types/GenericTypeParameterType.swift +++ b/Sources/FrontEnd/Types/GenericTypeParameterType.swift @@ -20,7 +20,7 @@ public struct GenericTypeParameterType: TypeProtocol { self.init(ast[trait].receiver, ast: ast) } - public var flags: TypeFlags { [.isCanonical, .hasGenericTypeParameter] } + public var flags: ValueFlags { .hasSkolem } } diff --git a/Sources/FrontEnd/Types/MetatypeType.swift b/Sources/FrontEnd/Types/MetatypeType.swift index 69f07df75..b8480245a 100644 --- a/Sources/FrontEnd/Types/MetatypeType.swift +++ b/Sources/FrontEnd/Types/MetatypeType.swift @@ -14,7 +14,7 @@ public struct MetatypeType: TypeProtocol { self.instance = instance } - public var flags: TypeFlags { instance.flags } + public var flags: ValueFlags { instance.flags } public func transformParts( mutating m: inout M, _ transformer: (inout M, AnyType) -> TypeTransformAction diff --git a/Sources/FrontEnd/Types/MethodType.swift b/Sources/FrontEnd/Types/MethodType.swift index 09ce71d1d..e97c97fa0 100644 --- a/Sources/FrontEnd/Types/MethodType.swift +++ b/Sources/FrontEnd/Types/MethodType.swift @@ -15,7 +15,7 @@ public struct MethodType: TypeProtocol { /// The output type of the method. public let output: AnyType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init( @@ -28,11 +28,7 @@ public struct MethodType: TypeProtocol { self.receiver = receiver self.inputs = inputs self.output = output - - var fs = receiver.flags - for i in inputs { fs.merge(i.type.flags) } - fs.merge(output.flags) - flags = fs + self.flags = inputs.reduce(output.flags | receiver.flags, { (fs, p) in fs | p.type.flags }) } /// Returns the type of the variant `k` in this bundle. diff --git a/Sources/FrontEnd/Types/ModuleType.swift b/Sources/FrontEnd/Types/ModuleType.swift index f3c3f98df..3dacfe07f 100644 --- a/Sources/FrontEnd/Types/ModuleType.swift +++ b/Sources/FrontEnd/Types/ModuleType.swift @@ -15,7 +15,7 @@ public struct ModuleType: TypeProtocol { self.name = Incidental(ast[decl].baseName) } - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } } diff --git a/Sources/FrontEnd/Types/NamespaceType.swift b/Sources/FrontEnd/Types/NamespaceType.swift index 186f6129b..05d9f9517 100644 --- a/Sources/FrontEnd/Types/NamespaceType.swift +++ b/Sources/FrontEnd/Types/NamespaceType.swift @@ -15,7 +15,7 @@ public struct NamespaceType: TypeProtocol { self.name = Incidental(ast[decl].baseName) } - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } } diff --git a/Sources/FrontEnd/Types/ParameterType.swift b/Sources/FrontEnd/Types/ParameterType.swift index 08ce30ae0..0888641aa 100644 --- a/Sources/FrontEnd/Types/ParameterType.swift +++ b/Sources/FrontEnd/Types/ParameterType.swift @@ -12,7 +12,7 @@ public struct ParameterType: TypeProtocol { /// `true` iff the parameter is an autoclosure. public let isAutoclosure: Bool - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init(_ access: AccessEffect, _ bareType: AnyType, isAutoclosure: Bool = false) { diff --git a/Sources/FrontEnd/Types/ProductType.swift b/Sources/FrontEnd/Types/ProductType.swift index 2d38a396a..537ceb48e 100644 --- a/Sources/FrontEnd/Types/ProductType.swift +++ b/Sources/FrontEnd/Types/ProductType.swift @@ -9,15 +9,14 @@ public struct ProductType: TypeProtocol { /// The name of the product type. public let name: Incidental - public let flags: TypeFlags - /// Creates an instance denoting the product type declared by `decl`. public init(_ decl: ProductTypeDecl.ID, ast: AST) { self.decl = decl self.name = Incidental(ast[decl].baseName) - self.flags = TypeFlags.isCanonical } + public var flags: ValueFlags { .init() } + } extension ProductType: CustomStringConvertible { diff --git a/Sources/FrontEnd/Types/RemoteType.swift b/Sources/FrontEnd/Types/RemoteType.swift index 82a721320..a1379b39d 100644 --- a/Sources/FrontEnd/Types/RemoteType.swift +++ b/Sources/FrontEnd/Types/RemoteType.swift @@ -7,13 +7,13 @@ public struct RemoteType: TypeProtocol { /// The type of the projected object. public let bareType: AnyType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init(_ access: AccessEffect, _ bareType: AnyType) { self.access = access self.bareType = bareType - self.flags = bareType.flags.inserting(.hasRemoteType) + self.flags = bareType.flags } /// Creates an instance converting `t`. diff --git a/Sources/FrontEnd/Types/SubscriptImplType.swift b/Sources/FrontEnd/Types/SubscriptImplType.swift index 98830a9c8..00ad2ee2d 100644 --- a/Sources/FrontEnd/Types/SubscriptImplType.swift +++ b/Sources/FrontEnd/Types/SubscriptImplType.swift @@ -16,7 +16,7 @@ public struct SubscriptImplType: TypeProtocol { /// The type of the value projected by the subscript implementation. public let output: AnyType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init( @@ -31,11 +31,7 @@ public struct SubscriptImplType: TypeProtocol { self.environment = environment self.inputs = inputs self.output = output - - var fs = environment.flags - for i in inputs { fs.merge(i.type.flags) } - fs.merge(output.flags) - flags = fs + self.flags = inputs.reduce(output.flags | environment.flags, { (fs, p) in fs | p.type.flags }) } /// Indicates whether `self` has an empty environment. @@ -55,16 +51,6 @@ public struct SubscriptImplType: TypeProtocol { output: output.transform(mutating: &m, transformer)) } - private static func makeTransformer() -> (inout M, AnyType) -> TypeTransformAction { - { (m, t) in - if let type = RemoteType(t), type.access == .yielded { - return .stepInto(^RemoteType(.let, type.bareType)) - } else { - return .stepInto(t) - } - } - } - } extension SubscriptImplType: CustomStringConvertible { diff --git a/Sources/FrontEnd/Types/SubscriptType.swift b/Sources/FrontEnd/Types/SubscriptType.swift index 960a8f239..a0a68a29e 100644 --- a/Sources/FrontEnd/Types/SubscriptType.swift +++ b/Sources/FrontEnd/Types/SubscriptType.swift @@ -18,7 +18,7 @@ public struct SubscriptType: TypeProtocol { /// The type of the value projected by the subscript. public let output: AnyType - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance with the given properties. public init( @@ -33,11 +33,7 @@ public struct SubscriptType: TypeProtocol { self.environment = environment self.inputs = inputs self.output = output - - var fs = environment.flags - for i in inputs { fs.merge(i.type.flags) } - fs.merge(output.flags) - flags = fs + self.flags = inputs.reduce(output.flags | environment.flags, { (fs, p) in fs | p.type.flags }) } /// Accesses the individual elements of the subscript's environment. diff --git a/Sources/FrontEnd/Types/TraitType.swift b/Sources/FrontEnd/Types/TraitType.swift index 0b2c1175a..c4d45933c 100644 --- a/Sources/FrontEnd/Types/TraitType.swift +++ b/Sources/FrontEnd/Types/TraitType.swift @@ -15,7 +15,7 @@ public struct TraitType: TypeProtocol { self.name = Incidental(ast[decl].baseName) } - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } } diff --git a/Sources/FrontEnd/Types/TupleType.swift b/Sources/FrontEnd/Types/TupleType.swift index 86db2f8b7..6b26c60a8 100644 --- a/Sources/FrontEnd/Types/TupleType.swift +++ b/Sources/FrontEnd/Types/TupleType.swift @@ -23,12 +23,12 @@ public struct TupleType: TypeProtocol { /// The elements of the tuple. public let elements: [Element] - public let flags: TypeFlags + public let flags: ValueFlags /// Creates a tuple type with a sequence of elements. public init(_ elements: S) where S.Element == Element { self.elements = Array(elements) - self.flags = TypeFlags(merging: self.elements.map(\.type.flags)) + self.flags = ValueFlags(self.elements.map(\.type.flags)) } /// Creates a tuple type with a sequence of label-type pairs. @@ -49,10 +49,9 @@ public struct TupleType: TypeProtocol { public func transformParts( mutating m: inout M, _ transformer: (inout M, AnyType) -> TypeTransformAction ) -> Self { - let newElements = elements.map( - { (e) -> Element in - .init(label: e.label, type: e.type.transform(mutating: &m, transformer)) - }) + let newElements = elements.map { (e) -> Element in + .init(label: e.label, type: e.type.transform(mutating: &m, transformer)) + } return TupleType(newElements) } diff --git a/Sources/FrontEnd/Types/TypeAliasType.swift b/Sources/FrontEnd/Types/TypeAliasType.swift index 9650df7df..bd94c7cad 100644 --- a/Sources/FrontEnd/Types/TypeAliasType.swift +++ b/Sources/FrontEnd/Types/TypeAliasType.swift @@ -13,7 +13,7 @@ public struct TypeAliasType: TypeProtocol { public let aliasee: Incidental /// A set of flags describing recursive properties. - public let flags: TypeFlags + public let flags: ValueFlags /// Creates a type alias resolving to `aliasee` and declared by `d` in `ast`. public init(aliasing aliasee: AnyType, declaredBy d: TypeAliasDecl.ID, in ast: AST) { @@ -25,7 +25,7 @@ public struct TypeAliasType: TypeProtocol { self.decl = decl self.name = Incidental(name) self.aliasee = Incidental(aliasee) - self.flags = aliasee.flags.removing(.isCanonical) + self.flags = aliasee.flags | .hasNonCanonical } /// The transitive aliasee of this alias. diff --git a/Sources/FrontEnd/Types/TypeFlags.swift b/Sources/FrontEnd/Types/TypeFlags.swift deleted file mode 100644 index 4c88e12d0..000000000 --- a/Sources/FrontEnd/Types/TypeFlags.swift +++ /dev/null @@ -1,102 +0,0 @@ -/// A set of type flags. -public struct TypeFlags: Hashable { - - /// Universal flags. - private var universal: UInt8 - - /// Existential flags. - private var existential: UInt8 - - private init(universal: UInt8, existential: UInt8) { - self.universal = universal - self.existential = existential - } - - /// Create a new set of type flags that merges all sets in `elements`. - /// - /// - Note: `self` only contains `.isCanonical` if `elements` is empty. - public init(merging elements: C) where C.Element == TypeFlags { - if let first = elements.first { - self = elements.dropFirst().reduce(into: first, { (a, b) in a.merge(b) }) - } else { - self = .isCanonical - } - } - - /// Returns whether the set contains all the specified flags. - public func contains(_ flags: TypeFlags) -> Bool { - (universal & flags.universal == flags.universal) - && (existential & flags.existential == flags.existential) - } - - /// Inserts the specified flags. - public mutating func insert(_ flags: TypeFlags) { - universal = universal | flags.universal - existential = existential | flags.existential - } - - /// Returns a set of flags in which `flags` have been inserted. - public func inserting(_ flags: TypeFlags) -> TypeFlags { - var newFlags = self - newFlags.insert(flags) - return newFlags - } - - /// Removes the specified flags. - public mutating func remove(_ flags: TypeFlags) { - universal = universal & ~flags.universal - existential = existential & ~flags.existential - } - - /// Returns a set of flags in which `flags` have been removed. - public func removing(_ flags: TypeFlags) -> TypeFlags { - var newFlags = self - newFlags.remove(flags) - return newFlags - } - - /// Merge this set of flags with another set. - public mutating func merge(_ flags: TypeFlags) { - universal = universal & flags.universal - existential = existential | flags.existential - } - - /// Returns a set of flags in which `flags` have been merged. - public func merging(_ flags: TypeFlags) -> TypeFlags { - var newFlags = self - newFlags.merge(flags) - return newFlags - } - - /// The type is in canonical from. - public static let isCanonical = TypeFlags(universal: 1 << 0, existential: 0) - - /// The type contains one or more error types. - public static let hasError = TypeFlags(universal: 0, existential: 1 << 0) - - /// Te type contains one or more type variables. - public static let hasVariable = TypeFlags(universal: 0, existential: 1 << 1) - - /// The type contains one or more generic type parameters. - public static let hasGenericTypeParameter = TypeFlags(universal: 0, existential: 1 << 2) - - /// The type contains one or more generic value parameters. - public static let hasGenericValueParameter = TypeFlags(universal: 0, existential: 1 << 3) - - /// The type contains one or more remote types. - public static let hasRemoteType = TypeFlags(universal: 0, existential: 1 << 4) - -} - -extension TypeFlags: ExpressibleByArrayLiteral { - - public init(arrayLiteral elements: TypeFlags...) { - universal = 0 - existential = 0 - for e in elements { - universal |= e.universal - existential |= e.existential - } - } - -} diff --git a/Sources/FrontEnd/Types/TypeProtocol.swift b/Sources/FrontEnd/Types/TypeProtocol.swift index c4341c134..cd152c67a 100644 --- a/Sources/FrontEnd/Types/TypeProtocol.swift +++ b/Sources/FrontEnd/Types/TypeProtocol.swift @@ -3,8 +3,8 @@ import Utils /// A protocol describing the API of a Hylo type. public protocol TypeProtocol: Hashable { - /// A set of flags describing recursive properties. - var flags: TypeFlags { get } + /// Properties about the representation of `self`. + var flags: ValueFlags { get } /// Apply `transform(_:_:)` on `m` and the types that are part of `self`. func transformParts( @@ -36,7 +36,12 @@ extension TypeProtocol { } /// Returns whether the specified flags are raised on this type. - public subscript(fs: TypeFlags) -> Bool { flags.contains(fs) } + public subscript(fs: ValueFlags) -> Bool { flags.contains(fs) } + + /// Indicates whether `self` is canonical. + public var isCanonical: Bool { + !self[.hasNonCanonical] + } /// Returns this type transformed with `transformer` applied to `m`. /// diff --git a/Sources/FrontEnd/Types/TypeVariable.swift b/Sources/FrontEnd/Types/TypeVariable.swift index 4150318cc..ff4ceecfd 100644 --- a/Sources/FrontEnd/Types/TypeVariable.swift +++ b/Sources/FrontEnd/Types/TypeVariable.swift @@ -6,14 +6,13 @@ public struct TypeVariable: TypeProtocol { /// The identifier of the variable. public let rawValue: UInt64 - /// A set of flags describing recursive properties. - public let flags: TypeFlags = [.isCanonical, .hasVariable] - /// Creates an instance with given `rawValue`. public init(_ rawValue: UInt64) { self.rawValue = rawValue } + public var flags: ValueFlags { .hasVariable } + /// The context in which this instance was created. var context: UInt8 { UInt8(rawValue >> 56) } diff --git a/Sources/FrontEnd/Types/UnionType.swift b/Sources/FrontEnd/Types/UnionType.swift index a93c89b74..9034057f0 100644 --- a/Sources/FrontEnd/Types/UnionType.swift +++ b/Sources/FrontEnd/Types/UnionType.swift @@ -15,15 +15,15 @@ public struct UnionType: TypeProtocol { /// The elements of the union. public let elements: Elements - public let flags: TypeFlags + public let flags: ValueFlags /// Creates an instance type with the specified elements. public init(_ elements: S) where S.Element == AnyType { self.elements = Array(elements) - var fs = TypeFlags(merging: self.elements.map(\.flags)) + var fs = ValueFlags(self.elements.map(\.flags)) if self.elements.count == 1 { - fs.remove(.isCanonical) + fs.insert(.hasNonCanonical) } self.flags = fs } diff --git a/Sources/FrontEnd/Types/WtinessType.swift b/Sources/FrontEnd/Types/WtinessType.swift index 04436ff86..87d3cbd41 100644 --- a/Sources/FrontEnd/Types/WtinessType.swift +++ b/Sources/FrontEnd/Types/WtinessType.swift @@ -10,7 +10,7 @@ public struct WitnessType: TypeProtocol { } /// A set of flags describing recursive properties. - public var flags: TypeFlags { container.flags } + public var flags: ValueFlags { container.flags } /// Apply `transform(_:_:)` on `m` and the types that are part of `self`. public func transformParts( diff --git a/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift b/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift index 1940502a4..3f2ec4f61 100644 --- a/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift +++ b/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift @@ -2,8 +2,8 @@ import ArgumentParser import Foundation import Utils -/// A command-line tool that generates XCTest cases for a list of annotated ".hylo" -/// files as part of our build process. +/// A command-line tool that generates XCTest cases for a list of annotated ".hylo" files as part +/// of our build process. @main struct GenerateHyloFileTests: ParsableCommand { @@ -23,19 +23,21 @@ struct GenerateHyloFileTests: ParsableCommand { transform: URL.init(fileURLWithPath:)) var hyloSourceFiles: [URL] - /// Returns the Swift source of the test function for the Hylo file at `source`. - func swiftFunctionTesting(valAt source: URL) throws -> String { + /// Returns the Swift source of the test function for the Hylo program at `source`, which is the + /// URL of a single source file or the root directory of a module. + func swiftFunctionTesting(hyloProgramAt source: URL) throws -> String { let firstLine = try String(contentsOf: source).prefix { !$0.isNewline } let parsed = try firstLine.parsedAsFirstLineOfAnnotatedHyloFileTest() let testID = source.deletingPathExtension().lastPathComponent.asSwiftIdentifier - return parsed.reduce(into: "") { (o, p) in - o += """ + return parsed.reduce(into: "") { (swiftCode, test) in + let trailing = test.arguments.reduce(into: "", { (s, a) in s.write(", \(a)") }) + swiftCode += """ - func test_\(p.methodName)_\(testID)() throws { - try \(p.methodName)( + func test_\(test.methodName)_\(testID)() throws { + try \(test.methodName)( \(String(reflecting: source.fileSystemPath)), - expectSuccess: \(p.expectSuccess)) + extending: programToExtend!\(trailing)) } """ @@ -45,16 +47,16 @@ struct GenerateHyloFileTests: ParsableCommand { func run() throws { var output = """ - import XCTest import TestUtils + import XCTest - final class \(testCaseName.asSwiftIdentifier): XCTestCase { + final class \(testCaseName.asSwiftIdentifier): HyloTestCase { """ for f in hyloSourceFiles { do { - output += try swiftFunctionTesting(valAt: f) + output += try swiftFunctionTesting(hyloProgramAt: f) } catch let e as FirstLineError { try! FileHandle.standardError.write( contentsOf: Data("\(f.fileSystemPath):1: error: \(e.details)\n".utf8)) @@ -87,50 +89,116 @@ extension StringProtocol where Self.SubSequence == Substring { fileprivate func parsedAsFirstLineOfAnnotatedHyloFileTest() throws -> [TestDescription] { var text = self[...] if !text.removeLeading("//- ") { - throw FirstLineError("first line of annotated test file must begin with “//-”.") + throw FirstLineError("first line of annotated test file must begin with '//-'") } - let methodName = text.removeFirstUntil(it: \.isWhitespace) - if methodName.isEmpty { - throw FirstLineError("missing test method name.") + let m = text.removeFirstUntil(it: \.isWhitespace) + guard let methodName = TestMethod(rawValue: String(m)) else { + let s = m.isEmpty ? "missing test method name" : "unknown test method '\(m)'" + throw FirstLineError(s) } - text.removeFirstWhile(it: \.isWhitespace) - if !text.removeLeading("expecting:") { - throw FirstLineError("missing “expecting:” after test method name.") + var arguments: [TestArgument] = [] + while !text.isEmpty { + // Parse a label. + text.removeFirstWhile(it: \.isWhitespace) + let l = text.removeFirstUntil(it: { $0 == ":" }) + if l.isEmpty { + break + } else if !text.removeLeading(":") { + throw FirstLineError("missing colon after argument label") + } + + // Parse a value. + text.removeFirstWhile(it: \.isWhitespace) + let v = text.removeFirstUntil(it: \.isWhitespace) + if v.isEmpty { throw FirstLineError("missing value after '\(l):'") } + + if let a = TestArgument(l, v) { + arguments.append(a) + } else { + throw FirstLineError("invalid argument '\(l): \(v)'") + } } - text.removeFirstWhile(it: \.isWhitespace) - let expectation = text.removeFirstUntil(it: \.isWhitespace) - if expectation != "success" && expectation != "failure" { - throw FirstLineError( - "illegal expectation “\(expectation)” must be “success” or “failure”." - ) + var tests = [TestDescription(methodName: methodName, arguments: arguments)] + if methodName == .compileAndRun { + tests.append(TestDescription(methodName: .compileAndRunOptimized, arguments: arguments)) } - let expectSuccess = expectation == "success" + return tests + } + +} + +/// The name of a method implementing the logic of a test runner. +fileprivate enum TestMethod: String { + + /// Compiles and runs the program. + case compileAndRun + + /// Compiles and runs the program with optimizations. + case compileAndRunOptimized + + /// Compiles the program down to LLVM IR. + case compileToLLVM + + /// Compiles the program down to Hylo IR. + case lowerToFinishedIR - if !text.drop(while: \.isWhitespace).isEmpty { - throw FirstLineError("illegal trailing text “\(text)”.") + /// Parses the program. + case parse + + /// Type checks the program. + case typeCheck + +} + +/// An argument of a test runner. +fileprivate struct TestArgument: CustomStringConvertible { + + /// The label of an argument. + enum Label: String { + + /// The label of an argument specifying the expected outcome of the test. + case expecting + + } + + /// The label of the argument. + let label: Label + + /// The value of the argument. + let value: String + + /// Creates an instance with the given properties or returns `nil` if the argument is invalid. + init?(_ label: Substring, _ value: Substring) { + // Validate the label. + guard let l = Label(rawValue: String(label)) else { + return nil } - var tests = [TestDescription(methodName: methodName, expectSuccess: expectSuccess)] - if methodName == "compileAndRun" { - tests.append( - .init(methodName: "compileAndRunWithOptimizations", expectSuccess: expectSuccess)) + // Validate the value. + switch l { + case .expecting: + if (value != ".success") && (value != ".failure") { return nil } } - return tests + + self.label = l + self.value = String(value) } + var description: String { "\(label): \(value)" } + } /// Information necessary to generate a test case. -fileprivate struct TestDescription { +private struct TestDescription { /// The name of the method implementing the logic of the test runner. - let methodName: Substring + let methodName: TestMethod - /// `true` iff the invoked compilation is expected to succeed. - let expectSuccess: Bool + /// The arguments of the method. + let arguments: [TestArgument] } diff --git a/Sources/IR/Analysis/Module+AccessReification.swift b/Sources/IR/Analysis/Module+AccessReification.swift index 2c64b5389..c788ff25b 100644 --- a/Sources/IR/Analysis/Module+AccessReification.swift +++ b/Sources/IR/Analysis/Module+AccessReification.swift @@ -57,18 +57,20 @@ extension Module { while let i = work.popFirst() { let s = self[i] as! ReifiableAccess let available = s.capabilities - var requested: AccessEffectSet = [available.weakest!] + assert(!available.isSingleton, "access already reified") + + var lower = AccessEffect.let + var upper = AccessEffect.let forEachClient(of: i) { (u) in - let r = requests(u).intersection(available) - if let k = r.uniqueElement { - requested = [requested.strongest(including: k)] - } else { - requested.formUnion(r) - } + let rs = requests(u) + if let w = rs.weakest { lower = max(w, lower) } + upper = rs.strongest(including: upper) } - if let k = requested.uniqueElement { + if lower == upper { + // We have to "promote" a request if it can be satisfied by a stronger capability. + let k = available.elements.first(where: { (a) in a >= lower }) ?? available.weakest! reify(i, as: k) } else { work.append(i) diff --git a/Sources/IR/Analysis/Module+CloseBorrows.swift b/Sources/IR/Analysis/Module+CloseBorrows.swift index 9ebc1b9a9..b95c03c0d 100644 --- a/Sources/IR/Analysis/Module+CloseBorrows.swift +++ b/Sources/IR/Analysis/Module+CloseBorrows.swift @@ -43,6 +43,12 @@ extension Module { this.makeCloseCapture(.register(i), at: site) } + case is OpenUnion: + let region = extendedLiveRange(of: .register(i)) + insertClose(i, atBoundariesOf: region) { (this, site) in + this.makeCloseUnion(.register(i), at: site) + } + case is Project: let region = extendedLiveRange(of: .register(i)) insertClose(i, atBoundariesOf: region) { (this, site) in diff --git a/Sources/IR/Analysis/Module+Depolymorphize.swift b/Sources/IR/Analysis/Module+Depolymorphize.swift index 4e8539c9a..e1e3cbb06 100644 --- a/Sources/IR/Analysis/Module+Depolymorphize.swift +++ b/Sources/IR/Analysis/Module+Depolymorphize.swift @@ -79,7 +79,8 @@ extension IR.Program { // TODO: Use existentialization unless the subscript is inlinable - let g = monomorphize(s.callee, for: s.specialization, usedIn: modules[m]!.scope(containing: i)) + let z = base.canonical(s.specialization, in: modules[m]!.scope(containing: i)) + let g = monomorphize(s.callee, for: z, usedIn: modules[m]!.scope(containing: i)) let new = modules[m]!.makeProject( s.projection, applying: g, specializedBy: .empty, to: s.operands, at: s.site) modules[m]!.replace(i, with: new) @@ -112,13 +113,13 @@ extension IR.Program { monomorphize(r.function, for: r.specialization, usedIn: scopeOfUse) } - /// Returns a reference to the monomorphized form of `f` for `specialization` in `scopeOfUse`, + /// Returns a reference to the monomorphized form of `f` for specialization `z` in `scopeOfUse`, /// reading definitions from `ir`. fileprivate mutating func monomorphize( - _ f: Function.ID, - for specialization: GenericArguments, usedIn scopeOfUse: AnyScopeID + _ f: Function.ID, for z: GenericArguments, usedIn scopeOfUse: AnyScopeID ) -> Function.ID { - let result = demandMonomorphizedDeclaration(of: f, for: specialization, usedIn: scopeOfUse) + precondition(z.allSatisfy(\.value.isCanonical)) + let result = demandMonomorphizedDeclaration(of: f, for: z, usedIn: scopeOfUse) let target = base.module(containing: scopeOfUse) if modules[target]![result].entry != nil { @@ -130,16 +131,15 @@ extension IR.Program { for b in modules[source]![f].blocks.addresses { let s = Block.ID(f, b) let inputs = modules[source]![s].inputs.map { (t) in - monomorphize(t, for: specialization, usedIn: scopeOfUse) + monomorphize(t, for: z, usedIn: scopeOfUse) } let t = modules[target]![result].appendBlock(in: modules[source]![s].scope, taking: inputs) rewrittenBlock[s] = Block.ID(result, t) } - let rewrittenGenericValue = modules[target]!.defineGenericValueArguments( - specialization, in: result) + let rewrittenGenericValue = modules[target]!.defineGenericValueArguments(z, in: result) var monomorphizer = Monomorphizer( - specialization: specialization, scopeOfUse: scopeOfUse, + specialization: z, scopeOfUse: scopeOfUse, rewrittenGenericValue: rewrittenGenericValue, rewrittenBlock: rewrittenBlock) // Iterate over the basic blocks of the source function in a way that guarantees we always @@ -243,28 +243,28 @@ extension IR.Program { return .init(ast: u, isAddress: t.isAddress) } - /// Returns the IR function monomorphizing `f` for `specialization` in `scopeOfUse`. + /// Returns the IR function monomorphizing `f` for specialization `z` in `scopeOfUse`. private mutating func demandMonomorphizedDeclaration( - of f: Function.ID, for specialization: GenericArguments, usedIn scopeOfUse: AnyScopeID + of f: Function.ID, for z: GenericArguments, usedIn scopeOfUse: AnyScopeID ) -> Function.ID { let source = modules[module(defining: f)]![f] let target = base.module(containing: scopeOfUse) precondition(!source.genericParameters.isEmpty, "function is not generic") precondition( - source.genericParameters.allSatisfy({ specialization[$0] != nil }), + source.genericParameters.allSatisfy({ z[$0] != nil }), "incomplete monomorphization arguments") - let result = Function.ID(monomorphized: f, for: specialization) + let result = Function.ID(monomorphized: f, for: z) if modules[target]!.functions[result] != nil { return result } let inputs = source.inputs.map { (p) in - let t = monomorphize(p.type.bareType, for: specialization, usedIn: scopeOfUse) + let t = monomorphize(p.type.bareType, for: z, usedIn: scopeOfUse) return Parameter(decl: p.decl, type: ParameterType(p.type.access, t)) } - let output = monomorphize(source.output, for: specialization, usedIn: scopeOfUse) + let output = monomorphize(source.output, for: z, usedIn: scopeOfUse) let entity = Function( isSubscript: source.isSubscript, site: source.site, @@ -395,10 +395,11 @@ private struct Monomorphizer: InstructionTransformer { _ f: Function.ID, specializedBy z: GenericArguments, in ir: inout IR.Program ) -> Function.ID { let p = ir.base.specialize(z, for: specialization, in: scopeOfUse) + let q = ir.base.canonical(p, in: scopeOfUse) if let m = ir.base.requirementDeclaring(memberReferredBy: f) { - return ir.monomorphize(m.decl, requiredBy: m.trait, for: p, usedIn: scopeOfUse) + return ir.monomorphize(m.decl, requiredBy: m.trait, for: q, usedIn: scopeOfUse) } else { - return ir.monomorphize(f, for: p, usedIn: scopeOfUse) + return ir.monomorphize(f, for: q, usedIn: scopeOfUse) } } diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift index e7f6b917f..d054d680b 100644 --- a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift +++ b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift @@ -54,8 +54,6 @@ extension Module { pc = successor(of: user) case is EndProject: pc = interpret(endProject: user, in: &context) - case is EndProjectWitness: - pc = interpret(endProjectWitness: user, in: &context) case is GenericParameter: pc = interpret(genericParameter: user, in: &context) case is GlobalAddr: @@ -78,8 +76,6 @@ extension Module { pc = interpret(pointerToAddress: user, in: &context) case is Project: pc = interpret(project: user, in: &context) - case is ProjectWitness: - pc = interpret(projectWitness: user, in: &context) case is ReleaseCaptures: pc = successor(of: user) case is Return: @@ -90,6 +86,8 @@ extension Module { pc = interpret(subfieldView: user, in: &context) case is UnionDiscriminator: pc = interpret(unionDiscriminator: user, in: &context) + case is UnionSwitch: + pc = successor(of: user) case is Unreachable: pc = successor(of: user) case is WrapExistentialAddr: @@ -129,7 +127,8 @@ extension Module { if p.isEmpty { break } insertDeinit( - s.source, at: p, anchoredTo: s.site, before: i, reportingDiagnosticsTo: &diagnostics) + s.source, at: p, before: i, + anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics) o.value = .full(.uninitialized) context.forEachObject(at: s.source, { $0 = o }) @@ -251,8 +250,8 @@ extension Module { case .sink: insertDeinit( - s.start, at: projection.value.initializedSubfields, anchoredTo: s.site, before: i, - reportingDiagnosticsTo: &diagnostics) + s.start, at: projection.value.initializedSubfields, before: i, + anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics) context.withObject(at: l, { $0.value = .full(.uninitialized) }) case .yielded: @@ -305,8 +304,8 @@ extension Module { // erasing the deallocated memory from the context. let p = context.withObject(at: l, \.value.initializedSubfields) insertDeinit( - s.location, at: p, anchoredTo: s.site, before: i, - reportingDiagnosticsTo: &diagnostics) + s.location, at: p, before: i, + anchoringInstructionsTo: s.site, reportingDiagnosticsTo: &diagnostics) context.memory[l] = nil return successor(of: i) } @@ -323,18 +322,6 @@ extension Module { return successor(of: i) } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. - func interpret(endProjectWitness i: InstructionID, in context: inout Context) -> PC? { - let s = self[i] as! EndProjectWitness - let r = self[s.start.instruction!] as! ProjectWitness - - let sources = context.locals[r.container]!.unwrapLocations()! - finalize( - region: s.start, projecting: r.projection.access, from: sources, - exitedWith: i, in: &context) - return successor(of: i) - } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(genericParameter i: InstructionID, in context: inout Context) -> PC? { context.declareStorage(assignedTo: i, in: self, initially: .initialized) @@ -442,21 +429,21 @@ extension Module { return successor(of: i) } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. - func interpret(projectWitness i: InstructionID, in context: inout Context) -> PC? { - let s = self[i] as! ProjectWitness - initializeRegister(createdBy: i, projecting: s.projection, in: &context) - return successor(of: i) - } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(return i: InstructionID, in context: inout Context) -> PC? { // Make sure that all non-sink parameters are initialized on exit. let entry = entry(of: f)! - for (i, p) in self[f].inputs.enumerated() where p.type.access != .sink { - ensureInitializedOnExit( - .parameter(entry, i), passed: p.type.access, in: &context, - reportingDiagnosticsAt: diagnosticSite(for: p, in: f)) + for (k, p) in self[f].inputs.enumerated() { + let source = Operand.parameter(entry, k) + if p.type.access == .sink { + ensureUninitializedOnExit( + source, in: &context, insertingDeinitializationBefore: i, + reportingDiagnosticsAt: self[i].site) + } else { + ensureInitializedOnExit( + source, passed: p.type.access, in: &context, + reportingDiagnosticsAt: diagnosticSite(for: p, in: f)) + } } // Make sure that the return value is initialized on exit. @@ -581,6 +568,30 @@ extension Module { } } + /// Checks that entry parameter `p` is deinitialized in `context`, inserting definitialization + /// before instruction `i` if it isn't, reporting diagnostics at `site`. + func ensureUninitializedOnExit( + _ p: Operand, in context: inout Context, + insertingDeinitializationBefore i: InstructionID, + reportingDiagnosticsAt site: SourceRange + ) { + context.withObject(at: .root(p), { (o) in + let s = o.value.initializedSubfields + if s == [[]] && isDeinit(i.function) { + // We cannot call `deinit` in `deinit` itself. + insertDeinitParts( + of: p, before: i, + anchoringInstructionsTo: site, reportingDiagnosticsTo: &diagnostics) + } else { + insertDeinit( + p, at: s, before: i, + anchoringInstructionsTo: site, reportingDiagnosticsTo: &diagnostics) + } + + o.value = .full(.uninitialized) + }) + } + /// Checks that the return value is initialized in `context`. func ensureReturnValueIsInitialized( in context: inout Context, at site: SourceRange @@ -637,8 +648,8 @@ extension Module { case .sink: insertDeinit( - start, at: projection.value.initializedSubfields, anchoredTo: self[exit].site, - before: exit, reportingDiagnosticsTo: &diagnostics) + start, at: projection.value.initializedSubfields, before: exit, + anchoringInstructionsTo: self[exit].site, reportingDiagnosticsTo: &diagnostics) context.withObject(at: l, { $0.value = .full(.uninitialized) }) case .yielded: @@ -720,8 +731,9 @@ extension Module { /// Inserts IR for the deinitialization of `root` at given `initializedSubfields` before /// instruction `i`, anchoring instructions to `site`. private mutating func insertDeinit( - _ root: Operand, at initializedSubfields: [RecordPath], anchoredTo site: SourceRange, - before i: InstructionID, reportingDiagnosticsTo log: inout DiagnosticSet + _ root: Operand, at initializedSubfields: [RecordPath], before i: InstructionID, + anchoringInstructionsTo site: SourceRange, + reportingDiagnosticsTo log: inout DiagnosticSet ) { for path in initializedSubfields { Emitter.withInstance(insertingIn: &self, reportingDiagnosticsTo: &log) { (e) in @@ -732,6 +744,19 @@ extension Module { } } + /// Inserts ID for the deinitialization of `whole`'s parts before instruction `i`, anchoring + /// new instructions to `site`. + private mutating func insertDeinitParts( + of whole: Operand, before i: InstructionID, + anchoringInstructionsTo site: SourceRange, + reportingDiagnosticsTo log: inout DiagnosticSet + ) { + Emitter.withInstance(insertingIn: &self, reportingDiagnosticsTo: &log) { (e) in + e.insertionPoint = .before(i) + e.emitDeinitParts(of: whole, at: site) + } + } + /// Returns the site at which diagnostics related to the parameter `p` should be reported in `f`. private func diagnosticSite(for p: Parameter, in f: Function.ID) -> SourceRange { guard let d = p.decl else { return .empty(at: self[f].site.start) } diff --git a/Sources/IR/Analysis/Module+Ownership.swift b/Sources/IR/Analysis/Module+Ownership.swift index 39a8ae625..59ecf51ca 100644 --- a/Sources/IR/Analysis/Module+Ownership.swift +++ b/Sources/IR/Analysis/Module+Ownership.swift @@ -33,8 +33,6 @@ extension Module { interpret(endBorrow: user, in: &context) case is EndProject: interpret(endProject: user, in: &context) - case is EndProject: - interpret(endProjectWitness: user, in: &context) case is GenericParameter: interpret(genericParameter: user, in: &context) case is GlobalAddr: @@ -47,8 +45,6 @@ extension Module { interpret(pointerToAddress: user, in: &context) case is Project: interpret(project: user, in: &context) - case is ProjectWitness: - interpret(projectWitness: user, in: &context) case is SubfieldView: interpret(subfieldView: user, in: &context) case is WrapExistentialAddr: @@ -74,9 +70,14 @@ extension Module { } // The access must be immutable if the source of the access is a let-parameter. - if let c = passingConvention(of: s.source), (c == .let) && (request != .let) { - diagnostics.insert(.error(illegalMutableAccessAt: s.site)) - return + if (request != .let) && isBoundImmutably(s.source) { + // Built-in values are never consumed. + if self.type(of: s.source).ast.isBuiltin { + assert(request != .inout, "unexpected inout access on built-in value") + } else { + diagnostics.insert(.error(illegalMutableAccessAt: s.site)) + return + } } let former = reborrowedSource(s) @@ -219,13 +220,6 @@ extension Module { finalize(region: s.start, projecting: r.projection.access, exitedWith: i, in: &context) } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. - func interpret(endProjectWitness i: InstructionID, in context: inout Context) { - let s = self[i] as! EndProjectWitness - let r = self[s.start.instruction!] as! Project - finalize(region: s.start, projecting: r.projection.access, exitedWith: i, in: &context) - } - /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(genericParameter i: InstructionID, in context: inout Context) { context.declareStorage(assignedTo: i, in: self, initially: .unique) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index e50d53ee8..506f2a77c 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -494,7 +494,7 @@ struct Emitter { precondition(program.isGlobal(d)) precondition(read(program[d].pattern.introducer.value, { ($0 == .let) || ($0 == .sinklet) })) - let r = RemoteType(.set, program[d].type) + let r = RemoteType(.set, program.canonical(typeOf: d)) let l = ArrowType( receiverEffect: .set, environment: ^TupleType(types: [^r]), inputs: [], output: .void) let f = SynthesizedFunctionDecl( @@ -612,30 +612,27 @@ struct Emitter { /// /// - Requires: `d` is a local `let` or `inout` binding. private mutating func lower(projectedLocalBinding d: BindingDecl.ID) { - let access = AccessEffect(program[d].pattern.introducer.value) - precondition(access == .let || access == .inout) precondition(program.isLocal(d)) + let source = emitLValue(ast[d].initializer!) + assignProjections(of: source, to: program[d].pattern) + } - let initializer = ast[d].initializer! - let source = emitLValue(initializer) - let isSink = module.isSink(source) + /// Assigns the bindings declared in `d` to their corresponding projection of `rhs`. + private mutating func assignProjections(of rhs: Operand, to d: BindingPattern.ID) { + precondition(!program[d].introducer.value.isConsuming) + let k = AccessEffect(program[d].introducer.value) + let request: AccessEffectSet = module.isSink(rhs) ? [k, .sink] : [k] - for (path, name) in ast.names(in: program[d].pattern.subpattern) { - var part = emitSubfieldView(source, at: path, at: program[name].decl.site) + for (path, name) in ast.names(in: program[d].subpattern) { + var part = emitSubfieldView(rhs, at: path, at: program[name].decl.site) let partDecl = ast[name].decl - let t = canonical(program[partDecl].type) - part = emitCoerce(part, to: t, at: ast[partDecl].site) + let bindingType = canonical(program[partDecl].type) + part = emitCoerce(part, to: bindingType, at: ast[partDecl].site) - if isSink { - let b = module.makeAccess( - [.sink, access], from: part, correspondingTo: partDecl, at: ast[partDecl].site) - frames[partDecl] = insert(b)! - } else { - let b = module.makeAccess( - access, from: part, correspondingTo: partDecl, at: ast[partDecl].site) - frames[partDecl] = insert(b)! - } + let b = module.makeAccess( + request, from: part, correspondingTo: partDecl, at: ast[partDecl].site) + frames[partDecl] = insert(b)! } } @@ -645,15 +642,17 @@ struct Emitter { mutating func lower(synthetic d: SynthesizedFunctionDecl) { switch d.kind { case .deinitialize: - return withClearContext({ $0.lower(syntheticDeinit: d) }) + lower(syntheticDeinit: d) case .moveInitialization: - return withClearContext({ $0.lower(syntheticMoveInit: d) }) + lower(syntheticMoveInit: d) case .moveAssignment: - return withClearContext({ $0.lower(syntheticMoveAssign: d) }) + lower(syntheticMoveAssign: d) case .copy: - return withClearContext({ $0.lower(syntheticCopy: d) }) + lower(syntheticCopy: d) + case .equal: + lower(syntheticEqual: d) case .globalInitialization: - return withClearContext({ $0.lower(globalBindingInitializer: d) }) + lower(globalBindingInitializer: d) case .autoclosure: // nothing do to here; expansion is done at the caller side. break @@ -662,54 +661,34 @@ struct Emitter { /// Inserts the IR for `d`, which is a synthetic deinitializer. private mutating func lower(syntheticDeinit d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } + withPrologue(of: d) { (me, site, entry) in + // The receiver is a sink parameter representing the object to deinitialize. + let receiver = Operand.parameter(entry, 0) + me.emitDeinitParts(of: receiver, at: site) - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } - - // The receiver is a sink parameter representing the object to deinitialize. - let receiver = Operand.parameter(entry, 0) - emitDeinitParts(of: receiver, at: site) - - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) } /// Inserts the IR for `d`, which is a synthetic move initialization method. private mutating func lower(syntheticMoveInit d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } - - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } - - let receiver = Operand.parameter(entry, 0) - let argument = Operand.parameter(entry, 1) - let object = module.type(of: receiver).ast + withPrologue(of: d) { (me, site, entry) in + let receiver = Operand.parameter(entry, 0) + let argument = Operand.parameter(entry, 1) + let object = me.module.type(of: receiver).ast + + if object.hasRecordLayout { + me.emitMoveInitRecordParts(of: receiver, consuming: argument, at: site) + } else if object.base is UnionType { + me.emitMoveInitUnionPayload(of: receiver, consuming: argument, at: site) + } - if object.hasRecordLayout { - emitMoveInitRecordParts(of: receiver, consuming: argument, at: site) - } else if object.base is UnionType { - emitMoveInitUnionPayload(of: receiver, consuming: argument, at: site) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } - - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) } /// Inserts the IR for initializing the stored parts of `receiver`, which stores a record, @@ -755,20 +734,15 @@ struct Emitter { } // Otherwise, use a switch to select the correct move-initialization. - let elements = program.discriminatorToElement(in: t) - var successors: [Block.ID] = [] - for _ in t.elements { - successors.append(appendBlock()) - } - - let n = emitUnionDiscriminator(argument, at: site) - insert(module.makeSwitch(on: n, toOneOf: successors, at: site)) + let targets = UnionSwitch.Targets( + t.elements.map({ (e) in (key: e, value: appendBlock()) }), + uniquingKeysWith: { (a, _) in a }) + emitUnionSwitch(on: argument, toOneOf: targets, at: site) let tail = appendBlock() - for i in 0 ..< elements.count { - insertionPoint = .end(of: successors[i]) - emitMoveInitUnionPayload( - of: receiver, consuming: argument, containing: elements[i], at: site) + for (u, b) in targets { + insertionPoint = .end(of: b) + emitMoveInitUnionPayload(of: receiver, consuming: argument, containing: u, at: site) insert(module.makeBranch(to: tail, at: site)) } @@ -783,12 +757,9 @@ struct Emitter { of receiver: Operand, consuming argument: Operand, containing payload: AnyType, at site: SourceRange ) { - // Deinitialize the receiver. + // Move the argument. let x0 = insert( module.makeOpenUnion(receiver, as: payload, forInitialization: true, at: site))! - emitDeinit(x0, at: site) - - // Move the argument. let x1 = insert(module.makeOpenUnion(argument, as: payload, at: site))! emitMove([.set], x1, to: x0, at: site) @@ -799,57 +770,81 @@ struct Emitter { /// Inserts the IR for `d`, which is a synthetic move initialization method. private mutating func lower(syntheticMoveAssign d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } - - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } - - let receiver = Operand.parameter(entry, 0) - let argument = Operand.parameter(entry, 1) + withPrologue(of: d) { (me, site, entry) in + let receiver = Operand.parameter(entry, 0) + let argument = Operand.parameter(entry, 1) - // Deinitialize the receiver. - emitDeinit(receiver, at: site) + // Deinitialize the receiver. + me.emitDeinit(receiver, at: site) - // Apply the move-initializer. - emitMove([.set], argument, to: receiver, at: site) - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) + // Apply the move-initializer. + me.emitMove([.set], argument, to: receiver, at: site) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) + } } /// Inserts the IR for `d`, which is a synthetic copy method. private mutating func lower(syntheticCopy d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } + withPrologue(of: d) { (me, site, entry) in + let source = Operand.parameter(entry, 0) + let target = Operand.parameter(entry, 1) + let object = me.module.type(of: source).ast + + if object.hasRecordLayout { + me.emitCopyRecordParts(from: source, to: target, at: site) + } else if object.base is UnionType { + me.emitCopyUnionPayload(from: source, to: target, at: site) + } - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } + } - let source = Operand.parameter(entry, 0) - let target = Operand.parameter(entry, 1) - let object = module.type(of: source).ast + /// Inserts the ID for `d`, which is an equality operator. + private mutating func lower(syntheticEqual d: SynthesizedFunctionDecl) { + withPrologue(of: d) { (me, site, entry) in + let lhs = Operand.parameter(entry, 0) + let rhs = Operand.parameter(entry, 1) + let t = me.module.type(of: lhs).ast - if object.hasRecordLayout { - emitCopyRecordParts(from: source, to: target, at: site) - } else if object.base is UnionType { - emitCopyUnionPayload(from: source, to: target, at: site) + if t.hasRecordLayout { + me.emitStorePartsEquality(lhs, rhs, to: me.returnValue!, at: site) + } else if t.base is UnionType { + me.emitStoreUnionPayloadEquality(lhs, rhs, to: me.returnValue!, at: site) + } else { + UNIMPLEMENTED("synthetic equality for type '\(t)'") + } + + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } + } - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) + /// Declares `d` in the current module and returns its corresponding identifier, calls `action` + /// to generate its implementation if it should be emitted the current module. + @discardableResult + private mutating func withPrologue( + of d: SynthesizedFunctionDecl, + _ action: (inout Self, _ site: SourceRange, _ entry: Block.ID) -> Void + ) -> Function.ID { + withClearContext { (me) in + let f = me.module.demandDeclaration(lowering: d) + if me.shouldEmitBody(of: d, loweredTo: f) { + let site = me.ast[me.module.id].site + let entry = me.module.appendEntry(in: d.scope, to: f) + me.insertionPoint = .end(of: entry) + me.frames.push() + + action(&me, site, entry) + + me.frames.pop() + assert(me.frames.isEmpty) + } + return f + } } /// Inserts the IR for copying the stored parts of `source`, which stores a record, to `target` @@ -891,20 +886,16 @@ struct Emitter { return } - // Otherwise, use a switch to select the correct move-initialization. - let elements = program.discriminatorToElement(in: t) - var successors: [Block.ID] = [] - for _ in t.elements { - successors.append(appendBlock()) - } - - let n = emitUnionDiscriminator(source, at: site) - insert(module.makeSwitch(on: n, toOneOf: successors, at: site)) + // Otherwise, use a switch to select the correct copy method. + let targets = UnionSwitch.Targets( + t.elements.map({ (e) in (key: e, value: appendBlock()) }), + uniquingKeysWith: { (a, _) in a }) + emitUnionSwitch(on: source, toOneOf: targets, at: site) let tail = appendBlock() - for i in 0 ..< elements.count { - insertionPoint = .end(of: successors[i]) - emitCopyUnionPayload(from: source, containing: elements[i], to: target, at: site) + for (u, b) in targets { + insertionPoint = .end(of: b) + emitCopyUnionPayload(from: source, containing: u, to: target, at: site) insert(module.makeBranch(to: tail, at: site)) } @@ -927,31 +918,20 @@ struct Emitter { /// the lowered function. @discardableResult private mutating func lower(globalBindingInitializer d: SynthesizedFunctionDecl) -> Function.ID { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { - return f - } - - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } + withPrologue(of: d) { (me, _, entry) in + let storage = Operand.parameter(entry, 0) + guard case .globalInitialization(let binding) = d.kind else { unreachable() } - let storage = Operand.parameter(entry, 0) - guard case .globalInitialization(let binding) = d.kind else { unreachable() } - let initializer = program[binding].initializer! + let initializer = me.program[binding].initializer! + let site = me.program[initializer].site - emitInitStoredLocalBindings( - in: program[binding].pattern.subpattern, referringTo: [], relativeTo: storage, - consuming: initializer) - insert(module.makeMarkState(returnValue!, initialized: true, at: program[initializer].site)) - emitDeallocTopFrame(at: program[initializer].site) - insert(module.makeReturn(at: program[initializer].site)) - - return f + me.emitInitStoredLocalBindings( + in: me.program[binding].pattern.subpattern, referringTo: [], relativeTo: storage, + consuming: initializer) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) + } } private mutating func lower(syntheticAutoclosure d: SynthesizedFunctionDecl) -> Function.ID { @@ -1041,6 +1021,8 @@ struct Emitter { return emit(braceStmt: .init(s)!) case BreakStmt.self: return emit(breakStmt: .init(s)!) + case ConditionalBindingStmt.self: + return emit(conditionalBindingStmt: .init(s)!) case ConditionalCompilationStmt.self: return emit(condCompilationStmt: .init(s)!) case ConditionalStmt.self: @@ -1118,6 +1100,25 @@ struct Emitter { emit(stmtList: ast[s].expansion(for: ast.compilationConditions)) } + private mutating func emit(conditionalBindingStmt s: ConditionalBindingStmt.ID) -> ControlFlow { + let storage = emitAllocation(binding: ast[s].binding) + + let fail = appendBlock() + let next = emitConditionalNarrowing( + ast[s].binding, movingConsumedValuesTo: storage, + branchingOnFailureTo: fail, in: insertionScope!) + + insertionPoint = .end(of: fail) + let flow = emit(braceStmt: ast[s].fallback) + emitControlFlow(flow) { (me) in + // Control-flow can never jump here. + me.insert(me.module.makeUnreachable(at: me.ast[me.ast[s].fallback].site)) + } + + insertionPoint = .end(of: next) + return .next + } + private mutating func emit(conditionalStmt s: ConditionalStmt.ID) -> ControlFlow { let (firstBranch, secondBranch) = emitTest(condition: ast[s].condition, in: AnyScopeID(s)) let tail = appendBlock() @@ -1207,16 +1208,63 @@ struct Emitter { /// Inserts the IR for loop `s`, returning its effect on control flow. private mutating func emit(forStmt s: ForStmt.ID) -> ControlFlow { if program.ast.isConsuming(s) { - UNIMPLEMENTED("consuming for loops") + return emit(consumingForStmt: s) } else { return emit(nonConsumingForStmt: s) } } + /// Inserts the IR for consuming loop `s`, returning its effect on control flow. + private mutating func emit(consumingForStmt s: ForStmt.ID) -> ControlFlow { + let d = program[program[s].domain.value].type + let c = program.conformance( + of: d, to: ast.core.iterator.type, exposedTo: program[s].scope)! + let witness = IteratorWitness(c, in: &module) + let introducer = program[s].introducerSite + + // The collection on which the loop iterates. + let domain = emitLValue(program[s].domain.value) + // The element extracted before each iteration. + let element = emitAllocStack(for: ^ast.optional(witness.element), at: introducer) + // The storage containing the result of binding each element. + let storage = emitAllocation(binding: ast[s].binding) + + // The "head" of the loop; extracts the next element. + let head = appendBlock(in: s) + // The remainder of the program, after the loop. + let exit = appendBlock() + + loops.append(LoopID(depth: frames.depth, exit: exit)) + defer { loops.removeLast() } + + insert(module.makeBranch(to: head, at: introducer)) + insertionPoint = .end(of: head) + + let x0 = insert(module.makeAccess(.inout, from: domain, at: introducer))! + emitApply(witness.next, to: [x0], writingResultTo: element, at: introducer) + insert(module.makeEndAccess(x0, at: introducer)) + + let next = emitUnionNarrowing( + from: element, to: ast[ast[s].binding].pattern, typed: witness.element, + movingConsumedValuesTo: storage, branchingOnFailureTo: exit, + in: insertionScope!) + + // TODO: Filter + precondition(ast[s].filter == nil, "loop filters are not implemented") + + insertionPoint = .end(of: next) + let flow = emit(braceStmt: ast[s].body) + emitControlFlow(flow) { (me) in + me.insert(me.module.makeBranch(to: head, at: .empty(at: me.program[s].body.site.end))) + } + + insertionPoint = .end(of: exit) + return .next + } + /// Inserts the IR for non-consuming loop `s`, returning its effect on control flow. private mutating func emit(nonConsumingForStmt s: ForStmt.ID) -> ControlFlow { let domainType = program[program[s].domain.value].type - let collection = ast.core.collection let collectionConformance = program.conformance( of: domainType, to: collection.type, exposedTo: program[s].scope)! @@ -1276,6 +1324,7 @@ struct Emitter { introducedBy: program[s].binding.pattern, referringTo: [], relativeTo: x8) // TODO: Filter + precondition(ast[s].filter == nil, "loop filters are not implemented") let flow = emit(braceStmt: ast[s].body) emitControlFlow(flow) { (me) in @@ -1432,9 +1481,7 @@ struct Emitter { /// Inserts the IR for storing the value of `e` to `storage`. private mutating func emitStore(_ e: BooleanLiteralExpr.ID, to storage: Operand) { - let x0 = emitSubfieldView(storage, at: [0], at: ast[e].site) - let x1 = insert(module.makeAccess(.set, from: x0, at: ast[e].site))! - insert(module.makeStore(.i1(ast[e].value), at: x1, at: ast[e].site)) + emitStore(boolean: ast[e].value, to: storage, at: ast[e].site) } /// Inserts the IR for storing the value of `e` to `storage`. @@ -1581,17 +1628,9 @@ struct Emitter { usingExplicit: ast[e].arguments, synthesizingDefaultAt: .empty(at: ast[e].site.end)) let m = ast.isMarkedForMutation(ast[e].callee) let (callee, captures) = emitFunctionCallee(ast[e].callee, markedForMutation: m) - let inputs = captures + arguments // Call is evaluated last. - switch callee { - case .direct(let r): - emitApply(.constant(r), to: inputs, writingResultTo: storage, at: ast[e].site) - case .lambda(let r): - emitApply(r, to: inputs, writingResultTo: storage, at: ast[e].site) - case .bundle(let r): - emitApply(r, to: inputs, writingResultTo: storage, at: ast[e].site) - } + emitApply(callee, to: captures + arguments, writingResultTo: storage, at: ast[e].site) } /// Inserts the IR for storing the value of `e` to `storage`. @@ -1681,25 +1720,25 @@ struct Emitter { private mutating func emitStore(_ e: FoldedSequenceExpr, to storage: Operand) { switch e { case .infix(let callee, let lhs, let rhs): - let t = program[callee.expr].type - let calleeType = ArrowType(canonical(t))!.lifted + let t = canonical(program[callee.expr].type) + let calleeType = ArrowType(t)!.lifted // Emit the operands, starting with RHS. - let r = emit(infixOperand: rhs, passed: ParameterType(calleeType.inputs[1].type)!.access) - let l = emit(infixOperand: lhs, passed: ParameterType(calleeType.inputs[0].type)!.access) + let r = emit(infixOperand: rhs, passedTo: ParameterType(calleeType.inputs[1].type)!) + let l = emit(infixOperand: lhs, passedTo: ParameterType(calleeType.inputs[0].type)!) // The callee must be a reference to member function. guard case .member(let d, let a, _) = program[callee.expr].referredDecl else { unreachable() } - let o = FunctionReference(to: d, in: &module, specializedBy: a, in: insertionScope!) - let f = Operand.constant(o) - - // Emit the call. - let site = ast.site(of: e) - let result = insert(module.makeAccess(.set, from: storage, at: site))! - insert(module.makeCall(applying: f, to: [l, r], writingResultTo: result, at: site)) + let site = program[callee.expr].site + let lhsIsMarkedForMutation = program.ast.isMarkedForMutation(lhs) + let (callee, captures) = emitMemberFunctionCallee( + referringTo: d, memberOf: l, markedForMutation: lhsIsMarkedForMutation, + specializedBy: a, in: program[callee.expr].scope, + at: site) + emitApply(callee, to: captures + [r], writingResultTo: storage, at: site) case .leaf(let v): emitStore(value: v, to: storage) @@ -1833,6 +1872,16 @@ struct Emitter { insert(module.makeStore(x2, at: x1, at: syntax.site)) } + /// Writes an instance of `Hylo.Bool` with value `v` to `storage`. + /// + /// - Requires: `storage` is the address of uninitialized memory of type `Hylo.Int`. + private mutating func emitStore(boolean v: Bool, to storage: Operand, at site: SourceRange) { + let x0 = emitSubfieldView(storage, at: [0], at: site) + let x1 = insert(module.makeAccess(.set, from: x0, at: site))! + insert(module.makeStore(.i1(v), at: x1, at: site)) + insert(module.makeEndAccess(x1, at: site)) + } + /// Writes an instance of `Hylo.Int` with value `v` to `storage`. /// /// - Requires: `storage` is the address of uninitialized memory of type `Hylo.Int`. @@ -1872,6 +1921,21 @@ struct Emitter { frames.top.setMayHoldCaptures(s) } + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. + private mutating func emitApply( + _ callee: Callee, to arguments: [Operand], + writingResultTo storage: Operand, at site: SourceRange + ) { + switch callee { + case .direct(let r): + emitApply(.constant(r), to: arguments, writingResultTo: storage, at: site) + case .lambda(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: site) + case .bundle(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: site) + } + } + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. private mutating func emitApply( _ callee: Operand, to arguments: [Operand], @@ -2068,29 +2132,25 @@ struct Emitter { let arguments = emitArguments( to: ast[e].callee, in: CallID(e), usingExplicit: ast[e].arguments, synthesizingDefaultAt: .empty(at: ast[e].site.end)) - let m = ast.isMarkedForMutation(ast[e].callee) - let (callee, captures) = emitSubscriptCallee(ast[e].callee, markedForMutation: m) + let (callee, captures) = emitSubscriptCallee(ast[e].callee) return (callee, captures + arguments) } - /// Inserts the IR for infix operand `e` passed with convention `access`. + /// Inserts the IR for infix operand `e` passed to a parameter of type `p`. private mutating func emit( - infixOperand e: FoldedSequenceExpr, passed access: AccessEffect + infixOperand e: FoldedSequenceExpr, passedTo p: ParameterType ) -> Operand { - let storage: Operand - switch e { - case .infix(let callee, _, _): - let t = ArrowType(canonical(program[callee.expr].type))!.lifted - storage = emitAllocStack(for: t.output, at: ast.site(of: e)) - emitStore(e, to: storage) + case .infix(let f, _, _): + let t = ArrowType(canonical(program[f.expr].type))!.lifted + let s = emitAllocStack(for: t.output, at: ast.site(of: e)) + emitStore(e, to: s) + let u = emitCoerce(s, to: p.bareType, at: ast.site(of: e)) + return insert(module.makeAccess(p.access, from: u, at: ast.site(of: e)))! case .leaf(let e): - let x0 = emitLValue(e) - storage = unwrapCapture(x0, at: program[e].site) + return emitArgument(e, to: p, at: program[e].site) } - - return insert(module.makeAccess(access, from: storage, at: ast.site(of: e)))! } /// Emits the IR of a call to `f` with given `arguments` at `site`. @@ -2112,10 +2172,15 @@ struct Emitter { case .addressOf: let source = emitLValue(arguments[0].value) return insert(module.makeAddressToPointer(source, at: site))! + + case .markUninitialized: + let source = emitLValue(arguments[0].value) + insert(module.makeMarkState(source, initialized: false, at: site)) + return .void } } - /// Inserts the IR for given `callee`, which is marked for mutation if `isMutating` is `true`, + /// Inserts the IR for given `callee`, which is marked for mutation iff `isMutating` is `true`, /// and returns the callee's value along with its lifted arguments. /// /// Lifted arguments correspond to the captures of the `callee`, which are additional parameters @@ -2166,27 +2231,47 @@ struct Emitter { } } - /// Inserts the IR evaluating `callee`, which refers to a member function marked for mutation - /// iff `isMutating` is `true`, returning the callee's value along with the call receiver. + /// Inserts the IR evaluating `callee`, which refers to a member function, returning the callee's + /// value along with the call receiver. + /// + /// The callee is marked for mutation iff `isMutating` is `true`, in which case the receiver is + /// accessed with a `set` or `inout` capability. private mutating func emitMemberFunctionCallee( _ callee: NameExpr.ID, markedForMutation isMutating: Bool ) -> (callee: Callee, captures: [Operand]) { guard case .member(let d, let a, let s) = program[callee].referredDecl else { unreachable() } - let receiver = emitLValue(receiver: s, at: ast[callee].site) - let receiverType = module.type(of: receiver).ast + let r = emitLValue(receiver: s, at: ast[callee].site) + return emitMemberFunctionCallee( + referringTo: d, memberOf: r, markedForMutation: isMutating, + specializedBy: a, in:program[callee].scope, + at: program[callee].site) + } + + /// Inserts the IR constructing the callee of a call referring to `d`, which is a member function + /// of `r`, returning the callee's value along with the call receiver. + /// + /// The callee is marked for mutation iff `isMutating` is `true`, in which case the receiver is + /// accessed with a `set` or `inout` capability. + private mutating func emitMemberFunctionCallee( + referringTo d: AnyDeclID, memberOf r: Operand, markedForMutation isMutating: Bool, + specializedBy a: GenericArguments, in scopeOfUse: AnyScopeID, + at site: SourceRange + ) -> (callee: Callee, captures: [Operand]) { + let available = receiverCapabilities(program[d].type) + var requested = available.intersection(.forUseOfBundle(performingInPlaceMutation: isMutating)) + + // TODO: Should report an error when available is `let|sink` and requested is `inout/set` + requested = requested.isEmpty ? available : requested - let request = program.requestedCapabilities( - onBundleProviding: receiverCapabilities(program[callee].type), - forInPlaceMutation: isMutating) let entityToCall = module.memberCallee( - referringTo: d, memberOf: receiverType, accessedWith: request, - specializedBy: a, usedIn: program[callee].scope) + referringTo: d, memberOf: module.type(of: r).ast, accessedWith: requested, + specializedBy: a, usedIn: scopeOfUse) if case .bundle(let b) = entityToCall { - return emitMethodBundleCallee(referringTo: b, on: receiver, at: program[callee].site) + return emitMethodBundleCallee(referringTo: b, on: r, at: site) } else { - let c = insert(module.makeAccess(request, from: receiver, at: program[callee].site))! + let c = insert(module.makeAccess(requested, from: r, at: site))! return (callee: entityToCall, captures: [c]) } } @@ -2228,26 +2313,24 @@ struct Emitter { } } - /// Inserts the IR for given `callee`, which is marked for mutation if `isMutating` is `true`, - /// and returns the callee's value along with its lifted arguments. + /// Inserts the IR for given `callee` and returns its value along with its lifted arguments. private mutating func emitSubscriptCallee( - _ callee: AnyExprID, markedForMutation isMutating: Bool + _ callee: AnyExprID ) -> (callee: BundleReference, captures: [Operand]) { // TODO: Handle captures switch callee.kind { case NameExpr.self: - return emitNamedSubscriptCallee(.init(callee)!, markedForMutation: isMutating) + return emitNamedSubscriptCallee(.init(callee)!) case InoutExpr.self: - return emitSubscriptCallee(ast[InoutExpr.ID(callee)!].subject, markedForMutation: true) + return emitSubscriptCallee(ast[InoutExpr.ID(callee)!].subject) default: UNIMPLEMENTED("call to an anonymous subscript of an rvalue") } } - /// Inserts the IR for given `callee`, which is marked for mutation if `isMutating` is `true`, - /// and returns the callee's value along with its lifted arguments. + /// Inserts the IR for given `callee` and returns its value along with its lifted arguments. private mutating func emitNamedSubscriptCallee( - _ callee: NameExpr.ID, markedForMutation isMutating: Bool + _ callee: NameExpr.ID ) -> (callee: BundleReference, captures: [Operand]) { switch program[callee].referredDecl { case .direct(let d, let a) where d.kind == SubscriptDecl.self: @@ -2256,13 +2339,12 @@ struct Emitter { UNIMPLEMENTED("subscript with non-empty environment") } - let entityToCall = program.subscriptBundleReference( - to: .init(d)!, specializedBy: a, markedForMutation: isMutating) + let entityToCall = program.subscriptBundleReference(to: .init(d)!, specializedBy: a) return (entityToCall, []) case .member(let d, _, _) where d.kind == SubscriptDecl.self: // Callee is a member reference; the receiver is the only capture. - return emitMemberSubscriptCallee(callee, markedForMutation: isMutating) + return emitMemberSubscriptCallee(callee) case .builtinFunction, .builtinType: // There are no built-in subscripts. @@ -2273,51 +2355,46 @@ struct Emitter { } } - /// Inserts the IR evaluating `callee`, which refers to a member subscript marked for mutation - /// iff `isMutating` is `true`, returning the callee's value along with the call receiver. + /// Inserts the IR evaluating `callee`, which refers to a member subscript, returning the + /// callee's value along with the call receiver. private mutating func emitMemberSubscriptCallee( - _ callee: NameExpr.ID, markedForMutation isMutating: Bool + _ callee: NameExpr.ID ) -> (callee: BundleReference, captures: [Operand]) { guard case .member(let d, let a, let s) = program[callee].referredDecl else { unreachable() } - let entityToCall = program.subscriptBundleReference( - to: .init(d)!, specializedBy: a, markedForMutation: isMutating) + let entityToCall = program.subscriptBundleReference(to: .init(d)!, specializedBy: a) let r = emitLValue(receiver: s, at: ast[callee].site) let c = insert(module.makeAccess(entityToCall.capabilities, from: r, at: ast[callee].site))! return (entityToCall, [c]) } + /// Returns `(success: a, failure: b)` where `a` is the basic block reached if all items in /// `condition` hold and `b` is the basic block reached otherwise, creating new basic blocks /// in `scope`. private mutating func emitTest( condition: [ConditionItem], in scope: AnyScopeID ) -> (success: Block.ID, failure: Block.ID) { - let f = insertionFunction! - // Allocate storage for all the declarations in the condition before branching so that all - // `dealloc_stack` are to dominated by their corresponding `alloc_stack`. - var allocs: [Operand] = [] + // `dealloc_stack` are dominated by their corresponding `alloc_stack`. + var allocations: [Operand?] = [] for case .decl(let d) in condition { - let a = insert(module.makeAllocStack(program[d].type, at: ast[d].site))! - allocs.append(a) + allocations.append(emitAllocation(binding: d)) } - let failure = module.appendBlock(in: scope, to: f) + let failure = module.appendBlock(in: scope, to: insertionFunction!) for (i, item) in condition.enumerated() { switch item { case .expr(let e): let test = pushing(Frame(), { $0.emit(branchCondition: e) }) - let next = module.appendBlock(in: scope, to: f) + let next = appendBlock(in: scope) insert(module.makeCondBranch(if: test, then: next, else: failure, at: ast[e].site)) insertionPoint = .end(of: next) case .decl(let d): - let subject = emitLValue(ast[d].initializer!) - let patternType = canonical(program[d].type) let next = emitConditionalNarrowing( - subject, as: ast[d].pattern, typed: patternType, to: allocs[i], - else: failure, in: scope) + d, movingConsumedValuesTo: allocations[i], + branchingOnFailureTo: failure, in: scope) insertionPoint = .end(of: next) } } @@ -2325,68 +2402,92 @@ struct Emitter { return (success: insertionBlock!, failure: failure) } - /// Returns a basic block in which the names in `pattern` have been declared and initialized. + /// If `d` declares stored bindings, inserts the IR for allocating their storage and returns a + /// a rreference to that storage. Otherwise, returns `nil`. + private mutating func emitAllocation(binding d: BindingDecl.ID) -> Operand? { + if program[d].pattern.introducer.value.isConsuming { + return emitAllocStack(for: program[d].type, at: ast[d].site) + } else { + return nil + } + } + + /// Returns a basic block in which the names in `d` have been declared and initialized. /// /// This method emits IR to: - /// - /// - check whether the value in `subject` is an instance of `patternType`; + /// - evaluate the `d`'s initializer as value *v*, + /// - check whether the value in *v* is an instance of `d`'s type; /// - if it isn't, jump to `failure`; - /// - if it is, jump to a new basic block *b*, coerce the contents of `subject` into `storage`, - /// applying consuming coercions as necessary, and define the bindings declared in `pattern`. + /// - if it is, jump to a new basic block and define and initialize the bindings declared in `d`. /// - /// If `subject` always matches `patternType`, the narrowing is irrefutable and `failure` is - /// unreachable in the generated IR. + /// If `d` has a consuming introducer (e.g., `var`), the value of `d`'s initializer is moved to + /// `storage`, which denotes a memory location with `d`'s type. Otherwise, `storage` is `nil` and + /// the bindings in `d` are defined as new projections. In either case, the emitter's context is + /// is updated to associate each binding to its value. /// - /// The return value is the new basic block *b*, which is defined in `scope`. The emitter context - /// is updated to associate the bindings declared in `pattern` to their address in `storage`. + /// The return value of the method is a basic block, defined in `scope`. If *v* has the same type + /// as `d`, the narrowing is irrefutable and `failure` is unreachable in the generated IR. private mutating func emitConditionalNarrowing( - _ subject: Operand, - as pattern: BindingPattern.ID, typed patternType: AnyType, - to storage: Operand, - else failure: Block.ID, in scope: AnyScopeID + _ d: BindingDecl.ID, + movingConsumedValuesTo storage: Operand?, + branchingOnFailureTo failure: Block.ID, + in scope: AnyScopeID ) -> Block.ID { - switch module.type(of: subject).ast.base { - case let t as UnionType: - return emitConditionalNarrowing( - subject, typed: t, as: pattern, typed: patternType, to: storage, - else: failure, in: scope) - default: - break - } + let lhsType = canonical(program[d].type) + let rhs = emitLValue(ast[d].initializer!) + let lhs = ast[d].pattern - UNIMPLEMENTED() + assert(program[lhs].introducer.value.isConsuming || (storage == nil)) + + if module.type(of: rhs).ast.base is UnionType { + return emitUnionNarrowing( + from: rhs, to: lhs, typed: lhsType, + movingConsumedValuesTo: storage, branchingOnFailureTo: failure, + in: scope) + } else { + UNIMPLEMENTED() + } } - /// Returns a basic block in which the names in `pattern` have been declared and initialized. + /// Returns a basic block in which the names in `lhs` have been declared and initialized. /// - /// This method method implements conditional narrowing for union types. - private mutating func emitConditionalNarrowing( - _ subject: Operand, typed subjectType: UnionType, - as pattern: BindingPattern.ID, typed patternType: AnyType, - to storage: Operand, - else failure: Block.ID, in scope: AnyScopeID + /// - Parameters: + /// - rhs: A union container of a type that includes `lhsType`. + /// - storage: For a consuming narrowing, the storage of the bindings declared in `lhs`. + /// - failure: The basic block to which control flow jumps if the narrowing fails. + /// - scope: The scope in which the new basic block is introducked. + private mutating func emitUnionNarrowing( + from rhs: Operand, to lhs: BindingPattern.ID, typed lhsType: AnyType, + movingConsumedValuesTo storage: Operand?, + branchingOnFailureTo failure: Block.ID, + in scope: AnyScopeID ) -> Block.ID { - // TODO: Implement narrowing to an arbitrary subtype. - guard subjectType.elements.contains(patternType) else { UNIMPLEMENTED() } - let site = ast[pattern].site - - let i = program.discriminatorToElement(in: subjectType).firstIndex(of: patternType)! - let expected = IntegerConstant(i, bitWidth: 64) // FIXME: should be width of 'word' - let actual = emitUnionDiscriminator(subject, at: site) + let rhsType = UnionType(module.type(of: rhs).ast)! + precondition(rhsType.elements.contains(lhsType), "recursive narrowing is unimplemented") - let test = insert( - module.makeLLVM(applying: .icmp(.eq, .word), to: [.constant(expected), actual], at: site))! let next = appendBlock(in: scope) - insert(module.makeCondBranch(if: test, then: next, else: failure, at: site)) + let site = program[lhs].site + var targets = UnionSwitch.Targets( + rhsType.elements.map({ (e) in (key: e, value: failure) }), + uniquingKeysWith: { (a, _) in a }) + targets[lhsType] = next + emitUnionSwitch(on: rhs, toOneOf: targets, at: site) insertionPoint = .end(of: next) - let x0 = insert(module.makeOpenUnion(subject, as: patternType, at: site))! - pushing(Frame()) { (this) in - this.emitMove([.set], x0, to: storage, at: site) - } - insert(module.makeCloseUnion(x0, at: site)) - emitLocalDeclarations(introducedBy: pattern, referringTo: [], relativeTo: storage) + if let target = storage { + let x0 = insert(module.makeAccess(.sink, from: rhs, at: site))! + let x1 = insert(module.makeOpenUnion(x0, as: lhsType, at: site))! + emitMove([.set], x1, to: target, at: site) + emitLocalDeclarations(introducedBy: lhs, referringTo: [], relativeTo: target) + insert(module.makeCloseUnion(x1, at: site)) + insert(module.makeEndAccess(x0, at: site)) + } else { + let k = AccessEffect(program[lhs].introducer.value) + let x0 = insert(module.makeAccess(k, from: rhs, at: site))! + let x1 = insert(module.makeOpenUnion(x0, as: lhsType, at: site))! + assignProjections(of: x1, to: lhs) + } return next } @@ -2511,7 +2612,7 @@ struct Emitter { private func unexpectedCoercion( from lhs: AnyType, to rhs: AnyType, file: StaticString = #file, line: UInt = #line ) -> Never { - fatalError("unexpected coercion from '\(lhs)' to \(rhs)", file: file, line: line) + fatalError("unexpected coercion from '\(lhs)' to '\(rhs)'", file: file, line: line) } /// Inserts the IR for converting `foreign` to a value of type `ir`. @@ -2636,6 +2737,8 @@ struct Emitter { /// Inserts the IR for lvalue `e`. private mutating func emitLValue(upcast e: CastExpr.ID) -> Operand { switch ast[e].left.kind { + case BooleanLiteralExpr.self: + return emitStore(value: ast[e].left) case FloatLiteralExpr.self: return emitStore(value: ast[e].left) case IntegerLiteralExpr.self: @@ -2969,7 +3072,7 @@ struct Emitter { } else if m.isBuiltinOrRawTuple { insert(module.makeMarkState(storage, initialized: false, at: site)) } else { - report(.error(module.type(of: storage).ast, doesNotConformTo: d, at: site)) + report(.error(m, doesNotConformTo: d, at: site)) } } @@ -2994,7 +3097,7 @@ struct Emitter { /// If `storage` is deinitializable in `self.insertionScope`, inserts the IR for deinitializing /// it; reports a diagnostic for each part that isn't deinitializable otherwise. - private mutating func emitDeinitParts(of storage: Operand, at site: SourceRange) { + mutating func emitDeinitParts(of storage: Operand, at site: SourceRange) { let t = module.type(of: storage).ast if program.isTriviallyDeinitializable(t, in: insertionScope!) { @@ -3053,19 +3156,15 @@ struct Emitter { } // One successor per member in the union, ordered by their mangled representation. - let elements = program.discriminatorToElement(in: t) - var successors: [Block.ID] = [] - for _ in t.elements { - successors.append(appendBlock()) - } - - let n = emitUnionDiscriminator(storage, at: site) - insert(module.makeSwitch(on: n, toOneOf: successors, at: site)) + let targets = UnionSwitch.Targets( + t.elements.map({ (e) in (key: e, value: appendBlock()) }), + uniquingKeysWith: { (a, _) in a }) + emitUnionSwitch(on: storage, toOneOf: targets, at: site) let tail = appendBlock() - for i in 0 ..< elements.count { - insertionPoint = .end(of: successors[i]) - emitDeinitUnionPayload(of: storage, containing: elements[i], at: site) + for (u, b) in targets { + insertionPoint = .end(of: b) + emitDeinitUnionPayload(of: storage, containing: u, at: site) insert(module.makeBranch(to: tail, at: site)) } @@ -3083,6 +3182,113 @@ struct Emitter { insert(module.makeCloseUnion(x0, at: site)) } + // MARK: Equality + + private mutating func emitStoreEquality( + _ lhs: Operand, _ rhs: Operand, to target: Operand, at site: SourceRange + ) { + let m = module.type(of: lhs).ast + let d = program.ast.core.equatable.type + + if let equatable = program.conformance(of: m, to: d, exposedTo: insertionScope!) { + let d = module.demandEqualDeclaration(definedBy: equatable) + let f = module.reference(to: d, implementedFor: equatable) + + let x0 = insert(module.makeAccess(.set, from: target, at: site))! + let x1 = insert(module.makeAccess(.let, from: lhs, at: site))! + let x2 = insert(module.makeAccess(.let, from: rhs, at: site))! + insert(module.makeCall(applying: .constant(f), to: [x1, x2], writingResultTo: x0, at: site)) + insert(module.makeEndAccess(x2, at: site)) + insert(module.makeEndAccess(x1, at: site)) + insert(module.makeEndAccess(x0, at: site)) + } else { + report(.error(m, doesNotConformTo: d, at: site)) + } + } + + /// Inserts the IR writing in `target` whether the parts of `lhs` and `rhs` are pairwise equal. + private mutating func emitStorePartsEquality( + _ lhs: Operand, _ rhs: Operand, + to target: Operand, at site: SourceRange + ) { + let layout = AbstractTypeLayout( + of: module.type(of: lhs).ast, definedIn: module.program) + + // If the object is empty, return true. + var parts = layout.properties[...] + if parts.isEmpty { + emitStore(boolean: true, to: target, at: site) + return + } + + // Otherwise, compare all parts pairwise. + let tail = appendBlock() + while !parts.isEmpty { + let x0 = emitSubfieldView(lhs, at: [parts.startIndex], at: site) + let x1 = emitSubfieldView(rhs, at: [parts.startIndex], at: site) + emitStoreEquality(x0, x1, to: target, at: site) + + parts = parts.dropFirst() + if parts.isEmpty { + insert(module.makeBranch(to: tail, at: site)) + insertionPoint = .end(of: tail) + } else { + let x2 = emitLoadBuiltinBool(target, at: site) + let next = appendBlock() + insert(module.makeCondBranch(if: x2, then: next, else: tail, at: site)) + insertionPoint = .end(of: next) + } + } + } + + /// Inserts the IR writing in `target` whether the payloads of `lhs` and `rhs` are equal. + private mutating func emitStoreUnionPayloadEquality( + _ lhs: Operand, _ rhs: Operand, + to target: Operand, at site: SourceRange + ) { + let union = UnionType(module.type(of: lhs).ast)! + + // If the union is empty, return true. + if union.elements.isEmpty { + emitStore(boolean: true, to: target, at: site) + return + } + + // Otherwise, compare their payloads. + let same = appendBlock() + let targets = UnionSwitch.Targets( + union.elements.map({ (e) in (key: e, value: appendBlock()) }), + uniquingKeysWith: { (a, _) in a }) + let fail = appendBlock() + let tail = appendBlock() + + // The success blocks compare discriminators and then payloads. + let dl = emitUnionDiscriminator(lhs, at: site) + let dr = emitUnionDiscriminator(rhs, at: site) + let x0 = insert(module.makeLLVM(applying: .icmp(.eq, .discriminator), to: [dl, dr], at: site))! + insert(module.makeCondBranch(if: x0, then: same, else: fail, at: site)) + + insertionPoint = .end(of: same) + emitUnionSwitch(on: lhs, toOneOf: targets, at: site) + for (u, b) in targets { + insertionPoint = .end(of: b) + let y0 = insert(module.makeOpenUnion(lhs, as: u, at: site))! + let y1 = insert(module.makeOpenUnion(rhs, as: u, at: site))! + emitStoreEquality(y0, y1, to: target, at: site) + insert(module.makeCloseUnion(y1, at: site)) + insert(module.makeCloseUnion(y0, at: site)) + insert(module.makeBranch(to: tail, at: site)) + } + + // The failure block writes `false` to the return storage. + insertionPoint = .end(of: fail) + emitStore(boolean: false, to: target, at: site) + insert(module.makeBranch(to: tail, at: site)) + + // The tail block represents the continuation. + insertionPoint = .end(of: tail) + } + // MARK: Helpers /// Returns the canonical form of `t` in the current insertion scope. @@ -3175,6 +3381,16 @@ struct Emitter { return x1 } + /// Appends the IR for jumping to the block assigned to the type of `scrutinee`'s payload in + /// `targets`. + private mutating func emitUnionSwitch( + on scrutinee: Operand, toOneOf targets: UnionSwitch.Targets, at site: SourceRange + ) { + let u = UnionType(module.type(of: scrutinee).ast)! + let i = emitUnionDiscriminator(scrutinee, at: site) + insert(module.makeUnionSwitch(over: i, of: u, toOneOf: targets, at: site)) + } + /// Returns the result of calling `action` on a copy of `self` in which a `newFrame` is the top /// frame. /// diff --git a/Sources/IR/FunctionID.swift b/Sources/IR/FunctionID.swift index e89b0dae6..aa14aa169 100644 --- a/Sources/IR/FunctionID.swift +++ b/Sources/IR/FunctionID.swift @@ -39,19 +39,21 @@ extension Function { /// Creates the identity of the lowered form of `s`. init(_ s: SynthesizedFunctionDecl) { + precondition(s.type.isCanonical) self.value = .synthesized(s) } /// Creates the identity of the existentialized form of `base`. - public init(existentialized base: Function.ID) { + init(existentialized base: Function.ID) { self.value = .existentialized(base: base) } /// Creates the identity of the monomorphized form of `base` for `arguments`. /// /// - Requires: `arguments` is not empty. - public init(monomorphized base: Function.ID, for arguments: GenericArguments) { + init(monomorphized base: Function.ID, for arguments: GenericArguments) { precondition(!arguments.isEmpty) + precondition(arguments.allSatisfy(\.value.isCanonical)) self.value = .monomorphized(base: base, arguments: arguments) } diff --git a/Sources/IR/InstructionTransformer.swift b/Sources/IR/InstructionTransformer.swift index dd2eb16df..f0e7a9540 100644 --- a/Sources/IR/InstructionTransformer.swift +++ b/Sources/IR/InstructionTransformer.swift @@ -239,6 +239,16 @@ extension IR.Program { target.makeUnionDiscriminator(x0, at: s.site) } + case let s as UnionSwitch: + let x0 = t.transform(s.discriminator, in: &self) + let x1 = UnionType(t.transform(^s.union, in: &self))! + let x2 = s.targets.reduce(into: UnionSwitch.Targets()) { (d, kv) in + _ = d[t.transform(kv.key, in: &self)].setIfNil(t.transform(kv.value, in: &self)) + } + return insert(at: p, in:n) { (target) in + target.makeUnionSwitch(over: x0, of: x1, toOneOf: x2, at: s.site) + } + case let s as Unreachable: return modules[n]!.insert(s, at: p) diff --git a/Sources/IR/IteratorWitness.swift b/Sources/IR/IteratorWitness.swift new file mode 100644 index 000000000..a7455801a --- /dev/null +++ b/Sources/IR/IteratorWitness.swift @@ -0,0 +1,19 @@ +import FrontEnd + +/// The witness of a type's conformance to the `Iterator` trait from the standard library. +struct IteratorWitness { + + /// The implementation of the `Iterator.Element`. + let element: AnyType + + /// The implementation of `Iterator.next`. + let next: Operand + + /// Creates the lowered witness of the conformance `c` for use in `module`. + init(_ c: FrontEnd.Conformance, in module: inout Module) { + let iterator = module.program.ast.core.iterator + self.element = module.program.associatedType(iterator.element, for: c) + self.next = .constant(module.reference(toImplementationOf: iterator.next, for: c)) + } + +} diff --git a/Sources/IR/Mangling/DemangledEntity.swift b/Sources/IR/Mangling/DemangledEntity.swift index 940544774..8a0024649 100644 --- a/Sources/IR/Mangling/DemangledEntity.swift +++ b/Sources/IR/Mangling/DemangledEntity.swift @@ -5,7 +5,7 @@ import Utils public struct DemangledEntity: Hashable { /// The qualification of the symbol, if any. - public let qualification: Indirect? + public let qualification: DemangledQualification? /// The kind of the symbol, if known. public let kind: NodeKind? @@ -24,13 +24,13 @@ public struct DemangledEntity: Hashable { /// Creates an instance with the given properties. public init( - qualification: DemangledEntity?, + qualification: DemangledQualification?, kind: NodeKind, name: Name, genericArgumentLabels: [String?] = [], type: DemangledType? = nil ) { - self.qualification = qualification.map(Indirect.init(_:)) + self.qualification = qualification self.kind = kind self.name = name self.genericArgumentLabels = genericArgumentLabels @@ -39,8 +39,8 @@ public struct DemangledEntity: Hashable { } /// Creates an instance identifying an anonymous scope. - public init(anonymousScope id: Int, qualifiedBy q: DemangledEntity) { - self.qualification = Indirect(q) + public init(anonymousScope id: Int, qualifiedBy q: DemangledQualification) { + self.qualification = q self.kind = nil self.name = Name(stem: id.description) self.genericArgumentLabels = [] @@ -51,7 +51,9 @@ public struct DemangledEntity: Hashable { /// Creates an instance representing a core type declaration. public init(coreType: String) { self.init( - qualification: .hylo, kind: NodeKind(ProductTypeDecl.self), name: Name(stem: coreType)) + qualification: .entity(.hylo), + kind: NodeKind(ProductTypeDecl.self), + name: Name(stem: coreType)) } /// The `Hylo` module. @@ -121,20 +123,26 @@ extension DemangledEntity: CustomStringConvertible { } let i = inputs.reduce(into: "", { (s, p) in s += (p.label ?? "_") + ":" }) - return "\(name)(\(i))" + return "\(name)[\(i)]" } /// Returns the textual description of a qualification. - private static func describe(qualification: Indirect?) -> String { - guard let q = qualification else { + private static func describe(qualification: DemangledQualification?) -> String { + switch qualification { + case .some(.entity(let q)): + if q.kind?.value == TranslationUnit.self { + return describe(qualification: q.qualification) + } else { + return q.description + "." + } + + case .some(let q): + return q.description + + case nil: return "" } - if q.value.kind?.value == TranslationUnit.self { - return describe(qualification: q.value.qualification) - } else { - return q.description + "." - } } } diff --git a/Sources/IR/Mangling/DemangledQualification.swift b/Sources/IR/Mangling/DemangledQualification.swift new file mode 100644 index 000000000..b4d5f29b4 --- /dev/null +++ b/Sources/IR/Mangling/DemangledQualification.swift @@ -0,0 +1,41 @@ +/// The demangled qualification of a symbol. +public indirect enum DemangledQualification: Hashable { + + /// An entity. + case entity(DemangledEntity) + + /// A reference to the innermost enclosing entity. + case relative + + /// Creates an instance wrapping `e` iff it is not `nil`. + init?(_ e: DemangledEntity?) { + if let s = e { + self = .entity(s) + } else { + return nil + } + } + + /// The entity wrapped in `self` if its payload is `.entity`, or `nil` otherwise. + public var entity: DemangledEntity? { + if case .entity(let e) = self { + return e + } else { + return nil + } + } + +} + +extension DemangledQualification: CustomStringConvertible { + + public var description: String { + switch self { + case .entity(let d): + return d.description + case .relative: + return ".." + } + } + +} diff --git a/Sources/IR/Mangling/DemangledSymbol.swift b/Sources/IR/Mangling/DemangledSymbol.swift index 5c099ddcc..642e712c4 100644 --- a/Sources/IR/Mangling/DemangledSymbol.swift +++ b/Sources/IR/Mangling/DemangledSymbol.swift @@ -1,7 +1,7 @@ import Utils /// The demangled description of a or entity. -public enum DemangledSymbol: Hashable { +public indirect enum DemangledSymbol: Hashable { /// Creates an instance decoding the symbol mangled in `s`, returning `nil` if decoding failed. public init?(_ s: String) { @@ -41,6 +41,15 @@ public enum DemangledSymbol: Hashable { /// The declaration of an entity or bundle. case entity(DemangledEntity) + /// A binding declaration. + case binding(names: [DemangledEntity]) + + /// A synthesized declaration. + case synthesized(DemangledSynthesizedFunction) + + /// A monomorphized symbols. + case monomorphized(DemangledSymbol, arguments: [DemangledSymbol]) + /// A type. case type(DemangledType) @@ -61,6 +70,12 @@ extension DemangledSymbol: CustomStringConvertible { switch self { case .entity(let e): return e.description + case .binding(let n): + return n.description + case .synthesized(let d): + return d.description + case .monomorphized(let e, let z): + return "\(e) for \(z)" case .type(let t): return t.description } diff --git a/Sources/IR/Mangling/DemangledSynthesizedFunction.swift b/Sources/IR/Mangling/DemangledSynthesizedFunction.swift new file mode 100644 index 000000000..9253ae70f --- /dev/null +++ b/Sources/IR/Mangling/DemangledSynthesizedFunction.swift @@ -0,0 +1,49 @@ +import Utils + +/// The demangled description of a synthesized function. +public struct DemangledSynthesizedFunction: Hashable { + + /// The kind of a synthesized declaration. + public indirect enum Kind: Hashable { + + /// A deinitializer. + case deinitialize + + /// A move-initialization method. + case moveInitialization + + /// A move-assignment method. + case moveAssignment + + /// A copy method. + case copy + + /// An equality method. + case equal + + /// A global initializer for a binding declaration. + case globalInitialization(DemangledSymbol) + + /// Lambda generated for an autoclosure argument. + case autoclosure(Int) + + } + + /// The type of this declaration. + public let type: DemangledType + + /// The scope in which the declaration is defined. + public let scope: Indirect + + /// The kind of the declaration. + public let kind: Kind + +} + +extension DemangledSynthesizedFunction: CustomStringConvertible { + + public var description: String { + "\(scope).\(kind)" + } + +} diff --git a/Sources/IR/Mangling/DemangledType.swift b/Sources/IR/Mangling/DemangledType.swift index d4f0e4827..c071f0878 100644 --- a/Sources/IR/Mangling/DemangledType.swift +++ b/Sources/IR/Mangling/DemangledType.swift @@ -98,7 +98,7 @@ extension DemangledType: CustomStringConvertible { let i = inputs.map { (p) -> String in (p.label.map({ $0 + ": " }) ?? "") + p.type.description } - return "[\(environment)](\(list: i) \(effect) -> \(output)" + return "[\(environment)](\(list: i)) \(effect) -> \(output)" case .associatedType(let domain, let name): return "\(domain).\(name)" @@ -135,7 +135,7 @@ extension DemangledType: CustomStringConvertible { (p.label.map({ $0 + ": " }) ?? "") + p.type.description } let cs = capabilities.elements.descriptions(joinedBy: " ") - return "[\(environment)](\(list: i) : \(output) { \(cs) }" + return "[\(environment)](\(list: i)) : \(output) { \(cs) }" case .union(let elements): return "Union<\(list: elements)>" diff --git a/Sources/IR/Mangling/Demangler.swift b/Sources/IR/Mangling/Demangler.swift index fd894e45c..47a4a44c8 100644 --- a/Sources/IR/Mangling/Demangler.swift +++ b/Sources/IR/Mangling/Demangler.swift @@ -3,6 +3,9 @@ import FrontEnd /// Hylo's demangling algorithm. struct Demangler { + /// The list of demangled strings, in order of appearance (a.k.a. the string lookup table). + private var strings: [Substring] = [] + /// The list of demangled symbols, in order of appearance (a.k.a. the symbol lookup table). private var symbols: [DemangledSymbol] = [] @@ -11,7 +14,7 @@ struct Demangler { /// Demangles a symbol from `stream`. mutating func demangle(from stream: inout Substring) -> DemangledSymbol? { - var qualification: DemangledEntity? = nil + var qualification: DemangledQualification? = nil while let o = takeOperator(from: &stream) { let demangled: DemangledSymbol? @@ -19,12 +22,16 @@ struct Demangler { switch o { case .anonymousScope: demangled = takeAnonymousScope(qualifiedBy: qualification, from: &stream) + case .arrowType: + demangled = takeArrowType(from: &stream) case .associatedType: demangled = takeAssociatedType(from: &stream) case .associatedTypeDecl: demangled = take(AssociatedTypeDecl.self, qualifiedBy: qualification, from: &stream) case .associatedValueDecl: demangled = take(AssociatedValueDecl.self, qualifiedBy: qualification, from: &stream) + case .bindingDecl: + demangled = takeBindingDecl(qualifiedBy: qualification, from: &stream) case .boundGenericType: demangled = takeBoundGenericType(from: &stream) case .bufferType: @@ -61,8 +68,6 @@ struct Demangler { demangled = takeNominalType(declaredBy: GenericParameterDecl.self, from: &stream) case .importDecl: demangled = take(ImportDecl.self, qualifiedBy: qualification, from: &stream) - case .arrowType: - demangled = takeArrowType(from: &stream) case .lookup: demangled = takeLookup(from: &stream) case .memberwiseInitializerDecl: @@ -71,6 +76,8 @@ struct Demangler { demangled = takeMetatypeType(from: &stream) case .moduleDecl: demangled = takeModuleDecl(from: &stream) + case .monomorphizedFunctionDecl: + demangled = takeMonomorphizedFunctionDecl(from: &stream) case .namespaceDecl: demangled = take(NamespaceDecl.self, qualifiedBy: qualification, from: &stream) case .parameterDecl: @@ -95,6 +102,8 @@ struct Demangler { demangled = takeSubscriptImpl(qualifiedBy: qualification, from: &stream) case .subscriptType: demangled = takeSubscriptType(from: &stream) + case .synthesizedFunctionDecl: + demangled = takeSynthesizedFunctionDecl(from: &stream) case .traitDecl: demangled = take(TraitDecl.self, qualifiedBy: qualification, from: &stream) case .traitType: @@ -110,21 +119,21 @@ struct Demangler { case .varDecl: demangled = take(VarDecl.self, qualifiedBy: qualification, from: &stream) + case .lookupRelative: + qualification = .relative + continue + case .subscriptImplType: fatalError() case .existentializedFunctionDecl: return nil - case .monomorphizedFunctionDecl: - return nil case .methodDecl: return nil case .methodImpl: return nil case .methodType: return nil - case .synthesizedFunctionDecl: - return nil case .conformanceConstraint, .equalityConstraint, .valueConstraint, .whereClause: return nil case .witnessTable: @@ -133,13 +142,13 @@ struct Demangler { // End of sequence reached if `demangled` is `nil`. guard let d = demangled else { break } - if (o != .lookup) && (o != .reserved) { + if (o != .lookup) && (o != .lookupRelative) && (o != .reserved) { symbols.append(d) } // Update the context and look for the next symbol if we demangled a scope. if let e = d.entity, e.isScope { - qualification = e + qualification = .entity(e) continue } @@ -147,7 +156,7 @@ struct Demangler { return d } - if let e = qualification { + if let e = qualification?.entity { return .entity(e) } else { return nil @@ -158,25 +167,25 @@ struct Demangler { private mutating func demangleEntity( _: T.Type, from stream: inout Substring ) -> DemangledEntity? { - guard - case .entity(let e) = demangle(from: &stream), - e.kind?.value == T.self - else { return nil } - - return e + if case .entity(let e) = demangle(from: &stream), e.kind?.value == T.self { + return e + } else { + return nil + } } /// Demangles a type from `stream`. private mutating func demangleType(from stream: inout Substring) -> DemangledType? { - guard case .type(let t) = demangle(from: &stream) else { + if case .type(let t) = demangle(from: &stream) { + return t + } else { return nil } - return t } /// Demangles an entity declaration of type `T` from `stream`. private mutating func take( - _: T.Type, qualifiedBy qualification: DemangledEntity?, + _: T.Type, qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -188,7 +197,7 @@ struct Demangler { /// Demangles an conformance declaration from `stream`. private mutating func take( - _: T.Type, qualifiedBy qualification: DemangledEntity?, + _: T.Type, qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -215,7 +224,7 @@ struct Demangler { /// Demangles an anonymous scope from `stream`. private mutating func takeAnonymousScope( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -225,9 +234,20 @@ struct Demangler { return .entity(.init(anonymousScope: Int(i.rawValue), qualifiedBy: q)) } + /// Demangles a binding declaration from `stream`. + private mutating func takeBindingDecl( + qualifiedBy qualification: DemangledQualification?, + from stream: inout Substring + ) -> DemangledSymbol? { + let vs = takeItems(from: &stream) { (me, s) in + me.demangle(from: &s)?.entity + } + return vs.map({ (ns) in .binding(names: ns) }) + } + /// Demangles a function from `stream`. private mutating func takeFunctionDecl( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -246,9 +266,67 @@ struct Demangler { return .entity(e) } + /// Demangles a synthesized function from `stream`. + private mutating func takeSynthesizedFunctionDecl( + from stream: inout Substring + ) -> DemangledSymbol? { + guard + let k = takeSynthesizedFunctionDeclKind(from: &stream), + let s = demangle(from: &stream), + let t = demangleType(from: &stream) + else { return nil } + return .synthesized(.init(type: t, scope: .init(s), kind: k)) + } + + /// Demangles the kind of a synthesized function from `stream`. + private mutating func takeSynthesizedFunctionDeclKind( + from stream: inout Substring + ) -> DemangledSynthesizedFunction.Kind? { + guard let k = takeBase64Digit(from: &stream) else { return nil } + switch k.rawValue { + case 0: return .deinitialize + case 1: return .moveInitialization + case 2: return .moveAssignment + case 3: return .copy + case 4: return .equal + case 5: return takeSynthesizedGlobalInitializationKind(from: &stream) + case 6: return takeSynthesizedAutoclosureKind(from: &stream) + default: return nil + } + } + + /// Demangles the kind of a synthesized global initializer from `stream`. + private mutating func takeSynthesizedGlobalInitializationKind( + from stream: inout Substring + ) -> DemangledSynthesizedFunction.Kind? { + demangle(from: &stream).map { (d) in + DemangledSynthesizedFunction.Kind.globalInitialization(d) + } + } + + /// Demangles the kind of a synthesized autoclosure from `stream`. + private func takeSynthesizedAutoclosureKind( + from stream: inout Substring + ) -> DemangledSynthesizedFunction.Kind? { + takeInteger(from: &stream).map { (n) in + DemangledSynthesizedFunction.Kind.autoclosure(Int(n.rawValue)) + } + } + + /// Demangles a monomorphized function from `stream`. + private mutating func takeMonomorphizedFunctionDecl( + from stream: inout Substring + ) -> DemangledSymbol? { + guard + let f = demangle(from: &stream), + let z = takeGenericArguments(from: &stream) + else { return nil } + return .monomorphized(f, arguments: z) + } + /// Demangles a memberwise initializer from `stream`. private func takeMemberwiseInitializerDecl( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -275,7 +353,7 @@ struct Demangler { /// Demangles a property declaration from `stream`. private mutating func takePropertyDecl( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -294,7 +372,7 @@ struct Demangler { /// Demangles a property declaration from `stream`. private mutating func takeSubscriptDecl( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -315,7 +393,7 @@ struct Demangler { /// Demangles a property declaration from `stream`. private mutating func takeSubscriptImpl( - qualifiedBy qualification: DemangledEntity?, + qualifiedBy qualification: DemangledQualification?, from stream: inout Substring ) -> DemangledSymbol? { guard @@ -332,27 +410,17 @@ struct Demangler { /// Demangles a direct declaration reference from `stream`. private mutating func takeDirectDeclReference(from stream: inout Substring) -> DemangledSymbol? { - guard - let e = demangle(from: &stream) - else { return nil } - print(e) - return nil + UNIMPLEMENTED() } /// Demangles a reference to a symbol from `stream`. private mutating func takeLookup(from stream: inout Substring) -> DemangledSymbol? { - guard - let position = takeInteger(from: &stream) - else { return nil } - return symbols[Int(position.rawValue)] + takeInteger(from: &stream).flatMap({ (n) in symbols[Int(n.rawValue)] }) } /// Demangles a reserved symbol from `stream`. private mutating func takeReserved(from stream: inout Substring) -> DemangledSymbol? { - guard - let r = take(ReservedSymbol.self, from: &stream) - else { return nil } - return DemangledSymbol(reserved: r) + take(ReservedSymbol.self, from: &stream).map(DemangledSymbol.init(reserved:)) } /// Demangles an associated type from `stream`. @@ -368,19 +436,15 @@ struct Demangler { private mutating func takeBoundGenericType(from stream: inout Substring) -> DemangledSymbol? { guard let b = demangleType(from: &stream), - let argumentCount = takeInteger(from: &stream) + let z = takeGenericArguments(from: &stream) else { return nil } - var parameterization: [DemangledSymbol] = [] - parameterization.reserveCapacity(Int(argumentCount.rawValue)) - for _ in 0 ..< argumentCount.rawValue { - guard - let a = demangle(from: &stream) - else { return nil } - parameterization.append(a) - } + return .type(.boundGeneric(base: b, arguments: z)) + } - return .type(.boundGeneric(base: b, arguments: parameterization)) + /// Demangles a list of generic arguments from `stream`. + private mutating func takeGenericArguments(from stream: inout Substring) -> [DemangledSymbol]? { + takeItems(from: &stream, takingEachWith: { (me, s) in me.demangle(from: &s) }) } /// Demangles a buffer type from `stream`. @@ -394,26 +458,19 @@ struct Demangler { /// Demangles a built-in integer type from `stream`. private mutating func takeBuiltinIntegerType(from stream: inout Substring) -> DemangledSymbol? { - guard - let width = takeInteger(from: &stream) - else { return nil } - return .type(.builtin(.i(Int(width.rawValue)))) + takeInteger(from: &stream).map({ (w) in .type(.builtin(.i(Int(w.rawValue)))) }) } /// Demangles a built-in float type from `stream`. private mutating func takeBuiltinFloatType(from stream: inout Substring) -> DemangledSymbol? { - guard - let width = takeInteger(from: &stream) - else { return nil } - - switch width.rawValue { - case 16: + switch takeInteger(from: &stream)?.rawValue { + case .some(16): return .type(.builtin(.float16)) - case 32: + case .some(32): return .type(.builtin(.float32)) - case 64: + case .some(64): return .type(.builtin(.float64)) - case 128: + case .some(128): return .type(.builtin(.float128)) default: return nil @@ -424,50 +481,24 @@ struct Demangler { private mutating func takeExistentialGenericType( from stream: inout Substring ) -> DemangledSymbol? { - guard - let interface = demangleType(from: &stream) - else { return nil } - return .type(.existentialGeneric(interface)) + demangleType(from: &stream).map({ (i) in .type(.existentialGeneric(i)) }) } /// Demangles an existential generic type from `stream`. private mutating func takeExistentialTraitType( from stream: inout Substring ) -> DemangledSymbol? { - guard - let traitCount = takeInteger(from: &stream) - else { return nil } - - var traits: [DemangledType] = [] - traits.reserveCapacity(Int(traitCount.rawValue)) - for _ in 0 ..< traitCount.rawValue { - guard - let t = demangleType(from: &stream) - else { return nil } - traits.append(t) + let ts = takeItems(from: &stream) { (me, s) in + me.demangleType(from: &s) } - - return .type(.existentialTrait(traits)) + return ts.map({ (s) in .type(.existentialTrait(s))}) } /// Demangles an arrow type from `stream`. private mutating func takeArrowType(from stream: inout Substring) -> DemangledSymbol? { guard let environment = demangleType(from: &stream), - let inputCount = takeInteger(from: &stream) - else { return nil } - - var inputs: [DemangledType.Parameter] = [] - inputs.reserveCapacity(Int(inputCount.rawValue)) - for _ in 0 ..< inputCount.rawValue { - guard - let l = takeString(from: &stream), - let t = demangleType(from: &stream) - else { return nil } - inputs.append(.init(label: l.isEmpty ? nil : String(l), type: t)) - } - - guard + let inputs = takeParameters(from: &stream), let output = demangleType(from: &stream) else { return nil } @@ -478,18 +509,12 @@ struct Demangler { private mutating func takeNominalType( declaredBy _: T.Type, from stream: inout Substring ) -> DemangledSymbol? { - guard - let e = demangleEntity(T.self, from: &stream) - else { return nil } - return .type(.nominal(e)) + demangleEntity(T.self, from: &stream).map({ (e) in .type(.nominal(e)) }) } /// Demangles a metatype from `stream`. private mutating func takeMetatypeType(from stream: inout Substring) -> DemangledSymbol? { - guard - let instance = demangleType(from: &stream) - else { return nil } - return .type(.metatype(instance)) + demangleType(from: &stream).map({ (t) in .type(.metatype(t)) }) } /// Demangles a parameter type from `stream`. @@ -515,20 +540,7 @@ struct Demangler { guard let capabilities = take(AccessEffectSet.self, from: &stream), let environment = demangleType(from: &stream), - let inputCount = takeInteger(from: &stream) - else { return nil } - - var inputs: [DemangledType.Parameter] = [] - inputs.reserveCapacity(Int(inputCount.rawValue)) - for _ in 0 ..< inputCount.rawValue { - guard - let l = takeString(from: &stream), - let t = demangleType(from: &stream) - else { return nil } - inputs.append(.init(label: l.isEmpty ? nil : String(l), type: t)) - } - - guard + let inputs = takeParameters(from: &stream), let output = demangleType(from: &stream) else { return nil } @@ -539,21 +551,7 @@ struct Demangler { /// Demangles a tuple type from `stream`. private mutating func takeTupleType(from stream: inout Substring) -> DemangledSymbol? { - guard - let count = takeInteger(from: &stream) - else { return nil } - - var elements: [DemangledType.Parameter] = [] - elements.reserveCapacity(Int(count.rawValue)) - for _ in 0 ..< count.rawValue { - guard - let l = takeString(from: &stream), - let t = demangleType(from: &stream) - else { return nil } - elements.append(.init(label: l.isEmpty ? nil : String(l), type: t)) - } - - return .type(.tuple(elements)) + takeParameters(from: &stream).map({ (s) in .type(.tuple(s)) }) } /// Demangles a union type from `stream`. @@ -576,6 +574,22 @@ struct Demangler { return .type(.union(elements)) } + /// Demangles a list of parameters or tuple elements from `stream`. + private mutating func takeParameters( + from stream: inout Substring + ) -> [DemangledType.Parameter]? { + takeItems(from: &stream, takingEachWith: { (me, s) in me.takeParameter(from: &s) }) + } + + /// Demangles a parameter or tuple element from `stream`. + private mutating func takeParameter(from stream: inout Substring) -> DemangledType.Parameter? { + guard + let l = takeString(from: &stream), + let t = demangleType(from: &stream) + else { return nil } + return .init(label: l.isEmpty ? nil : String(l), type: t) + } + /// If `stream` starts with a mangling operator, consumes and returns it; returns /// `nil` without mutating `stream` otherwise. private func takeOperator(from stream: inout Substring) -> ManglingOperator? { @@ -597,7 +611,7 @@ struct Demangler { /// Assuming `stream` starts with a mangled name, consumes and returns it. Returns `nil` iff /// data seems corrupted - private func takeName(from stream: inout Substring) -> Name? { + private mutating func takeName(from stream: inout Substring) -> Name? { guard let tag = takeBase64Digit(from: &stream) else { return nil } @@ -627,15 +641,21 @@ struct Demangler { /// Assuming `stream` starts with a mangled string, consumes and returns it. Returns `nil` iff ///the data seems corrupted - private func takeString(from stream: inout Substring) -> Substring? { - guard let length = takeInteger(from: &stream) else { + private mutating func takeString(from stream: inout Substring) -> Substring? { + switch takeInteger(from: &stream)?.rawValue { + case .some(0): + return stream[stream.startIndex ..< stream.startIndex] + case .some(1): + return takeInteger(from: &stream).flatMap({ (m) in strings[Int(m.rawValue)] }) + case .some(let n): + let j = stream.index(stream.startIndex, offsetBy: Int(n - 2)) + let r = stream[..( _: T.Type, from stream: inout Substring ) -> T? where T.RawValue == UInt8 { - guard let d = takeBase64Digit(from: &stream) else { - return nil + takeBase64Digit(from: &stream).flatMap({ (d) in T(rawValue: d.rawValue) }) + } + + /// Demangles a list of `T`s from `stream`, calling `takeItem` to parse each individual element. + private mutating func takeItems( + from stream: inout Substring, + takingEachWith takeItem: (inout Self, inout Substring) -> T? + ) -> [T]? { + guard let n = takeInteger(from: &stream) else { return nil } + var xs: [T] = [] + xs.reserveCapacity(Int(n.rawValue)) + + for _ in 0 ..< n.rawValue { + guard let x = takeItem(&self, &stream) else { return nil } + xs.append(x) } - return T(rawValue: d.rawValue) + return xs } } diff --git a/Sources/IR/Mangling/Mangler.swift b/Sources/IR/Mangling/Mangler.swift index f41e5620e..8715d5a6d 100644 --- a/Sources/IR/Mangling/Mangler.swift +++ b/Sources/IR/Mangling/Mangler.swift @@ -21,702 +21,762 @@ struct Mangler { /// The program defining the symbols being defined. private let program: TypedProgram - /// The scope in which names are mangled. - private let scopeOfUse: AnyScopeID + /// A table mapping mangled strings to their position in the string lookup table. + private var stringPosition: [String: Int] = [:] /// A table mapping mangled symbols to their position in the symbol lookup table. - private var symbolID: [Symbol: Int] = [:] + private var symbolPosition: [Symbol: Int] = [:] - /// The ID of the next symbol inserted in the symbol lookup table. - private var nextSymbolID = 0 + /// The innermost scope being mangled, if any. + private var qualification: AnyScopeID? /// A table mapping known symbols to their reserved mangled identifier. - private var reserved: [Symbol: ReservedSymbol] = [ - .type(.any): .any, - .type(.void): .void, - .type(.never): .never, - ] - - /// Creates an instance mangling symbols defined in `programs` in `scopeOfUse`. - init(_ program: TypedProgram, in scopeOfUse: AnyScopeID) { + private var reserved: [Symbol: ReservedSymbol] = [:] + + /// Creates an instance mangling symbols defined in `programs`. + init(_ program: TypedProgram) { self.program = program - self.scopeOfUse = scopeOfUse + initializeReservedSymbols() + } + /// Initializes the table of reserved symbols. + private mutating func initializeReservedSymbols() { + reserved[.type(.any)] = .any + reserved[.type(.never)] = .never + reserved[.type(.void)] = .void if program.ast.coreModuleIsLoaded { - self.reserved[.node(AnyNodeID(program.ast.coreLibrary!))] = .hylo - register(coreType: "Bool", as: .bool) - register(coreType: "Int", as: .int) - register(coreType: "Float64", as: .float64) - register(coreType: "String", as: .string) + reserved[.node(AnyNodeID(program.ast.coreLibrary!))] = .hylo + registerReservedCoreType("Bool", as: .bool) + registerReservedCoreType("Int", as: .int) + registerReservedCoreType("Float64", as: .float64) + registerReservedCoreType("String", as: .string) } } - /// Associates `coreType` to `r`. - private mutating func register(coreType: String, as r: ReservedSymbol) { - let d = AnyNodeID(program.ast.coreType(coreType)!.decl) - reserved[.node(d)] = r + /// Extends `self.reserved` to associate the core type named `n` to the reserved symbol `s`. + private mutating func registerReservedCoreType(_ n: String, as s: ReservedSymbol) { + reserved[.node(AnyNodeID(program.ast.coreType(n)!.decl))] = s } - /// Writes the mangled representation of `d` to `output`. - mutating func mangle(decl d: T, to output: inout Output) { - if let m = ModuleDecl.ID(d) { - write(scope: AnyScopeID(m), to: &output) - return - } + /// Returns the mangled representation of `s`. + mutating func mangled(type s: AnyType) -> Output { + mangled(s, manglingWith: { (me, s, o) in me.append(type: s, to: &o) }) + } - if writeLookup(.node(AnyNodeID(d)), to: &output) { - return + /// Returns the mangled representation of `s`. + mutating func mangled(decl s: T) -> Output { + mangled(s, manglingWith: { (me, s, o) in me.append(decl: s, to: &o) }) + } + + /// Returns the mangled representation of `s` + mutating func mangled(function s: Function.ID) -> Output { + mangled(s, manglingWith: { (me, s, o) in me.append(function: s, to: &o) }) + } + + /// Returns the mangled representation of `s` + mutating func mangled(table s: WitnessTable) -> Output { + mangled(s, manglingWith: { (me, s, o) in me.append(table: s, to: &o) }) + } + + /// Returns the mangled representation of `s`, calling `mangle` to compute it. + private mutating func mangled( + _ s: T, manglingWith mangle: (inout Self, T, inout Output) -> Void + ) -> Output { + var c = stringPosition + var p = symbolPosition + var q = qualification + defer { + swap(&c, &stringPosition) + swap(&p, &symbolPosition) + swap(&q, &qualification) } - writeQualification(of: d, to: &output) + var output = "" + mangle(&self, s, &output) + return output + } + /// Writes the mangled representation of `d` to `output`. + private mutating func append(decl d: T, to output: inout Output) { + let s = Symbol.node(.init(d)) + if appendIf(reservedOrRecorded: s, to: &output) { return } + + appendQualification(of: d, to: &output) if let s = AnyScopeID(d) { - write(scope: s, to: &output) + append(scope: s, to: &output) return } switch d.kind { case AssociatedTypeDecl.self: - write(entity: AssociatedTypeDecl.ID(d)!, to: &output) + append(entity: AssociatedTypeDecl.ID(d)!, to: &output) case AssociatedValueDecl.self: - write(entity: AssociatedValueDecl.ID(d)!, to: &output) + append(entity: AssociatedValueDecl.ID(d)!, to: &output) case BindingDecl.self: - write(entity: BindingDecl.ID(d)!, to: &output) + append(entity: BindingDecl.ID(d)!, to: &output) case ImportDecl.self: - write(entity: ImportDecl.ID(d)!, to: &output) + append(entity: ImportDecl.ID(d)!, to: &output) case GenericParameterDecl.self: - write(entity: GenericParameterDecl.ID(d)!, to: &output) + append(entity: GenericParameterDecl.ID(d)!, to: &output) case ParameterDecl.self: - write(entity: ParameterDecl.ID(d)!, to: &output) + append(entity: ParameterDecl.ID(d)!, to: &output) case VarDecl.self: - write(entity: VarDecl.ID(d)!, to: &output) + append(entity: VarDecl.ID(d)!, to: &output) default: unexpected(d, in: program.ast) } - symbolID[.node(AnyNodeID(d))] = nextSymbolID - nextSymbolID += 1 + symbolPosition[.node(AnyNodeID(d))] = symbolPosition.count } /// Writes the mangled representation of `d` to `output`. - private mutating func write(entity d: BindingDecl.ID, to output: inout Output) { - let n = program.ast.names(in: program[d].pattern).first! - let v = program.ast[n.pattern].decl - mangle(decl: v, to: &output) + private mutating func append(entity d: T.ID, to output: inout Output) { + append(operator: .init(for: T.self), to: &output) + append(string: program.ast[d].baseName, to: &output) + } + + /// Writes the mangled representation of `d` to `output`. + private mutating func append(entity d: BindingDecl.ID, to output: inout Output) { + append(operator: .bindingDecl, to: &output) + append(items: program.ast.names(in: program[d].pattern), to: &output) { (me, n, o) in + me.append(decl: me.program.ast[n.pattern].decl, to: &o) + } } /// Writes the mangled qualification of `n` to `output`. - private mutating func writeQualification(of n: T, to output: inout Output) { - var qualification: [AnyScopeID] = [] + private mutating func appendQualification(of n: T, to output: inout Output) { + // Modules have no qualification. + if n.kind == ModuleDecl.self { return } + + // Find the prefix of the qualification that should be mangled as a reference. + var qs: [AnyScopeID] = [] for s in program.scopes(from: program[n].scope) { - if writeLookup(.node(AnyNodeID(s)), to: &output) { + // Anonymous scopes corresponding to the body of a function aren't mangled. + if let d = BraceStmt.ID(s), program.isCallableBody(d) { + continue + } else if appendIf(reservedOrRecorded: .node(.init(s)), to: &output) { + break + } else if s == qualification { + append(operator: .lookupRelative, to: &output) break } else { - qualification.append(s) + qs.append(s) } } - for s in qualification.reversed() { - // Anonymous scopes corresponding to the body of a function aren't mangled. - if let p = BraceStmt.ID(s), program.isCallableBody(p) { - continue - } - write(scope: s, to: &output) + // Write the mangled representation of the qualification's suffix. + for s in qs.reversed() { + append(scope: s, to: &output) } } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(scope symbol: AnyScopeID, to output: inout Output) { - if writeLookup(.node(AnyNodeID(symbol)), to: &output) { - return - } - - symbolID[.node(AnyNodeID(symbol))] = nextSymbolID - nextSymbolID += 1 + /// Writes the mangled representation of `s` to `output`. + private mutating func append(scope s: AnyScopeID, to output: inout Output) { + let n = Symbol.node(.init(s)) + if appendIf(reservedOrRecorded: n, to: &output) { return } - switch symbol.kind { + let q = qualification + qualification = s + switch s.kind { case BraceStmt.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) case ConditionalExpr.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) case ConditionalStmt.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) case ConformanceDecl.self: - write(conformance: ConformanceDecl.ID(symbol)!, to: &output) + append(conformance: ConformanceDecl.ID(s)!, to: &output) case ExtensionDecl.self: - write(extension: ExtensionDecl.ID(symbol)!, to: &output) + append(extension: ExtensionDecl.ID(s)!, to: &output) case ForStmt.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) case FunctionDecl.self: - write(function: FunctionDecl.ID(symbol)!, to: &output) + append(function: FunctionDecl.ID(s)!, to: &output) case InitializerDecl.self: - write(initializer: InitializerDecl.ID(symbol)!, to: &output) + append(initializer: InitializerDecl.ID(s)!, to: &output) case MatchCase.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) case MethodDecl.self: - write(methodDecl: MethodDecl.ID(symbol)!, to: &output) + append(methodDecl: MethodDecl.ID(s)!, to: &output) case MethodImpl.self: - write(methodImpl: MethodImpl.ID(symbol)!, to: &output) + append(methodImpl: MethodImpl.ID(s)!, to: &output) case ModuleDecl.self: - write(entity: ModuleDecl.ID(symbol)!, to: &output) + append(entity: ModuleDecl.ID(s)!, to: &output) case NamespaceDecl.self: - write(entity: NamespaceDecl.ID(symbol)!, to: &output) + append(entity: NamespaceDecl.ID(s)!, to: &output) case ProductTypeDecl.self: - write(entity: ProductTypeDecl.ID(symbol)!, to: &output) + append(entity: ProductTypeDecl.ID(s)!, to: &output) case SubscriptDecl.self: - write(subscriptDecl: SubscriptDecl.ID(symbol)!, to: &output) + append(subscriptDecl: SubscriptDecl.ID(s)!, to: &output) case SubscriptImpl.self: - write(subscriptImpl: SubscriptImpl.ID(symbol)!, to: &output) + append(subscriptImpl: SubscriptImpl.ID(s)!, to: &output) case TraitDecl.self: - write(entity: TraitDecl.ID(symbol)!, to: &output) + append(entity: TraitDecl.ID(s)!, to: &output) case TranslationUnit.self: - write(translationUnit: TranslationUnit.ID(symbol)!, to: &output) + append(translationUnit: TranslationUnit.ID(s)!, to: &output) case TypeAliasDecl.self: - write(entity: TypeAliasDecl.ID(symbol)!, to: &output) + append(entity: TypeAliasDecl.ID(s)!, to: &output) case WhileStmt.self: - write(anonymousScope: symbol, to: &output) + append(anonymous: s, to: &output) default: - unexpected(symbol, in: program.ast) + unexpected(s, in: program.ast) } + qualification = q + symbolPosition[n] = symbolPosition.count } /// Writes the mangled representation of `d` to `output`. - private mutating func write(entity d: T.ID, to output: inout Output) { - write(operator: .init(for: T.self), to: &output) - write(string: program.ast[d].baseName, to: &output) + private mutating func append(anonymous d: AnyScopeID, to output: inout Output) { + append(operator: .anonymousScope, to: &output) + append(integer: Int(d.rawValue.bits), to: &output) } /// Writes the mangled representation of `d` to `output`. - private mutating func write(anonymousScope d: AnyScopeID, to output: inout Output) { - write(operator: .anonymousScope, to: &output) - write(integer: d.rawValue, to: &output) - } - - /// Writes the mangled representation of `d` to `output`. - private mutating func write(conformance d: ConformanceDecl.ID, to output: inout Output) { - write(operator: .conformanceDecl, to: &output) - mangle(type: program[d].type, to: &output) + private mutating func append(conformance d: ConformanceDecl.ID, to output: inout Output) { + append(operator: .conformanceDecl, to: &output) + append(typeOf: d, to: &output) if let c = program.ast[d].whereClause { - write(whereClause: c.value, to: &output) + append(whereClause: c.value, to: &output) } else { - write(operator: .endOfSequence, to: &output) + append(operator: .endOfSequence, to: &output) } } /// Writes the mangled representation of `d` to `output`. - private mutating func write(extension d: ExtensionDecl.ID, to output: inout Output) { - write(operator: .extensionDecl, to: &output) - mangle(type: program[d].type, to: &output) + private mutating func append(extension d: ExtensionDecl.ID, to output: inout Output) { + append(operator: .extensionDecl, to: &output) + append(typeOf: d, to: &output) if let c = program.ast[d].whereClause { - write(whereClause: c.value, to: &output) + append(whereClause: c.value, to: &output) } else { - write(operator: .endOfSequence, to: &output) + append(operator: .endOfSequence, to: &output) } } /// Writes the mangled representation of `d` to `output`. - private mutating func write(function d: FunctionDecl.ID, to output: inout Output) { + private mutating func append(function d: FunctionDecl.ID, to output: inout Output) { // If the function is anonymous, just encode a unique ID. guard let n = program.ast.name(of: d) else { - write(anonymousScope: AnyScopeID(d), to: &output) + append(anonymous: AnyScopeID(d), to: &output) return } if program.ast[d].isStatic { - write(operator: .staticFunctionDecl, to: &output) + append(operator: .staticFunctionDecl, to: &output) } else { - write(operator: .functionDecl, to: &output) + append(operator: .functionDecl, to: &output) } - write(name: n, to: &output) - write(integer: program.ast[d].genericParameters.count, to: &output) - mangle(type: program[d].type, to: &output) + append(name: n, to: &output) + append(integer: program.ast[d].genericParameters.count, to: &output) + append(typeOf: d, to: &output) } /// Writes the mangled representation of `d` to `output`. - private mutating func write(initializer d: InitializerDecl.ID, to output: inout Output) { + private mutating func append(initializer d: InitializerDecl.ID, to output: inout Output) { // There's at most one memberwise initializer per product type declaration. if program.ast[d].isMemberwise { - write(operator: .memberwiseInitializerDecl, to: &output) + append(operator: .memberwiseInitializerDecl, to: &output) return } // Other initializers are mangled like static member functions. - write(operator: .staticFunctionDecl, to: &output) - write(name: Name(stem: "init"), to: &output) - write(integer: program.ast[d].genericParameters.count, to: &output) - mangle(type: program[d].type, to: &output) + append(operator: .staticFunctionDecl, to: &output) + append(name: Name(stem: "init"), to: &output) + append(integer: program.ast[d].genericParameters.count, to: &output) + append(typeOf: d, to: &output) } /// Writes the mangled representation of `d` to `output`. - private mutating func write(methodDecl d: MethodDecl.ID, to output: inout Output) { - write(operator: .methodDecl, to: &output) - write(string: program.ast[d].identifier.value, to: &output) - mangle(type: program[d].type, to: &output) + private mutating func append(methodDecl d: MethodDecl.ID, to output: inout Output) { + append(operator: .methodDecl, to: &output) + append(string: program.ast[d].identifier.value, to: &output) + append(typeOf: d, to: &output) } /// Writes the mangled representation of `d` to `output`. - private mutating func write(methodImpl d: MethodImpl.ID, to output: inout Output) { - write(operator: .methodImpl, to: &output) - write(base64Digit: program.ast[d].introducer.value, to: &output) + private mutating func append(methodImpl d: MethodImpl.ID, to output: inout Output) { + append(operator: .methodImpl, to: &output) + append(base64Digit: program.ast[d].introducer.value, to: &output) } /// Writes the mangled representation of `d` to `output`. - private mutating func write(subscriptDecl d: SubscriptDecl.ID, to output: inout Output) { + private mutating func append(subscriptDecl d: SubscriptDecl.ID, to output: inout Output) { if program.ast[d].isProperty { - write(operator: .propertyDecl, to: &output) - write(string: program.ast[d].identifier?.value ?? "", to: &output) + append(operator: .propertyDecl, to: &output) + append(string: program.ast[d].identifier?.value ?? "", to: &output) } else { - write(operator: .subscriptDecl, to: &output) - write(string: program.ast[d].identifier?.value ?? "", to: &output) - write(integer: program.ast[d].genericParameters.count, to: &output) + append(operator: .subscriptDecl, to: &output) + append(string: program.ast[d].identifier?.value ?? "", to: &output) + append(integer: program.ast[d].genericParameters.count, to: &output) } - - mangle(type: program[d].type, to: &output) + append(typeOf: d, to: &output) } /// Writes the mangled representation of `u` to `output`. - private mutating func write(subscriptImpl d: SubscriptImpl.ID, to output: inout Output) { - write(operator: .subscriptImpl, to: &output) - write(base64Digit: program.ast[d].introducer.value, to: &output) + private mutating func append(subscriptImpl d: SubscriptImpl.ID, to output: inout Output) { + append(operator: .subscriptImpl, to: &output) + append(base64Digit: program.ast[d].introducer.value, to: &output) } /// Writes the mangled representation of `u` to `output`. - private mutating func write(translationUnit u: TranslationUnit.ID, to output: inout Output) { + private mutating func append(translationUnit u: TranslationUnit.ID, to output: inout Output) { // Note: assumes all files in a module have a different base name. - write(operator: .translatonUnit, to: &output) - write(string: program.ast[u].site.file.baseName, to: &output) + append(operator: .translatonUnit, to: &output) + append(string: program.ast[u].site.file.baseName, to: &output) } /// Writes the mangled representation of `clause` to `output`. - private mutating func write(whereClause clause: WhereClause, to output: inout Output) { - write(operator: .whereClause, to: &output) - write(set: clause.constraints, to: &output) { (m, c) -> String in - var s = "" - m.write(constraint: c.value, to: &s) - return s + private mutating func append(whereClause clause: WhereClause, to output: inout Output) { + append(operator: .whereClause, to: &output) + append(unordered: clause.constraints, to: &output) { (me, c, o) in + me.append(constraint: c.value, to: &o) } } /// Writes the mangled representation of `c` to `output`. - private mutating func write(constraint c: WhereClause.ConstraintExpr, to output: inout Output) { + private mutating func append(constraint c: WhereClause.ConstraintExpr, to output: inout Output) { switch c { case .value: UNIMPLEMENTED() case .bound(let lhs, let rhs): - write(operator: .conformanceConstraint, to: &output) - mangle(type: program[lhs].type, to: &output) - write(integer: rhs.count, to: &output) - for t in rhs { - mangle(type: program[t].type, to: &output) - } + append(operator: .conformanceConstraint, to: &output) + append(typeOf: lhs, to: &output) + append(items: rhs, to: &output) { (me, r, o) in me.append(typeOf: r, to: &o) } case .equality(let lhs, let rhs): - write(operator: .equalityConstraint, to: &output) - mangle(type: program[lhs].type, to: &output) - mangle(type: program[rhs].type, to: &output) + append(operator: .equalityConstraint, to: &output) + append(typeOf: lhs, to: &output) + append(typeOf: rhs, to: &output) } } - /// Writes the mangled representation of `symbol` to `output`. - mutating func mangle(function symbol: Function.ID, to output: inout Output) { - switch symbol.value { + /// Writes the mangled representation of `s` to `output`. + private mutating func append(function s: Function.ID, to output: inout Output) { + switch s.value { case .lowered(let d): - mangle(decl: d, to: &output) + append(decl: d, to: &output) case .monomorphized(let f, let a): - write(monomorphized: f, for: a, to: &output) + append(monomorphized: f, for: a, to: &output) case .synthesized(let d): - write(synthesized: d, to: &output) + append(synthesized: d, to: &output) case .existentialized: UNIMPLEMENTED() } } - /// Writes the mangled representation of `symbol` monomorphized for `arguments` to `output`. - private mutating func write( - monomorphized symbol: Function.ID, for arguments: GenericArguments, to output: inout Output + /// Writes the mangled representation of `s` monomorphized for `arguments` to `output`. + private mutating func append( + monomorphized s: Function.ID, for arguments: GenericArguments, to output: inout Output ) { - write(operator: .monomorphizedFunctionDecl, to: &output) - mangle(function: symbol, to: &output) - write(specialization: arguments, to: &output) + append(operator: .monomorphizedFunctionDecl, to: &output) + append(function: s, to: &output) + append(specialization: arguments, to: &output) } - /// Writes the mangled representation of `specialization` to `output`. - private mutating func write(specialization: GenericArguments, to output: inout Output) { - write(integer: specialization.count, to: &output) - for (_, v) in specialization.sorted(by: \.key.rawValue) { - mangle(value: v, to: &output) + /// Writes the mangled representation of `z` to `output`. + private mutating func append(specialization z: GenericArguments, to output: inout Output) { + append(items: z.sorted(by: \.key.rawValue), to: &output) { (me, a, o) in + me.append(value: a.value, to: &o) } } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write( - synthesized symbol: SynthesizedFunctionDecl, to output: inout Output - ) { - write(operator: .synthesizedFunctionDecl, to: &output) - write(synthesizedKind: symbol.kind, to: &output) - - if symbol.scope.kind != ModuleDecl.self { - writeQualification(of: symbol.scope, to: &output) - } - write(scope: symbol.scope, to: &output) - mangle(type: ^symbol.type, to: &output) + /// Writes the mangled representation of `s` to `output`. + private mutating func append(synthesized s: SynthesizedFunctionDecl, to output: inout Output) { + append(operator: .synthesizedFunctionDecl, to: &output) + append(synthesizedKind: s.kind, to: &output) + appendQualification(of: s.scope, to: &output) + append(scope: s.scope, to: &output) + append(operator: .endOfSequence, to: &output) + append(type: ^s.type, to: &output) } /// Writes the mangled representation of `k` to `output`. - private mutating func write( + private mutating func append( synthesizedKind k: SynthesizedFunctionDecl.Kind, to output: inout Output ) { switch k { case .deinitialize: - write(base64Didit: 0, to: &output) + append(base64Didit: 0, to: &output) case .moveInitialization: - write(base64Didit: 1, to: &output) + append(base64Didit: 1, to: &output) case .moveAssignment: - write(base64Didit: 2, to: &output) + append(base64Didit: 2, to: &output) case .copy: - write(base64Didit: 3, to: &output) + append(base64Didit: 3, to: &output) + case .equal: + append(base64Didit: 4, to: &output) case .globalInitialization(let d): - write(base64Didit: 4, to: &output) - write(entity: d, to: &output) + append(base64Didit: 5, to: &output) + append(entity: d, to: &output) case .autoclosure(let e): - write(base64Didit: 5, to: &output) + append(base64Didit: 6, to: &output) // To allow using multiple autoclosures in the same scope, also write the expression ID. - write(integer: e.rawValue, to: &output) + append(integer: Int(e.rawValue.bits), to: &output) } } - /// Writes the mangled representation of `r` to `output`. - mutating func mangle(reference r: DeclReference, to output: inout Output) { - switch r { - case .direct(let d, let z): - write(operator: .directDeclReference, to: &output) - mangle(decl: d, to: &output) - write(specialization: z, to: &output) - - default: - UNIMPLEMENTED() - } + /// Writes the mangled representation of `s` to `output`. + private mutating func append(table s: WitnessTable, to output: inout Output) { + append(operator: .witnessTable, to: &output) + append(scope: s.scope, to: &output) + append(type: program.canonical(s.witness, in: s.scope), to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - mutating func mangle(table symbol: WitnessTable, to output: inout Output) { - write(operator: .witnessTable, to: &output) - write(scope: symbol.scope, to: &output) - mangle(type: symbol.witness, to: &output) + /// Writes the mangled representation of `s` to `output`. + private mutating func append(value s: CompileTimeValue, to output: inout Output) { + switch s { + case .type(let t): + append(type: t, to: &output) + case .term(let t): + append(term: t, to: &output) + } } /// Writes the mangled representation of `symbol` to `output`. - mutating func mangle(value symbol: CompileTimeValue, to output: inout Output) { - switch symbol { - case .type(let t): - mangle(type: t, to: &output) - case .compilerKnown(let v) where v is Int: - write(integer: v as! Int, to: &output) + mutating func append(term symbol: AnyTerm, to output: inout Output) { + switch symbol.base { + case let t as ConcreteTerm: + let v = (t.value as? Int) ?? UNIMPLEMENTED() + append(integer: v, to: &output) + case let t as GenericTermParameter: + append(entity: t.decl, to: &output) default: UNIMPLEMENTED() } } - /// Writes the mangled representation of `symbol` to `output`. - mutating func mangle(type symbol: AnyType, to output: inout Output) { - let s = program.canonical(symbol, in: scopeOfUse) + /// Writes the mangled representation of `d`'s type to `output`. + private mutating func append(typeOf d: T, to output: inout Output) { + append(type: program.canonical(typeOf: d), to: &output) + } - if writeLookup(.type(s), to: &output) { - return - } + /// Writes the mangled representation of `e`'s type to `output`. + private mutating func append(typeOf e: T, to output: inout Output) { + append(type: program.canonical(typeOf: e), to: &output) + } + /// Writes the mangled representation of `s` to `output`. + private mutating func append(type s: AnyType, to output: inout Output) { + let n = Symbol.type(s) + if appendIf(reservedOrRecorded: n, to: &output) { return } + + assert(s.isCanonical) switch s.base { + case let t as ArrowType: + append(arrow: t, to: &output) case let t as AssociatedTypeType: - write(associatedType: t, to: &output) - + append(associatedType: t, to: &output) case let t as BoundGenericType: - write(boundGenericType: t, to: &output) - + append(boundGenericType: t, to: &output) case let t as BufferType: - write(bufferType: t, to: &output) - + append(buffer: t, to: &output) case let t as BuiltinType: - write(builtinType: t, to: &output) - + append(builtin: t, to: &output) case let t as ExistentialType: - write(existentialType: t, to: &output) - + append(existential: t, to: &output) case let t as GenericTypeParameterType: - write(operator: .genericTypeParameterType, to: &output) - mangle(decl: AnyDeclID(t.decl), to: &output) - - case let t as ArrowType: - write(arrow: t, to: &output) - + append(genericTypeParameter: t, to: &output) case let t as MethodType: - write(method: t, to: &output) - + append(method: t, to: &output) case let t as MetatypeType: - write(operator: .metatypeType, to: &output) - mangle(type: t.instance, to: &output) - + append(metatype: t, to: &output) case let t as ParameterType: - write(operator: .parameterType, to: &output) - write(base64Digit: t.access, to: &output) - mangle(type: t.bareType, to: &output) - + append(parameter: t, to: &output) case let t as ProductType: - write(operator: .productType, to: &output) - mangle(decl: AnyDeclID(t.decl), to: &output) - - // End of sequence required because `t.decl` is a scope. - write(operator: .endOfSequence, to: &output) - + append(product: t, to: &output) case let t as RemoteType: - write(operator: .remoteType, to: &output) - write(base64Digit: t.access, to: &output) - mangle(type: t.bareType, to: &output) - + append(remote: t, to: &output) case let t as SubscriptType: - write(subscriptType: t, to: &output) - - case let t as UnionType: - write(unionType: t, to: &output) - + append(subscript: t, to: &output) case let t as TraitType: - write(operator: .traitType, to: &output) - mangle(decl: AnyDeclID(t.decl), to: &output) - - // End of sequence required because `t.decl` is a scope. - write(operator: .endOfSequence, to: &output) - + append(trait: t, to: &output) case let t as TupleType: - write(tupleType: t, to: &output) - + append(tuple: t, to: &output) + case let t as UnionType: + append(union: t, to: &output) default: unreachable() } + symbolPosition[n] = symbolPosition.count + } - symbolID[.type(s)] = nextSymbolID - nextSymbolID += 1 + /// Writes the mangled representation of `t` to `output`. + private mutating func append(arrow t: ArrowType, to output: inout Output) { + append(operator: .arrowType, to: &output) + append(type: t.environment, to: &output) + append(items: t.inputs, to: &output) { (me, i, o) in + me.append(string: i.label ?? "", to: &o) + me.append(type: i.type, to: &o) + } + append(type: t.output, to: &output) } /// Writes the mangled representation of `t` to `output`. - private mutating func write(associatedType t: AssociatedTypeType, to output: inout Output) { - write(operator: .associatedType, to: &output) - mangle(decl: t.decl, to: &output) - mangle(type: t.domain, to: &output) + private mutating func append(associatedType t: AssociatedTypeType, to output: inout Output) { + append(operator: .associatedType, to: &output) + append(decl: t.decl, to: &output) + append(type: t.domain, to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(boundGenericType t: BoundGenericType, to output: inout Output) { - write(operator: .boundGenericType, to: &output) - mangle(type: t.base, to: &output) - write(integer: t.arguments.count, to: &output) - for u in t.arguments.values { - mangle(value: u, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(boundGenericType t: BoundGenericType, to output: inout Output) { + append(operator: .boundGenericType, to: &output) + append(type: t.base, to: &output) + append(items: t.arguments.values, to: &output) { (me, v, o) in + me.append(value: v, to: &o) } } /// Writes the mangled representation of `t` to `output`. - private mutating func write(bufferType t: BufferType, to output: inout Output) { - write(operator: .bufferType, to: &output) - mangle(type: t.element, to: &output) - mangle(value: t.count, to: &output) + private mutating func append(buffer t: BufferType, to output: inout Output) { + append(operator: .bufferType, to: &output) + append(type: t.element, to: &output) + append(term: t.count, to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(builtinType t: BuiltinType, to output: inout Output) { + /// Writes the mangled representation of `z` to `output`. + private mutating func append(builtin t: BuiltinType, to output: inout Output) { switch t { case .i(let width): - write(operator: .builtinIntegerType, to: &output) - write(integer: width, to: &output) + append(operator: .builtinIntegerType, to: &output) + append(integer: width, to: &output) case .word: - write(operator: .builtinWordType, to: &output) + append(operator: .builtinWordType, to: &output) case .float16: - write(operator: .builtinFloatType, to: &output) - write(integer: 16, to: &output) + append(operator: .builtinFloatType, to: &output) + append(integer: 16, to: &output) case .float32: - write(operator: .builtinFloatType, to: &output) - write(integer: 32, to: &output) + append(operator: .builtinFloatType, to: &output) + append(integer: 32, to: &output) case .float64: - write(operator: .builtinFloatType, to: &output) - write(integer: 64, to: &output) + append(operator: .builtinFloatType, to: &output) + append(integer: 64, to: &output) case .float128: - write(operator: .builtinFloatType, to: &output) - write(integer: 128, to: &output) + append(operator: .builtinFloatType, to: &output) + append(integer: 128, to: &output) case .ptr: - write(operator: .builtinPointerType, to: &output) + append(operator: .builtinPointerType, to: &output) case .module: - write(operator: .builtinModuleType, to: &output) + append(operator: .builtinModuleType, to: &output) } } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(existentialType t: ExistentialType, to output: inout Output) { + /// Writes the mangled representation of `t` to `output`. + private mutating func append(existential t: ExistentialType, to output: inout Output) { switch t.interface { case .metatype: - write(operator: .existentialMetatype, to: &output) + append(operator: .existentialMetatype, to: &output) case .generic(let interface): - write(operator: .existentialGenericType, to: &output) - mangle(type: interface, to: &output) + append(operator: .existentialGenericType, to: &output) + append(type: interface, to: &output) case .traits(let interface): - write(operator: .existentialTraitType, to: &output) - write(set: interface, to: &output) { (m, e) -> String in - var s = "" - m.mangle(type: ^e, to: &s) - return s + append(operator: .existentialTraitType, to: &output) + append(unordered: interface, to: &output) { (me, e, o) in + me.append(type: ^e, to: &o) } } } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(arrow t: ArrowType, to output: inout Output) { - write(operator: .arrowType, to: &output) - mangle(type: t.environment, to: &output) - - write(integer: t.inputs.count, to: &output) - for i in t.inputs { - write(string: i.label ?? "", to: &output) - mangle(type: i.type, to: &output) - } - - mangle(type: t.output, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append( + genericTypeParameter t: GenericTypeParameterType, to output: inout Output + ) { + append(operator: .genericTypeParameterType, to: &output) + append(decl: AnyDeclID(t.decl), to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(method t: MethodType, to output: inout Output) { - write(operator: .methodType, to: &output) - write(base64Digit: t.capabilities, to: &output) - mangle(type: t.receiver, to: &output) - - write(integer: t.inputs.count, to: &output) - for i in t.inputs { - write(string: i.label ?? "", to: &output) - mangle(type: i.type, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(method t: MethodType, to output: inout Output) { + append(operator: .methodType, to: &output) + append(base64Digit: t.capabilities, to: &output) + append(type: t.receiver, to: &output) + append(items: t.inputs, to: &output) { (me, i, o) in + me.append(string: i.label ?? "", to: &o) + me.append(type: i.type, to: &o) } - - mangle(type: t.output, to: &output) + append(type: t.output, to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(subscriptType t: SubscriptType, to output: inout Output) { - write(operator: .subscriptType, to: &output) - write(base64Digit: t.capabilities, to: &output) - mangle(type: t.environment, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(metatype t: MetatypeType, to output: inout Output) { + append(operator: .metatypeType, to: &output) + append(type: t.instance, to: &output) + } - write(integer: t.inputs.count, to: &output) - for i in t.inputs { - write(string: i.label ?? "", to: &output) - mangle(type: i.type, to: &output) - } + /// Writes the mangled representation of `t` to `output`. + private mutating func append(parameter t: ParameterType, to output: inout Output) { + append(operator: .parameterType, to: &output) + append(base64Digit: t.access, to: &output) + append(type: t.bareType, to: &output) + } - mangle(type: t.output, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(product t: ProductType, to output: inout Output) { + append(operator: .productType, to: &output) + append(decl: AnyDeclID(t.decl), to: &output) + append(operator: .endOfSequence, to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(tupleType t: TupleType, to output: inout Output) { - write(operator: .tupleType, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(remote t: RemoteType, to output: inout Output) { + append(operator: .remoteType, to: &output) + append(base64Digit: t.access, to: &output) + append(type: t.bareType, to: &output) + } - write(integer: t.elements.count, to: &output) - for e in t.elements { - write(string: e.label ?? "", to: &output) - mangle(type: e.type, to: &output) + /// Writes the mangled representation of `t` to `output`. + private mutating func append(subscript t: SubscriptType, to output: inout Output) { + append(operator: .subscriptType, to: &output) + append(base64Digit: t.capabilities, to: &output) + append(type: t.environment, to: &output) + append(items: t.inputs, to: &output) { (me, i, o) in + me.append(string: i.label ?? "", to: &o) + me.append(type: i.type, to: &o) } + append(type: t.output, to: &output) } - /// Writes the mangled representation of `symbol` to `output`. - private mutating func write(unionType t: UnionType, to output: inout Output) { - write(operator: .unionType, to: &output) - write(set: t.elements, to: &output) { (m, e) -> String in - var s = "" - m.mangle(type: e, to: &s) - return s + /// Writes the mangled representation of `t` to `output`. + private mutating func append(tuple t: TupleType, to output: inout Output) { + append(operator: .tupleType, to: &output) + append(items: t.elements, to: &output) { (me, e, o) in + me.append(string: e.label ?? "", to: &o) + me.append(type: e.type, to: &o) } } - /// If `symbol` is reserved or has already been inserted in the symbol lookup table, writes a - /// lookup reference to it and returns `true`; returns `false` otherwise. - private func writeLookup(_ symbol: Symbol, to output: inout Output) -> Bool { - if let r = reserved[symbol] { - write(operator: .reserved, to: &output) - r.write(to: &output) - return true - } + /// Writes the mangled representation of `t` to `output`. + private mutating func append(trait t: TraitType, to output: inout Output) { + append(operator: .traitType, to: &output) + append(decl: AnyDeclID(t.decl), to: &output) + } - if let i = symbolID[symbol] { - write(operator: .lookup, to: &output) - write(integer: i, to: &output) - return true + /// Writes the mangled representation of `t` to `output`. + private mutating func append(union t: UnionType, to output: inout Output) { + append(operator: .unionType, to: &output) + append(unordered: t.elements, to: &output) { (me, e, o) in + me.append(type: e, to: &o) } - - return false } /// Writes the mangled representation of `name` to `output`. - private func write(name: Name, to output: inout Output) { + private mutating func append(name: Name, to output: inout Output) { // Only encode notation and introducer; labels are encoded in types. var tag: UInt8 = 0 if name.notation != nil { tag = 1 } if name.introducer != nil { tag = tag | 2 } - write(base64Didit: tag, to: &output) + append(base64Didit: tag, to: &output) if let n = name.notation { - write(base64Digit: n, to: &output) + append(base64Digit: n, to: &output) } if let i = name.introducer { - write(base64Digit: i, to: &output) + append(base64Digit: i, to: &output) } - write(string: name.stem, to: &output) + append(string: name.stem, to: &output) } /// Writes `string` to `output`, prefixed by its length encoded as a variable-length integer. - private func write(string: T, to output: inout Output) { - write(integer: string.count, to: &output) - string.write(to: &output) + private mutating func append(string: T, to output: inout Output) { + let s = String(string) + + if s.isEmpty { + append(integer: 0, to: &output) + } else if let n = stringPosition[s] { + append(integer: 1, to: &output) + append(integer: n, to: &output) + } else { + append(integer: s.count + 2, to: &output) + string.write(to: &output) + stringPosition[s] = stringPosition.count + } } /// Writes `v` encoded as a variable-length integer to `output`. - private func write(integer v: Int, to output: inout Output) { + private func append(integer v: Int, to output: inout Output) { Base64VarUInt(v).write(to: &output) } /// Writes the raw value of `v` encoded as a base 64 digit to `output`. - private func write( - base64Digit v: T, to output: inout Output - ) where T.RawValue == UInt8 { - write(base64Didit: v.rawValue, to: &output) + private func append>(base64Digit v: T, to output: inout Output) { + append(base64Didit: v.rawValue, to: &output) } /// Writes `v` encoded as a base 64 digit to `output`. - private func write(base64Didit v: UInt8, to output: inout Output) { + private func append(base64Didit v: UInt8, to output: inout Output) { Base64Digit(rawValue: v)!.description.write(to: &output) } /// Writes `o` to `output`. - private func write(operator o: ManglingOperator, to output: inout Output) { + private func append(operator o: ManglingOperator, to output: inout Output) { o.write(to: &output) } - /// Writes the mangled representation of `elements`, which is an unordered set, calling - /// `mangleElement` to mangle individual elements. - private mutating func write( - set elements: S, to output: inout Output, - manglingElementsWith mangleElement: (inout Self, S.Element) -> String + /// Writes the mangled representation of `items`, calling `appendItem` to mangle each individual + /// element to `output`. + private mutating func append( + items: T, to output: inout Output, + appendingEachWith appendItem: (inout Self, T.Element, inout Output) -> Void ) { - write(integer: elements.count, to: &output) + append(integer: items.count, to: &output) + for i in items { + appendItem(&self, i, &output) + } + } + /// Writes the mangled representation of `items`, which is an unordered set, calling `mangleItem` + /// to mangle each individual element. + private func append( + unordered items: T, to output: inout Output, + manglingElementsWith mangleItem: (inout Self, T.Element, inout Output) -> Void + ) { + append(integer: items.count, to: &output) var mangled: [String] = [] - for e in elements { + for e in items { // Copy `self` to share the symbol looking table built so far. var m = self - let s = mangleElement(&m, e) + var s = "" + mangleItem(&m, e, &s) let i = mangled.partitioningIndex(where: { s < $0 }) mangled.insert(s, at: i) } - mangled.joined().write(to: &output) } + /// If `s` is reserved or has already been inserted in the symbol lookup table, writes a lookup + /// reference to it and returns `true`. Otherwise, returns `false`. + private func appendIf(reservedOrRecorded s: Symbol, to output: inout Output) -> Bool { + appendIf(reserved: s, to: &output) || appendIf(recorded: s, to: &output) + } + + /// Writes a lookup reference to `s` and returns `true` iff `s` is a reserved symbol. Otherwise, + /// returns `false` without modifying `output`. + private func appendIf(reserved s: Symbol, to output: inout Output) -> Bool { + if let r = reserved[s] { + append(operator: .reserved, to: &output) + r.write(to: &output) + return true + } else { + return false + } + } + + /// Writes a lookup reference to `s` and returns `true` iff `s` in the lookup table. Otherwise, + /// returns `false` without modifying `output`. + private func appendIf(recorded s: Symbol, to output: inout Output) -> Bool { + if let p = symbolPosition[s] { + append(operator: .lookup, to: &output) + append(integer: p, to: &output) + return true + } else { + return false + } + } + } diff --git a/Sources/IR/Mangling/ManglingOperator.swift b/Sources/IR/Mangling/ManglingOperator.swift index e165ff109..337ba7417 100644 --- a/Sources/IR/Mangling/ManglingOperator.swift +++ b/Sources/IR/Mangling/ManglingOperator.swift @@ -6,6 +6,8 @@ public enum ManglingOperator: String { case productTypeDecl = "A" + case bindingDecl = "bD" + case traitDecl = "C" case conformanceDecl = "cD" @@ -44,6 +46,8 @@ public enum ManglingOperator: String { case lookup = "K" + case lookupRelative = "rK" + case moduleDecl = "M" case namespaceDecl = "N" diff --git a/Sources/IR/Mangling/TypedProgram+Mangling.swift b/Sources/IR/Mangling/TypedProgram+Mangling.swift index 0f2f28e31..9d59590f1 100644 --- a/Sources/IR/Mangling/TypedProgram+Mangling.swift +++ b/Sources/IR/Mangling/TypedProgram+Mangling.swift @@ -4,33 +4,30 @@ extension TypedProgram { /// Returns the mangled representation of `d`. public func mangled(_ d: T) -> String { - mangled(d, applying: { (s, m, o) in m.mangle(decl: s, to: &o) }) + mangled(d, applying: { (s, m) in m.mangled(decl: s) }) } /// Returns the mangled representation of `t`. public func mangled(_ t: T) -> String { - mangled(^t, applying: { (s, m, o) in m.mangle(type: s, to: &o) }) + mangled(^t, applying: { (s, m) in m.mangled(type: s) }) } /// Returns the mangled representation of `w`. public func mangled(_ w: WitnessTable) -> String { - mangled(w, applying: { (s, m, o) in m.mangle(table: s, to: &o) }) + mangled(w, applying: { (s, m) in m.mangled(table: s) }) } /// Returns the mangled representation of `f`. public func mangled(_ f: Function.ID) -> String { - mangled(f, applying: { (s, m, o) in m.mangle(function: s, to: &o) }) + mangled(f, applying: { (s, m) in m.mangled(function: s) }) } - /// Returns the mangled representation of `symbol` in `scopeOfuse`, applying `mangle` to compute - /// that representation. + /// Returns the mangled representation of `s`, applying `mangle` to build it. private func mangled( - _ symbol: T, applying mangle: (T, inout Mangler, inout String) -> Void + _ s: T, applying mangle: (T, inout Mangler) -> String ) -> String { - var output = "" - var m = Mangler(self, in: AnyScopeID(ast.coreLibrary!)) // FIXME: use actual scope - mangle(symbol, &m, &output) - return output.assemblySanitized + var m = Mangler(self) + return mangle(s, &m).assemblySanitized } } diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index a19a0c216..cfc98e648 100644 --- a/Sources/IR/Module.swift +++ b/Sources/IR/Module.swift @@ -99,19 +99,61 @@ public struct Module { } } - /// If `p` is a parameter, returns its passing convention. Otherwise, returns `nil`. + /// Returns `true` iff cannot be used to modify or update a value. + public func isBoundImmutably(_ p: Operand) -> Bool { + switch p { + case .parameter(let e, let i): + let f = e.function + return (entry(of: f) == e) && (passingConvention(parameter: i, of: f) == .let) + case .constant: + return false + case .register(let i): + return isBoundImmutably(register: i) + } + } + + /// Returns `true` iff the result of `i` cannot be used to modify or update a value. + public func isBoundImmutably(register i: InstructionID) -> Bool { + switch self[i] { + case is AllocStack: + return false + case let s as AdvancedByBytes: + return isBoundImmutably(s.base) + case let s as Access: + return isBoundImmutably(s.source) + case let s as OpenCapture: + return s.isAccess(.let) + case is OpenUnion: + return false + case let s as PointerToAddress: + return s.isAccess(.let) + case let s as Project: + return s.projection.access == .let + case let s as SubfieldView: + return isBoundImmutably(s.recordAddress) + case let s as WrapExistentialAddr: + return isBoundImmutably(s.witness) + default: + return true + } + } + + /// If `p` is a function parameter, returns its passing convention. Otherwise, returns `nil`. public func passingConvention(of p: Operand) -> AccessEffect? { - if case .parameter(let e, let i) = p { - assert(entry(of: e.function) == e) - return read(self[e.function].inputs) { (ps) in - // The last parameter of a function denotes its return value. - (i == ps.count) ? .set : ps[i].type.access - } + if case .parameter(let e, let i) = p, (entry(of: e.function) == e) { + return passingConvention(parameter: i, of: e.function) } else { return nil } } + /// Returns the passing convention of the `i`-th parameter of `f`. + public func passingConvention(parameter i: Int, of f: Function.ID) -> AccessEffect { + // The last parameter of a function denotes its return value. + let ps = self[f].inputs + return (i == ps.count) ? .set : ps[i].type.access + } + /// Returns the scope in which `i` is used. public func scope(containing i: InstructionID) -> AnyScopeID { functions[i.function]![i.block].scope @@ -179,6 +221,20 @@ public struct Module { return d.dominates(lhs.block, rhs.block) } + /// Returns `true` if `i` is a deinitializer. + public func isDeinit(_ i: Function.ID) -> Bool { + switch i.value { + case .lowered(let d): + return FunctionDecl.ID(d).map({ (n) in program.ast[n].isDeinit }) ?? false + case .existentialized(let j): + return isDeinit(j) + case .monomorphized(let j, arguments: _): + return isDeinit(j) + case .synthesized(let d): + return d.kind == .deinitialize + } + } + /// Returns whether the IR in `self` is well-formed. /// /// Use this method as a sanity check to verify the module's invariants. @@ -462,6 +518,16 @@ public struct Module { return demandDeclaration(lowering: conformanceToCopyable.implementations[d]!) } + /// Returns the IR function implementing the operator defined in `conformanceToEquatable`. + /// + /// - Parameter conformanceToEquatable: A conformance to `Equatable`. + mutating func demandEqualDeclaration( + definedBy conformanceToEquatable: FrontEnd.Conformance + ) -> Function.ID { + let d = program.ast.core.equatable.equal + return demandDeclaration(lowering: conformanceToEquatable.implementations[d]!) + } + /// Returns a function reference to the implementation of the requirement `r` in `witness`. /// /// - Requires: `r` identifies a function or subscript requirement in the trait for which @@ -507,12 +573,19 @@ public struct Module { /// Returns the lowered declarations of `d`'s parameters. private func loweredParameters(of d: FunctionDecl.ID) -> [Parameter] { - let captures = ArrowType(program[d].type)!.captures.lazy.map { (e) in + let declType = ArrowType(program[d].type)! + let captures = declType.captures.lazy.map { (e) in program.canonical(e.type, in: program[d].scope) } + var result: [Parameter] = zip(program.captures(of: d), captures).map({ (c, e) in - .init(c, capturedAs: e) + if let t = RemoteType(e) { + return Parameter(decl: c, type: ParameterType(t)) + } else { + return Parameter(decl: c, type: ParameterType(declType.receiverEffect, e)) + } }) + result.append(contentsOf: program.ast[d].parameters.map(pairedWithLoweredType(parameter:))) return result } @@ -537,11 +610,17 @@ public struct Module { /// Returns the lowered declarations of `d`'s parameters. private func loweredParameters(of d: SubscriptImpl.ID) -> [Parameter] { - let captures = SubscriptImplType(program[d].type)!.captures.lazy.map { (e) in + let declType = SubscriptImplType(program[d].type)! + let captures = declType.captures.lazy.map { (e) in program.canonical(e.type, in: program[d].scope) } + var result: [Parameter] = zip(program.captures(of: d), captures).map({ (c, e) in - .init(c, capturedAs: e) + if let t = RemoteType(e) { + return Parameter(decl: c, type: ParameterType(t)) + } else { + return Parameter(decl: c, type: ParameterType(declType.receiverEffect, e)) + } }) let bundle = SubscriptDecl.ID(program[d].scope)! @@ -878,11 +957,9 @@ public struct Module { case let s as Access: return provenances(s.source) case let s as Project: - return s.operands.reduce( - into: [], - { (p, o) in - if type(of: o).isAddress { p.formUnion(provenances(o)) } - }) + return s.operands.reduce(into: []) { (p, o) in + if type(of: o).isAddress { p.formUnion(provenances(o)) } + } case let s as SubfieldView: return provenances(s.recordAddress) case let s as WrapExistentialAddr: diff --git a/Sources/IR/Operands/Constant/FunctionReference.swift b/Sources/IR/Operands/Constant/FunctionReference.swift index b801f889d..ed3497671 100644 --- a/Sources/IR/Operands/Constant/FunctionReference.swift +++ b/Sources/IR/Operands/Constant/FunctionReference.swift @@ -21,7 +21,7 @@ public struct FunctionReference: Constant, Hashable { let v = module[f] let t = ArrowType(inputs: v.inputs.map({ .init(type: ^$0.type) }), output: v.output) - assert(t[.isCanonical]) + assert(t.isCanonical) self.function = f self.type = .address(t) diff --git a/Sources/IR/Operands/Constant/WitnessTableType.swift b/Sources/IR/Operands/Constant/WitnessTableType.swift index 2cc66c1c9..902f4f881 100644 --- a/Sources/IR/Operands/Constant/WitnessTableType.swift +++ b/Sources/IR/Operands/Constant/WitnessTableType.swift @@ -6,7 +6,7 @@ public struct WitnessTableType: TypeProtocol, CustomStringConvertible { /// Creates an instance. public init() {} - public var flags: TypeFlags { .isCanonical } + public var flags: ValueFlags { .init() } public var description: String { "WitnessTable" } diff --git a/Sources/IR/Operands/Instruction/AllocStack.swift b/Sources/IR/Operands/Instruction/AllocStack.swift index e83cecc8c..681b7096d 100644 --- a/Sources/IR/Operands/Instruction/AllocStack.swift +++ b/Sources/IR/Operands/Instruction/AllocStack.swift @@ -39,7 +39,7 @@ extension Module { /// /// - Requires: `t` is canonical. func makeAllocStack(_ t: AnyType, at site: SourceRange) -> AllocStack { - precondition(t[.isCanonical]) + precondition(t.isCanonical) return .init(allocatedType: t, site: site) } diff --git a/Sources/IR/Operands/Instruction/EndProjectWitness.swift b/Sources/IR/Operands/Instruction/EndProjectWitness.swift deleted file mode 100644 index 07e47a178..000000000 --- a/Sources/IR/Operands/Instruction/EndProjectWitness.swift +++ /dev/null @@ -1,13 +0,0 @@ -import FrontEnd - -/// Ends the lifetime of a projection. -public typealias EndProjectWitness = RegionExit - -extension Module { - - /// Creates an `end_project` anchored at `site` that ends the projection created by `start`. - func makeEndProjectWitness(_ start: Operand, at site: SourceRange) -> EndProjectWitness { - makeRegionExit(start, at: site) - } - -} diff --git a/Sources/IR/Operands/Instruction/OpenUnion.swift b/Sources/IR/Operands/Instruction/OpenUnion.swift index 7a1d4eb64..d4692596f 100644 --- a/Sources/IR/Operands/Instruction/OpenUnion.swift +++ b/Sources/IR/Operands/Instruction/OpenUnion.swift @@ -75,7 +75,7 @@ extension Module { at site: SourceRange ) -> OpenUnion { precondition(type(of: container).isAddress) - precondition(payload[.isCanonical]) + precondition(payload.isCanonical) return .init( container: container, payloadType: payload, diff --git a/Sources/IR/Operands/Instruction/ProjectWitness.swift b/Sources/IR/Operands/Instruction/ProjectWitness.swift deleted file mode 100644 index d7d10cd60..000000000 --- a/Sources/IR/Operands/Instruction/ProjectWitness.swift +++ /dev/null @@ -1,55 +0,0 @@ -import FrontEnd - -/// Projects a the witness of an existential container. -public struct ProjectWitness: RegionEntry { - - public typealias Exit = EndProjectWitness - - /// The type of the projected value. - public let projection: RemoteType - - /// The operands of the instruction. - public private(set) var operands: [Operand] - - /// The site of the code corresponding to that instruction. - public let site: SourceRange - - /// Creates an instance with the given properties. - fileprivate init(projection: RemoteType, container: Operand, site: SourceRange) { - self.projection = projection - self.operands = [container] - self.site = site - } - - /// The container whose witness is projected. - public var container: Operand { operands[0] } - - /// The types of the instruction's results. - public var result: IR.`Type`? { .address(projection.bareType) } - - public mutating func replaceOperand(at i: Int, with new: Operand) { - operands[i] = new - } - -} - -extension ProjectWitness: CustomStringConvertible { - - public var description: String { - "project_witness [\(projection.access)] \(container)" - } - -} - -extension Module { - - /// Creates a `project_witness` anchored at `site` that projects the witness of `container`, - /// which is an existential container, as an address of type `projection`. - func makeProjectWitness( - of container: Operand, as projection: RemoteType, at site: SourceRange - ) -> ProjectWitness { - precondition(projection[.isCanonical]) - return .init(projection: projection, container: container, site: site) - } - -} diff --git a/Sources/IR/Operands/Instruction/RegionExit.swift b/Sources/IR/Operands/Instruction/RegionExit.swift index d5dc85e9b..c75356862 100644 --- a/Sources/IR/Operands/Instruction/RegionExit.swift +++ b/Sources/IR/Operands/Instruction/RegionExit.swift @@ -36,8 +36,6 @@ extension RegionExit: CustomStringConvertible { return "close_union \(start)" case let s where s == Project.self: return "end_project \(start)" - case let s where s == ProjectWitness.self: - return "end_project_witness \(start)" default: return "end_region \(start)" } diff --git a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift index 90b35e998..a2b30b66b 100644 --- a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift +++ b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift @@ -16,7 +16,7 @@ public struct UnionDiscriminator: Instruction { } public var result: IR.`Type`? { - .object(BuiltinType.word) + .object(BuiltinType.discriminator) } public var operands: [Operand] { diff --git a/Sources/IR/Operands/Instruction/UnionSwitch.swift b/Sources/IR/Operands/Instruction/UnionSwitch.swift new file mode 100644 index 000000000..849bc9589 --- /dev/null +++ b/Sources/IR/Operands/Instruction/UnionSwitch.swift @@ -0,0 +1,86 @@ +import FrontEnd +import OrderedCollections + +/// Branches to one of several basic blocks based on the discriminator of a union. +public struct UnionSwitch: Terminator { + + /// The type of a map from payload type to its target. + public typealias Targets = OrderedDictionary + + /// The discriminator of the union container over which the instruction switches. + public private(set) var discriminator: Operand + + /// The type of the union over which the instruction switches. + public let union: UnionType + + /// A map from payload type to its target. + public private(set) var targets: Targets + + /// The site of the code corresponding to that instruction. + public let site: SourceRange + + /// Creates an instance with the given properties. + fileprivate init(discriminator: Operand, union: UnionType, targets: Targets, site: SourceRange) { + self.discriminator = discriminator + self.union = union + self.targets = targets + self.site = site + } + + + + public var operands: [Operand] { + [discriminator] + } + + public var successors: [Block.ID] { + Array(targets.values) + } + + public mutating func replaceOperand(at i: Int, with new: Operand) { + precondition(i == 0) + discriminator = new + } + + mutating func replaceSuccessor(_ old: Block.ID, with new: Block.ID) -> Bool { + precondition(new.function == successors[0].function) + for (t, b) in targets { + if b == old { targets[t] = b; return true } + } + return false + } + +} + +extension UnionSwitch: CustomStringConvertible { + + public var description: String { + var s = "union_switch \(discriminator)" + for (t, b) in targets { + s.write(", \(t) => \(b)") + } + return s + } + +} + +extension Module { + + /// Creates a `union_switch` anchored at `site` that switches over `discriminator`, which is the + /// discriminator of a container of type `union`, jumping to corresponding block in `target`. + /// + /// If `union` is generic, `discriminator` should be the result of `union_discriminator` rather + /// than a constant. + /// + /// - Requires: `targets` has a key defined for each of `union`. + func makeUnionSwitch( + over discriminator: Operand, of union: UnionType, toOneOf targets: UnionSwitch.Targets, + at site: SourceRange + ) -> UnionSwitch { + let t = type(of: discriminator) + precondition(t.isObject && t.ast.isBuiltinInteger) + precondition(union.elements.allSatisfy({ (e) in targets[e] != nil })) + return .init(discriminator: discriminator, union: union, targets: targets, site: site) + } + +} diff --git a/Sources/IR/Parameter.swift b/Sources/IR/Parameter.swift index f33a0b549..b8bbd50d4 100644 --- a/Sources/IR/Parameter.swift +++ b/Sources/IR/Parameter.swift @@ -25,7 +25,7 @@ public struct Parameter { case let u as RemoteType: self.init(decl: d, type: ParameterType(u)) default: - self.init(decl: d, type: ParameterType(.sink, t)) + self.init(decl: d, type: ParameterType(.inout, t)) } } diff --git a/Sources/IR/StaticStorage.swift b/Sources/IR/StaticStorage.swift index b000e5227..05289b371 100644 --- a/Sources/IR/StaticStorage.swift +++ b/Sources/IR/StaticStorage.swift @@ -17,7 +17,7 @@ public struct StaticStorage { public init( _ t: AnyType, identifiedBy id: AnyDeclID, initializedWith initializer: Function.ID ) { - precondition(t[.isCanonical]) + precondition(t.isCanonical) self.id = id self.pointee = t self.initializer = initializer diff --git a/Sources/IR/Type.swift b/Sources/IR/Type.swift index e88b817ee..015de1838 100644 --- a/Sources/IR/Type.swift +++ b/Sources/IR/Type.swift @@ -16,7 +16,7 @@ public struct Type: Hashable { /// /// - Requires: `ast` must be canonical. public init(ast: T, isAddress: Bool) { - precondition(ast[.isCanonical], "source type is not canonical") + precondition(ast.isCanonical, "source type is not canonical") self.ast = ^ast self.isAddress = isAddress } diff --git a/Sources/IR/TypedProgram+Extensions.swift b/Sources/IR/TypedProgram+Extensions.swift index 5cc7b26b6..60da0bb3c 100644 --- a/Sources/IR/TypedProgram+Extensions.swift +++ b/Sources/IR/TypedProgram+Extensions.swift @@ -28,28 +28,12 @@ extension TypedProgram { } } - /// Returns a subscript bundle reference to `d`, which occurs specialized by `z` and is marked - /// for mutation iff `isMutating` is `true`. + /// Returns a subscript bundle reference to `d`, which occurs specialized by `z`. func subscriptBundleReference( - to d: SubscriptDecl.ID, specializedBy z: GenericArguments, markedForMutation isMutating: Bool + to d: SubscriptDecl.ID, specializedBy z: GenericArguments ) -> BundleReference { - let t = SubscriptType(canonical(self[d].type, in: self[d].scope))! - let r = requestedCapabilities( - onBundleProviding: t.capabilities, forInPlaceMutation: isMutating) - return BundleReference(to: d, specializedBy: z, requesting: r) - } - - /// Returns the capabilities potentially requested by an access on a subscript or method bundle - /// defining `available`, used for mutation iff `m` is `true`. - func requestedCapabilities( - onBundleProviding available: AccessEffectSet, forInPlaceMutation m: Bool - ) -> AccessEffectSet { - let requested = available.intersection( - AccessEffectSet.forUseOfBundle(performingInPlaceMutation: m)) - - // TODO: requested is empty iff the program is ill-typed w.r.t. mutation markers - // assert(!requested.isEmpty) - return requested.isEmpty ? available : requested + let available = SubscriptType(canonical(self[d].type, in: self[d].scope))!.capabilities + return BundleReference(to: d, specializedBy: z, requesting: available) } } diff --git a/Sources/TestUtils/AnnotatedHyloFileTest.swift b/Sources/TestUtils/AnnotatedHyloFileTest.swift index d2a5605d1..c63208cc4 100644 --- a/Sources/TestUtils/AnnotatedHyloFileTest.swift +++ b/Sources/TestUtils/AnnotatedHyloFileTest.swift @@ -1,5 +1,6 @@ import Driver import FrontEnd +import IR import Utils import XCTest @@ -30,15 +31,25 @@ extension Result { } +/// The expected outcome of a test. +public enum ExpectedTestOutcome { + + case success, failure + +} + extension XCTestCase { /// The effects of running the `processAndCheck` parameter to `checkAnnotatedHyloFiles`. - fileprivate typealias ProcessingEffects = ( + private struct ProcessingEffects { + /// Test failures generated by processing. - testFailures: [XCTIssue], + let testFailures: [XCTIssue] + /// Hylo diagnostics generated by processing. - diagnostics: DiagnosticSet - ) + let diagnostics: DiagnosticSet + + } /// Applies `processAndCheck` to `hyloToTest` and the subset of its annotations whose commands /// match `checkedCommands`, recording resulting XCTest failures along with any additional @@ -50,7 +61,7 @@ extension XCTestCase { /// - processAndCheck: applies some compilation phases to `file`, updating `diagnostics` /// with any generated diagnostics, then checks `annotationsToCheck` against the results, /// returning corresponding test failures. Throws an `Error` if any phases failed. - fileprivate func checkAnnotations( + private func checkAnnotations( in hyloToTest: SourceFile, checkingAnnotationCommands checkedCommands: Set = [], _ processAndCheck: ( @@ -76,10 +87,9 @@ extension XCTestCase { thrownError = r.failure return failuresToReport( - effectsOfProcessing: ( + effectsOfProcessing: .init( testFailures: r.success ?? [], - diagnostics: diagnostics - ), + diagnostics: diagnostics), unhandledAnnotations: diagnosticAnnotations) }) @@ -99,10 +109,9 @@ extension XCTestCase { /// - expectSuccess: true if an error from `process` represents a test failure, false if the /// lack of an error represents a test failure; nil if that information is to be derived /// from the contents of the file. - @nonobjc - public func checkAnnotatedHyloFileDiagnostics( + private func checkAnnotatedHyloFileDiagnostics( inFileAt hyloFilePath: String, - expectSuccess: Bool, + expecting expectation: ExpectedTestOutcome, _ process: (_ file: SourceFile, _ diagnostics: inout DiagnosticSet) throws -> Void ) throws { let f = try SourceFile(at: hyloFilePath) @@ -115,7 +124,7 @@ extension XCTestCase { return [] } - if (thrownError == nil) != expectSuccess { + if (thrownError == nil) != (expectation == .success) { record(XCTIssue(unexpectedOutcomeDiagnostic(thrownError: thrownError, at: f.wholeRange))) } } @@ -131,7 +140,7 @@ extension XCTestCase { /// Given the effects of processing, the annotations not specifically handled by `processAndCheck` /// above, returns the final set of test failures to be reported to XCTest. - fileprivate func failuresToReport( + private func failuresToReport( effectsOfProcessing processing: ProcessingEffects, unhandledAnnotations: ArraySlice ) -> [XCTIssue] { @@ -164,16 +173,20 @@ extension XCTestCase { return testFailures } - /// Calls `compileAndRun(hyloFilePath, withOptimizations: false, expectSuccess: expectSuccess)`. + /// Calls `compileAndRun` with optimizations disabled. @nonobjc - public func compileAndRun(_ hyloFilePath: String, expectSuccess: Bool) throws { - try compileAndRun(hyloFilePath, withOptimizations: false, expectSuccess: expectSuccess) + public func compileAndRun( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { + try compileAndRun(hyloFilePath, withOptimizations: false, extending: p, expecting: expectation) } - /// Calls `compileAndRun(hyloFilePath, withOptimizations: true, expectSuccess: expectSuccess)`. + /// Calls `compileAndRun` with optimizations enabled. @nonobjc - public func compileAndRunWithOptimizations(_ hyloFilePath: String, expectSuccess: Bool) throws { - try compileAndRun(hyloFilePath, withOptimizations: true, expectSuccess: expectSuccess) + public func compileAndRunOptimized( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { + try compileAndRun(hyloFilePath, withOptimizations: true, extending: p, expecting: expectation) } /// Compiles and runs the hylo file at `hyloFilePath`, applying program optimizations iff @@ -181,87 +194,108 @@ extension XCTestCase { /// annotated expectations. @nonobjc public func compileAndRun( - _ hyloFilePath: String, withOptimizations: Bool, expectSuccess: Bool + _ hyloFilePath: String, withOptimizations: Bool, extending p: TypedProgram, + expecting expectation: ExpectedTestOutcome ) throws { if swiftyLLVMMandatoryPassesCrash { return } try checkAnnotatedHyloFileDiagnostics( - inFileAt: hyloFilePath, expectSuccess: expectSuccess - ) { (hyloSource, diagnostics) in + inFileAt: hyloFilePath, expecting: expectation + ) { (hyloSource, log) in try compileAndRun( - hyloSource, withOptimizations: withOptimizations, reportingDiagnosticsTo: &diagnostics) + hyloSource, withOptimizations: withOptimizations, extending: p, + reportingDiagnosticsTo: &log) } } /// Compiles and runs `hyloSource`, applying program optimizations iff `withOptimizations` is /// `true`, and `XCTAssert`ing that diagnostics and exit codes match annotated expectations. private func compileAndRun( - _ hyloSource: SourceFile, withOptimizations: Bool, - reportingDiagnosticsTo diagnostics: inout DiagnosticSet + _ hyloSource: SourceFile, withOptimizations: Bool, extending baseProgram: TypedProgram, + reportingDiagnosticsTo log: inout DiagnosticSet ) throws { - var arguments = ["--emit", "binary"] - if withOptimizations { arguments.append("-O") } - - var executable: URL - do { - executable = try compile(hyloSource.url, with: arguments) - } catch let d as DiagnosticSet { - // Recapture the diagnostics so the annotation testing framework can use them. The need for - // this ugliness makes me wonder how important it is to test cli.execute, which after all is - // just a thin wrapper over cli.executeCommand (currently private). - diagnostics = d - throw d - } - - // discard any outputs. - _ = try Process.run(executable, arguments: []) + var options = ["--emit", "binary"] + if withOptimizations { options.append("-O") } + + let compilation = try Driver.compileToTemporary( + hyloSource.url, withOptions: options, extending: baseProgram) + log.formUnion(compilation.diagnostics) + try compilation.diagnostics.throwOnError() + _ = try Process.run(compilation.output, arguments: []) } - /// Compiles the hylo file at `hyloFilePath` up until emitting LLVM code, `XCTAssert`ing that diagnostics and exit - /// codes match annotated expectations. + /// Compiles the hylo file at `hyloFilePath` up until emitting LLVM code, `XCTAssert`ing that + /// diagnostics and exit codes match annotated expectations. @nonobjc - public func compileToLLVM(_ hyloFilePath: String, expectSuccess: Bool) throws { + public func compileToLLVM( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { if swiftyLLVMMandatoryPassesCrash { return } - try checkAnnotatedHyloFileDiagnostics(inFileAt: hyloFilePath, expectSuccess: expectSuccess) { - (hyloSource, diagnostics) in - - do { - let _ = try compile(hyloSource.url, with: ["--emit", "llvm"]) - } catch let d as DiagnosticSet { - // Recapture the diagnostics so the annotation testing framework can use them. The need for - // this ugliness makes me wonder how important it is to test cli.execute, which after all is - // just a thin wrapper over cli.executeCommand (currently private). - diagnostics = d - throw d - } + try checkAnnotatedHyloFileDiagnostics( + inFileAt: hyloFilePath, expecting: expectation + ) { (hyloSource, log) in + let options = ["--emit", "llvm"] + let compilation = try Driver.compileToTemporary( + hyloSource.url, withOptions: options, extending: p) + log.formUnion(compilation.diagnostics) } } - /// Compiles `input` with the given arguments and returns the URL of the output file, throwing - /// diagnostics if there are any errors. + /// Lowers the hylo file at `hyloFilePath` to IR, applying any mandatory passes, and `XCTAssert`s + /// that diagnostics and thrown errors match annotated expectations. @nonobjc - public func compile(_ input: URL, with arguments: [String]) throws -> URL { - let output = FileManager.default.makeTemporaryFileURL() - let cli = try Driver.parse(arguments + ["-o", output.relativePath, input.relativePath]) - let (status, diagnostics) = try cli.execute() - if !status.isSuccess { - throw diagnostics - } - - XCTAssert( - !diagnostics.containsError, - "CLI reported success but \(input) contains errors: \(diagnostics.rendered())") + public func lowerToFinishedIR( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { + try checkAnnotatedHyloFileDiagnostics( + inFileAt: hyloFilePath, expecting: expectation + ) { (hyloSource, log) in + + let (p, m) = try p.loadModule(reportingDiagnosticsTo: &log) { (ast, log, space) in + // Note: built-in module is visible so that we can test built-in function calls. + try ast.loadModule( + hyloSource.baseName, parsing: [hyloSource], inNodeSpace: space, + withBuiltinModuleAccess: true, + reportingDiagnosticsTo: &log) + } - #if os(Windows) - let executableSuffix = ".exe" - #else - let executableSuffix = "" - #endif + // Emit Hylo IR. + var ir = try Module(lowering: m, in: p, reportingDiagnosticsTo: &log) + // Run mandatory IR analysis and transformation passes. + try ir.applyMandatoryPasses(reportingDiagnosticsTo: &log) + } + } - XCTAssert( - FileManager.default.fileExists(atPath: output.relativePath + executableSuffix), - "Compilation output file not found: \(output.relativePath)") + /// Parses the hylo file at `hyloFilePath`, `XCTAssert`ing that diagnostics and thrown + /// errors match annotated expectations. + @nonobjc + public func parse( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { + try checkAnnotatedHyloFileDiagnostics( + inFileAt: hyloFilePath, expecting: expectation + ) { (hyloSource, log) in + var ast = AST() + _ = try ast.loadModule( + hyloSource.baseName, parsing: [hyloSource], reportingDiagnosticsTo: &log) + } + } - return output + /// Type-checks the Hylo file at `hyloFilePath`, `XCTAssert`ing that diagnostics and thrown + /// errors match annotated expectations. + @nonobjc + public func typeCheck( + _ hyloFilePath: String, extending p: TypedProgram, expecting expectation: ExpectedTestOutcome + ) throws { + try checkAnnotatedHyloFileDiagnostics( + inFileAt: hyloFilePath, expecting: expectation + ) { (hyloSource, log) in + _ = try p.loadModule(reportingDiagnosticsTo: &log) { (ast, log, space) in + try ast.loadModule( + hyloSource.baseName, parsing: [hyloSource], inNodeSpace: space, + withBuiltinModuleAccess: true, + reportingDiagnosticsTo: &log) + } + } } } diff --git a/Sources/TestUtils/HyloTestCase.swift b/Sources/TestUtils/HyloTestCase.swift new file mode 100644 index 000000000..05e978b2c --- /dev/null +++ b/Sources/TestUtils/HyloTestCase.swift @@ -0,0 +1,50 @@ +import FrontEnd +import IR +import Utils +import XCTest + +/// A test driver for executing test cases generated from Hylo source files. +/// +/// This class is intended to be extended by test cases running Hylo programs through the compiler. +/// It contains the logic for loading and sharing a type checked instance of the standard library +/// across each individual test function, accessible. A unique copy of that shared instance is +/// assigned to `programToExtend` before each test. +/// +/// All tests of derived classes are skipped if the loading of the standard library fails. +open class HyloTestCase: XCTestCase { + + /// A shared program instance containing only the standard library. + private static var base = SharedMutable(nil) + + /// The program being processed by the test case. + /// + /// This property is assigned before each test to a fresh copy of `base` and is intended to be + /// extended with the code under test. + public var programToExtend: TypedProgram? + + /// Returns a copy of `base`, creating it if necessary. + private func checkedBaseProgram() throws -> TypedProgram { + return try self.checkNoDiagnostic { (d) in + let a = try Utils.Host.hostedLibraryAST.get() + let b = ScopedProgram(a) + return try TypedProgram(annotating: b, reportingDiagnosticsTo: &d) + } + } + + /// Assigns `programToExtend` to a copy of the shared instance or throws `XCTSkip`. + open override func setUpWithError() throws { + do { + programToExtend = try HyloTestCase.base.modify(applying: { (p) in + try p.setIfNil(checkedBaseProgram()) + }) + } catch _ { + throw XCTSkip() + } + } + + /// Destroys `programToExtend`. + open override func tearDown() { + programToExtend = nil + } + +} diff --git a/Sources/TestUtils/LoweringTests.swift b/Sources/TestUtils/LoweringTests.swift deleted file mode 100644 index de8ccd6de..000000000 --- a/Sources/TestUtils/LoweringTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -import FrontEnd -import IR -import StandardLibrary -import Utils -import XCTest - -extension XCTestCase { - - /// Lowers the hylo file at `hyloFilePath` to IR, applying any mandatory passes, and `XCTAssert`s - /// that diagnostics and thrown errors match annotated expectations. - @nonobjc - public func lowerToFinishedIR(_ hyloFilePath: String, expectSuccess: Bool) throws { - - try checkAnnotatedHyloFileDiagnostics(inFileAt: hyloFilePath, expectSuccess: expectSuccess) { - (valSource, diagnostics) in - // Note: built-in module is visible so that we can test built-in function calls. - var ast = try Host.freestandingLibraryAST.get() - - let module = try ast.makeModule( - valSource.baseName, sourceCode: [valSource], builtinModuleAccess: true, - diagnostics: &diagnostics) - - // Run the type checker - let base = ScopedProgram(ast) - let typedProgram = try TypedProgram(annotating: base, reportingDiagnosticsTo: &diagnostics) - - // Emit Hylo IR. - var irModule = try Module( - lowering: module, in: typedProgram, reportingDiagnosticsTo: &diagnostics) - - // Run mandatory IR analysis and transformation passes. - try irModule.applyMandatoryPasses(reportingDiagnosticsTo: &diagnostics) - } - - } - -} diff --git a/Sources/TestUtils/ParseTest.swift b/Sources/TestUtils/ParseTest.swift deleted file mode 100644 index f23acd50b..000000000 --- a/Sources/TestUtils/ParseTest.swift +++ /dev/null @@ -1,20 +0,0 @@ -import FrontEnd -import XCTest - -extension XCTestCase { - - /// Parses the hylo file at `hyloFilePath`, `XCTAssert`ing that diagnostics and thrown - /// errors match annotated expectations. - @nonobjc - public func parse(_ hyloFilePath: String, expectSuccess: Bool) throws { - - try checkAnnotatedHyloFileDiagnostics(inFileAt: hyloFilePath, expectSuccess: expectSuccess) { - (hyloSource, diagnostics) in - var ast = AST() - _ = try ast.makeModule( - hyloSource.baseName, sourceCode: [hyloSource], diagnostics: &diagnostics) - } - - } - -} diff --git a/Sources/TestUtils/TypeCheckerTests.swift b/Sources/TestUtils/TypeCheckerTests.swift deleted file mode 100644 index 7794e0ed9..000000000 --- a/Sources/TestUtils/TypeCheckerTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -import FrontEnd -import StandardLibrary -import Utils -import XCTest - -extension XCTestCase { - - /// Type-checks the Hylo file at `hyloFilePath`, `XCTAssert`ing that diagnostics and thrown - /// errors match annotated expectations. - @nonobjc - public func typeCheck(_ hyloFilePath: String, expectSuccess: Bool) throws { - - try checkAnnotatedHyloFileDiagnostics(inFileAt: hyloFilePath, expectSuccess: expectSuccess) { - (source, diagnostics) in - - var ast = try Host.freestandingLibraryAST.get() - - _ = try ast.makeModule( - source.baseName, sourceCode: [source], builtinModuleAccess: true, - diagnostics: &diagnostics) - let base = ScopedProgram(ast) - _ = try TypedProgram(annotating: base, reportingDiagnosticsTo: &diagnostics) - } - - } - -} diff --git a/Sources/Utils/DirectedGraph.swift b/Sources/Utils/DirectedGraph.swift index d3bef0659..0fa587afe 100644 --- a/Sources/Utils/DirectedGraph.swift +++ b/Sources/Utils/DirectedGraph.swift @@ -15,12 +15,6 @@ public struct NoLabel: Hashable { /// to be `Equatable`. public struct DirectedGraph { - /// The base of an edge. - public typealias EdgeBase = (source: Vertex, label: Label) - - /// The tip of an edge. - public typealias EdgeTip = (target: Vertex, label: Label) - /// A collection with the outgoing edges of a vertex. public typealias OutgoingEdges = [Vertex: Label] @@ -38,17 +32,25 @@ public struct DirectedGraph { } + /// The type of a table mapping a vertex to its outgoing edges. + private typealias Out = [Vertex: OutgoingEdges] + /// A table from a vertex to its outgoing edges. - private var outgoingEdges: [Vertex: OutgoingEdges] + private var out: Out /// Creates an empty graph. public init() { - outgoingEdges = [:] + out = [:] + } + + /// The vertices of the graph. + public var vertices: some Collection { + out.keys } /// The edges of the graph. public var edges: some Collection { - outgoingEdges.lazy + out.lazy .map({ (s, o) in o.lazy.map({ (t, l) in .init(source: s, label: l, target: t) }) }) .joined() } @@ -65,6 +67,19 @@ public struct DirectedGraph { bfs(from: u).contains(v) } + /// Inserts `v` in `self` and returns `true` if it was not already present. + @discardableResult + public mutating func insertVertex(_ v: Vertex) -> Bool { + modify(&out[v]) { (o) in + if (o == nil) { + o = [:] + return true + } else { + return false + } + } + } + /// Inserts an edge from `source` to `target`, labeled by `label`. /// /// - Returns: `(true, label)` if there was no edge between `source` and `target`. Otherwise, @@ -75,16 +90,15 @@ public struct DirectedGraph { public mutating func insertEdge( from source: Vertex, to target: Vertex, labeledBy label: Label ) -> (inserted: Bool, labelAfterInsert: Label) { - modify( - &outgoingEdges[source, default: [:]], - { tips in - if let currentLabel = tips[target] { - return (false, currentLabel) - } else { - tips[target] = label - return (true, label) - } - }) + _ = out[target].setIfNil([:]) + return modify(&out[source, default: [:]]) { (tips) in + if let currentLabel = tips[target] { + return (false, currentLabel) + } else { + tips[target] = label + return (true, label) + } + } } /// Removes the edge from `source` to `target`. @@ -94,16 +108,14 @@ public struct DirectedGraph { /// - Complexity: O(1). @discardableResult public mutating func removeEdge(from source: Vertex, to target: Vertex) -> Label? { - modify( - &outgoingEdges[source, default: [:]], - { tips in - if let i = tips.index(forKey: target) { - defer { tips.remove(at: i) } - return tips[i].value - } else { - return nil - } - }) + modify(&out[source, default: [:]]) { (tips) in + if let i = tips.index(forKey: target) { + defer { tips.remove(at: i) } + return tips[i].value + } else { + return nil + } + } } /// Accesses the label on the edge from `source` to `target`. @@ -111,16 +123,16 @@ public struct DirectedGraph { /// - Returns: If there exists a edge from from `source` to `target`, the label of that edge. /// Otherwise, `nil`. public subscript(from source: Vertex, to target: Vertex) -> Label? { - _read { yield outgoingEdges[source]?[target] } - _modify { yield &outgoingEdges[source, default: [:]][target] } + _read { yield out[source]?[target] } + _modify { yield &out[source, default: [:]][target] } } /// Accesses the outgoing edges of `source`. /// /// - Complexity: O(1). public subscript(from source: Vertex) -> OutgoingEdges { - _read { yield outgoingEdges[source, default: [:]] } - _modify { yield &outgoingEdges[source, default: [:]] } + _read { yield out[source, default: [:]] } + _modify { yield &out[source, default: [:]] } } } @@ -143,10 +155,10 @@ extension DirectedGraph: Equatable where Label: Equatable { public static func == (l: Self, r: Self) -> Bool { var sources: Set = [] - sources.reserveCapacity(l.outgoingEdges.count) + sources.reserveCapacity(l.out.count) - for (source, lhs) in l.outgoingEdges { - let rhs = r.outgoingEdges[source, default: [:]] + for (source, lhs) in l.out { + let rhs = r.out[source, default: [:]] if lhs.count != rhs.count { return false } for (target, label) in lhs { if rhs[target] != label { return false } @@ -154,7 +166,7 @@ extension DirectedGraph: Equatable where Label: Equatable { sources.insert(source) } - return r.outgoingEdges.keys.allSatisfy(sources.contains(_:)) + return r.out.keys.allSatisfy(sources.contains(_:)) } } @@ -163,7 +175,7 @@ extension DirectedGraph: Hashable where Label: Hashable { public func hash(into hasher: inout Hasher) { var h = 0 - for (source, tips) in outgoingEdges where !tips.isEmpty { + for (source, tips) in out where !tips.isEmpty { var _hasher = hasher _hasher.combine(source) _hasher.combine(tips) diff --git a/Sources/Utils/DistinctNameGenerator.swift b/Sources/Utils/DistinctNameGenerator.swift new file mode 100644 index 000000000..b66cd3cec --- /dev/null +++ b/Sources/Utils/DistinctNameGenerator.swift @@ -0,0 +1,42 @@ +public struct DistinctNameGenerator { + + private var nameToOccurrences: [String: [Key]] = [:] + + public init() {} + + public mutating func name(_ n: String, keyedBy k: Key) -> String { + modify(&nameToOccurrences[n, default: []]) { (occurrences) in + if let i = occurrences.firstIndex(of: k) { + return "\(n)\(Self.superscript(i))" + } else { + occurrences.append(k) + return "\(n)\(Self.superscript(occurrences.count - 1))" + } + } + } + + private static func superscript(_ n: Int) -> String { + if n == 0 { + return "" + } else { + return String(n).map(digitToSuperscript).joined() + } + } + + private static func digitToSuperscript(_ c: Character) -> String { + switch c.asciiValue! { + case 48: "⁰" + case 49: "¹" + case 50: "²" + case 51: "³" + case 52: "⁴" + case 53: "⁵" + case 54: "⁶" + case 55: "⁷" + case 56: "⁸" + case 57: "⁹" + default: unreachable() + } + } + +} diff --git a/Sources/Utils/FileManager+Extensions.swift b/Sources/Utils/FileManager+Extensions.swift index f5ec1e3bf..aac4eb060 100644 --- a/Sources/Utils/FileManager+Extensions.swift +++ b/Sources/Utils/FileManager+Extensions.swift @@ -14,4 +14,11 @@ extension FileManager { temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: false) } + /// Writes `s` to a temporary file and returns its URL. + public func temporaryFile(containing s: String) throws -> URL { + let f = self.makeTemporaryFileURL() + try s.write(to: f, atomically: true, encoding: .utf8) + return f + } + } diff --git a/Sources/Utils/Optional+Extensions.swift b/Sources/Utils/Optional+Extensions.swift index 846e9903a..85c8a4f9f 100644 --- a/Sources/Utils/Optional+Extensions.swift +++ b/Sources/Utils/Optional+Extensions.swift @@ -7,9 +7,11 @@ extension Optional { } /// If `self` is `nil`, wraps and returns `newValue`; returns the wrapped value otherwise. - public mutating func setIfNil(_ newValue: @autoclosure () -> Wrapped) -> Wrapped { + public mutating func setIfNil( + _ newValue: @autoclosure () throws -> Wrapped + ) rethrows -> Wrapped { if let v = self { return v } - let v = newValue() + let v = try newValue() self = v return v } diff --git a/Sources/Utils/Ordering.swift b/Sources/Utils/Ordering.swift index 5ebbb842d..9dbbfa6cc 100644 --- a/Sources/Utils/Ordering.swift +++ b/Sources/Utils/Ordering.swift @@ -10,6 +10,11 @@ public enum StrictOrdering: Hashable { /// The LHS is ordered after the RHS. case descending + /// Creates the comparison of `a` with `b`. + public init(between a: T, and b: T) { + self = (a < b) ? .ascending : ((b < a) ? .descending : .equal) + } + } /// The result type of a three-way comparison implementing a strict partial order. diff --git a/Sources/Utils/StronglyConnectedComponents.swift b/Sources/Utils/StronglyConnectedComponents.swift new file mode 100644 index 000000000..aa99bedcc --- /dev/null +++ b/Sources/Utils/StronglyConnectedComponents.swift @@ -0,0 +1,81 @@ +/// The strongly connected components of a directed graph. +public struct StronglyConnectedComponents { + + /// A map from vertex identifier to its properties. + private var properties: [Vertex: Properties] = [:] + + /// A map from component to its vertices. + private var components: [[Vertex]] = [] + + /// The stack of vertices being visited. + private var stack: [Vertex] = [] + + /// The next vertex index. + private var nextIndex: Int = 0 + + /// Creates an empty instance. + public init() {} + + /// Returns the vertices in the given component. + public func vertices(in c: Int) -> [Vertex] { + components[c] + } + + /// Returns the strongly connected component containing `v`, calling `enumerateSuccessors` to + /// obtain the successors of a vertex. + public mutating func component( + containing v: Vertex, + enumeratingSuccessorsWith enumerateSuccessors: (Vertex) -> [Vertex] + ) -> Int { + if let c = properties[v]?.component { return c } + + properties[v] = .init(low: nextIndex, index: nextIndex, component: -1, isOnStack: true) + stack.append(v) + nextIndex += 1 + + for w in enumerateSuccessors(v) { + if properties[w] == nil { + // Successor w has not yet been visited; recurse on it + _ = component(containing: w, enumeratingSuccessorsWith: enumerateSuccessors) + properties[v]!.low = min(properties[v]!.low, properties[w]!.low) + } else if properties[w]!.isOnStack { + properties[v]!.low = min(properties[v]!.low, properties[w]!.index) + } + } + + if properties[v]!.low == properties[v]!.index { + let c = components.count + components.append([]) + + while true { + let w = stack.removeLast() + components[c].append(w) + modify(&properties[w]!) { (pw) in + pw.isOnStack = false + pw.component = c + } + if (w == v) { break } + } + } + + return properties[v]!.component + } + + /// The properties associated with a vertex in Tarjan's algorithm. + private struct Properties { + + /// The smallest index of any vertex reachable from this vertex. + var low: Int + + /// The index of this vertex. + var index: Int + + /// The component in which this vertex belongs. + var component: Int + + /// True iff this vertex is on the stack. + var isOnStack: Bool + + } + +} diff --git a/Sources/Utils/Trie.swift b/Sources/Utils/Trie.swift new file mode 100644 index 000000000..6591f6c2a --- /dev/null +++ b/Sources/Utils/Trie.swift @@ -0,0 +1,366 @@ +/// A trie (a.k.a. prefix tree). +public struct Trie where Key.Element: Hashable { + + /// A node representing either one of the strings in a trie or a prefix thereof. + fileprivate struct Node { + + /// The identifier of a node in a trie. + typealias Identifier = Int + + ///The outgoing edges of a node. + typealias Children = [Key.Element: Identifier] + + /// The outgoing edges of this node. + var children: Children + + /// The value associated with the member string represented by `self`, if any. + var value: Value? + + /// `true` iff `self` is not part of a member string. + var isTombstone: Bool { + children.isEmpty && (value == nil) + } + + } + + /// The nodes in the trie, the first of which is the root. + fileprivate var nodes: [Node] + + /// Creates an empty trie. + public init() { + self.nodes = [.init(children: [:], value: nil)] + } + + /// The number of key/value pairs in `self`. + public var count: Int { + nodes.reduce(0, { (c, n) in c + (n.value == nil ? 0 : 1) }) + } + + /// `true` iff `self` is empty. + public var isEmpty: Bool { + nodes[0].isTombstone + } + + /// Returns a collection with the key/value pairs in `self`. + public var elements: Elements { + .init(base: self, root: 0) + } + + /// Returns a pair `(n, i)` such that `key[..( + startingWith key: K + ) -> (trie: SubTrie, position: K.Index) where K.Element == Key.Element { + SubTrie(base: self, root: 0).longestPrefix(startingWith: key) + } + + /// Accesses the value associated with `key`. + public subscript(key: K) -> Value? where K.Element == Key.Element { + _read { + if let n = find(key, from: 0) { + yield nodes[n].value + } else { + yield nil + } + } + + _modify { + var path: [(n: Node.Identifier, i: K.Index)] = [] + path.reserveCapacity(key.count) + path.append((0, key.startIndex)) + + while path.last!.i != key.endIndex { + let (n, i) = path.last! + if let m = nodes[n].children[key[i]] { + path.append((m, key.index(after: i))) + } else { + break + } + } + + // There's already a node for `k`. + if path.last!.i == key.endIndex { + defer { + while (path.count > 1) && nodes[path.last!.n].isTombstone { + let (n, i) = path[path.count - 2] + nodes[n].children.removeValue(forKey: key[i]) + path.removeLast() + } + } + yield &nodes[path.last!.n].value + } + + // There's no node for `k`. + else { + var value: Value? = nil + defer { + if value != nil { + var (n, i) = path.last! + while i != key.endIndex { + nodes[n].children[key[i]] = nodes.count + n = nodes.count + i = key.index(after: i) + nodes.append(.init(children: [:], value: nil)) + } + nodes[nodes.count - 1].value = value + } + } + yield &value + } + } + } + + /// Returns a sub-trie mapping the keys prefixed by `key` to their corresponding value in `self`, + /// or `nil` if `self` contains no key starting with by `key`. + public subscript( + prefix key: K + ) -> SubTrie? where K.Element == Key.Element { + SubTrie(base: self, root: 0)[prefix: key] + } + + /// Returns the node corresponding to the longest prefix shared with `key` looked up from `root`. + fileprivate func longestPrefix( + startingWith key: K, from root: Node.Identifier + ) -> (node: Node.Identifier, position: K.Index) where K.Element == Key.Element { + var n = root + var i = key.startIndex + while i != key.endIndex { + if let m = nodes[n].children[key[i]] { + n = m + i = key.index(after: i) + } else { + break + } + } + return (n, i) + } + + /// Returns the node corresponding to `key` looked up from `root`. + fileprivate func find( + _ key: K, from root: Node.Identifier + ) -> Node.Identifier? where K.Element == Key.Element { + let (n, i) = longestPrefix(startingWith: key, from: root) + return i == key.endIndex ? n : nil + } + +} + +extension Trie { + + /// A collection containing the elements of a `Trie` or `SubTrie`, in some arbitrary order. + public struct Elements: Collection { + + /// A position in an instance of `Trie.Elements`. + public struct Index: Hashable, Comparable { + + fileprivate struct Edge: Hashable, Comparable { + + var source: Node.Identifier + + var label: Node.Children.Index + + static func < (lhs: Self, rhs: Self) -> Bool { + (lhs.source == rhs.source) && (lhs.label < rhs.label) || (lhs.source < rhs.source) + } + + } + + fileprivate var path: [Edge] + + public static func < (lhs: Self, rhs: Self) -> Bool { + // Empty path is the end index. + if lhs.path.isEmpty { + return false + } else if rhs.path.isEmpty { + return true + } else { + return lhs.path.lexicographicallyPrecedes(rhs.path) + } + } + + } + + /// A key/value pair stored in a `Trie`. + public typealias Element = (key: [Key.Element], value: Value) + + /// The `Trie` of which `self` is a projection. + fileprivate let base: Trie + + /// The root of the sub-tree of which `self` is a projection. + fileprivate let root: Trie.Node.Identifier + + /// Returns the first index in `self`. + public var startIndex: Index { + if base.nodes[root].isTombstone { return endIndex } + var path = [Index.Edge(source: root, label: base.nodes[root].children.startIndex)] + extendPathToFirstLeaf(&path) + return .init(path: path) + } + + /// Returns the "past-the-end" index in `self`. + public var endIndex: Index { + return .init(path: []) + } + + /// Returns the position immediately after `p`. + /// + /// - Precondition: `p` must be a valid index in `self` different from `endIndex`. + public func index(after p: Index) -> Index { + var nextPath: Array = p.path.dropLast() + while !nextPath.isEmpty { + let e = nextPath.count - 1 + nextPath[e].label = base.nodes[nextPath[e].source].children.index(after: nextPath[e].label) + + if nextPath[e].label != base.nodes[nextPath[e].source].children.endIndex { + extendPathToFirstLeaf(&nextPath) + return .init(path: nextPath) + } else if base.nodes[nextPath[e].source].value != nil { + return .init(path: nextPath) + } else { + nextPath.removeLast() + } + } + return endIndex + } + + /// Accesses the key/value pair at position `p`. + /// + /// - Precondition: `p` must be a valid index in `self` different from `endIndex`. + public subscript(p: Index) -> (key: [Key.Element], value: Value) { + let k = p.path.dropLast().map({ (edge) in base.nodes[edge.source].children[edge.label].key }) + let v = base.nodes[p.path.last!.source].value! + return (key: k, value: v) + } + + /// Extends `path` so to point at the first leaf of the node currently at the end of `path`. + private func extendPathToFirstLeaf(_ path: inout [Index.Edge]) { + while let p = path.last, p.label != base.nodes[p.source].children.endIndex { + let m = base.nodes[p.source].children[p.label].value + path.append(Index.Edge(source: m, label: base.nodes[m].children.startIndex)) + } + } + + } + +} + +extension Trie: Equatable where Value: Equatable { + + /// Returns `true` iff `self` shares storage with `other`. + private func isSharingStorage(with other: Self) -> Bool { + self.nodes.withUnsafeBytes { (a) in + other.nodes.withUnsafeBytes({ (b) in a.baseAddress == b.baseAddress }) + } + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + // Fast path: `lhs` and `rhs` have the same storage. + if lhs.isSharingStorage(with: rhs) { return true } + + // Slow path: check if both collections contain the same elements. + for (k, v) in lhs.elements { + if rhs[k] != v { return false } + } + return lhs.count == rhs.count + } + +} + +extension Trie: Hashable where Value: Hashable { + + public func hash(into hasher: inout Hasher) { + let copy = hasher + var hash = copy.finalize() + for (k, v) in elements { + hash ^= k.hashValue ^ v.hashValue + } + hasher.combine(hash) + } + +} + +extension Trie.Node: Equatable where Value: Equatable {} + +extension Trie.Node: Hashable where Value: Hashable {} + +extension Trie: CustomStringConvertible { + + public var description: String { + return "[" + elements.map({ (k, v) in "\(k): \(v)" }).joined(separator: ", ") + "]" + } + +} + +/// A part of a trie. +public struct SubTrie where Key.Element: Hashable { + + /// The type of a trie projected by an instance of `Self`. + public typealias Base = Trie + + /// The `Trie` of which `self` is a projection. + fileprivate let base: Base + + /// The root of this sub-tree. + fileprivate let root: Base.Node.Identifier + + /// The number of key/value pairs in `self`. + public var count: Int { + var work = [root] + var result = 0 + while let n = work.popLast() { + if base.nodes[n].value != nil { result += 1 } + work.append(contentsOf: base.nodes[n].children.values) + } + return result + } + + /// Returns a collection with the key/value pairs in `self`. + public var elements: Base.Elements { + .init(base: base, root: root) + } + + /// Accesses the value associated with `key`. + public subscript(key: K) -> Value? where K.Element == Key.Element { + _read { + if let n = base.find(key, from: root) { + yield base.nodes[n].value + } else { + yield nil + } + } + } + + /// Returns a sub-trie mapping the keys prefixed by `key` to their corresponding value in `self`, + /// or `nil` if `self` contains no key starting with by `key`. + public subscript(prefix key: K) -> SubTrie? where K.Element == Key.Element { + if let n = base.find(key, from: root) { + return .init(base: base, root: n) + } else { + return nil + } + } + + /// Returns a pair `(n, i)` such that `key[..( + startingWith key: K + ) -> (trie: SubTrie, position: K.Index) where K.Element == Key.Element { + let (n, i) = base.longestPrefix(startingWith: key, from: root) + return (.init(base: base, root: n), i) + } + +} + +extension SubTrie: CustomStringConvertible { + + public var description: String { + return "[" + elements.map({ (k, v) in "\(k): \(v)" }).joined(separator: ", ") + "]" + } + +} diff --git a/StandardLibrary/AST+Extensions.swift b/StandardLibrary/AST+Extensions.swift index 778868d3e..5cb71615f 100644 --- a/StandardLibrary/AST+Extensions.swift +++ b/StandardLibrary/AST+Extensions.swift @@ -8,11 +8,11 @@ extension AST { init(libraryRoot: URL, _ compilationConditions: ConditionalCompilationFactors) throws { self.init(compilationConditions) var diagnostics = DiagnosticSet() - coreLibrary = try makeModule( + coreLibrary = try loadModule( "Hylo", - sourceCode: sourceFiles(in: [libraryRoot]), - builtinModuleAccess: true, - diagnostics: &diagnostics) + parsing: sourceFiles(in: [libraryRoot]), + withBuiltinModuleAccess: true, + reportingDiagnosticsTo: &diagnostics) assert(coreModuleIsLoaded) self.coreTraits = .init(self) } diff --git a/StandardLibrary/Sources/Array.hylo b/StandardLibrary/Sources/Array.hylo index a1618408c..4c1d61e88 100644 --- a/StandardLibrary/Sources/Array.hylo +++ b/StandardLibrary/Sources/Array.hylo @@ -11,6 +11,22 @@ public type Array: SemiRegular { &storage = .new() } + /// Creates an array with the given `elements`. + public init(_ elements: sink Element[n]) { + &storage = .new() + &reserve_capacity(n) + + // Note: lifetime of `p` is guaranteed by the use of `elements` after the loop. + let p = PointerToMutable(type_punning: mutable_pointer[to: &elements]) + var i = 0 + while i < n { + &append(p.advance(by: i).unsafe_pointee()) + &i += 1 + } + + Builtin.mark_uninitialized(elements) + } + /// Creates an array whose contents are the results, of calling `initialize` with the values /// `0 ..< count`, in order. /// @@ -86,7 +102,7 @@ public type Array: SemiRegular { /// The projected pointer is valid only for the duration of the projection and can be advanced up /// to `count()`. It may be null if `self` is empty. public property contiguous_storage: Pointer { - yield if capacity() == 0 { .null() } else { .new(pointer_to_element[at: 0]) } + yield if capacity() == 0 { .null() } else { .new(pointer_to_element(at: 0)) } } /// Calls `action` with a pointer to the start of the array's mutable contiguous storage. @@ -96,13 +112,13 @@ public type Array: SemiRegular { public fun with_mutable_contiguous_storage( _ action: inout [E](PointerToMutable) inout -> T ) inout -> T { - if capacity() == 0 { &action(.null()) } else { &action(pointer_to_element[at: 0]) } + if capacity() == 0 { &action(.null()) } else { &action(pointer_to_element(at: 0)) } } /// Adds a new element at the end of the array. public fun append(_ source: sink Element) inout { &reserve_capacity(count() + 1) - pointer_to_element[at: count()].unsafe_initialize_pointee(source) + pointer_to_element(at: count()).unsafe_initialize_pointee(source) &storage.header += 1 } @@ -117,11 +133,11 @@ public type Array: SemiRegular { var c = count() &reserve_capacity(c + 1) while c > index { - pointer_to_element[at: c].unsafe_initialize_pointee( - pointer_to_element[at: c - 1].unsafe_pointee()) + pointer_to_element(at: c).unsafe_initialize_pointee( + pointer_to_element(at: c - 1).unsafe_pointee()) &c -= 1 } - pointer_to_element[at: c].unsafe_initialize_pointee(source) + pointer_to_element(at: c).unsafe_initialize_pointee(source) &storage.header += 1 } @@ -130,11 +146,11 @@ public type Array: SemiRegular { /// - Requires: `index` is in the range `0 ..< count()`. /// - Complexity: O(n), where n is the number of elements in `self`. public fun remove(at index: Int) inout -> Element { - let result = pointer_to_element[at: index].unsafe_pointee() + let result = pointer_to_element(at: index).unsafe_pointee() var i = index + 1 while i < count() { - pointer_to_element[at: i - 1].unsafe_initialize_pointee( - pointer_to_element[at: i].unsafe_pointee()) + pointer_to_element(at: i - 1).unsafe_initialize_pointee( + pointer_to_element(at: i).unsafe_pointee()) &i += 1 } &storage.header -= 1 @@ -148,7 +164,7 @@ public type Array: SemiRegular { let c = count() var i = 0 while i < c { - &pointer_to_element[at: i].unsafe_pointee().deinit() + &pointer_to_element(at: i).unsafe_pointee().deinit() &i += 1 } &storage.header = 0 @@ -161,7 +177,7 @@ public type Array: SemiRegular { public fun pop_last() inout -> Optional { let c = count() if c > 0 { - let result = pointer_to_element[at: c - 1].unsafe_pointee() + let result = pointer_to_element(at: c - 1).unsafe_pointee() &storage.header -= 1 return result as _ } else { @@ -182,10 +198,10 @@ public type Array: SemiRegular { } } - /// Projects the address of the element at `position`. + /// Returns the address of the element at `position`. /// /// - Requires: `position` is in the range `0 ..< capacity()`. - subscript pointer_to_element(at position: Int): PointerToMutable { + fun pointer_to_element(at position: Int) -> PointerToMutable { storage.first_element_address().advance(by: position) } @@ -245,11 +261,11 @@ public conformance Array: Collection { public subscript(_ position: Int): Element { let { precondition((position >= 0) && (position < count()), "position is out of bounds") - yield pointer_to_element[at: position].unsafe[] + yield pointer_to_element(at: position).unsafe[] } inout { precondition((position >= 0) && (position < count()), "position is out of bounds") - yield &pointer_to_element[at: position].unsafe[] + yield &(pointer_to_element(at: position).unsafe[]) } } @@ -261,7 +277,10 @@ public conformance Array: MutableCollection { precondition((i >= 0) && (i < count()), "position is out of bounds") precondition((j >= 0) && (j < count()), "position is out of bounds") if i == j { return } - &pointer_to_element[at: i].unsafe[].exchange(with: &pointer_to_element[at: j].unsafe[]) + + var p = pointer_to_element(at: i) + var q = pointer_to_element(at: j) + &p.unsafe[].exchange(with: &q.unsafe[]) } } diff --git a/StandardLibrary/Sources/Concurrency/Future.hylo b/StandardLibrary/Sources/Concurrency/Future.hylo index 900adca69..85b263868 100644 --- a/StandardLibrary/Sources/Concurrency/Future.hylo +++ b/StandardLibrary/Sources/Concurrency/Future.hylo @@ -1,5 +1,5 @@ /// A future that cannot escape the local scope. -public type Future { +public type Future: Deinitializable { // TODO public typealias Result = Int diff --git a/StandardLibrary/Sources/Core/Bitcast.hylo b/StandardLibrary/Sources/Core/Bitcast.hylo index 1dd57d900..51909d734 100644 --- a/StandardLibrary/Sources/Core/Bitcast.hylo +++ b/StandardLibrary/Sources/Core/Bitcast.hylo @@ -14,5 +14,6 @@ public subscript unsafe_bitcast(_ value: T): U { /// Returns `value` with its memory representation reinterpreted as a value of type `U`. public fun unsafe_bitcast(consuming value: sink T) -> U { sink let p: PointerToMutable = PointerToMutable(type_punning: mutable_pointer[to: &value]) + Builtin.mark_uninitialized(value) return p.unsafe_pointee() } diff --git a/StandardLibrary/Sources/Core/Collection.hylo b/StandardLibrary/Sources/Core/Collection.hylo index 64c890737..d7d9ba30d 100644 --- a/StandardLibrary/Sources/Core/Collection.hylo +++ b/StandardLibrary/Sources/Core/Collection.hylo @@ -28,6 +28,28 @@ public trait Collection { public extension Collection { + /// Returns the number of elements in `self`. + /// + /// - Complexity: O(n), where n is the number of elements in `self`. + public fun count() -> Int { + var r = 0 + for let _ in self { &r += 1 } + return r + } + + /// Returns the position of the first element of `self` satisfying `predicate`, or + /// `end_position()` if no such element exists. + /// + /// - Complexity: O(n), where n is the number of elements in `self`. + public fun first_position(where predicate: [E](Element) -> Bool) -> Position { + var i = start_position() + let j = end_position() + while (i != j) && !predicate(self[i]) { + &i = self.position(after: i) + } + return i + } + /// Returns the result of applying `combine` on an accumulator, initialized with `initial_value`, /// and each element of `self`, in order. /// @@ -63,3 +85,15 @@ public extension Collection { } } + +public extension Collection where Element: Equatable { + + /// Returns the position of the first element of `self` that is equal to `needle`, or + /// `end_position()` if no such element exists. + /// + /// - Complexity: O(n), where n is the number of elements in `self`. + public fun first_position(of needle: Element) -> Position { + first_position(where: fun (_ e) { e == needle }) + } + +} diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/Int32.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/Int32.hylo index 1c706bef6..725d934cf 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/Int32.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/Int32.hylo @@ -308,7 +308,7 @@ public conformance Int32: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -318,7 +318,7 @@ public conformance Int32: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/Int64.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/Int64.hylo index 5618934b9..9f816b2cf 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/Int64.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/Int64.hylo @@ -309,7 +309,7 @@ public conformance Int64: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -319,7 +319,7 @@ public conformance Int64: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/Int8.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/Int8.hylo index 9ea05de50..02b6dd122 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/Int8.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/Int8.hylo @@ -314,7 +314,7 @@ public conformance Int8: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -324,7 +324,7 @@ public conformance Int8: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/UInt32.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/UInt32.hylo index 819e60d48..842419dfe 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/UInt32.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/UInt32.hylo @@ -267,7 +267,7 @@ public conformance UInt32: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -277,7 +277,7 @@ public conformance UInt32: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/UInt64.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/UInt64.hylo index 020d35778..bdf77b206 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/UInt64.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/UInt64.hylo @@ -268,7 +268,7 @@ public conformance UInt64: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -278,7 +278,7 @@ public conformance UInt64: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Numbers/Integers/UInt8.hylo b/StandardLibrary/Sources/Core/Numbers/Integers/UInt8.hylo index da8407db0..e6d505c68 100644 --- a/StandardLibrary/Sources/Core/Numbers/Integers/UInt8.hylo +++ b/StandardLibrary/Sources/Core/Numbers/Integers/UInt8.hylo @@ -273,7 +273,7 @@ public conformance UInt8: FixedWidthInteger { public fun infix&<< (_ n: Int) -> Self { var lhs = self.copy() - lhs &<<= n + &lhs &<<= n return lhs } @@ -283,7 +283,7 @@ public conformance UInt8: FixedWidthInteger { public fun infix&>> (_ n: Int) -> Self { var lhs = self.copy() - lhs &>>= n + &lhs &>>= n return lhs } diff --git a/StandardLibrary/Sources/Core/Optional.hylo b/StandardLibrary/Sources/Core/Optional.hylo index ced3b2c4f..11a9a9329 100644 --- a/StandardLibrary/Sources/Core/Optional.hylo +++ b/StandardLibrary/Sources/Core/Optional.hylo @@ -9,11 +9,6 @@ public type None: Regular { /// Creates a value denoting the absence of an instance of `T`. public memberwise init - // TODO: Remove when #1078 is implemented. - public fun infix==(_ other: Self) -> Bool { - true - } - } public extension Optional { @@ -23,4 +18,46 @@ public extension Optional { None() as Optional } + /// Returns `true` iff `self` is equal to `.none()`. + public fun is_empty() -> Bool { + if let _: T = self { false } else { true } + } + + /// Returns an optional containing the result of calling `transform` on the value wrapped in + /// `self` or `.none()` is `self` is empty. + public fun map(_ transform: inout [E](T) -> R) -> Optional { + if let w? = self { transform(w) as _ } else { .none() } + } + +} + +public extension Optional where T: Movable { + + /// Returns the value wrapped in `self` or stops execution if `self` is empty. + /// + /// - Requires: `self` is not empty. + public fun postfix!() sink -> T { + if sink let r? = self { + return r + } else { + fatal_error("optional is empty") + } + } + + /// Returns the value wrapped in `self` and assigns it to `.none()`. + /// + /// - Requires: `self` is not empty. + public fun release() inout -> T { + sink let wrapped = self! + &self = .none() + return wrapped + } + } + +// Note: We can't declare confitional conformance of `Optional` to "umbrella traits" yet without +// causing ambiguities. See #1566 + +public conformance Optional: Deinitializable where T: Deinitializable {} + +public conformance Optional: Equatable where T: Equatable {} diff --git a/StandardLibrary/Sources/Core/String/UTF8Array.hylo b/StandardLibrary/Sources/Core/String/UTF8Array.hylo index 599eadb1d..f579b2067 100644 --- a/StandardLibrary/Sources/Core/String/UTF8Array.hylo +++ b/StandardLibrary/Sources/Core/String/UTF8Array.hylo @@ -168,7 +168,9 @@ public type UTF8Array: Regular { &self = with_extended_lifetime( nullterminated, do: fun[sink let c = count(), sink let k = new_capacity.copy()] (_ source) { - UTF8Array(unsafely_copying: PointerToBuffer(start: source.copy(), count: c), reserving: k) + UTF8Array( + unsafely_copying: PointerToBuffer(start: source.copy(), count: c.copy()), + reserving: k) }) } diff --git a/StandardLibrary/StandardLibrary.swift b/StandardLibrary/StandardLibrary.swift index 18ec1ffb1..df9378710 100644 --- a/StandardLibrary/StandardLibrary.swift +++ b/StandardLibrary/StandardLibrary.swift @@ -2,17 +2,31 @@ import Foundation import FrontEnd import Utils -// This path points into the source tree rather than to some copy so that when diagnostics are -// issued for the standard library, they point to the original source files and edits to those -// source files actually fix the problems (see https://github.com/hylo-lang/hylo/issues/932). If we -// ever change that, some other mechanism is needed to ensure that diagnostics refer to the original -// source files. -// -/// The parent directory of the standard library sources directory. -private let libraryRoot = URL(fileURLWithPath: #filePath).deletingLastPathComponent() +private let standardLibrarySourceFolder = URL( + fileURLWithPath: #filePath).deletingLastPathComponent().appendingPathComponent("Sources") + +#if SWIFT_PACKAGE // Check if SPM is being used + /// The parent directory of the standard library sources directory. + private let useBuiltinStandardLibrary = ProcessInfo.processInfo.environment["HYLO_USE_BUILTIN_STDLIB"] == "true" + + private let libraryRoot = if useBuiltinStandardLibrary { + // This path points into the source tree rather than to some copy so that when diagnostics are + // issued for the standard library, they point to the original source files and edits to those + // source files actually fix the problems (see https://github.com/hylo-lang/hylo/issues/932). + // If we ever change that, some other mechanism is needed to ensure that diagnostics refer to + // the original source files. + standardLibrarySourceFolder + } else { + Bundle.module.url(forResource: "Sources", withExtension: nil)! + } +#else + // Default to source location on SPM. TODO make this able to use exported resources + private let libraryRoot = standardLibrarySourceFolder +#endif + /// The root of a directory hierarchy containing all the standard library sources. -private let hostedLibrarySourceRoot = libraryRoot.appendingPathComponent("Sources") +private let hostedLibrarySourceRoot = libraryRoot /// The root of a directory hierarchy containing the sources for the standard library's freestanding /// core. @@ -22,12 +36,18 @@ extension Utils.Host { /// An AST representing the whole standard library, conditionally compiled for targeting the host /// platform. - public static let hostedLibraryAST - = Result { try AST(libraryRoot: hostedLibrarySourceRoot, ConditionalCompilationFactors(freestanding: false)) } + public static let hostedLibraryAST = Result { + try AST( + libraryRoot: hostedLibrarySourceRoot, + ConditionalCompilationFactors(freestanding: false)) + } /// An AST representing the freestanding core of standard library, conditionally compiled for /// targeting the host platform. - public static let freestandingLibraryAST - = Result { try AST(libraryRoot: freestandingLibrarySourceRoot, ConditionalCompilationFactors(freestanding: true)) } + public static let freestandingLibraryAST = Result { + try AST( + libraryRoot: freestandingLibrarySourceRoot, + ConditionalCompilationFactors(freestanding: true)) + } } diff --git a/Tests/DriverTests/DriverTests.swift b/Tests/DriverTests/DriverTests.swift index 8a450da0d..33f870925 100644 --- a/Tests/DriverTests/DriverTests.swift +++ b/Tests/DriverTests/DriverTests.swift @@ -7,48 +7,35 @@ import XCTest final class DriverTests: XCTestCase { - /// The result of a compiler invocation. - private struct CompilationResult { - - /// The exit status of the compiler. - let status: ExitCode - - /// The URL of the output file. - let output: URL - - /// The text of generated diagnostics. - let diagnosticText: String - - /// `XCTAssert`'s that `diagnosticText` matches `expectedDiagnostics`, with better output - /// formatting on failure. - func checkDiagnosticText( - `is` expectedDiagnostics: String, file: StaticString = #filePath, line: UInt = #line - ) { - XCTAssertEqual("\n" + diagnosticText, "\n" + expectedDiagnostics, file: file, line: line) - } - } - func testNoInput() throws { let cli = try Driver.parse([]) - XCTAssertThrowsError(try cli.execute()) + var log = DiagnosticSet() + XCTAssertThrowsError(try cli.executeCommand(reportingDiagnosticsTo: &log)) + XCTAssert(log.isEmpty) } func testRawAST() throws { - let result = try compile(["--emit", "raw-ast"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "raw-ast"]) XCTAssert(result.status.isSuccess) XCTAssert(FileManager.default.fileExists(atPath: result.output.relativePath)) result.checkDiagnosticText(is: "") } func testRawIR() throws { - let result = try compile(["--emit", "raw-ir"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "raw-ir"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") XCTAssert(FileManager.default.fileExists(atPath: result.output.relativePath)) } func testIR() throws { - let result = try compile(["--emit", "ir"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "ir"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") XCTAssert(FileManager.default.fileExists(atPath: result.output.relativePath)) @@ -57,7 +44,9 @@ final class DriverTests: XCTestCase { func testLLVM() throws { if swiftyLLVMMandatoryPassesCrash { return } - let result = try compile(["--emit", "llvm"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "llvm"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") XCTAssert(FileManager.default.fileExists(atPath: result.output.relativePath)) @@ -66,7 +55,9 @@ final class DriverTests: XCTestCase { func testIntelASM() throws { if swiftyLLVMMandatoryPassesCrash { return } - let result = try compile(["--emit", "intel-asm"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "intel-asm"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") XCTAssert(FileManager.default.fileExists(atPath: result.output.relativePath)) @@ -75,7 +66,9 @@ final class DriverTests: XCTestCase { func testBinary() throws { if swiftyLLVMMandatoryPassesCrash { return } - let result = try compile(["--emit", "binary"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--emit", "binary"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") @@ -86,13 +79,27 @@ final class DriverTests: XCTestCase { #endif } + func testFreestanding() throws { + let source = try FileManager.default.temporaryFile( + containing: "public fun main() { print(0) }") + let result = try Driver.compileToTemporary(source, withOptions: ["--freestanding"]) + XCTAssertFalse(result.status.isSuccess) + result.checkDiagnosticText( + is: """ + \(source.relativePath):1.21-26: error: undefined name 'print' in this scope + public fun main() { print(0) } + ~~~~~ + + """) + } + func testParseFailure() throws { - let valSource = try newFile(containing: "fun x") - let result = try compile([], valSource) + let source = try FileManager.default.temporaryFile(containing: "fun x") + let result = try Driver.compileToTemporary(source, withOptions: []) XCTAssertFalse(result.status.isSuccess) result.checkDiagnosticText( is: """ - \(valSource.relativePath):1.6: error: expected function signature + \(source.relativePath):1.6: error: expected function signature fun x ^ @@ -100,19 +107,21 @@ final class DriverTests: XCTestCase { } func testTypeCheckSuccess() throws { - let result = try compile(["--typecheck"], newFile(containing: "public fun main() {}")) + let result = try Driver.compileToTemporary( + FileManager.default.temporaryFile(containing: "public fun main() {}"), + withOptions: ["--typecheck"]) XCTAssert(result.status.isSuccess) result.checkDiagnosticText(is: "") XCTAssertFalse(FileManager.default.fileExists(atPath: result.output.relativePath)) } func testTypeCheckFailure() throws { - let valSource = try newFile(containing: "public fun main() { foo() }") - let result = try compile(["--typecheck"], valSource) + let source = try FileManager.default.temporaryFile(containing: "public fun main() { foo() }") + let result = try Driver.compileToTemporary(source, withOptions: ["--typecheck"]) XCTAssertFalse(result.status.isSuccess) result.checkDiagnosticText( is: """ - \(valSource.relativePath):1.21-24: error: undefined name 'foo' in this scope + \(source.relativePath):1.21-24: error: undefined name 'foo' in this scope public fun main() { foo() } ~~~ @@ -120,29 +129,17 @@ final class DriverTests: XCTestCase { XCTAssertFalse(FileManager.default.fileExists(atPath: result.output.relativePath)) } - /// Writes `s` to a temporary file and returns its URL. - private func newFile(containing s: String) throws -> URL { - let f = FileManager.default.makeTemporaryFileURL() - try s.write(to: f, atomically: true, encoding: .utf8) - return f - } +} + +extension Driver.CompilationResult { - /// Compiles `input` with the given command line arguments and returns the compiler's exit - /// status, the URL of the output file, and the contents of the standard error. - private func compile( - _ arguments: [String], - _ input: URL - ) throws -> CompilationResult { - // Create a temporary output. - let output = FileManager.default.makeTemporaryFileURL() - - // Parse the command line's arguments. - let cli = try Driver.parse(arguments + ["-o", output.relativePath, input.relativePath]) - - // Execute the command. - let (status, diagnostics) = try cli.execute() - return CompilationResult( - status: status, output: output, diagnosticText: diagnostics.rendered()) + /// `XCTAssert`'s that `diagnosticText` matches `expectedDiagnostics`, with better output + /// formatting on failure. + func checkDiagnosticText( + `is` expectedDiagnostics: String, file: StaticString = #filePath, line: UInt = #line + ) { + let observedDiagnostics = diagnostics.rendered() + XCTAssertEqual("\n" + observedDiagnostics, "\n" + expectedDiagnostics, file: file, line: line) } } diff --git a/Tests/EndToEndTests/ExecutionTests.swift b/Tests/EndToEndTests/ExecutionTests.swift index c611dd068..1f648f5a4 100644 --- a/Tests/EndToEndTests/ExecutionTests.swift +++ b/Tests/EndToEndTests/ExecutionTests.swift @@ -1,3 +1,4 @@ +import Driver import TestUtils import XCTest @@ -6,18 +7,19 @@ final class ExecutionTests: XCTestCase { func testHelloWorld() throws { if swiftyLLVMMandatoryPassesCrash { return } - let f = FileManager.default.makeTemporaryFileURL() - let s = #"public fun main() { print("Hello, World!") }"# - try s.write(to: f, atomically: true, encoding: .utf8) + let source = try FileManager.default.temporaryFile( + containing: #"public fun main() { print("Hello, World!") }"#) - let executable = try compile(f, with: ["--emit", "binary", "-o", "hello"]) + let compilation = try Driver.compileToTemporary( + source, withOptions: ["--emit", "binary", "-o", "hello"]) + try compilation.diagnostics.throwOnError() func runAndCheckOutput() throws { - let outputText = try Process.run(executable).standardOutput[] + let output = try Process.run(compilation.output).standardOutput[] // Remember, Windows has a different newline character - XCTAssert(outputText.last?.isNewline ?? false, "Expected a final newline") - XCTAssertEqual(outputText.dropLast(), "Hello, World!") + XCTAssert(output.last?.isNewline ?? false, "Expected a final newline") + XCTAssertEqual(output.dropLast(), "Hello, World!") } XCTAssertNoThrow(try runAndCheckOutput()) diff --git a/Tests/EndToEndTests/TestCases/AddressOf.hylo b/Tests/EndToEndTests/TestCases/AddressOf.hylo index 2591143d5..d15d26eee 100644 --- a/Tests/EndToEndTests/TestCases/AddressOf.hylo +++ b/Tests/EndToEndTests/TestCases/AddressOf.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { let x = 42 diff --git a/Tests/EndToEndTests/TestCases/Autoclosure.hylo b/Tests/EndToEndTests/TestCases/Autoclosure.hylo index b5a47aaea..c957e266b 100644 --- a/Tests/EndToEndTests/TestCases/Autoclosure.hylo +++ b/Tests/EndToEndTests/TestCases/Autoclosure.hylo @@ -1,48 +1,32 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success -let counter: Int = 0 +let log: PointerToMutable = .allocate(count: 1) fun prime() -> Int { - &counter += 1 + &(log.copy()).unsafe[] += 1 return 17 } -fun logic( - with_delayed value: @autoclosure []() -> Int, - counter_starting_at s: Int, +fun run( + lazily_evaluating value: @autoclosure [E]() -> Int, + starting_at s: Int, ending_at e: Int ) { - precondition(counter == s) + precondition(log.unsafe[] == s) let x = value() - precondition(counter == e) - precondition(x == 17) -} - -fun logic2(with_delayed value: @autoclosure [E]() -> Int) { - precondition(counter == 0) - let x = value() - precondition(counter == 1) + precondition(log.unsafe[] == e) precondition(x == 17) } public fun main() { - &counter = 0 + _ = log.unsafe_initialize_pointee(fun (_ a: set Int) -> Void { &a = 0 }) - // Test with function call. - logic(with_delayed: prime(), counter_starting_at: 0, ending_at: 1) - - // Test with constant. - logic(with_delayed: 17, counter_starting_at: 1, ending_at: 1) - - &counter = 0 - // Test with generics. - logic2(with_delayed: prime()) + // Lazy parameter without captures. + run(lazily_evaluating: prime(), starting_at: 0, ending_at: 1) + run(lazily_evaluating: 17, starting_at: 1, ending_at: 1) + run(lazily_evaluating: prime(), starting_at: 1, ending_at: 2) // TODO: Test with generics and non-void environments. - // &counter = 0 - // let r = 17 - // logic2(with_delayed: fun() -> Int { - // &counter += 1 - // return r - // }) + + log.copy().deallocate() } diff --git a/Tests/EndToEndTests/TestCases/BoundParameter.hylo b/Tests/EndToEndTests/TestCases/BoundParameter.hylo index 77a5e2507..2d71eeee8 100644 --- a/Tests/EndToEndTests/TestCases/BoundParameter.hylo +++ b/Tests/EndToEndTests/TestCases/BoundParameter.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success trait P { fun foo() -> Int } diff --git a/Tests/EndToEndTests/TestCases/Break.hylo b/Tests/EndToEndTests/TestCases/Break.hylo index 5f4303d5e..718332f83 100644 --- a/Tests/EndToEndTests/TestCases/Break.hylo +++ b/Tests/EndToEndTests/TestCases/Break.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { // Break from a do-while loop. diff --git a/Tests/EndToEndTests/TestCases/BufferInitialization.hylo b/Tests/EndToEndTests/TestCases/BufferInitialization.hylo index bd06a178c..94880d953 100644 --- a/Tests/EndToEndTests/TestCases/BufferInitialization.hylo +++ b/Tests/EndToEndTests/TestCases/BufferInitialization.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun use(_ x: Bool[2]) {} diff --git a/Tests/EndToEndTests/TestCases/Concurrency/concurrent_greeting.hylo b/Tests/EndToEndTests/TestCases/Concurrency/concurrent_greeting.hylo index 26746a911..a4ecb29fb 100644 --- a/Tests/EndToEndTests/TestCases/Concurrency/concurrent_greeting.hylo +++ b/Tests/EndToEndTests/TestCases/Concurrency/concurrent_greeting.hylo @@ -1,4 +1,4 @@ -//- compileToLLVM expecting: success +//- compileToLLVM expecting: .success fun do_greet() -> Int { print("Hello, concurrent world!") diff --git a/Tests/EndToEndTests/TestCases/Concurrency/concurrent_sort.hylo b/Tests/EndToEndTests/TestCases/Concurrency/concurrent_sort.hylo index cf52b486d..137c0f70d 100644 --- a/Tests/EndToEndTests/TestCases/Concurrency/concurrent_sort.hylo +++ b/Tests/EndToEndTests/TestCases/Concurrency/concurrent_sort.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success let size_threshold = 16 @@ -15,7 +15,7 @@ fun my_concurrent_sort(_ a: inout ArraySlice Int { - inout rhs = &(q.copy().unsafe[]) + inout rhs = &(q.copy()).unsafe[] return my_concurrent_sort(&rhs) }) @@ -112,6 +112,7 @@ type ArraySlice : Deinitializable, Movable { &self.start_index = full_array.start_position() &self.end_index = full_array.end_position() } + /// Initializes `self` to represent elements [`start`, `end`) of `full_array`. public init(source: Self, from start: Int, to end: Int) { precondition(start >= 0 && start <= end && end <= source.count()) @@ -126,9 +127,9 @@ type ArraySlice : Deinitializable, Movable { } /// Sorts the elements in `self`. - public fun sort() { + public fun sort() inout { // Use bubble sort for simplicity. - inout elements = origin.unsafe[] + inout elements = &origin.unsafe[] do { var swapped = false var i = start_index.copy() @@ -137,7 +138,7 @@ type ArraySlice : Deinitializable, Movable { &elements.swap_at(i, i + 1) &swapped = true } - i += 1 + &i += 1 } } while swapped } @@ -149,8 +150,8 @@ type ArraySlice : Deinitializable, Movable { } /// Partitions the slice in 3 parts: one with elements lower than `mid_value`, one with elements equal to `mid_value` and one with elements greater than `mid_value`, returning the indices that separates these parts. - public fun partition(on mid_value: Element) -> {Int, Int} { - inout elements = origin.unsafe[] + public fun partition(on mid_value: Element) inout -> {Int, Int} { + inout elements = &origin.unsafe[] // First pass to move elements smaller than mid_value to the left. var i = start_index.copy() var j = end_index.copy() @@ -182,7 +183,7 @@ type ArraySlice : Deinitializable, Movable { /// Returns a slice of `self` from `start` to `end` (relative indices). public fun slice(from start: Int, to end: Int) -> Self { precondition(0 <= start && start <= end && end <= count()) - let r: Self = .new(full_array: &origin.unsafe[]) + let r: Self = .new(full_array: &(origin.copy()).unsafe[]) &r.start_index = start_index + start &r.end_index = start_index + end return r diff --git a/Tests/EndToEndTests/TestCases/Concurrency/escaping_spawn.hylo b/Tests/EndToEndTests/TestCases/Concurrency/escaping_spawn.hylo index 61866fa5e..7db1c6583 100644 --- a/Tests/EndToEndTests/TestCases/Concurrency/escaping_spawn.hylo +++ b/Tests/EndToEndTests/TestCases/Concurrency/escaping_spawn.hylo @@ -1,4 +1,4 @@ -//- compileToLLVM expecting: success +//- compileToLLVM expecting: .success // TODO: type-erasure for the future type fun do_spawn() -> EscapingFuture<{}> { diff --git a/Tests/EndToEndTests/TestCases/Concurrency/mutating_spawn.hylo b/Tests/EndToEndTests/TestCases/Concurrency/mutating_spawn.hylo index e87e2c8b9..f6a8f1958 100644 --- a/Tests/EndToEndTests/TestCases/Concurrency/mutating_spawn.hylo +++ b/Tests/EndToEndTests/TestCases/Concurrency/mutating_spawn.hylo @@ -1,4 +1,4 @@ -//- compileToLLVM expecting: success +//- compileToLLVM expecting: .success fun test_mutating_spawn() -> Int { @@ -6,7 +6,7 @@ fun test_mutating_spawn() -> Int { let p = mutable_pointer[to: &local_variable] var future = spawn_(fun[sink let q=p.copy()] () -> Int { - &(q.copy().unsafe[]) = 19 + &(q.copy()).unsafe[] = 19 return 1 }) let y = future.await() diff --git a/Tests/EndToEndTests/TestCases/ConditionalBinding.hylo b/Tests/EndToEndTests/TestCases/ConditionalBinding.hylo new file mode 100644 index 000000000..d63dcb6ba --- /dev/null +++ b/Tests/EndToEndTests/TestCases/ConditionalBinding.hylo @@ -0,0 +1,11 @@ +//- compileAndRun expecting: .success + +public fun main() { + var x: Union = true as _ + + let y: Bool = x else { fatal_error() } + precondition(y) + + let _: Int = x else { return } + fatal_error() +} diff --git a/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo b/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo index a019896cf..aa9f7839a 100644 --- a/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo +++ b/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun use(_ x: T) {} @@ -169,6 +169,30 @@ fun testNot() { use(b) } +fun testMultipleOperandConditions() { + var n = 0 + + #if compiler_version(>= 0.0) + &n += 1 + #else if true + + #endif + + #if os(macOS) + &n += 1 + #elseif os(macOS) && !true + &n += 1 + #elseif os(macOS) && false || !true || false + &n += 1 + #elseif os(macOS) || compiler_version(< 1.0) + &n += 1 + #else + &n += 1 + #endif + + use(n) +} + public fun main() { testTrue() testFalse() @@ -180,4 +204,5 @@ public fun main() { testHyloVersion() testSkipParsingNested() testNot() + testMultipleOperandConditions() } diff --git a/Tests/EndToEndTests/TestCases/CustomMove.hylo b/Tests/EndToEndTests/TestCases/CustomMove.hylo index f6d77d101..342a8b877 100644 --- a/Tests/EndToEndTests/TestCases/CustomMove.hylo +++ b/Tests/EndToEndTests/TestCases/CustomMove.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Deinitializable { public var witness: Int diff --git a/Tests/EndToEndTests/TestCases/DefaultImplementation.hylo b/Tests/EndToEndTests/TestCases/DefaultImplementation.hylo index 76510a576..3bf8655c3 100644 --- a/Tests/EndToEndTests/TestCases/DefaultImplementation.hylo +++ b/Tests/EndToEndTests/TestCases/DefaultImplementation.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public trait Eq { fun infix== (_ other: Self) -> Bool diff --git a/Tests/EndToEndTests/TestCases/Destroy.hylo b/Tests/EndToEndTests/TestCases/Destroy.hylo index 356966ad5..442a1ed4b 100644 --- a/Tests/EndToEndTests/TestCases/Destroy.hylo +++ b/Tests/EndToEndTests/TestCases/Destroy.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { _ = 42 diff --git a/Tests/EndToEndTests/TestCases/Destructure.hylo b/Tests/EndToEndTests/TestCases/Destructure.hylo index 9682fae69..8c21e5de9 100644 --- a/Tests/EndToEndTests/TestCases/Destructure.hylo +++ b/Tests/EndToEndTests/TestCases/Destructure.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun triplet() -> {Int, Int, Int} { (1, 2, 3) diff --git a/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo b/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo index eb1c39873..7375cec0d 100644 --- a/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo +++ b/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { _ = f() diff --git a/Tests/EndToEndTests/TestCases/Existential.hylo b/Tests/EndToEndTests/TestCases/Existential.hylo index edaf769ad..467c71d21 100644 --- a/Tests/EndToEndTests/TestCases/Existential.hylo +++ b/Tests/EndToEndTests/TestCases/Existential.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun use(_ x: T) {} diff --git a/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo b/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo index 35d28e3b7..7ceea3a98 100644 --- a/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo +++ b/Tests/EndToEndTests/TestCases/ExplicitCaptures.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun apply(_ f: [E]() -> Int) -> Int { f() @@ -8,7 +8,7 @@ public fun main() { var local_variable = 0 let p = mutable_pointer[to: &local_variable] let n = apply(fun[sink let q = p.copy()]() -> Int { - &(q.unsafe[]) = 19 + &(q.copy()).unsafe[] = 19 return 19 }) precondition(n == local_variable) diff --git a/Tests/EndToEndTests/TestCases/Factorial.hylo b/Tests/EndToEndTests/TestCases/Factorial.hylo index 15397e219..f3d8fbe2e 100644 --- a/Tests/EndToEndTests/TestCases/Factorial.hylo +++ b/Tests/EndToEndTests/TestCases/Factorial.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun factorial(_ n: Int) -> Int { if n < 2 { 1 } else { n * factorial(n - 1) } diff --git a/Tests/EndToEndTests/TestCases/Fibonacci.hylo b/Tests/EndToEndTests/TestCases/Fibonacci.hylo index 292e3453b..55813c6ed 100644 --- a/Tests/EndToEndTests/TestCases/Fibonacci.hylo +++ b/Tests/EndToEndTests/TestCases/Fibonacci.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun fibonacci(_ n: Int) -> Int { precondition(n >= 0, "domain error") diff --git a/Tests/EndToEndTests/TestCases/For.hylo b/Tests/EndToEndTests/TestCases/For.hylo new file mode 100644 index 000000000..b2c573a42 --- /dev/null +++ b/Tests/EndToEndTests/TestCases/For.hylo @@ -0,0 +1,7 @@ +//- compileAndRun expecting: .success + +public fun main() { + var x = 0 + for sink let i in 0 ..< 3 { &x = i } + precondition(x == 2) +} diff --git a/Tests/EndToEndTests/TestCases/GenericValueParameter.hylo b/Tests/EndToEndTests/TestCases/GenericValueParameter.hylo index bd5077f80..5f8758adf 100644 --- a/Tests/EndToEndTests/TestCases/GenericValueParameter.hylo +++ b/Tests/EndToEndTests/TestCases/GenericValueParameter.hylo @@ -1,6 +1,6 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success -public fun foo() -> Int { +public fun foo() -> Int { n.copy() } diff --git a/Tests/EndToEndTests/TestCases/Geometry.hylo b/Tests/EndToEndTests/TestCases/Geometry.hylo index d5a5018f7..46f78c121 100644 --- a/Tests/EndToEndTests/TestCases/Geometry.hylo +++ b/Tests/EndToEndTests/TestCases/Geometry.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success /// The space between two intersecting lines. public type Angle: Deinitializable { diff --git a/Tests/EndToEndTests/TestCases/GlobalBinding.hylo b/Tests/EndToEndTests/TestCases/GlobalBinding.hylo index 4d4d2a94c..da591c72f 100644 --- a/Tests/EndToEndTests/TestCases/GlobalBinding.hylo +++ b/Tests/EndToEndTests/TestCases/GlobalBinding.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { precondition((x + y) == 42) diff --git a/Tests/EndToEndTests/TestCases/ImperativeSum.hylo b/Tests/EndToEndTests/TestCases/ImperativeSum.hylo index 6afe6ff7f..380c80e37 100644 --- a/Tests/EndToEndTests/TestCases/ImperativeSum.hylo +++ b/Tests/EndToEndTests/TestCases/ImperativeSum.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { var a = Array() diff --git a/Tests/EndToEndTests/TestCases/Implicits.hylo b/Tests/EndToEndTests/TestCases/Implicits.hylo index f1095e4de..023008861 100644 --- a/Tests/EndToEndTests/TestCases/Implicits.hylo +++ b/Tests/EndToEndTests/TestCases/Implicits.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun foo(x?: let Int) -> Int { bar() } diff --git a/Tests/EndToEndTests/TestCases/Lambda.hylo b/Tests/EndToEndTests/TestCases/Lambda.hylo index caae98f05..c2f20053d 100644 --- a/Tests/EndToEndTests/TestCases/Lambda.hylo +++ b/Tests/EndToEndTests/TestCases/Lambda.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun apply(_ f: inout ([E]() inout -> Int)) -> Int { &f() @@ -35,7 +35,7 @@ public fun main() { var z = 0 let p = mutable_pointer[to: &z] var f = fun() -> Int { - &(p.copy().unsafe[]) = 42 + &(p.copy()).unsafe[] = 42 return p.unsafe[].copy() } precondition(f() == 42) diff --git a/Tests/EndToEndTests/TestCases/LocalInout.hylo b/Tests/EndToEndTests/TestCases/LocalInout.hylo index 5d2042bb2..b781aab6f 100644 --- a/Tests/EndToEndTests/TestCases/LocalInout.hylo +++ b/Tests/EndToEndTests/TestCases/LocalInout.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success extension Int { diff --git a/Tests/EndToEndTests/TestCases/MemoryLayout.hylo b/Tests/EndToEndTests/TestCases/MemoryLayout.hylo index a47eedb70..7b2d152d8 100644 --- a/Tests/EndToEndTests/TestCases/MemoryLayout.hylo +++ b/Tests/EndToEndTests/TestCases/MemoryLayout.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type Vector2 { diff --git a/Tests/EndToEndTests/TestCases/MethodBundle.hylo b/Tests/EndToEndTests/TestCases/MethodBundle.hylo index 4ec9c0a89..80cb2c708 100644 --- a/Tests/EndToEndTests/TestCases/MethodBundle.hylo +++ b/Tests/EndToEndTests/TestCases/MethodBundle.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Deinitializable { @@ -13,8 +13,27 @@ type A: Deinitializable { } +public type B: Deinitializable { + + public let x: Int + + public memberwise init + + public fun foo(_ n: sink Int) { + let { B(x: n) } + inout { &self.x = n } + } + +} + public fun main() { var a = A(x: 42) precondition(a.foo().1 == 42) precondition(a.foo().1 == 42) + + var b0 = B(x: 1) + let b1 = b0.foo(2) + precondition(b1.x == 2) + &b0.foo(3) + precondition(b0.x == 3) } diff --git a/Tests/EndToEndTests/TestCases/Monomorphize.hylo b/Tests/EndToEndTests/TestCases/Monomorphize.hylo index 1a3a4e2a1..c985de7ff 100644 --- a/Tests/EndToEndTests/TestCases/Monomorphize.hylo +++ b/Tests/EndToEndTests/TestCases/Monomorphize.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun poly(_ x: T) -> Int { 0 } diff --git a/Tests/EndToEndTests/TestCases/MonomorphizeRequirements.hylo b/Tests/EndToEndTests/TestCases/MonomorphizeRequirements.hylo index 6407672e5..1b2c2a1aa 100644 --- a/Tests/EndToEndTests/TestCases/MonomorphizeRequirements.hylo +++ b/Tests/EndToEndTests/TestCases/MonomorphizeRequirements.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success trait P { fun f() -> Int diff --git a/Tests/EndToEndTests/TestCases/MonomorphizeSynthesizedImplementation.hylo b/Tests/EndToEndTests/TestCases/MonomorphizeSynthesizedImplementation.hylo index 3c9920f1c..3e2b89d41 100644 --- a/Tests/EndToEndTests/TestCases/MonomorphizeSynthesizedImplementation.hylo +++ b/Tests/EndToEndTests/TestCases/MonomorphizeSynthesizedImplementation.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success trait A { type P: Regular diff --git a/Tests/EndToEndTests/TestCases/NestedGeneric.hylo b/Tests/EndToEndTests/TestCases/NestedGeneric.hylo index 6d2f85d21..aaf56de86 100644 --- a/Tests/EndToEndTests/TestCases/NestedGeneric.hylo +++ b/Tests/EndToEndTests/TestCases/NestedGeneric.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Deinitializable { public let x: Int diff --git a/Tests/EndToEndTests/TestCases/Pointer.hylo b/Tests/EndToEndTests/TestCases/Pointer.hylo index 6c2e9d9da..0da8ec639 100644 --- a/Tests/EndToEndTests/TestCases/Pointer.hylo +++ b/Tests/EndToEndTests/TestCases/Pointer.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { let q = PointerToMutable.allocate(count: 1) diff --git a/Tests/EndToEndTests/TestCases/Pragma.hylo b/Tests/EndToEndTests/TestCases/Pragma.hylo index 0700f4f4c..a0ed78bb3 100644 --- a/Tests/EndToEndTests/TestCases/Pragma.hylo +++ b/Tests/EndToEndTests/TestCases/Pragma.hylo @@ -1,11 +1,11 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun f(_ f: sink Int = #line) -> Int { f } fun g() { var i = true while i { - f() + _ = f() &i = false } } diff --git a/Tests/EndToEndTests/TestCases/Puning.hylo b/Tests/EndToEndTests/TestCases/Puning.hylo index 140c2d5fc..269a4c95f 100644 --- a/Tests/EndToEndTests/TestCases/Puning.hylo +++ b/Tests/EndToEndTests/TestCases/Puning.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { let x = 42 diff --git a/Tests/EndToEndTests/TestCases/RemotePart.hylo b/Tests/EndToEndTests/TestCases/RemotePart.hylo index 6c2578765..2e62ab5dc 100644 --- a/Tests/EndToEndTests/TestCases/RemotePart.hylo +++ b/Tests/EndToEndTests/TestCases/RemotePart.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun inefficient_eq(_ x: Int, _ y: Int) { precondition(x - y == 0) diff --git a/Tests/EndToEndTests/TestCases/Subscript.hylo b/Tests/EndToEndTests/TestCases/Subscript.hylo index 71dbd9389..6b26a75fe 100644 --- a/Tests/EndToEndTests/TestCases/Subscript.hylo +++ b/Tests/EndToEndTests/TestCases/Subscript.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Deinitializable { diff --git a/Tests/EndToEndTests/TestCases/SubscriptFree.hylo b/Tests/EndToEndTests/TestCases/SubscriptFree.hylo index 1d235b44a..5614d000c 100644 --- a/Tests/EndToEndTests/TestCases/SubscriptFree.hylo +++ b/Tests/EndToEndTests/TestCases/SubscriptFree.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success subscript foo(): Int { 42 } diff --git a/Tests/EndToEndTests/TestCases/SubscriptRvalue.hylo b/Tests/EndToEndTests/TestCases/SubscriptRvalue.hylo index d00b183d1..a32f2b2fa 100644 --- a/Tests/EndToEndTests/TestCases/SubscriptRvalue.hylo +++ b/Tests/EndToEndTests/TestCases/SubscriptRvalue.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Deinitializable { diff --git a/Tests/EndToEndTests/TestCases/SubscriptWithArgument.hylo b/Tests/EndToEndTests/TestCases/SubscriptWithArgument.hylo index 52c4be9f6..eed4d1dc1 100644 --- a/Tests/EndToEndTests/TestCases/SubscriptWithArgument.hylo +++ b/Tests/EndToEndTests/TestCases/SubscriptWithArgument.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public type A: Deinitializable { diff --git a/Tests/EndToEndTests/TestCases/SyntheticCopy.hylo b/Tests/EndToEndTests/TestCases/SyntheticCopy.hylo index 5f6edffab..aca8ec226 100644 --- a/Tests/EndToEndTests/TestCases/SyntheticCopy.hylo +++ b/Tests/EndToEndTests/TestCases/SyntheticCopy.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success type A: Regular { public var x: Optional @@ -12,11 +12,9 @@ public fun main() { var b = a.copy() &a.y = 1337 - let ax = if let x: Int = a.x { x } else { 0 } - precondition(ax == 42) + precondition(a.x == (42 as _)) precondition(a.y == 1337) - let bx = if let x: Int = b.x { x } else { 0 } - precondition(bx == 42) + precondition(b.x == (42 as _)) precondition(b.y == 1) } diff --git a/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo b/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo new file mode 100644 index 000000000..78408699a --- /dev/null +++ b/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo @@ -0,0 +1,28 @@ +//- compileAndRun expecting: .success + +type A: Regular { + public let x: Int + public let y: Int + public memberwise init +} + +type B: Regular { + public let a: A + public memberwise init +} + +typealias IntOrBool = Union + +conformance IntOrBool: Equatable {} + +public fun main() { + precondition(B(a: A(x: 1, y: 2)) == B(a: A(x: 1, y: 2))) + precondition(B(a: A(x: 1, y: 2)) != B(a: A(x: 2, y: 1))) + + let n: IntOrBool = 42 as _ + let m: IntOrBool = 1337 as _ + let b: IntOrBool = true as _ + precondition(n == n) + precondition(n != m) // inequal payloads + precondition(n != b) // inequal discriminators +} diff --git a/Tests/EndToEndTests/TestCases/TraitExtension.hylo b/Tests/EndToEndTests/TestCases/TraitExtension.hylo index 133b259b3..206aec16f 100644 --- a/Tests/EndToEndTests/TestCases/TraitExtension.hylo +++ b/Tests/EndToEndTests/TestCases/TraitExtension.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success trait Copier { type Original: Copyable diff --git a/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo b/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo index a55697a19..fd3d4af7f 100644 --- a/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo +++ b/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success // This program tests the dispatch behavior of calls to trait members. `P` declares a single // requirement `f` that is given a default implementation. Two conformances to `P` are defined: diff --git a/Tests/EndToEndTests/TestCases/TupleKeyPath.hylo b/Tests/EndToEndTests/TestCases/TupleKeyPath.hylo index 9608a870b..d5936b6c3 100644 --- a/Tests/EndToEndTests/TestCases/TupleKeyPath.hylo +++ b/Tests/EndToEndTests/TestCases/TupleKeyPath.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public type A: Deinitializable, Movable { public memberwise init @@ -7,4 +7,4 @@ public type A: Deinitializable, Movable { public fun main() { let x = (1, (false, A())) precondition(!x.1.0) -} \ No newline at end of file +} diff --git a/Tests/EndToEndTests/TestCases/UnionNarrowing.hylo b/Tests/EndToEndTests/TestCases/UnionNarrowing.hylo index 58e18f45d..1d8b280d2 100644 --- a/Tests/EndToEndTests/TestCases/UnionNarrowing.hylo +++ b/Tests/EndToEndTests/TestCases/UnionNarrowing.hylo @@ -1,4 +1,8 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success + +fun f(_ u: sink Union) -> Bool { + if let _: T = u { true } else { false } +} public fun main() { var x: Union<{a: Bool}, {b: Int}> = (b: 42) @@ -7,4 +11,8 @@ public fun main() { } else { fatal_error() } + + precondition(f(42 as _)) + precondition(f(42 as _)) + precondition(!f(true as _)) } diff --git a/Tests/EndToEndTests/TestCases/UnionOperations.hylo b/Tests/EndToEndTests/TestCases/UnionOperations.hylo index 5db8b6b2e..765b54e29 100644 --- a/Tests/EndToEndTests/TestCases/UnionOperations.hylo +++ b/Tests/EndToEndTests/TestCases/UnionOperations.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun modify(_ x: inout T) {} diff --git a/Tests/HyloTests/ASTTests.swift b/Tests/HyloTests/ASTTests.swift index c93531096..591ee2748 100644 --- a/Tests/HyloTests/ASTTests.swift +++ b/Tests/HyloTests/ASTTests.swift @@ -8,12 +8,15 @@ final class ASTTests: XCTestCase { func testAppendModule() throws { var ast = AST() - let i = checkNoDiagnostic { (d) in - ast.insert(ModuleDecl("Hylo", sources: []), diagnostics: &d) + var log = DiagnosticSet() + let i = ast.loadModule(reportingDiagnosticsTo: &log) { (tree, _, k) in + tree.insert(synthesized: ModuleDecl("A", sources: []), inNodeSpace: k) } XCTAssert(ast.modules.contains(i)) - let j = ast.insert(synthesized: ModuleDecl("Hylo1", sources: [])) + let j = ast.loadModule(reportingDiagnosticsTo: &log) { (tree, _, k) in + tree.insert(synthesized: ModuleDecl("B", sources: []), inNodeSpace: k) + } XCTAssert(ast.modules.contains(j)) } @@ -22,7 +25,7 @@ final class ASTTests: XCTestCase { var a = AST() let m = try checkNoDiagnostic { (d) in - try a.makeModule("Main", sourceCode: [input], diagnostics: &d) + try a.loadModule("Main", parsing: [input], reportingDiagnosticsTo: &d) } // Note: we use `XCTUnwrap` when we're expecting a non-nil value produced by a subscript under @@ -60,7 +63,7 @@ final class ASTTests: XCTestCase { var a = AST() let m = try checkNoDiagnostic { (d) in - try a.makeModule("Main", sourceCode: [input], diagnostics: &d) + try a.loadModule("Main", parsing: [input], reportingDiagnosticsTo: &d) } struct V: ASTWalkObserver { diff --git a/Tests/HyloTests/AccessEffectSetTests.swift b/Tests/HyloTests/AccessEffectSetTests.swift new file mode 100644 index 000000000..66d79dc63 --- /dev/null +++ b/Tests/HyloTests/AccessEffectSetTests.swift @@ -0,0 +1,83 @@ +import FrontEnd +import XCTest + +final class AccessEffectSetTests: XCTestCase { + + func testIsEmpty() { + XCTAssert(AccessEffectSet().elements.isEmpty) + XCTAssertFalse(AccessEffectSet([.let]).elements.isEmpty) + } + + func testContains() { + XCTAssertFalse(AccessEffectSet().contains(.inout)) + XCTAssertFalse(AccessEffectSet([.set]).contains(.inout)) + XCTAssert(AccessEffectSet([.inout]).contains(.inout)) + XCTAssert(AccessEffectSet([.inout, .sink]).contains(.inout)) + } + + func testIsSingleton() { + XCTAssertFalse(AccessEffectSet().isSingleton) + XCTAssertFalse(AccessEffectSet([.let, .set]).isSingleton) + XCTAssert(AccessEffectSet([.let]).isSingleton) + } + + func testUniqueElement() { + XCTAssertNil(AccessEffectSet().uniqueElement) + XCTAssertNil(AccessEffectSet([.let, .set]).uniqueElement) + XCTAssertEqual(AccessEffectSet([.inout]).uniqueElement, .inout) + } + + func testWeakest() { + XCTAssertNil(AccessEffectSet().weakest) + XCTAssertEqual(AccessEffectSet([.inout, .sink]).weakest, .inout) + } + + func testStrongestIncluding() { + XCTAssertEqual(AccessEffectSet().strongest(including: .inout), .inout) + XCTAssertEqual(AccessEffectSet([.inout, .sink]).strongest(including: .let), .sink) + } + + func testInsert() { + var i = false + var k = AccessEffect.let + var s = AccessEffectSet() + + (i, k) = s.insert(.inout) + XCTAssert(i) + XCTAssertEqual(k, .inout) + + (i, k) = s.insert(.sink) + XCTAssert(i) + XCTAssertEqual(k, .sink) + + (i, k) = s.insert(.inout) + XCTAssertFalse(i) + XCTAssertEqual(k, .inout) + } + + func testRemove() { + var s = AccessEffectSet.all + + XCTAssertEqual(s.remove(.inout), .inout) + XCTAssertFalse(s.contains(.inout)) + XCTAssertNil(s.remove(.inout)) + } + + func testUpdate() { + var s = AccessEffectSet() + XCTAssertNil(s.update(with: .inout)) + XCTAssertNil(s.update(with: .sink)) + XCTAssertEqual(s.update(with: .inout), .inout) + } + + func testForUseOfBundle() { + XCTAssertEqual(AccessEffectSet.forUseOfBundle(performingInPlaceMutation: false), .letOrSink) + XCTAssertEqual(AccessEffectSet.forUseOfBundle(performingInPlaceMutation: true), .setOrInout) + } + + func testElements() { + XCTAssert(AccessEffectSet().elements.elementsEqual([])) + XCTAssert(AccessEffectSet.letOrSink.elements.elementsEqual([.let, .sink])) + } + +} diff --git a/Tests/HyloTests/ParserTests.swift b/Tests/HyloTests/ParserTests.swift index 3aec7ed40..5220e42cf 100644 --- a/Tests/HyloTests/ParserTests.swift +++ b/Tests/HyloTests/ParserTests.swift @@ -20,7 +20,7 @@ final class ParserTests: XCTestCase { var a = AST() try checkNoDiagnostic { d in - _ = try a.makeModule("Main", sourceCode: [input], diagnostics: &d) + _ = try a.loadModule("Main", parsing: [input], reportingDiagnosticsTo: &d) } } @@ -39,7 +39,7 @@ final class ParserTests: XCTestCase { var a = AST() let m = try checkNoDiagnostic { d in - try a.makeModule("Main", sourceCode: [input], diagnostics: &d) + try a.loadModule("Main", parsing: [input], reportingDiagnosticsTo: &d) } XCTAssertEqual(a[a[m].sources.first!].decls.count, 4) } @@ -837,6 +837,22 @@ final class ParserTests: XCTestCase { XCTAssertEqual(decl.baseName, "T") } + func testGenericParameterWithTypeIntroducer() throws { + let input: SourceFile = "type T" + let (d, a) = try apply(Parser.genericParameter, on: input) + let n = try XCTUnwrap(a[d]) + XCTAssertEqual(n.baseName, "T") + XCTAssertEqual(n.introducer?.value, .type) + } + + func testGenericParameterWithValueIntroducer() throws { + let input: SourceFile = "value T" + let (d, a) = try apply(Parser.genericParameter, on: input) + let n = try XCTUnwrap(a[d]) + XCTAssertEqual(n.baseName, "T") + XCTAssertEqual(n.introducer?.value, .value) + } + func testGenericParameterWithConformances() throws { let input: SourceFile = "T: Foo & Bar" let (declID, ast) = try apply(Parser.genericParameter, on: input) @@ -1442,19 +1458,9 @@ final class ParserTests: XCTestCase { } } - // func testWhereClauseValueConstraintSansHint() throws { - // let input: SourceFile = "x > 2" - // let constraint = try XCTUnwrap(try apply(Parser.valueConstraint, on: input).element) - // if case .value(let exprID) = constraint.value { - // XCTAssertEqual(exprID.kind, .init(SequenceExpr.self)) - // } else { - // XCTFail() - // } - // } - - func testTraitComposition() throws { + func testBoundComposition() throws { let input: SourceFile = "T & U & V" - let list = try XCTUnwrap(try apply(Parser.traitComposition, on: input).element) + let list = try XCTUnwrap(try apply(Parser.boundComposition, on: input).element) XCTAssertEqual(list.count, 3) } @@ -1504,6 +1510,14 @@ final class ParserTests: XCTestCase { XCTAssertEqual(ast[pattern.decl].baseName, "foo") } + func testOptionPattern() throws { + let input: SourceFile = "let foo?" + let (p, ast) = try input.parse(with: Parser.parseBindingPattern(in:)) + let b = try XCTUnwrap(p) + let o = try XCTUnwrap(OptionPattern.ID(ast[b].subpattern)) + XCTAssertEqual(ast[ast[ast[o].name].decl].baseName, "foo") + } + func testTuplePattern() throws { let input: SourceFile = "()" let (patternID, ast) = try apply(Parser.tuplePattern, on: input) @@ -1630,39 +1644,11 @@ final class ParserTests: XCTestCase { } func testConditionalBinding() throws { - let input: SourceFile = "var x = foo() else return" - let (s, ast) = try apply(Parser.bindingStmt, on: input, context: .functionBody) - let stmt = try XCTUnwrap(ast[s.flatMap(CondBindingStmt.ID.init)]) - if case .exit = stmt.fallback { - } else { - XCTFail() - } - } - - func testConditionalBindingBlock() throws { - let input: SourceFile = "var x = foo() else { bar(); return }" - let (s, ast) = try apply(Parser.bindingStmt, on: input, context: .functionBody) - let stmt = try XCTUnwrap(ast[s.flatMap(CondBindingStmt.ID.init)]) - if case .exit = stmt.fallback { - } else { - XCTFail() - } - } - - func testConditionalBindingExpr() throws { - let input: SourceFile = "var x = foo() else fatal_error()" - let (s, ast) = try apply(Parser.bindingStmt, on: input, context: .functionBody) - let stmt = try XCTUnwrap(ast[s.flatMap(CondBindingStmt.ID.init)]) - if case .expr = stmt.fallback { - } else { - XCTFail() - } - } - - func testConditionalBindingFallback() throws { - let input: SourceFile = "return" - XCTAssertNotNil( - try apply(Parser.conditionalBindingFallbackStmt, on: input, context: .functionBody)) + let input: SourceFile = "var x = foo() else { return }" + let (s, a) = try apply(Parser.bindingStmt, on: input, context: .functionBody) + let n = try XCTUnwrap(a[s.flatMap(ConditionalBindingStmt.ID.init(_:))]) + let m = try XCTUnwrap(a[n.fallback].stmts.first) + XCTAssert(m.kind == ReturnStmt.self) } func testDeclStmt() throws { @@ -1710,10 +1696,12 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.condition, .operatingSystem("macOs")) XCTAssertEqual(stmt.stmts.count, 1) XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .operatingSystem("Linux")) XCTAssertEqual(stmt2.stmts.count, 1) XCTAssertEqual(stmt2.fallback.count, 1) + let stmt3 = try XCTUnwrap(ast[stmt2.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt3.condition, .operatingSystem("Windows")) XCTAssertEqual(stmt3.stmts.count, 1) @@ -1728,14 +1716,17 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.condition, .architecture("x86_64")) XCTAssertEqual(stmt.stmts.count, 1) XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .architecture("i386")) XCTAssertEqual(stmt2.stmts.count, 1) XCTAssertEqual(stmt2.fallback.count, 1) + let stmt3 = try XCTUnwrap(ast[stmt2.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt3.condition, .architecture("arm64")) XCTAssertEqual(stmt3.stmts.count, 1) XCTAssertEqual(stmt3.fallback.count, 1) + let stmt4 = try XCTUnwrap(ast[stmt3.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt4.condition, .architecture("arm")) XCTAssertEqual(stmt4.stmts.count, 1) @@ -1851,6 +1842,7 @@ final class ParserTests: XCTestCase { .hyloVersion(comparison: .less(SemanticVersion(major: 0, minor: 1, patch: 0)))) XCTAssertEqual(stmt.stmts.count, 0) // Body not parsed XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .operatingSystem("bla")) XCTAssertEqual(stmt2.stmts.count, 0) @@ -1890,6 +1882,7 @@ final class ParserTests: XCTestCase { // all good } } + func testConditionalControlNotOperatorOnFalse() throws { let input: SourceFile = "#if !os(abracadabra) foo() #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1905,6 +1898,7 @@ final class ParserTests: XCTestCase { // We should expand to nothing. XCTAssertEqual(stmt.expansion(for: ConditionalCompilationFactors()).count, 0) } + func testConditionalControlNotNot() throws { let input: SourceFile = "#if ! !true foo() #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1912,6 +1906,7 @@ final class ParserTests: XCTestCase { // We should expand to the body. XCTAssertEqual(stmt.expansion(for: ConditionalCompilationFactors()).count, 1) } + func testConditionalControlSkipParsingAfterNot() throws { let input: SourceFile = "#if !compiler_version(< 0.1) foo() #else #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1920,12 +1915,31 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.fallback.count, 0) // don't parse the #else part } + func testConditionalControlInfix() throws { + let input = SourceFile.diagnosableLiteral( + """ + #if os(macOS) || os(Linux) && hylo_version(< 1.0.0) || os(Windows) + do_something() + #endif + """) + let (stmtID, ast) = try apply(Parser.stmt, on: input) + let stmt = try XCTUnwrap(ast[stmtID] as? ConditionalCompilationStmt) + XCTAssertEqual( + stmt.condition, + .or( + .or( + .operatingSystem("macOS"), + .and( + .operatingSystem("Linux"), + .hyloVersion(comparison: .less(SemanticVersion(major: 1, minor: 0, patch: 0))))), + .operatingSystem("Windows"))) + } + // MARK: Operators func testTakeOperator() throws { let input: SourceFile = "+ & == | < <= > >=" - var context = ParserState( - ast: AST(), lexer: Lexer(tokenizing: input)) + var context = ParserState(ast: AST(), space: 0, lexer: Lexer(tokenizing: input)) XCTAssertEqual(context.takeOperator()?.value, "+") XCTAssertEqual(context.takeOperator()?.value, "&") XCTAssertEqual(context.takeOperator()?.value, "==") @@ -1979,13 +1993,14 @@ extension SourceFile { inContext context: ParserState.Context? = nil, with parser: (inout ParserState) throws -> Element ) rethrows -> (element: Element, ast: AST) { - var state = ParserState(ast: AST(), lexer: Lexer(tokenizing: self)) + var tree = AST() + let k = tree.createNodeSpace() + var s = ParserState(ast: tree, space: k, lexer: Lexer(tokenizing: self)) if let c = context { - state.contexts.append(c) + s.contexts.append(c) } - - let element = try parser(&state) - return (element, state.ast) + let element = try parser(&s) + return (element, s.ast) } /// Parses `self` with `parser`, optionally setting `context` in the parser state. diff --git a/Tests/HyloTests/RequirementPrinterTests.swift b/Tests/HyloTests/RequirementPrinterTests.swift new file mode 100644 index 000000000..bae2a2a0e --- /dev/null +++ b/Tests/HyloTests/RequirementPrinterTests.swift @@ -0,0 +1,30 @@ +import Utils +import XCTest + +@testable import FrontEnd + +final class RequirementPrinterTests: XCTestCase { + + func testShowRequirementSystem() throws { + // This test is merely exercising the code for printing requirement systems, which is meant for + // debugging the type checker. + var d = DiagnosticSet() + let a = try Utils.Host.freestandingLibraryAST.get() + let p = try TypedProgram(annotating: ScopedProgram(a), reportingDiagnosticsTo: &d) + + let m = a.core.movable.decl + let n = a.core.copyable.decl + var s = RequirementSystem() + s.insert( + RequirementRule([.parameterType(a[m].receiver), .trait(m)], [.parameterType(a[m].receiver)]), + orderingTermsWith: { (_, _) in .descending }) + s.insert( + RequirementRule([.parameterType(a[m].receiver)], [.parameterType(a[n].receiver)]), + orderingTermsWith: { (_, _) in .descending }) + + _ = p.describe(s) + _ = p.describe(s.rules.first!) + _ = p.describe(s.rules.first!.lhs) + } + +} diff --git a/Tests/HyloTests/RewritingSystemTests.swift b/Tests/HyloTests/RewritingSystemTests.swift new file mode 100644 index 000000000..61bc20443 --- /dev/null +++ b/Tests/HyloTests/RewritingSystemTests.swift @@ -0,0 +1,151 @@ +import Utils +import XCTest + +@testable import FrontEnd + +final class RewritingSystemTests: XCTestCase { + + func testInsert() { + var s = RewritingSystem() + let r1 = s.insert(.init("abcd", "ab")) + XCTAssert(r1.inserted) + XCTAssertEqual(s.rules[r1.ruleAfterInsertion], .init("abcd", "ab")) + + // Insertion is idempotent. + let r2 = s.insert(.init("abcd", "ab")) + XCTAssertFalse(r2.inserted) + XCTAssertEqual(r2.ruleAfterInsertion, r1.ruleAfterInsertion) + + // Inserting a "less direct" rule inserts an intermediate step. + let r3 = s.insert(.init("abcd", "abc")) + XCTAssert(r3.inserted) + XCTAssertEqual(s.rules[r3.ruleAfterInsertion], .init("abc", "ab")) + + // Inserting a "more direct" rule triggers right simplification. + let r4 = s.insert(.init("abcd", "a")) + XCTAssert(r4.inserted) + XCTAssert(s.rules[r1.ruleAfterInsertion].flags.contains(.isRightSimplified)) + } + + func testReduce() { + var s = RewritingSystem() + s.insert(.init("ab", "a")) + s.insert(.init("cd", "ab")) + XCTAssertEqual(s.reduce("abcd"), "aa") + } + + func testCompletion() { + var s = RewritingSystem() + s.insert(.init("12", "1")) + s.insert(.init("21", "2")) + + // Knuth-Bendix succeeds. + XCTAssert(s.complete(orderingTermsWith: StrictOrdering.init)) + XCTAssert(s.rules.contains(.init("11", "1"))) + XCTAssert(s.rules.contains(.init("22", "2"))) + } + + func testNonTerminatingCompletion() { + var s = RewritingSystem() + s.insert(.init("12", "1")) + s.insert(.init("21", "2")) + s.insert(.init("31", "13")) + + // Knuth-Bendix fails. + XCTAssertFalse(s.complete(orderingTermsWith: StrictOrdering.init)) + } + + func testLeftSimplification() { + var s = RewritingSystem() + let (_, r1) = s.insert(.init("abcabc", "abc")) + s.insert(.init("c", "b")) + let (_, r2) = s.insert(.init("abc", "a")) + XCTAssert(s.complete(orderingTermsWith: StrictOrdering.init)) + XCTAssert(s.rules[r1].flags.contains(.isLeftSimplified)) + XCTAssert(s.rules[r2].flags.contains(.isLeftSimplified)) + } + +} + +/// A term made of UTF8 code units. +struct Term: Hashable { + + /// The symbols in the term. + let symbols: [UTF8.CodeUnit] + + /// Creates an instance with the value of `s`. + init(_ s: String) { + symbols = .init(s.utf8) + } + +} + +extension Term: ExpressibleByStringLiteral { + + init(stringLiteral s: String) { + symbols = .init(s.utf8) + } + +} + +extension Term: Comparable { + + static func < (lhs: Term, rhs: Term) -> Bool { + lhs.symbols.lexicographicallyPrecedes(rhs.symbols) + } + +} + +extension Term: RewritingTerm { + + init>(_ s: S) { + symbols = .init(s) + } + + func substituting(_ s: Self, for t: Self) -> Self { + .init(symbols.replacing(s.symbols, with: t.symbols)) + } + + static func + (u: Self, v: Self) -> Self { + .init(u.symbols + v.symbols) + } + + static func + >(u: Self, v: S) -> Self { + .init(u.symbols + v) + } + + static func + >(u: S, v: Self) -> Self { + .init(u + v.symbols) + } + +} + +extension Term: Collection { + + typealias Element = UTF8.CodeUnit + + typealias Index = Int + + var startIndex: Int { 0 } + + var endIndex: Int { symbols.count } + + func index(after i: Int) -> Int { i + 1 } + + subscript(p: Int) -> UTF8.CodeUnit { symbols[p] } + +} + +extension Term: CustomStringConvertible { + var description: String { String(bytes: symbols, encoding: .utf8)! } +} + +extension RewritingSystem where Term: Comparable { + + /// Inserts the given rule. + @discardableResult + mutating func insert(_ r: RewritingRule) -> (inserted: Bool, ruleAfterInsertion: RuleID) { + insert(r, orderingTermsWith: StrictOrdering.init) + } + +} diff --git a/Tests/HyloTests/TestCases/Lowering/Assignment.hylo b/Tests/HyloTests/TestCases/Lowering/Assignment.hylo index e84e6212b..82aa8e604 100644 --- a/Tests/HyloTests/TestCases/Lowering/Assignment.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Assignment.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure public fun main() { var x = 0 diff --git a/Tests/HyloTests/TestCases/Lowering/BindingEscape.hylo b/Tests/HyloTests/TestCases/Lowering/BindingEscape.hylo index 2be7ccf03..b96c5f696 100644 --- a/Tests/HyloTests/TestCases/Lowering/BindingEscape.hylo +++ b/Tests/HyloTests/TestCases/Lowering/BindingEscape.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun f() -> Int { let x = 0 diff --git a/Tests/HyloTests/TestCases/Lowering/BuiltinFunction.hylo b/Tests/HyloTests/TestCases/Lowering/BuiltinFunction.hylo index 8aaa68a47..e12b5d890 100644 --- a/Tests/HyloTests/TestCases/Lowering/BuiltinFunction.hylo +++ b/Tests/HyloTests/TestCases/Lowering/BuiltinFunction.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success public fun main() { _ = Builtin.zeroinitializer_i64() diff --git a/Tests/HyloTests/TestCases/Lowering/Cast.hylo b/Tests/HyloTests/TestCases/Lowering/Cast.hylo index dd101ace2..85d297292 100644 --- a/Tests/HyloTests/TestCases/Lowering/Cast.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Cast.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success type A: Deinitializable { public memberwise init diff --git a/Tests/HyloTests/TestCases/Lowering/DI.hylo b/Tests/HyloTests/TestCases/Lowering/DI.hylo index 156c1ec41..f33adbc72 100644 --- a/Tests/HyloTests/TestCases/Lowering/DI.hylo +++ b/Tests/HyloTests/TestCases/Lowering/DI.hylo @@ -1,10 +1,10 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun read(_ x: T) {} fun modify(_ x: inout T) {} -fun consume(_ x: sink T) {} +fun consume(_ x: sink T) {} // fun assign(_ x: set T, to y: sink T) { &x = y } diff --git a/Tests/HyloTests/TestCases/Lowering/DefaultArgument.hylo b/Tests/HyloTests/TestCases/Lowering/DefaultArgument.hylo index 42a38c204..650356efc 100644 --- a/Tests/HyloTests/TestCases/Lowering/DefaultArgument.hylo +++ b/Tests/HyloTests/TestCases/Lowering/DefaultArgument.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun f0(x: Int, y: Bool = true) {} diff --git a/Tests/HyloTests/TestCases/Lowering/Deinit.hylo b/Tests/HyloTests/TestCases/Lowering/Deinit.hylo index 27bf366d1..343fabe8b 100644 --- a/Tests/HyloTests/TestCases/Lowering/Deinit.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Deinit.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun use(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo b/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo index 73d82fb9c..0a2eda618 100644 --- a/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo +++ b/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success public type Foo { public init() { } diff --git a/Tests/HyloTests/TestCases/Lowering/Factorial.hylo b/Tests/HyloTests/TestCases/Lowering/Factorial.hylo index 2b1e5deee..13f8ca368 100644 --- a/Tests/HyloTests/TestCases/Lowering/Factorial.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Factorial.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun factorial(_ n: Int) -> Int { if n < 2 { 1 } else { n * factorial(n - 1) } diff --git a/Tests/HyloTests/TestCases/Lowering/Identity.hylo b/Tests/HyloTests/TestCases/Lowering/Identity.hylo index e0cf87bd6..330c8f1fa 100644 --- a/Tests/HyloTests/TestCases/Lowering/Identity.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Identity.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun identity(_ x: sink T) -> T { x diff --git a/Tests/HyloTests/TestCases/Lowering/IllegalMove.hylo b/Tests/HyloTests/TestCases/Lowering/IllegalMove.hylo index fcb96f36b..8fa1d574b 100644 --- a/Tests/HyloTests/TestCases/Lowering/IllegalMove.hylo +++ b/Tests/HyloTests/TestCases/Lowering/IllegalMove.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure // Note that some missing conformances to `Movable` can't be caught before we've emitted refined IR // because must determine whether values can be constructed in place, which typically can't be done diff --git a/Tests/HyloTests/TestCases/Lowering/IllegalMutation.hylo b/Tests/HyloTests/TestCases/Lowering/IllegalMutation.hylo index a2f069b5f..5b6dc6703 100644 --- a/Tests/HyloTests/TestCases/Lowering/IllegalMutation.hylo +++ b/Tests/HyloTests/TestCases/Lowering/IllegalMutation.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun inc(_ x: Int) { //! @+1 diagnostic illegal mutable access diff --git a/Tests/HyloTests/TestCases/Lowering/IllegalPropertyMutation.hylo b/Tests/HyloTests/TestCases/Lowering/IllegalPropertyMutation.hylo new file mode 100644 index 000000000..c2eb35c74 --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/IllegalPropertyMutation.hylo @@ -0,0 +1,19 @@ +//- lowerToFinishedIR expecting: .failure + +type Thing : Deinitializable { + public let position: Int + public let speed: Int + public memberwise init +} + +fun update_physics(_ t: Thing) { +//! @+1 diagnostic illegal mutable access + &t.position += t.speed +} + +public fun main() { + let t = Thing(position: 0, speed: 1) + update_physics(t) + update_physics(t) + print(t.position) +} diff --git a/Tests/HyloTests/TestCases/Lowering/ImplicitReturn.hylo b/Tests/HyloTests/TestCases/Lowering/ImplicitReturn.hylo index 279e96ddb..00e502112 100644 --- a/Tests/HyloTests/TestCases/Lowering/ImplicitReturn.hylo +++ b/Tests/HyloTests/TestCases/Lowering/ImplicitReturn.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun f0() -> Int { if true { 42 } else { 1337 } diff --git a/Tests/HyloTests/TestCases/Lowering/Init.hylo b/Tests/HyloTests/TestCases/Lowering/Init.hylo index 5f91482a1..55eea1e39 100644 --- a/Tests/HyloTests/TestCases/Lowering/Init.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Init.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success type A { var value: Float64 diff --git a/Tests/HyloTests/TestCases/Lowering/InvalidRvalue.hylo b/Tests/HyloTests/TestCases/Lowering/InvalidRvalue.hylo index ed834d690..c58f6487a 100644 --- a/Tests/HyloTests/TestCases/Lowering/InvalidRvalue.hylo +++ b/Tests/HyloTests/TestCases/Lowering/InvalidRvalue.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure subscript s(_ x: Int): Int { let { yield x } diff --git a/Tests/HyloTests/TestCases/Lowering/Loops.hylo b/Tests/HyloTests/TestCases/Lowering/Loops.hylo index 1bcd19746..280026866 100644 --- a/Tests/HyloTests/TestCases/Lowering/Loops.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Loops.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun foo() {} diff --git a/Tests/HyloTests/TestCases/Lowering/MemberLambda.hylo b/Tests/HyloTests/TestCases/Lowering/MemberLambda.hylo index f5d1b34cd..d42e0840d 100644 --- a/Tests/HyloTests/TestCases/Lowering/MemberLambda.hylo +++ b/Tests/HyloTests/TestCases/Lowering/MemberLambda.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success // This test ensures that the emitter is capable of dstinguishing a call to a member method from a // call to a member property with a lambda type. diff --git a/Tests/HyloTests/TestCases/Lowering/Methods.hylo b/Tests/HyloTests/TestCases/Lowering/Methods.hylo index 1b89cff4e..7d666a352 100644 --- a/Tests/HyloTests/TestCases/Lowering/Methods.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Methods.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success extension Int { public fun foo() let -> Int { copy() } diff --git a/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo b/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo new file mode 100644 index 000000000..08ee8f54b --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/MissingDeinit.hylo @@ -0,0 +1,10 @@ +//- lowerToFinishedIR expecting: .failure + +type A { + public memberwise init +} + +//! @+1 diagnostic type 'A' does not conform to trait 'Deinitializable' +fun f(_ a: sink A) {} + +public fun main() { f(A()) } diff --git a/Tests/HyloTests/TestCases/Lowering/Narrowing.hylo b/Tests/HyloTests/TestCases/Lowering/Narrowing.hylo new file mode 100644 index 000000000..84e2ca347 --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/Narrowing.hylo @@ -0,0 +1,18 @@ +//- lowerToFinishedIR expecting: .success + +fun use(_ x: T) {} + +public fun main() { + var x: Optional = 1 as _ + + if let y: Int = x { + // Additional access to `x` is legal. + use(x) + use(y) + } + + if sink let y: Int = x { + // Mutation of `x` is legal since it has been consumed. + &x = .none() + } +} diff --git a/Tests/HyloTests/TestCases/Lowering/NeverReturning.hylo b/Tests/HyloTests/TestCases/Lowering/NeverReturning.hylo index 0b841684a..5521d90ab 100644 --- a/Tests/HyloTests/TestCases/Lowering/NeverReturning.hylo +++ b/Tests/HyloTests/TestCases/Lowering/NeverReturning.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success public fun f() -> Int { _ = 1 diff --git a/Tests/HyloTests/TestCases/Lowering/NumericLiteral.hylo b/Tests/HyloTests/TestCases/Lowering/NumericLiteral.hylo index 88277275d..c095f4c3b 100644 --- a/Tests/HyloTests/TestCases/Lowering/NumericLiteral.hylo +++ b/Tests/HyloTests/TestCases/Lowering/NumericLiteral.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure public fun main() { let _: Int = 1 diff --git a/Tests/HyloTests/TestCases/Lowering/Ownership.hylo b/Tests/HyloTests/TestCases/Lowering/Ownership.hylo index 7c65cc64d..81cf3ce51 100644 --- a/Tests/HyloTests/TestCases/Lowering/Ownership.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Ownership.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun read(_ x: T) {} fun modify(_ x: inout T) {} diff --git a/Tests/HyloTests/TestCases/Lowering/Ownership2.hylo b/Tests/HyloTests/TestCases/Lowering/Ownership2.hylo index eb586ae0e..2275d8fbf 100644 --- a/Tests/HyloTests/TestCases/Lowering/Ownership2.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Ownership2.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure type A: Deinitializable { public var x: Int diff --git a/Tests/HyloTests/TestCases/Lowering/ParameterEscape.hylo b/Tests/HyloTests/TestCases/Lowering/ParameterEscape.hylo index 72e8c6585..db228c82f 100644 --- a/Tests/HyloTests/TestCases/Lowering/ParameterEscape.hylo +++ b/Tests/HyloTests/TestCases/Lowering/ParameterEscape.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun f0(_ x: sink Int) -> Int { x } diff --git a/Tests/HyloTests/TestCases/Lowering/ParameterSet.hylo b/Tests/HyloTests/TestCases/Lowering/ParameterSet.hylo index 6b7fcc337..d742d14d1 100644 --- a/Tests/HyloTests/TestCases/Lowering/ParameterSet.hylo +++ b/Tests/HyloTests/TestCases/Lowering/ParameterSet.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure fun f0(_ x: set Int) { &x = 0 } diff --git a/Tests/HyloTests/TestCases/Lowering/ProjectionEscape.hylo b/Tests/HyloTests/TestCases/Lowering/ProjectionEscape.hylo index eb7d38816..66ff8c838 100644 --- a/Tests/HyloTests/TestCases/Lowering/ProjectionEscape.hylo +++ b/Tests/HyloTests/TestCases/Lowering/ProjectionEscape.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure public type A: Deinitializable { public var guts: Int diff --git a/Tests/HyloTests/TestCases/Lowering/ReturnInSubscript.hylo b/Tests/HyloTests/TestCases/Lowering/ReturnInSubscript.hylo index 4b21ed55f..0ace855cd 100644 --- a/Tests/HyloTests/TestCases/Lowering/ReturnInSubscript.hylo +++ b/Tests/HyloTests/TestCases/Lowering/ReturnInSubscript.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- lowerToFinishedIR expecting: .failure public subscript s(x: Int): Int { //! @+1 diagnostic return statement in subscript diff --git a/Tests/HyloTests/TestCases/Lowering/Sink.hylo b/Tests/HyloTests/TestCases/Lowering/Sink.hylo index 3cb497a11..8d67f4567 100644 --- a/Tests/HyloTests/TestCases/Lowering/Sink.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Sink.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun eat(_ x: sink T) { _ = x diff --git a/Tests/HyloTests/TestCases/Lowering/StructuralConformance.hylo b/Tests/HyloTests/TestCases/Lowering/StructuralConformance.hylo index a9f41201f..53682f774 100644 --- a/Tests/HyloTests/TestCases/Lowering/StructuralConformance.hylo +++ b/Tests/HyloTests/TestCases/Lowering/StructuralConformance.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success public type A: Deinitializable, Movable { public memberwise init diff --git a/Tests/HyloTests/TestCases/Lowering/Tuple.hylo b/Tests/HyloTests/TestCases/Lowering/Tuple.hylo index e987bb60b..822191cb8 100644 --- a/Tests/HyloTests/TestCases/Lowering/Tuple.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Tuple.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun use(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/Lowering/Unused.hylo b/Tests/HyloTests/TestCases/Lowering/Unused.hylo index ba6b06c7d..e7d6cb04f 100644 --- a/Tests/HyloTests/TestCases/Lowering/Unused.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Unused.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success fun use(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/Lowering/Upcast.hylo b/Tests/HyloTests/TestCases/Lowering/Upcast.hylo index e58427b20..17c3aaed6 100644 --- a/Tests/HyloTests/TestCases/Lowering/Upcast.hylo +++ b/Tests/HyloTests/TestCases/Lowering/Upcast.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: success +//- lowerToFinishedIR expecting: .success public fun main() { var x: Optional = None() diff --git a/Tests/HyloTests/TestCases/Parsing/ArgumentLists.hylo b/Tests/HyloTests/TestCases/Parsing/ArgumentLists.hylo index d40690d7e..27e2cd987 100644 --- a/Tests/HyloTests/TestCases/Parsing/ArgumentLists.hylo +++ b/Tests/HyloTests/TestCases/Parsing/ArgumentLists.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure public fun main() { ; foo(1 1) //! diagnostic expected ',' separator diff --git a/Tests/HyloTests/TestCases/Parsing/BindingDecl.hylo b/Tests/HyloTests/TestCases/Parsing/BindingDecl.hylo index 5b1526cd5..6e5a76236 100644 --- a/Tests/HyloTests/TestCases/Parsing/BindingDecl.hylo +++ b/Tests/HyloTests/TestCases/Parsing/BindingDecl.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure let x0: Int diff --git a/Tests/HyloTests/TestCases/Parsing/ConditionalExpr.hylo b/Tests/HyloTests/TestCases/Parsing/ConditionalExpr.hylo index 753421438..2755c1ff8 100644 --- a/Tests/HyloTests/TestCases/Parsing/ConditionalExpr.hylo +++ b/Tests/HyloTests/TestCases/Parsing/ConditionalExpr.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { _ = if a { () } else { () } diff --git a/Tests/HyloTests/TestCases/Parsing/ConditionalStmt.hylo b/Tests/HyloTests/TestCases/Parsing/ConditionalStmt.hylo index 7d63d2a07..a407dbc1f 100644 --- a/Tests/HyloTests/TestCases/Parsing/ConditionalStmt.hylo +++ b/Tests/HyloTests/TestCases/Parsing/ConditionalStmt.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { if a {} diff --git a/Tests/HyloTests/TestCases/Parsing/DeclModifiers.hylo b/Tests/HyloTests/TestCases/Parsing/DeclModifiers.hylo index 2c020c070..b79ef599a 100644 --- a/Tests/HyloTests/TestCases/Parsing/DeclModifiers.hylo +++ b/Tests/HyloTests/TestCases/Parsing/DeclModifiers.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure type A0 { diff --git a/Tests/HyloTests/TestCases/Parsing/IllegalImplicit.hylo b/Tests/HyloTests/TestCases/Parsing/IllegalImplicit.hylo index 567c92f14..9a4552690 100644 --- a/Tests/HyloTests/TestCases/Parsing/IllegalImplicit.hylo +++ b/Tests/HyloTests/TestCases/Parsing/IllegalImplicit.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic 'inout'-parameter cannot be implicit fun bar(x?: inout Int) -> Int { x.copy() } diff --git a/Tests/HyloTests/TestCases/Parsing/IllegalTypealias.hylo b/Tests/HyloTests/TestCases/Parsing/IllegalTypealias.hylo index eea5e2c28..9cf1a1623 100644 --- a/Tests/HyloTests/TestCases/Parsing/IllegalTypealias.hylo +++ b/Tests/HyloTests/TestCases/Parsing/IllegalTypealias.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic declaration requires definition typealias X: Y = Z diff --git a/Tests/HyloTests/TestCases/Parsing/MatchExpr.hylo b/Tests/HyloTests/TestCases/Parsing/MatchExpr.hylo index c74cb0b0a..c19dd7fa0 100644 --- a/Tests/HyloTests/TestCases/Parsing/MatchExpr.hylo +++ b/Tests/HyloTests/TestCases/Parsing/MatchExpr.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure public fun main() { match true { diff --git a/Tests/HyloTests/TestCases/Parsing/MemberwiseInit.hylo b/Tests/HyloTests/TestCases/Parsing/MemberwiseInit.hylo index 76af07745..485705bcd 100644 --- a/Tests/HyloTests/TestCases/Parsing/MemberwiseInit.hylo +++ b/Tests/HyloTests/TestCases/Parsing/MemberwiseInit.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure type A { var x: Int diff --git a/Tests/HyloTests/TestCases/Parsing/MethodDecl.hylo b/Tests/HyloTests/TestCases/Parsing/MethodDecl.hylo index e6b762fdc..04714312f 100644 --- a/Tests/HyloTests/TestCases/Parsing/MethodDecl.hylo +++ b/Tests/HyloTests/TestCases/Parsing/MethodDecl.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure type A0 { diff --git a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEBlockCommentInBetween.hylo b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEBlockCommentInBetween.hylo index 424d7e55a..a3568f800 100644 --- a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEBlockCommentInBetween.hylo +++ b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEBlockCommentInBetween.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { var x = 0 /*hello*/ diff --git a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByELineCommentInBetween.hylo b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByELineCommentInBetween.hylo index d22b2f166..751066ea1 100644 --- a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByELineCommentInBetween.hylo +++ b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByELineCommentInBetween.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { var x = 0 //hello diff --git a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEOnNewLine.hylo b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEOnNewLine.hylo index 7080bcb24..e49c3f7d0 100644 --- a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEOnNewLine.hylo +++ b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByEOnNewLine.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { var x = 0 diff --git a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByESemiInBetween.hylo b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByESemiInBetween.hylo index f30e65328..c6eba3188 100644 --- a/Tests/HyloTests/TestCases/Parsing/NumberFollowedByESemiInBetween.hylo +++ b/Tests/HyloTests/TestCases/Parsing/NumberFollowedByESemiInBetween.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success public fun main() { var x = 0; eat(x) diff --git a/Tests/HyloTests/TestCases/Parsing/NumberWithEInvalid.hylo b/Tests/HyloTests/TestCases/Parsing/NumberWithEInvalid.hylo index 2c8da76e1..e8d80797d 100644 --- a/Tests/HyloTests/TestCases/Parsing/NumberWithEInvalid.hylo +++ b/Tests/HyloTests/TestCases/Parsing/NumberWithEInvalid.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure let x0 = 1e1 diff --git a/Tests/HyloTests/TestCases/Parsing/ParameterDecl.hylo b/Tests/HyloTests/TestCases/Parsing/ParameterDecl.hylo index 522c83399..1ea6fab78 100644 --- a/Tests/HyloTests/TestCases/Parsing/ParameterDecl.hylo +++ b/Tests/HyloTests/TestCases/Parsing/ParameterDecl.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure fun f1(x) -> U {} //! diagnostic missing type annotation diff --git a/Tests/HyloTests/TestCases/Parsing/Pragma.hylo b/Tests/HyloTests/TestCases/Parsing/Pragma.hylo index 9449b40a3..9a9ac2c78 100644 --- a/Tests/HyloTests/TestCases/Parsing/Pragma.hylo +++ b/Tests/HyloTests/TestCases/Parsing/Pragma.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic unknown pragma 'unknown' let x: Int = #unknown diff --git a/Tests/HyloTests/TestCases/Parsing/Semicolons.hylo b/Tests/HyloTests/TestCases/Parsing/Semicolons.hylo index 968e4230f..cf05539e6 100644 --- a/Tests/HyloTests/TestCases/Parsing/Semicolons.hylo +++ b/Tests/HyloTests/TestCases/Parsing/Semicolons.hylo @@ -1,4 +1,4 @@ -//- parse expecting: success +//- parse expecting: .success ;; import Foo; diff --git a/Tests/HyloTests/TestCases/Parsing/StructDecl.hylo b/Tests/HyloTests/TestCases/Parsing/StructDecl.hylo index 3b410c38b..1cd8b36f4 100644 --- a/Tests/HyloTests/TestCases/Parsing/StructDecl.hylo +++ b/Tests/HyloTests/TestCases/Parsing/StructDecl.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure // Empty struct. type A0 {} diff --git a/Tests/HyloTests/TestCases/Parsing/SubscriptDecl.hylo b/Tests/HyloTests/TestCases/Parsing/SubscriptDecl.hylo index b95fec578..0d4a0dbb7 100644 --- a/Tests/HyloTests/TestCases/Parsing/SubscriptDecl.hylo +++ b/Tests/HyloTests/TestCases/Parsing/SubscriptDecl.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure type A0 { diff --git a/Tests/HyloTests/TestCases/Parsing/UnterminatedComment.hylo b/Tests/HyloTests/TestCases/Parsing/UnterminatedComment.hylo index e420353c2..2faab8ad8 100644 --- a/Tests/HyloTests/TestCases/Parsing/UnterminatedComment.hylo +++ b/Tests/HyloTests/TestCases/Parsing/UnterminatedComment.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic unterminated comment /* This block never ends diff --git a/Tests/HyloTests/TestCases/Parsing/UnterminatedNestedComment.hylo b/Tests/HyloTests/TestCases/Parsing/UnterminatedNestedComment.hylo index 0a3a98272..9fb958784 100644 --- a/Tests/HyloTests/TestCases/Parsing/UnterminatedNestedComment.hylo +++ b/Tests/HyloTests/TestCases/Parsing/UnterminatedNestedComment.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic unterminated comment /* This block never ends diff --git a/Tests/HyloTests/TestCases/Parsing/UnterminatedString.hylo b/Tests/HyloTests/TestCases/Parsing/UnterminatedString.hylo index 6c9f2518d..198af7110 100644 --- a/Tests/HyloTests/TestCases/Parsing/UnterminatedString.hylo +++ b/Tests/HyloTests/TestCases/Parsing/UnterminatedString.hylo @@ -1,4 +1,4 @@ -//- parse expecting: failure +//- parse expecting: .failure //! @+1 diagnostic unterminated string "This string never ends diff --git a/Tests/HyloTests/TestCases/TypeChecking/AbstractType.hylo b/Tests/HyloTests/TestCases/TypeChecking/AbstractType.hylo new file mode 100644 index 000000000..d36223a41 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/AbstractType.hylo @@ -0,0 +1,48 @@ +//- typeCheck expecting: .success + +trait P: SemiRegular { + type T: SemiRegular + type U: SemiRegular + + fun t(_ x: T) + fun u(_ x: U) +} + +trait Q: SemiRegular { + type V: P + property v: V { let } +} + +type A: Q { + public let _v: V + public init (v: sink V) { &self._v = v } + public property v: V { yield _v } + public fun infix== (_ other: Self) -> Bool { self.v == other.v } +} + +type B: Deinitializable { + public let w: W + public memberwise init + + public fun t(_ x: W.V.T) { w.v.t(x) } + public fun u(_ x: W.V.U) { w.v.u(x) } +} + +conformance Array: P { + public typealias T = Element + public typealias U = Self.Position + + public fun t(_ x: Element) {} + public fun u(_ x: Self.Position) {} +} + +fun foo(p: sink X, t: sink X.T, u: sink X.U) { + let a = A(v: p) + let b = B(w: a) + b.t(t) + b.u(u) +} + +public fun main() { + foo(p: Array(), t: true, u: 1) +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/AccessModifiers.hylo b/Tests/HyloTests/TestCases/TypeChecking/AccessModifiers.hylo index 605bc26f0..a81b629bc 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/AccessModifiers.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/AccessModifiers.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A: Deinitializable { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/ArgumentLabels.hylo b/Tests/HyloTests/TestCases/TypeChecking/ArgumentLabels.hylo index 6feb426b5..65857dc4d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ArgumentLabels.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ArgumentLabels.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure // This test case ensures that arguments labels are used to disambiguate overloaded symbols in call // expressions, in order to get more precise diagnostics. diff --git a/Tests/HyloTests/TestCases/TypeChecking/Assignment.hylo b/Tests/HyloTests/TestCases/TypeChecking/Assignment.hylo index 889e10f9b..e0c88ae80 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Assignment.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Assignment.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success public fun main() { var x = 0 diff --git a/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypeLookup.hylo b/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypeLookup.hylo index bf7c1b3af..0aa359c86 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypeLookup.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypeLookup.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait T { type X } diff --git a/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypesWithConformances.hylo b/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypesWithConformances.hylo index 40e578284..3d251d81b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypesWithConformances.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/AssociatedTypesWithConformances.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { type Z } trait Q { type Y } diff --git a/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInference.hylo b/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInference.hylo index 669ceb7a9..6aedef3e3 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInference.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInference.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success let x0 = () let (y0, y1) = ((), ()) diff --git a/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInferenceWithHints.hylo b/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInferenceWithHints.hylo index c94e9dffa..a727f92e0 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInferenceWithHints.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/BindingTypeInferenceWithHints.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/BodylessDecl.hylo b/Tests/HyloTests/TestCases/TypeChecking/BodylessDecl.hylo index cb114342b..918d05c62 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/BodylessDecl.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/BodylessDecl.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+1 diagnostic declaration requires a body fun f() diff --git a/Tests/HyloTests/TestCases/TypeChecking/BufferExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/BufferExpr.hylo new file mode 100644 index 000000000..1ba4ad4d7 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/BufferExpr.hylo @@ -0,0 +1,5 @@ +//- typeCheck expecting: .failure + +public fun main() { + var x: Int[true] //! diagnostic expected type 'Int' but found 'Bool' +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallFunction.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallFunction.hylo index daf05fa6e..16d5af9c2 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallFunction.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallFunction.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun f(_ x: sink {}) -> {} { x } diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallGenericFunction.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallGenericFunction.hylo index b26f8f847..bd0121bf0 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallGenericFunction.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallGenericFunction.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait P { fun foo() } diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallGenericUnnamedSubscript.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallGenericUnnamedSubscript.hylo index 517be5967..965cd5142 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallGenericUnnamedSubscript.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallGenericUnnamedSubscript.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { @@ -14,5 +14,5 @@ public fun main() { let a = A() let b: Int = a[0] let c: Float64 = a[0] - let d: Bool = a[0] //! diagnostic incompatible types 'Bool' and 'Int' + let d: Bool = a[0] //! diagnostic incompatible types 'Int' and 'Bool' } diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallInit.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallInit.hylo index b8ea0bf44..7f0eeb50e 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallInit.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallInit.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A: Movable { diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallMemberwiseInit.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallMemberwiseInit.hylo index 6d9c5b3ef..7157342d1 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallMemberwiseInit.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallMemberwiseInit.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { var foo: Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallNonCallable.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallNonCallable.hylo index a3652b0c2..e91bf4525 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallNonCallable.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallNonCallable.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { let forty_two = 42 diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallOperator.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallOperator.hylo index e74b37de7..fb1277d01 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallOperator.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallOperator.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { let a = 42 diff --git a/Tests/HyloTests/TestCases/TypeChecking/CallStaticFunction.hylo b/Tests/HyloTests/TestCases/TypeChecking/CallStaticFunction.hylo index 88d008dc0..c00548e1b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CallStaticFunction.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CallStaticFunction.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { diff --git a/Tests/HyloTests/TestCases/TypeChecking/CastDown.hylo b/Tests/HyloTests/TestCases/TypeChecking/CastDown.hylo index e82cd605c..9451d3ac7 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CastDown.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CastDown.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success public fun main() { let a: Any = () diff --git a/Tests/HyloTests/TestCases/TypeChecking/CastUp.hylo b/Tests/HyloTests/TestCases/TypeChecking/CastUp.hylo index d7e536f3b..c1997188c 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CastUp.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CastUp.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/CircularRefinement.hylo b/Tests/HyloTests/TestCases/TypeChecking/CircularRefinement.hylo index 2554f5edf..b3868b1f9 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/CircularRefinement.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/CircularRefinement.hylo @@ -1,7 +1,9 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure +//! @+1 diagnostic circular trait refinement trait T: U {} +//! @+1 diagnostic circular trait refinement trait U: V {} //! @+1 diagnostic circular trait refinement diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalBinding.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalBinding.hylo new file mode 100644 index 000000000..2d5d51210 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalBinding.hylo @@ -0,0 +1,13 @@ +//- typeCheck expecting: .failure + +public fun main() { + //! @+1 diagnostic fallback branch of conditional binding cannot fall through + let x? = Optional.none() else {} + print(x) + + let y? = Optional.none() else { + let z = 0 + //! @+1 diagnostic fallback branch of conditional binding cannot fall through + print(z) + } +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo new file mode 100644 index 000000000..d0ab0f0b7 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo @@ -0,0 +1,8 @@ +//- typeCheck expecting: .success + +type Box { + public let contents: T + public memberwise init +} + +conformance Box: Equatable where T: Equatable {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExpr.hylo index ae51207df..a8e95c14c 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} @@ -9,7 +9,7 @@ public fun main() { let x1 = if true { 1 } else { 2 as Float64 } check(x1) - //! @+1 diagnostic type 'Bool' does not conform to trait 'ExpressibleByIntegerLiteral' + //! @+1 diagnostic incompatible types 'Int' and 'Bool' let x2 = if true { 1 } else { false } //! @+1 diagnostic conditional expression has mismatching types 'Int' and 'Float64' diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension.hylo index 859d05f83..8147e638f 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait T { fun foo() diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension2.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension2.hylo index ad97d8e68..59e7accff 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension2.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension2.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { type X } trait Q { type Y } diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension3.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension3.hylo new file mode 100644 index 000000000..681e37fe6 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalExtension3.hylo @@ -0,0 +1,20 @@ +//- typeCheck expecting: .failure + +type Box { + public var contents: Contents + public memberwise init +} + +extension Box where Contents == Int { + public fun contains_zero() -> Bool { + contents == 0 + } +} + +public fun main() { + let b0 = Box(contents: true) + _ = b0.contains_zero() //! diagnostic reference to 'contains_zero' requires that 'Bool' be equal to 'Int' + + let b1 = Box(contents: 1) + _ = b1.contains_zero() +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalStmt.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalStmt.hylo index 47040981d..ad93c2f76 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConditionalStmt.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalStmt.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun use(_ x: Bool) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/Conformance.hylo b/Tests/HyloTests/TestCases/TypeChecking/Conformance.hylo index dd9230046..8a746255d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Conformance.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Conformance.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait P { fun f() diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceGeneric.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceGeneric.hylo index 4a6130f9b..af51144dd 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceGeneric.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceGeneric.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait T { fun foo() diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceStructural.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceStructural.hylo index 9a3073d02..9d6635e11 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceStructural.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceStructural.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+1 diagnostic type 'A' does not conform to trait 'Deinitializable' type A: Deinitializable { diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithAssociatedType.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithAssociatedType.hylo index 61a51ae64..8760c3378 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithAssociatedType.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithAssociatedType.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { type X diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithGenericRequirement.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithGenericRequirement.hylo index 2ad6a12e7..4b74370cb 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithGenericRequirement.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithGenericRequirement.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { fun red(_ x: T) -> Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithPrivateImpl.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithPrivateImpl.hylo index 6566b6235..f23e431c3 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithPrivateImpl.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithPrivateImpl.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure // Implementations of the requirements of a trait `P` in a type `A` must be exposed to the same // scopes as the conformance `A : P`. diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithRefinement.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithRefinement.hylo index 95d55e412..f9e61a123 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithRefinement.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithRefinement.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P: Regular { property n: Int { let } diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithSubscript.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithSubscript.hylo index 941ace3ec..bdc00298a 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithSubscript.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ConformanceWithSubscript.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { type X diff --git a/Tests/HyloTests/TestCases/TypeChecking/Constructor.hylo b/Tests/HyloTests/TestCases/TypeChecking/Constructor.hylo index ac06a8939..64b82dcdb 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Constructor.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Constructor.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type Message { diff --git a/Tests/HyloTests/TestCases/TypeChecking/DefaultArgument.hylo b/Tests/HyloTests/TestCases/TypeChecking/DefaultArgument.hylo index 324e830c7..a20816f3b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DefaultArgument.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DefaultArgument.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { public memberwise init } diff --git a/Tests/HyloTests/TestCases/TypeChecking/DefaultAssociatedType.hylo b/Tests/HyloTests/TestCases/TypeChecking/DefaultAssociatedType.hylo index 0c1459ea7..a448e324f 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DefaultAssociatedType.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DefaultAssociatedType.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait T { type U = Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/Destructuring.hylo b/Tests/HyloTests/TestCases/TypeChecking/Destructuring.hylo index a53ae7b76..41623829b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Destructuring.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Destructuring.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/DoWhile.hylo b/Tests/HyloTests/TestCases/TypeChecking/DoWhile.hylo index e66dd4fa2..e4a8099b3 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DoWhile.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DoWhile.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { do { diff --git a/Tests/HyloTests/TestCases/TypeChecking/DuplicateCapture.hylo b/Tests/HyloTests/TestCases/TypeChecking/DuplicateCapture.hylo index 662319672..3a3e6003c 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DuplicateCapture.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DuplicateCapture.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { let x = 0 diff --git a/Tests/HyloTests/TestCases/TypeChecking/DuplicateFunctionParameter.hylo b/Tests/HyloTests/TestCases/TypeChecking/DuplicateFunctionParameter.hylo index cabf407a7..d8e424ede 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DuplicateFunctionParameter.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DuplicateFunctionParameter.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+1 diagnostic duplicate parameter name 'a' fun f0(_ a: {}, _ a: {}) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/DuplicateOperatorDecl.hylo b/Tests/HyloTests/TestCases/TypeChecking/DuplicateOperatorDecl.hylo index 1b67531ad..fb3ef6a69 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/DuplicateOperatorDecl.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/DuplicateOperatorDecl.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure operator prefix-* operator infix-* : addition diff --git a/Tests/HyloTests/TestCases/TypeChecking/Existential.hylo b/Tests/HyloTests/TestCases/TypeChecking/Existential.hylo index 8611fd42b..56fa9ca8f 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Existential.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Existential.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait P1 {} trait P2 {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ExistentialGenericAPI.hylo b/Tests/HyloTests/TestCases/TypeChecking/ExistentialGenericAPI.hylo index d6a6b53d6..10dd8c814 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ExistentialGenericAPI.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ExistentialGenericAPI.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/ExistentialTraitAPI.hylo b/Tests/HyloTests/TestCases/TypeChecking/ExistentialTraitAPI.hylo index 1ce5c7401..0c91e890d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ExistentialTraitAPI.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ExistentialTraitAPI.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait T { fun foo() -> Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/ExplicitCapture.hylo b/Tests/HyloTests/TestCases/TypeChecking/ExplicitCapture.hylo index 0052a6a67..f2d3aff53 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ExplicitCapture.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ExplicitCapture.hylo @@ -1,3 +1,3 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun f0[sink let x = ()]() {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ExtensionIllegal.hylo b/Tests/HyloTests/TestCases/TypeChecking/ExtensionIllegal.hylo index f50c57c36..b4a431fa3 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ExtensionIllegal.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ExtensionIllegal.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait T {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ExtensionOfImport.hylo b/Tests/HyloTests/TestCases/TypeChecking/ExtensionOfImport.hylo index 0afb8b924..6c27e713d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ExtensionOfImport.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ExtensionOfImport.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success extension Float64 { public static fun gravity() -> Float64 { 9.81 } diff --git a/Tests/HyloTests/TestCases/TypeChecking/FloatLiteralExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/FloatLiteralExpr.hylo index 7c2374c78..61789ce2a 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/FloatLiteralExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/FloatLiteralExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/For.hylo b/Tests/HyloTests/TestCases/TypeChecking/For.hylo index 3887bf4a8..e81b0679b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/For.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/For.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type Counter: Iterator, Deinitializable { public var n: Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/FunctionIdentity.hylo b/Tests/HyloTests/TestCases/TypeChecking/FunctionIdentity.hylo index aea2d325c..5f5c4bef9 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/FunctionIdentity.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/FunctionIdentity.hylo @@ -1,3 +1,3 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success public fun identity(_ x: sink T) -> T { x } diff --git a/Tests/HyloTests/TestCases/TypeChecking/FunctionTrivial.hylo b/Tests/HyloTests/TestCases/TypeChecking/FunctionTrivial.hylo index 8c387bdc3..9f6ee5983 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/FunctionTrivial.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/FunctionTrivial.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun f0() {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/GenericArguments.hylo b/Tests/HyloTests/TestCases/TypeChecking/GenericArguments.hylo index e36ea8855..2eb801a49 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/GenericArguments.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/GenericArguments.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type Box { public var contents: X diff --git a/Tests/HyloTests/TestCases/TypeChecking/GenericEnvironment.hylo b/Tests/HyloTests/TestCases/TypeChecking/GenericEnvironment.hylo index ebff16dba..679806908 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/GenericEnvironment.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/GenericEnvironment.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { type Element diff --git a/Tests/HyloTests/TestCases/TypeChecking/GenericParameters.hylo b/Tests/HyloTests/TestCases/TypeChecking/GenericParameters.hylo index e1e1fdf89..340da54e2 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/GenericParameters.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/GenericParameters.hylo @@ -1,9 +1,10 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+1 diagnostic expected type but 'v' denotes a value -fun f0(_ x: X, _ y: v) {} +fun f0(_ x: X, _ y: v) {} -//! @+1 diagnostic only one annotation is allowed on generic value parameter declarations +//! @+2 diagnostic conformance to non-trait type 'Float64' +//! @+1 diagnostic conformance to non-trait type 'Int' fun f1(_ x: X) {} //! @+1 diagnostic conformance to non-trait type 'Float64' diff --git a/Tests/HyloTests/TestCases/TypeChecking/GenericTypeAlias.hylo b/Tests/HyloTests/TestCases/TypeChecking/GenericTypeAlias.hylo index 71ff5e564..dd161fa72 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/GenericTypeAlias.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/GenericTypeAlias.hylo @@ -1,7 +1,7 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success typealias Pair = { first: X, second: Y } typealias AnyPair = Pair -typealias AliasWithValue = { buffer: X } +typealias AliasWithValue = { buffer: X } diff --git a/Tests/HyloTests/TestCases/TypeChecking/IllegalExtension.hylo b/Tests/HyloTests/TestCases/TypeChecking/IllegalExtension.hylo index 32c3f7f4f..0bcb94141 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/IllegalExtension.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/IllegalExtension.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+1 diagnostic expected type but expression denotes a value extension true {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/ImplicitImmutableCapture.hylo b/Tests/HyloTests/TestCases/TypeChecking/ImplicitImmutableCapture.hylo index c8194bfd5..c5feed8f3 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ImplicitImmutableCapture.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ImplicitImmutableCapture.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun main() { let foo = () diff --git a/Tests/HyloTests/TestCases/TypeChecking/ImplicitMutableCapture.hylo b/Tests/HyloTests/TestCases/TypeChecking/ImplicitMutableCapture.hylo index 7a58d69f5..c58540fb2 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ImplicitMutableCapture.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ImplicitMutableCapture.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun main() { var foo = () diff --git a/Tests/HyloTests/TestCases/TypeChecking/ImplicitQualification.hylo b/Tests/HyloTests/TestCases/TypeChecking/ImplicitQualification.hylo index 2b1aa1d34..821b2b6a9 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/ImplicitQualification.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/ImplicitQualification.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A: Deinitializable { diff --git a/Tests/HyloTests/TestCases/TypeChecking/Import.hylo b/Tests/HyloTests/TestCases/TypeChecking/Import.hylo index c65ee39a0..900e5b40d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Import.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Import.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure import Hylo import Import //! diagnostic needless import: source file is part of 'Import' diff --git a/Tests/HyloTests/TestCases/TypeChecking/InoutExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/InoutExpr.hylo index df7cc044d..0fd65d244 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/InoutExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/InoutExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/IntegerLiteralExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/IntegerLiteralExpr.hylo index 90bdec32c..aab8bd661 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/IntegerLiteralExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/IntegerLiteralExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/InvalidAnnotation.hylo b/Tests/HyloTests/TestCases/TypeChecking/InvalidAnnotation.hylo index df92baec2..51777ad31 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/InvalidAnnotation.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/InvalidAnnotation.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/LambdaCoercion.hylo b/Tests/HyloTests/TestCases/TypeChecking/LambdaCoercion.hylo index 5dca9c6ba..48569c491 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/LambdaCoercion.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/LambdaCoercion.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun apply_immutable(_ f: ([E]() -> Void)) { f() } diff --git a/Tests/HyloTests/TestCases/TypeChecking/LambdaTypeInference.hylo b/Tests/HyloTests/TestCases/TypeChecking/LambdaTypeInference.hylo index ae3143bd6..78dca2ea7 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/LambdaTypeInference.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/LambdaTypeInference.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/LargeBufferLiteral.hylo b/Tests/HyloTests/TestCases/TypeChecking/LargeBufferLiteral.hylo new file mode 100644 index 000000000..fd1d9de4c --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/LargeBufferLiteral.hylo @@ -0,0 +1,19 @@ +//- typeCheck expecting: .success + +fun check(_ x: T) {} + +public fun main() { + let x0: Int8[100] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + check(x0) +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo b/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo index bf6fd099d..312c3d159 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { sink let x: Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/MatchExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/MatchExpr.hylo index 0e0b946ae..124a544de 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/MatchExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/MatchExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success public fun main() { let a = 42 as Any diff --git a/Tests/HyloTests/TestCases/TypeChecking/MetatypeType.hylo b/Tests/HyloTests/TestCases/TypeChecking/MetatypeType.hylo index 30e9542d6..f6b414adc 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/MetatypeType.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/MetatypeType.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A {} type B {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/MethodBundle.hylo b/Tests/HyloTests/TestCases/TypeChecking/MethodBundle.hylo index 20c2e3ba9..81d32aa82 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/MethodBundle.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/MethodBundle.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} @@ -7,7 +7,7 @@ type A { memberwise init // Functional variants of a method bundle return instances of `{self: Self, Output}`, unless the - // declared return type of the bundle is `Void`, in which case they return instances of `Output`. + // declared return type of the bundle is `Void`, in which case they return instances of `Self`. // In-place variants always return instances of `Output`. fun foo(_ x: Self) { diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookup.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookup.hylo index ad44791c3..5b2f6713d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookup.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookup.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric.hylo index bca02de93..e6672fa0a 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A: Movable, Deinitializable { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric2.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric2.hylo index df0400184..738f24557 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric2.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric2.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait DefaultConstructible { init() diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric3.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric3.hylo index 65f41dacc..dcd271311 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric3.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupGeneric3.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success // This test ensures that the type checker is able to determine the kind of a generic parameter // introduced in a member of a trait extension. Name lookup is a little tricky in this situation @@ -11,5 +11,5 @@ trait Q { } extension P { - public fun f(x: T.B) {} + public fun f(x: T.B) {} } diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupLabels.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupLabels.hylo index ff40f3d05..83767b2c8 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupLabels.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupLabels.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun add(x: Int, y: Int) -> Int { x + y diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupViaTraitExtension.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupViaTraitExtension.hylo index 88ae61b75..8509cff25 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupViaTraitExtension.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupViaTraitExtension.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait P { fun foo() } diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithAlias.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithAlias.hylo index b37fbeecd..8468810e9 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithAlias.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithAlias.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success public type X { public let a: Int = 32 @@ -9,4 +9,4 @@ typealias Y = X fun f(x: X, y: Y) -> Int { let c = x.a return c + y.a -} \ No newline at end of file +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithDottedPath.hylo b/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithDottedPath.hylo index 8bc1cbcc2..abfb3d0e5 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithDottedPath.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NameLookupWithDottedPath.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type Item: SemiRegular { diff --git a/Tests/HyloTests/TestCases/TypeChecking/NestedGenerics.hylo b/Tests/HyloTests/TestCases/TypeChecking/NestedGenerics.hylo index 4996da8df..61ca1755f 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/NestedGenerics.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/NestedGenerics.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type M { public var x: T diff --git a/Tests/HyloTests/TestCases/TypeChecking/OptionPattern.hylo b/Tests/HyloTests/TestCases/TypeChecking/OptionPattern.hylo new file mode 100644 index 000000000..aee7f7a38 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/OptionPattern.hylo @@ -0,0 +1,12 @@ +//- typeCheck expecting: .failure + +fun use(_ x: T) {} + +public fun main() { + let x: Optional = 42 as _ + + if let y? = x { use(y) } + + //! @+1 diagnostic optional pattern may only be used as a condition + let z? : Int = 0 +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/Overloading.hylo b/Tests/HyloTests/TestCases/TypeChecking/Overloading.hylo index be45f81ed..c4a8474e1 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Overloading.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Overloading.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} @@ -39,5 +39,5 @@ public fun main() { check(x6) let a = 1 - fn(a, a) //! diagnostic ambiguous use of 'fn' + _ = fn(a, a) //! diagnostic ambiguous use of 'fn' } diff --git a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo index ab849921a..bd0c72a35 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A: Deinitializable { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/RecursiveConstraints.hylo b/Tests/HyloTests/TestCases/TypeChecking/RecursiveConstraints.hylo new file mode 100644 index 000000000..f94496d6a --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/RecursiveConstraints.hylo @@ -0,0 +1,37 @@ +//- typeCheck expecting: .failure + +fun use(_ x: T) {} + +trait Z3 { + type A where A : Z3, A.A.A == Self +} + +fun f(_ v: T) { + use(v) +} + +trait S { + type A + type B + type C: S where C.A == B, C.C == Self +} + +fun g(_ v: T.C.B) { + use(v) +} + +//! @+1 diagnostic requirement system too complex to build +trait I { + type A: I + type B: I + type C: I + type D: I + type E: I where + A.C == C.A, + A.D == D.A, + B.C == C.B, + B.D == D.B, + C.E == E.C.A, + D.E == E.D.B, + C.C.A == C.C.A.E +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/RecursiveParameter.hylo b/Tests/HyloTests/TestCases/TypeChecking/RecursiveParameter.hylo index 304fa3edc..307f33bbc 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/RecursiveParameter.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/RecursiveParameter.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure //! @+3 diagnostic definition recursively depends on itself //! @+2 diagnostic undefined name 'x' in this scope diff --git a/Tests/HyloTests/TestCases/TypeChecking/RemoteExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/RemoteExpr.hylo index 916405bf0..2018a6fbb 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/RemoteExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/RemoteExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/Return.hylo b/Tests/HyloTests/TestCases/TypeChecking/Return.hylo index f1fc05bbb..f4199ff01 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Return.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Return.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun forty_two() -> Int { return 42 } diff --git a/Tests/HyloTests/TestCases/TypeChecking/Shadowing.hylo b/Tests/HyloTests/TestCases/TypeChecking/Shadowing.hylo index bbdb1df6f..d7c82839e 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Shadowing.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Shadowing.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun foo(_ x: Int) -> Bool { true } diff --git a/Tests/HyloTests/TestCases/TypeChecking/Skolemization.hylo b/Tests/HyloTests/TestCases/TypeChecking/Skolemization.hylo index 0ff3aa5a9..43b73f3ea 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Skolemization.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Skolemization.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success type A { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/StoredPropertyDecl.hylo b/Tests/HyloTests/TestCases/TypeChecking/StoredPropertyDecl.hylo index 7f60a21e6..16d2a50b5 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/StoredPropertyDecl.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/StoredPropertyDecl.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { diff --git a/Tests/HyloTests/TestCases/TypeChecking/StringLiteralExpr.hylo b/Tests/HyloTests/TestCases/TypeChecking/StringLiteralExpr.hylo index 89f4bbf18..7ec806d55 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/StringLiteralExpr.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/StringLiteralExpr.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/TraitDecl.hylo b/Tests/HyloTests/TestCases/TypeChecking/TraitDecl.hylo index e0006ce65..ba6934196 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/TraitDecl.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/TraitDecl.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure trait T { diff --git a/Tests/HyloTests/TestCases/TypeChecking/TraitExtension.hylo b/Tests/HyloTests/TestCases/TypeChecking/TraitExtension.hylo index 3c5ed6347..4222c0f2d 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/TraitExtension.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/TraitExtension.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success trait T { fun foo() -> Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/TupleMembers.hylo b/Tests/HyloTests/TestCases/TypeChecking/TupleMembers.hylo index bf1c9200a..ec07a4b3e 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/TupleMembers.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/TupleMembers.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure fun check(_ x: T) {} diff --git a/Tests/HyloTests/TestCases/TypeChecking/TypeExpressionSugars.hylo b/Tests/HyloTests/TestCases/TypeChecking/TypeExpressionSugars.hylo new file mode 100644 index 000000000..f6f655f0b --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/TypeExpressionSugars.hylo @@ -0,0 +1,11 @@ +//- typeCheck expecting: .failure + +public fun main() { + // Buffer type. + let x0: Int[2] = [1, 2] + // Array type. + let x1: Int[] = Array() + + //! @+1 diagnostic buffer type requires exactly one generic argument (found 2) + let x2: Int[1, 2] +} diff --git a/Tests/HyloTests/TestCases/TypeChecking/UndefinedOperator.hylo b/Tests/HyloTests/TestCases/TypeChecking/UndefinedOperator.hylo index 29466d366..d5c221e08 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UndefinedOperator.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UndefinedOperator.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { //! @+1 diagnostic undefined operator '&&&' diff --git a/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo b/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo index b0ad25a71..f0ebf363b 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { // Standard cases. diff --git a/Tests/HyloTests/TestCases/TypeChecking/UnionTypeGeneric.hylo b/Tests/HyloTests/TestCases/TypeChecking/UnionTypeGeneric.hylo index 954ca037d..05440b946 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UnionTypeGeneric.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UnionTypeGeneric.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success typealias MysteryBox = Union, B> diff --git a/Tests/HyloTests/TestCases/TypeChecking/UnresolvedUnnamedSubscript.hylo b/Tests/HyloTests/TestCases/TypeChecking/UnresolvedUnnamedSubscript.hylo index 8ddd2cc8c..b7dc1dbea 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UnresolvedUnnamedSubscript.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UnresolvedUnnamedSubscript.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A { public memberwise init } diff --git a/Tests/HyloTests/TestCases/TypeChecking/UnusedResult.hylo b/Tests/HyloTests/TestCases/TypeChecking/UnusedResult.hylo index 22185cc64..98880b869 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UnusedResult.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UnusedResult.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: success +//- typeCheck expecting: .success fun f() -> Int { 1 + 1 //! diagnostic unused result of type 'Int' diff --git a/Tests/HyloTests/TestCases/TypeChecking/WhereClause.hylo b/Tests/HyloTests/TestCases/TypeChecking/WhereClause.hylo index fe276ce0b..76d841d4e 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/WhereClause.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/WhereClause.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure type A: Deinitializable { public memberwise init diff --git a/Tests/HyloTests/TestCases/TypeChecking/While.hylo b/Tests/HyloTests/TestCases/TypeChecking/While.hylo index 0f143a46d..c3339419c 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/While.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/While.hylo @@ -1,4 +1,4 @@ -//- typeCheck expecting: failure +//- typeCheck expecting: .failure public fun main() { var x = true diff --git a/Tests/LibraryTests/TestCases/ArrayTests.hylo b/Tests/LibraryTests/TestCases/ArrayTests.hylo index 08cd34669..0405d5231 100644 --- a/Tests/LibraryTests/TestCases/ArrayTests.hylo +++ b/Tests/LibraryTests/TestCases/ArrayTests.hylo @@ -1,10 +1,17 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun test_init_empty() { var d = Array() precondition(d.count() == 0) } +fun test_init_with_buffer() { + let d = Array([21, 42]) + precondition(d.count() == 2) + precondition(d[0] == 21) + precondition(d[1] == 42) +} + fun test_init_with_lambda() { let d = Array(count: 10, initialized_with: fun (_ i) { i * 2 }) precondition(d.count() == 10) @@ -144,6 +151,7 @@ fun test_array_is_copyable() { public fun main() { test_init_empty() + test_init_with_buffer() test_init_with_lambda() test_append() test_insert_at() diff --git a/Tests/LibraryTests/TestCases/BitArrayTests.hylo b/Tests/LibraryTests/TestCases/BitArrayTests.hylo index 70098a18f..ccfcc337f 100644 --- a/Tests/LibraryTests/TestCases/BitArrayTests.hylo +++ b/Tests/LibraryTests/TestCases/BitArrayTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun test_modify() { var b = BitArray() diff --git a/Tests/LibraryTests/TestCases/BoolTests.hylo b/Tests/LibraryTests/TestCases/BoolTests.hylo index 29b7ae309..e9a82cb71 100644 --- a/Tests/LibraryTests/TestCases/BoolTests.hylo +++ b/Tests/LibraryTests/TestCases/BoolTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { // Bool.init(), infix==, infix!= diff --git a/Tests/LibraryTests/TestCases/CollectionOfOneTests.hylo b/Tests/LibraryTests/TestCases/CollectionOfOneTests.hylo index 4ccd02bce..dfa92a15a 100644 --- a/Tests/LibraryTests/TestCases/CollectionOfOneTests.hylo +++ b/Tests/LibraryTests/TestCases/CollectionOfOneTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { let c = CollectionOfOne(42) diff --git a/Tests/LibraryTests/TestCases/CollectionTests.hylo b/Tests/LibraryTests/TestCases/CollectionTests.hylo index 25c81c1ef..0b24a76e2 100644 --- a/Tests/LibraryTests/TestCases/CollectionTests.hylo +++ b/Tests/LibraryTests/TestCases/CollectionTests.hylo @@ -1,4 +1,28 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success + +conformance Int: Collection { + + public typealias Element = Bool + public typealias Position = Int + + public fun start_position() -> Int { 0 } + public fun end_position() -> Int { Int.bit_width() } + public fun position(after p: Int) -> Int { p + 1 } + + public subscript(_ p: Int): Bool { (self & (1 << p)) != 0 } + +} + +fun test_count() { + let a = 0 + precondition(a.count() == 64) +} + +fun test_first_position() { + let a = 4 + precondition(a.first_position(where: fun (_ x) { x.copy() }) == 2) + precondition(a.first_position(of: true) == 2) +} fun test_reduce() { var a = Array() @@ -31,6 +55,8 @@ fun test_all_satisfy() { } public fun main() { + test_count() + test_first_position() test_reduce() test_contains_where() test_all_satisfy() diff --git a/Tests/LibraryTests/TestCases/DynamicBufferTests.hylo b/Tests/LibraryTests/TestCases/DynamicBufferTests.hylo index 631645d85..d2d73e224 100644 --- a/Tests/LibraryTests/TestCases/DynamicBufferTests.hylo +++ b/Tests/LibraryTests/TestCases/DynamicBufferTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun test_init_empty() { var d = DynamicBuffer() diff --git a/Tests/LibraryTests/TestCases/HeapTests.hylo b/Tests/LibraryTests/TestCases/HeapTests.hylo index 0e2a658df..78c75780f 100644 --- a/Tests/LibraryTests/TestCases/HeapTests.hylo +++ b/Tests/LibraryTests/TestCases/HeapTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { let p = hylo_aligned_alloc(16, 8) diff --git a/Tests/LibraryTests/TestCases/IntTests.hylo b/Tests/LibraryTests/TestCases/IntTests.hylo index 216c3fa35..76518487f 100644 --- a/Tests/LibraryTests/TestCases/IntTests.hylo +++ b/Tests/LibraryTests/TestCases/IntTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun test_min() { precondition(min[1, 2] == 1) diff --git a/Tests/LibraryTests/TestCases/MutableCollectionTests.hylo b/Tests/LibraryTests/TestCases/MutableCollectionTests.hylo index b007d59f5..a8afe9215 100644 --- a/Tests/LibraryTests/TestCases/MutableCollectionTests.hylo +++ b/Tests/LibraryTests/TestCases/MutableCollectionTests.hylo @@ -1,11 +1,11 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success /// Appends `0 ..< limit` to `a` fun append_count(up_to limit: Int, to a: inout Array) { var i = 0 while i < limit { &a.append(i.copy()) - i += 1 + &i += 1 } } @@ -30,10 +30,10 @@ fun test_rotate(length N: Int) { if a[j] != (pivot + j) % N { &failed = true } - j += 1 + &j += 1 } - pivot += 1 + &pivot += 1 } precondition(!failed) } @@ -44,7 +44,7 @@ fun test_rotate() { var n = 0 while n < 17 { test_rotate(length: n) - n += 1 + &n += 1 } } diff --git a/Tests/LibraryTests/TestCases/OptionalTests.hylo b/Tests/LibraryTests/TestCases/OptionalTests.hylo index ad15284fa..3ba195d71 100644 --- a/Tests/LibraryTests/TestCases/OptionalTests.hylo +++ b/Tests/LibraryTests/TestCases/OptionalTests.hylo @@ -1,11 +1,30 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun main() { + precondition(None() == None()) + var x = 42 as Optional + precondition(x == x) + let y = if let i: Int = x { i.copy() } else { 0 } precondition(y == 42) &x = .none() let z = if let i: Int = x { i.copy() } else { 0 } precondition(z == 0) + + &x = 42 as _ + precondition(x! == 42) + + &x = 42 as _ + precondition(!x.is_empty()) + + &x = 42 as _ + precondition(x.release() == 42) + precondition(x.is_empty()) + + &x = 42 as _ + &x = x.map(fun(_ n) { n + 1 }) + precondition(x.release() == 43) + precondition((x.map(fun(_ n) { n + 1 })).is_empty()) } diff --git a/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo b/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo index 47d7575af..1b81fd9fc 100644 --- a/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo +++ b/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success fun test_advance() { var c = PointerToMutable.allocate_bytes( diff --git a/Tests/LibraryTests/TestCases/RangeTests.hylo b/Tests/LibraryTests/TestCases/RangeTests.hylo index 86ea05660..22e83b531 100644 --- a/Tests/LibraryTests/TestCases/RangeTests.hylo +++ b/Tests/LibraryTests/TestCases/RangeTests.hylo @@ -1,14 +1,14 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public fun test_conformance_to_iterator() { var r = 1 ..< 3 var x: Int - &x = if let y: Int = &r.next() { y } else { -1 } + &x = if sink let y: Int = &r.next() { y } else { -1 } precondition(x == 1) - &x = if let y: Int = &r.next() { y } else { -1 } + &x = if sink let y: Int = &r.next() { y } else { -1 } precondition(x == 2) - &x = if let y: Int = &r.next() { y } else { -1 } + &x = if sink let y: Int = &r.next() { y } else { -1 } precondition(x == -1) precondition(r.lower_bound == r.upper_bound) diff --git a/Tests/LibraryTests/TestCases/StrideableTests.hylo b/Tests/LibraryTests/TestCases/StrideableTests.hylo index 79ca3a459..3dbfa2a7b 100644 --- a/Tests/LibraryTests/TestCases/StrideableTests.hylo +++ b/Tests/LibraryTests/TestCases/StrideableTests.hylo @@ -1,4 +1,4 @@ -//- compileAndRun expecting: success +//- compileAndRun expecting: .success public type A: Deinitializable, Strideable { diff --git a/Tests/ManglingTests/MangledStrings.swift b/Tests/ManglingTests/MangledStrings.swift new file mode 100644 index 000000000..795c4a4da --- /dev/null +++ b/Tests/ManglingTests/MangledStrings.swift @@ -0,0 +1,10 @@ +extension ManglingTests { + + static let inputCase = + "mFxF0R0UfDynamicBufferA10sF06init1lTR736selfpT2bTa" + + "TK1Z2gTK1G8HeadergTK1G9ElementacapacitypT1aTR2Zqin" + + "itializing_header_withpT1lTgTrKG3E10pT2K4R7R7YVQOu" + + ".fZlTtT112lTtT215rT1K9dinit_headerrT1Ke10pT2tT215K" + + "9gpayload_headerK4R70R72K9tT13cKj" + +} diff --git a/Tests/ManglingTests/ManglingTests.swift b/Tests/ManglingTests/ManglingTests.swift index d8ddc37db..25cbaa37f 100644 --- a/Tests/ManglingTests/ManglingTests.swift +++ b/Tests/ManglingTests/ManglingTests.swift @@ -65,10 +65,10 @@ final class ManglingTests: XCTestCase { } """ - let input = SourceFile(synthesizedText: text, named: "main") + let input = SourceFile(synthesizedText: text) let (p, m) = try checkNoDiagnostic { (d) in var ast = try Utils.Host.hostedLibraryAST.get() - let main = try ast.makeModule("Main", sourceCode: [input], diagnostics: &d) + let main = try ast.loadModule("Main", parsing: [input], reportingDiagnosticsTo: &d) let base = ScopedProgram(ast) return (try TypedProgram(annotating: base, reportingDiagnosticsTo: &d), main) } @@ -81,7 +81,7 @@ final class ManglingTests: XCTestCase { "Main.Stash", "Main.Stash.A", "Main.Angle.init", - "Main.Angle.degrees().inout", + "Main.Angle.degrees[].inout", "Main.français(_:label:).bar", ] @@ -118,6 +118,10 @@ final class ManglingTests: XCTestCase { is: "any Hylo.Movable") } + func testMonomorphized() { + XCTAssertNotNil(DemangledSymbol(Self.inputCase)) + } + } /// An AST visitation callback that collects mangled symbols, asserting that they are unique. diff --git a/Tests/UtilsTests/DirectedGraphTests.swift b/Tests/UtilsTests/DirectedGraphTests.swift index 5c0c3a94a..756816d45 100644 --- a/Tests/UtilsTests/DirectedGraphTests.swift +++ b/Tests/UtilsTests/DirectedGraphTests.swift @@ -3,6 +3,23 @@ import XCTest final class DirectedGraphTests: XCTestCase { + func testInsertVertex() { + var g = DirectedGraph() + XCTAssert(g.insertVertex(0)) + XCTAssert(g.insertVertex(1)) + XCTAssertFalse(g.insertVertex(0)) + } + + func testVertices() { + var g = DirectedGraph() + XCTAssert(g.vertices.isEmpty) + + g.insertVertex(0) + XCTAssertEqual(Array(g.vertices), [0]) + g.insertEdge(from: 0, to: 1, labeledBy: 2) + XCTAssertEqual(Array(g.vertices).sorted(), [0, 1]) + } + func testInsertEdge() { var g = DirectedGraph() @@ -103,6 +120,69 @@ final class DirectedGraphTests: XCTestCase { XCTAssertFalse(g.isReachable(2, from: 1)) } + func testEquatable() { + var g1 = DirectedGraph() + g1.insertEdge(from: 0, to: 1, labeledBy: true) + g1.insertEdge(from: 1, to: 0, labeledBy: false) + + var g2 = g1 + XCTAssertEqual(g1, g2) + g2.removeEdge(from: 0, to: 1) + XCTAssertNotEqual(g1, g2) + g2.insertEdge(from: 0, to: 1, labeledBy: true) + XCTAssertEqual(g1, g2) + } + + func testHashable() { + var g1 = DirectedGraph() + g1.insertEdge(from: 0, to: 1, labeledBy: true) + g1.insertEdge(from: 1, to: 0, labeledBy: false) + + var h1 = Hasher() + var h2 = Hasher() + g1.hash(into: &h1) + g1.hash(into: &h2) + XCTAssertEqual(h1.finalize(), h2.finalize()) + + var g2 = g1 + g2.removeEdge(from: 0, to: 1) + + h1 = Hasher() + h2 = Hasher() + g1.hash(into: &h1) + g2.hash(into: &h2) + XCTAssertNotEqual(h1.finalize(), h2.finalize()) + + g2.insertEdge(from: 0, to: 1, labeledBy: true) + h1 = Hasher() + h2 = Hasher() + g1.hash(into: &h1) + g2.hash(into: &h2) + XCTAssertEqual(h1.finalize(), h2.finalize()) + } + + func testSCC() { + var g = DirectedGraph() + g.insertEdge(from: 1, to: 0) + g.insertEdge(from: 0, to: 2) + g.insertEdge(from: 2, to: 1) + g.insertEdge(from: 0, to: 3) + g.insertEdge(from: 3, to: 4) + + var s = StronglyConnectedComponents() + + let a0 = s.component(containing: 0, in: g) + XCTAssertEqual(Set(s.vertices(in: a0)), [0, 1, 2]) + let a1 = s.component(containing: 1, in: g) + XCTAssertEqual(Set(s.vertices(in: a1)), [0, 1, 2]) + let a2 = s.component(containing: 2, in: g) + XCTAssertEqual(Set(s.vertices(in: a2)), [0, 1, 2]) + let a3 = s.component(containing: 3, in: g) + XCTAssertEqual(Set(s.vertices(in: a3)), [3]) + let a4 = s.component(containing: 4, in: g) + XCTAssertEqual(Set(s.vertices(in: a4)), [4]) + } + } extension DirectedGraph.Edge { @@ -112,3 +192,11 @@ extension DirectedGraph.Edge { } } + +extension StronglyConnectedComponents { + + mutating func component(containing v: Vertex, in g: DirectedGraph) -> Int { + component(containing: v, enumeratingSuccessorsWith: { (u) in g[from: u].map(\.key) }) + } + +} diff --git a/Tests/UtilsTests/TrieTests.swift b/Tests/UtilsTests/TrieTests.swift new file mode 100644 index 000000000..68ea3b43d --- /dev/null +++ b/Tests/UtilsTests/TrieTests.swift @@ -0,0 +1,172 @@ +import Utils +import XCTest + +final class TrieTests: XCTestCase { + + func testIsEmpty() { + var s = Trie() + XCTAssert(s.isEmpty) + + s["abc"] = 1 + s["ab"] = 2 + XCTAssertFalse(s.isEmpty) + + s["abc"] = nil + s["ab"] = nil + XCTAssert(s.isEmpty) + + s["ab"] = 3 + XCTAssertFalse(s.isEmpty) + } + + func testCount() { + var s = Trie() + XCTAssertEqual(s.count, 0) + + s["abc"] = 1 + s["ab"] = 2 + XCTAssertEqual(s.count, 2) + + s["abx"] = 3 + XCTAssertEqual(s.count, 3) + + s["abc"] = nil + s["abx"] = 4 + XCTAssertEqual(s.count, 2) + } + + func testElements() { + var s = Trie() + XCTAssert(s.elements.isEmpty) + + s["ab"] = 0 + s["abc"] = 1 + s["abC"] = 2 + s["xyz"] = 3 + XCTAssertEqual( + Array(s.elements.sorted(by: \.value).map({ (k, _) in String(k) })), + ["ab", "abc", "abC", "xyz"]) + + s["abC"] = nil + s["xyZ"] = 4 + XCTAssertEqual( + Array(s.elements.sorted(by: \.value).map({ (k, _) in String(k) })), + ["ab", "abc", "xyz", "xyZ"]) + } + + func testSubtrieElements() throws { + var s = Trie() + + s["ab"] = 0 + s["abc"] = 1 + s["abC"] = 2 + s["xyz"] = 3 + let t = try XCTUnwrap(s[prefix: "ab"]) + XCTAssertEqual( + Array(t.elements.sorted(by: \.value).map({ (k, _) in String(k) })), + ["", "c", "C"]) + } + + func testElementsIndexComparison() { + var s = Trie() + s["abc"] = 1 + s["abC"] = 2 + let a = s.elements.startIndex + XCTAssertLessThan(a, s.elements.index(after: a)) + XCTAssertLessThan(a, s.elements.endIndex) + XCTAssertGreaterThan(s.elements.endIndex, a) + } + + func testPrefix() throws { + var s = Trie<[Int], Int>() + XCTAssertNil(s[prefix: [1]]) + + s[[1, 2, 3]] = 1 + s[[1, 2, 5]] = 2 + s[[7, 8, 9]] = 3 + let t = try XCTUnwrap(s[prefix: [1, 2]]) + XCTAssertEqual(t.count, 2) + } + + func testLongestPrefix() { + var s = Trie<[Int], Int>() + + s[[1, 2, 3]] = 1 + s[[1, 2, 5]] = 2 + s[[7, 8, 9]] = 3 + let (t, i) = s.longestPrefix(startingWith: [1, 2, 0]) + XCTAssertEqual(i, 2) + XCTAssertEqual(t.count, 2) + } + + func testSubscript() { + var s = Trie() + + // Read + XCTAssertNil(s["x"]) + + // Write + s["abc"] = 1 + s["abcd"] = 2 + s["abcde"] = 3 + XCTAssertEqual(s["abc"], 1) + + // Modify + s["abcde"] = nil + s["abcdE"] = 3 + XCTAssertNil(s["abcde"]) + XCTAssertEqual(s["abcdE"], 3) + } + + func testSubtrieSubscript() throws { + var s = Trie() + s["abc"] = 1 + s["abcd"] = 2 + s["abcde"] = 3 + let t = try XCTUnwrap(s[prefix: []]) + XCTAssertNil(t["xyz"]) + XCTAssertEqual(t["abc"], 1) + } + + func testEquatable() { + var s = Trie() + s["abc"] = 1 + s["ab"] = 2 + + var t = s + XCTAssertEqual(s, t) + t["abc"] = nil + XCTAssertNotEqual(s, t) + t["abc"] = 1 + XCTAssertEqual(s, t) + } + + func testHashable() { + var s = Trie() + s["abc"] = 1 + s["ab"] = 2 + + var h1 = Hasher() + var h2 = Hasher() + s.hash(into: &h1) + s.hash(into: &h2) + XCTAssertEqual(h1.finalize(), h2.finalize()) + + var t = s + t["abc"] = nil + + h1 = Hasher() + h2 = Hasher() + s.hash(into: &h1) + t.hash(into: &h2) + XCTAssertNotEqual(h1.finalize(), h2.finalize()) + + t["abc"] = 1 + h1 = Hasher() + h2 = Hasher() + s.hash(into: &h1) + t.hash(into: &h2) + XCTAssertEqual(h1.finalize(), h2.finalize()) + } + +} diff --git a/cmake/TopLevelDefaults.cmake b/cmake/TopLevelDefaults.cmake index 48611ea96..fb834df73 100644 --- a/cmake/TopLevelDefaults.cmake +++ b/cmake/TopLevelDefaults.cmake @@ -14,7 +14,7 @@ block() set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE NEVER) FetchContent_Declare(Hylo-CMakeModules GIT_REPOSITORY https://github.com/hylo-lang/CMakeModules.git - GIT_TAG a080578 + GIT_TAG 6577fca OVERRIDE_FIND_PACKAGE )