Skip to content

Commit

Permalink
Merge pull request #1017 from hylo-lang/generic-argument-conformance
Browse files Browse the repository at this point in the history
Fix use of 'Movable' and 'Deinitializable' in generic contexts
  • Loading branch information
kyouko-taiga authored Sep 18, 2023
2 parents 35cf03d + 9564289 commit a46e62e
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 96 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
products: [
.executable(name: "hc", targets: ["hc"]),
.executable(name: "hylo-demangle", targets: ["hylo-demangle"]),
.library(name: "Hylo", targets: ["Driver"])
.library(name: "Hylo", targets: ["Driver"]),
],

dependencies: [
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Program.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ extension Program {
scopes(from: scope).first(TranslationUnit.self)!
}

/// Returns the trait defining `d` iff `d` is a requirement. Otherwise, returns nil.
/// Returns the trait of which `d` is a member, or `nil` if `d` isn't member of a trait.
public func trait<T: DeclID>(defining d: T) -> TraitDecl.ID? {
switch d.kind {
case AssociatedTypeDecl.self, AssociatedValueDecl.self:
Expand Down
26 changes: 18 additions & 8 deletions Sources/FrontEnd/TypeChecking/TypeChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,7 @@ struct TypeChecker {
return conformedTraits(of: TraitType(d, ast: program.ast), in: scopeOfUse)
}

// Conformances of other generic parameters are stored in generic environments.
var result = Set<TraitType>()
for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type {
let e = environment(of: s)!
result.formUnion(e.conformedTraits(of: ^t))
}

var result = conformedTraits(declaredInEnvironmentIntroducing: ^t, exposedTo: scopeOfUse)
result.formUnion(conformedTraits(declaredInExtensionsOf: ^t, exposedTo: scopeOfUse))
return result
}
Expand Down Expand Up @@ -240,6 +234,22 @@ struct TypeChecker {
return result
}

/// Returns the traits to which `t` is declared conforming in its generic environment.
///
/// `t` is a generic type parameter or an associated type introduced by a generic environment
/// logically containing `scopeOfUse`. The return value is the set of traits used as bounds of
/// `t` in that environment.
mutating func conformedTraits(
declaredInEnvironmentIntroducing t: AnyType, exposedTo scopeOfUse: AnyScopeID
) -> Set<TraitType> {
var result = Set<TraitType>()
for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type {
let e = environment(of: s)!
result.formUnion(e.conformedTraits(of: ^t))
}
return result
}

// MARK: Type transformations

/// Returns `generic` with occurrences of parameters keying `specialization` replaced by their
Expand Down Expand Up @@ -2939,7 +2949,7 @@ struct TypeChecker {
// The specialization of the match includes that of context in which it was looked up.
var specialization = context?.arguments ?? [:]

// If the match is a trait requirement, specialize its receiver as necessary.
// If the match is a trait member, specialize its receiver as necessary.
if let t = program.trait(defining: m) {
assert(specialization[program[t].receiver] == nil)
specialization[program[t].receiver] = context?.type
Expand Down
39 changes: 34 additions & 5 deletions Sources/FrontEnd/TypedProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,19 +329,26 @@ public struct TypedProgram {
of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID
) -> Conformance? {
let m = canonical(model, in: scopeOfUse)
if let c = declaredConformance(of: m, to: concept, exposedTo: scopeOfUse) {

if let c = explicitConformance(of: m, to: concept, exposedTo: scopeOfUse) {
return c
}

if let c = impliedConformance(of: m, to: concept, exposedTo: scopeOfUse) {
return c
} else {
return structuralConformance(of: m, to: concept, exposedTo: scopeOfUse)
}

// Last resort; maybe the conformance is structural.
return structuralConformance(of: m, to: concept, exposedTo: scopeOfUse)
}

/// Returns the explicitly declared conformance of `model` to `concept` that is exposed to
/// `scopeOfUse`, or `nil` if such a conformance doesn't exist.
///
/// This method returns `nil` if the conformance of `model` to `concept` is structural (e.g., a
/// tuple's synthesized conformance to `Movable`).
private func declaredConformance(
/// tuple's synthesized conformance to `Movable`) or if the conformance is implied by a trait
/// bound (e.g., `T: P` in `fun f<T: P>() {}`).
private func explicitConformance(
of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID
) -> Conformance? {
assert(model[.isCanonical])
Expand Down Expand Up @@ -376,6 +383,28 @@ public struct TypedProgram {
}
}

/// Returns the conformance of `model` to `concept` that is implied by the generic environment
/// introducing `model` in `scopeOfUse`, or `nil` if such a conformance doesn't exist.
private func impliedConformance(
of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID
) -> Conformance? {
var checker = TypeChecker(asContextFor: self)
let bounds = checker.conformedTraits(
declaredInEnvironmentIntroducing: model,
exposedTo: scopeOfUse)
guard bounds.contains(concept) else { return nil }

var implementations = Conformance.ImplementationMap()
for requirement in ast.requirements(of: concept.decl) {
implementations[requirement] = .concrete(requirement)
}

return .init(
model: model, concept: concept, arguments: [:], conditions: [], scope: scopeOfUse,
implementations: implementations, isStructural: true,
site: .empty(at: ast[scopeOfUse].site.first()))
}

/// Returns the implicit structural conformance of `model` to `concept` that is exposed to
/// `scopeOfUse`, or `nil` if such a conformance doesn't exist.
private func structuralConformance(
Expand Down
27 changes: 2 additions & 25 deletions Sources/IR/Analysis/Module+Depolymorphize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ extension Module {

let p = program.specialize(c.specialization, for: specialization, in: scopeOfUse)
let f: Function.ID
if let r = program.requirement(referredBy: c) {
f = monomorphize(requirement: r.declaration, of: r.trait, in: ir, for: p, in: scopeOfUse)
if let m = program.traitMember(referredBy: c.function) {
f = monomorphize(requirement: m.declaration, of: m.trait, in: ir, for: p, in: scopeOfUse)
} else {
f = monomorphize(c.function, in: ir, for: p, in: scopeOfUse)
}
Expand Down Expand Up @@ -552,26 +552,3 @@ extension Module {
}

}

extension TypedProgram {

/// If `r` refers to a requirement, returns the declaration of that requirement along with the
/// trait that defines it. Otherwise, returns `nil`.
fileprivate func requirement(
referredBy r: FunctionReference
) -> (declaration: AnyDeclID, trait: TraitType)? {
switch r.function.value {
case .lowered(let d):
guard let t = trait(defining: d) else { return nil }
return (declaration: d, trait: TraitType(t, ast: ast))

case .loweredSubscript(let d):
guard let t = trait(defining: d) else { return nil }
return (declaration: AnyDeclID(d), trait: TraitType(t, ast: ast))

default:
return nil
}
}

}
113 changes: 72 additions & 41 deletions Sources/IR/Emitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2247,7 +2247,7 @@ struct Emitter {
let predecessor = module.instruction(before: i)

insertionPoint = .before(i)
emitMove([semantics], s.object, to: s.target, at: s.site)
emitMove(semantics, s.object, to: s.target, withMovableConformance: s.movable, at: s.site)
module.removeInstruction(i)

if let p = predecessor {
Expand All @@ -2258,59 +2258,80 @@ struct Emitter {
}
}

/// Appends the IR for a call to move-initialize/assign `storage` with `value`, anchoring new
/// instructions at `site`.
/// Inserts the IR to move-initialize/assign `storage` with `value`, anchoring new instructions
/// at `site`.
///
/// The type of `value` must a built-in or conform to `Movable` in `insertionScope`.
///
/// The value of `semantics` defines the type of move to emit:
/// - `[.set]` unconditionally emits move-initialization.
/// - `[.inout]` unconditionally emits move-assignment.
/// - `[.inout, .set]` (default) emits a `move` instruction that will is later replaced during
/// definite initialization analysis by move-assignment if `storage` is found initialized or
/// move-initialization otherwise. after
/// - `[.set]` emits move-initialization.
/// - `[.inout]` emits move-assignment.
/// - `[.inout, .set]` emits a `move` instruction that will is later replaced during definite
/// initialization analysis by either move-assignment if `storage` is found initialized or
/// by move-initialization otherwise.
private mutating func emitMove(
_ semantics: AccessEffectSet, _ value: Operand, to storage: Operand, at site: SourceRange
) {
precondition(!semantics.isEmpty && semantics.isSubset(of: [.set, .inout]))

// Built-in are always stored.
let t = module.type(of: storage).ast

// Built-in types are handled as a special case.
if t.isBuiltin {
let x0 = insert(module.makeAccess(.set, from: storage, at: site))!
let x1 = insert(module.makeAccess(.sink, from: value, at: site))!
let x2 = insert(module.makeLoad(x1, at: site))!
insert(module.makeStore(x2, at: x0, at: site))
insert(module.makeEndAccess(x1, at: site))
insert(module.makeEndAccess(x0, at: site))
emitMoveBuiltIn(value, to: storage, at: site)
return
}

// Other types must be movable.
let movable = program.conformance(
of: t, to: program.ast.movableTrait, exposedTo: insertionScope!)!

// If the semantics of the move is known, emit a call to the corresponding move method.
// Insert a call to the approriate move implementation if its semantics is unambiguous.
// Otherwise, insert a call to the method bundle.
if let k = semantics.uniqueElement {
let d = module.demandMoveOperatorDeclaration(k, from: movable)
let r = FunctionReference(
to: d, in: module, specializedBy: movable.arguments, in: insertionScope!)
let f = Operand.constant(r)

let x0 = insert(module.makeAllocStack(.void, at: site))!
let x1 = insert(module.makeAccess(.set, from: x0, at: site))!
let x2 = insert(module.makeAccess(k, from: storage, at: site))!
let x3 = insert(module.makeAccess(.sink, from: value, at: site))!
insert(module.makeCall(applying: f, to: [x2, x3], writingResultTo: x1, at: site))
insert(module.makeEndAccess(x3, at: site))
insert(module.makeEndAccess(x2, at: site))
insert(module.makeEndAccess(x1, at: site))
insert(module.makeDeallocStack(for: x0, at: site))
return
emitMove(k, value, to: storage, withMovableConformance: movable, at: site)
} else {
insert(module.makeMove(value, to: storage, usingConformance: movable, at: site))
}
}

// Otherwise, emit a move.
insert(module.makeMove(value, to: storage, usingConformance: movable, at: site))
/// Implements `emitMove` for built-in types.
private mutating func emitMoveBuiltIn(
_ value: Operand, to storage: Operand, at site: SourceRange
) {
// Built-in are always stored.
let x0 = insert(module.makeAccess(.set, from: storage, at: site))!
let x1 = insert(module.makeAccess(.sink, from: value, at: site))!
let x2 = insert(module.makeLoad(x1, at: site))!
insert(module.makeStore(x2, at: x0, at: site))
insert(module.makeEndAccess(x1, at: site))
insert(module.makeEndAccess(x0, at: site))
}

/// Inserts IR for move-initializing/assigning `storage` with `value` using `movable` to locate
/// the implementations of these operations.
///
/// The value of `semantics` defines the type of move to emit:
/// - `.set` emits move-initialization.
/// - `.inout` emits move-assignment.
///
/// - Requires: `storage` does not have a built-in type.
private mutating func emitMove(
_ semantics: AccessEffect, _ value: Operand, to storage: Operand,
withMovableConformance movable: Core.Conformance, at site: SourceRange
) {
let d = module.demandMoveOperatorDeclaration(semantics, from: movable)
let f = reference(to: d, implementedFor: movable)

let x0 = insert(module.makeAllocStack(.void, at: site))!
let x1 = insert(module.makeAccess(.set, from: x0, at: site))!
let x2 = insert(module.makeAccess(semantics, from: storage, at: site))!
let x3 = insert(module.makeAccess(.sink, from: value, at: site))!
insert(module.makeCall(applying: .constant(f), to: [x2, x3], writingResultTo: x1, at: site))
insert(module.makeEndAccess(x3, at: site))
insert(module.makeEndAccess(x2, at: site))
insert(module.makeEndAccess(x1, at: site))
insert(module.makeDeallocStack(for: x0, at: site))
}

// MARK: Deinitialization
Expand All @@ -2332,28 +2353,27 @@ struct Emitter {
// Use custom conformance to `Deinitializable` if possible.
let concept = program.ast.deinitializableTrait
if let c = module.program.conformance(of: model, to: concept, exposedTo: insertionScope!) {
emitDeinit(storage, withConformanceToDeinitializable: c, at: site)
emitDeinit(storage, withDeinitializableConformance: c, at: site)
return
}

// Object is not deinitializable.
report(.error(module.type(of: storage).ast, doesNotConformTo: concept, at: site))
}

/// Inserts the IR for deinitializing `storage`, using `c` to identify the deinitializer to apply
/// and anchoring new instructions at `site`.
/// Inserts the IR for deinitializing `storage`, using `deinitializable` to identify the locate
/// the deinitializer to apply.
private mutating func emitDeinit(
_ storage: Operand, withConformanceToDeinitializable c: Core.Conformance,
_ storage: Operand, withDeinitializableConformance deinitializable: Core.Conformance,
at site: SourceRange
) {
let d = module.demandDeinitDeclaration(from: c)
let f = Operand.constant(
FunctionReference(to: d, in: module, specializedBy: c.arguments, in: insertionScope!))
let d = module.demandDeinitDeclaration(from: deinitializable)
let f = reference(to: d, implementedFor: deinitializable)

let x0 = insert(module.makeAllocStack(.void, at: site))!
let x1 = insert(module.makeAccess(.set, from: x0, at: site))!
let x2 = insert(module.makeAccess(.sink, from: storage, at: site))!
insert(module.makeCall(applying: f, to: [x2], writingResultTo: x1, at: site))
insert(module.makeCall(applying: .constant(f), to: [x2], writingResultTo: x1, at: site))
insert(module.makeEndAccess(x2, at: site))
insert(module.makeEndAccess(x1, at: site))
insert(module.makeMarkState(x0, initialized: false, at: site))
Expand Down Expand Up @@ -2452,6 +2472,17 @@ struct Emitter {

// MARK: Helpers

/// Returns a function reference to `d`, which is an implementation that's part of `c`.
private func reference(
to d: Function.ID, implementedFor c: Core.Conformance
) -> FunctionReference {
var a = module.specialization(in: insertionFunction!).merging(c.arguments)
if let m = program.traitMember(referredBy: d) {
a = a.merging([program[m.trait.decl].receiver: c.model])
}
return FunctionReference(to: d, in: module, specializedBy: a, in: insertionScope!)
}

/// Returns the canonical form of `t` in the current insertion scope.
private func canonical(_ t: AnyType) -> AnyType {
program.canonical(t, in: insertionScope!)
Expand Down
11 changes: 2 additions & 9 deletions Sources/IR/FunctionID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ extension Function {
/// The value of a function IR identity.
public enum Value: Hashable {

/// The identity of a lowered Hylo function, initializer, or method variant.
/// The identity of a lowered function, initializer, method variant, or subscript variant.
case lowered(AnyDeclID)

/// The identity of a lowered subscript variant.
case loweredSubscript(SubscriptImpl.ID)

/// The identity of a synthesized declaration.
case synthesized(SynthesizedFunctionDecl)

Expand All @@ -33,10 +30,8 @@ extension Function {
/// initializer, method implementation, or subscript implementation.s
init<T: DeclID>(_ d: T) {
switch d.kind {
case FunctionDecl.self, InitializerDecl.self, MethodImpl.self:
case FunctionDecl.self, InitializerDecl.self, MethodImpl.self, SubscriptImpl.self:
self.value = .lowered(AnyDeclID(d))
case SubscriptImpl.self:
self.value = .loweredSubscript(SubscriptImpl.ID(d)!)
default:
unreachable()
}
Expand Down Expand Up @@ -80,8 +75,6 @@ extension Function.ID: CustomStringConvertible {
switch value {
case .lowered(let d):
return d.description
case .loweredSubscript(let d):
return d.description
case .synthesized(let d):
return d.description
case .existentialized(let b):
Expand Down
2 changes: 0 additions & 2 deletions Sources/IR/Mangling/Mangler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,6 @@ struct Mangler {
switch symbol.value {
case .lowered(let d):
mangle(decl: d, to: &output)
case .loweredSubscript(let d):
mangle(decl: d, to: &output)
case .monomorphized(let f, let a):
write(monomorphized: f, for: a, to: &output)
case .synthesized(let d):
Expand Down
2 changes: 0 additions & 2 deletions Sources/IR/Module+Description.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ extension Module: TextOutputStreamable {
return "Existentialized form of '\(debugDescription(base))'"
case .lowered(let d):
return program.debugDescription(d)
case .loweredSubscript(let d):
return program.debugDescription(d)
case .monomorphized(let base, let arguments):
return "Monomorphized form of '\(debugDescription(base))' for <\(list: arguments.values)>"
case .synthesized(let d):
Expand Down
Loading

0 comments on commit a46e62e

Please sign in to comment.