Skip to content

Commit

Permalink
Merge pull request #1277 from hylo-lang/generic-for-loop
Browse files Browse the repository at this point in the history
Fix the lowering of for loops in generic contexts
  • Loading branch information
kyouko-taiga authored Jan 5, 2024
2 parents 30dacf0 + 20089ea commit 2561a57
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 66 deletions.
83 changes: 54 additions & 29 deletions Sources/FrontEnd/TypeChecking/TypeChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -536,34 +536,29 @@ struct TypeChecker {
}

/// Returns the origins of the conformances of `model` to `trait` exposed to `scopeOfUse`.
///
/// - Requires: `model` is canonical.
private mutating func originsOfConformance(
of model: AnyType, to trait: TraitType, exposedTo scopeOfUse: AnyScopeID
) -> [ConformanceOrigin] {
assert(model[.isCanonical])

var result: [ConformanceOrigin]

switch model.base {
case let t as BoundGenericType:
return originsOfConformance(of: t.base, to: trait, exposedTo: scopeOfUse)
case let t as ProductType:
let d = ProductTypeDecl.ID(t.decl)!
result = originsOfConformance(to: trait, declaredBy: d, exposedTo: scopeOfUse)
return originsOfConformance(of: t, to: trait, exposedTo: scopeOfUse)
case let t as TypeAliasType:
return originsOfConformance(of: t.resolved, to: trait, exposedTo: scopeOfUse)
default:
result = []
}

for e in extensions(of: model, exposedTo: scopeOfUse) {
guard let d = ConformanceDecl.ID(e) else { continue }
let s = originsOfConformance(to: trait, declaredBy: d, exposedTo: scopeOfUse)
result.append(contentsOf: s)
return originsOfConformance(to: trait, declaredInExtensionsOf: model, exposedTo: scopeOfUse)
}
}

return result
/// Returns the origins of the conformances of `model` to `trait` exposed to `scopeOfUse`.
private mutating func originsOfConformance(
of model: ProductType, to trait: TraitType, exposedTo scopeOfUse: AnyScopeID
) -> [ConformanceOrigin] {
let d = ProductTypeDecl.ID(model.decl)!
let r = originsOfConformance(to: trait, declaredBy: d, exposedTo: scopeOfUse)
let s = originsOfConformance(to: trait, declaredInExtensionsOf: ^model, exposedTo: scopeOfUse)
return r + s
}

/// Returns the origins of conformances to `trait` exposed to `scopeOfUse` and declared `d`.
Expand All @@ -580,18 +575,42 @@ struct TypeChecker {
return result
}

/// Returns the type satisfying `requirement` for `model` in `scopeOfUse`, or `nil` if `model`
/// does not satisfy such a requirement.
/// Returns the origins of the conformances of `model` to `trait` that are declared in extensions
/// exposed to `scopeOfUse`.
private mutating func originsOfConformance(
to trait: TraitType, declaredInExtensionsOf model: AnyType, exposedTo scopeOfUse: AnyScopeID
) -> [ConformanceOrigin] {
var result: [ConformanceOrigin] = []
for e in extensions(of: model, exposedTo: scopeOfUse) {
guard let d = ConformanceDecl.ID(e) else { continue }
let s = originsOfConformance(to: trait, declaredBy: d, exposedTo: scopeOfUse)
result.append(contentsOf: s)
}
return result
}

/// Returns the type implementing requirement `r` for the model `m` in `scopeOfUse`, or `nil` if
/// `m` does not implement `r`.
private mutating func demandImplementation(
of requirement: AssociatedTypeDecl.ID, for model: AnyType, in scopeOfUse: AnyScopeID
of r: AssociatedTypeDecl.ID, for m: AnyType, in scopeOfUse: AnyScopeID
) -> AnyType? {
let trait = traitDeclaring(requirement)!
guard
let c = demandConformance(of: model, to: trait, exposedTo: scopeOfUse),
let a = MetatypeType(uncheckedType(of: c.implementations[requirement]!.decl!))
else { return nil }
if let c = demandConformance(of: m, to: traitDeclaring(r)!, exposedTo: scopeOfUse) {
return demandImplementation(of: r, in: c)
} else if m.base is GenericTypeParameterType {
return ^AssociatedTypeType(r, domain: m, ast: program.ast)
} else {
return nil
}
}

return specialize(a.instance, for: c.arguments, in: c.scope)
/// Returns the type implementing requirement `r` in conformance `c` if `c` is well-formed.
private mutating func demandImplementation(
of r: AssociatedTypeDecl.ID, in c: Conformance
) -> AnyType? {
let t = uncheckedType(of: c.implementations[r]!.decl!)
return MetatypeType(t).map { (a) in
specialize(a.instance, for: c.arguments, in: c.scope)
}
}

// MARK: Type transformations
Expand Down Expand Up @@ -631,6 +650,8 @@ struct TypeChecker {
return ^AssociatedTypeType(t.decl, domain: d, ast: me.program.ast)
}

// DR: Maybe we could use `d`'s conformance if it has already been established.

// Otherwise, look for the member of the domain that implements the associated type.
var candidates = me.lookup(me.program[t.decl].baseName, memberOf: d, exposedTo: scopeOfUse)

Expand All @@ -644,11 +665,15 @@ struct TypeChecker {
}
}

if let selected = candidates.uniqueElement {
return MetatypeType(me.uncheckedType(of: selected))?.instance ?? .error
} else {
return .error
if let s = candidates.uniqueElement, let u = MetatypeType(me.uncheckedType(of: s)) {
if let b = BoundGenericType(me.canonical(d, in: scopeOfUse)) {
return me.specialize(u.instance, for: b.arguments, in: scopeOfUse)
} else {
return u.instance
}
}

return .error
}

func transform(mutating me: inout Self, _ t: BoundGenericType) -> AnyType {
Expand Down
24 changes: 11 additions & 13 deletions Sources/FrontEnd/TypedProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,36 +217,36 @@ public struct TypedProgram {
isTrivialModel(t, of: ast.core.deinitializable.type, in: scopeOfUse)
}

/// Returns `true` iff instances of `t` can be moved with a byte-wise copy.
public func isTriviallyMovable(_ t: AnyType, in scopeOfUse: AnyScopeID) -> Bool {
isTrivialModel(t, of: ast.core.movable.type, in: scopeOfUse)
}

/// Returns `true` iff `t` models `coreConcept` without any user-defined semantics.
private func isTrivialModel(
_ t: AnyType, of coreConcept: TraitType, in scopeOfUse: AnyScopeID
) -> Bool {
let model = canonical(t, in: scopeOfUse)

// Built-ins have no conformances, but they are trivial.
guard let c = conformance(of: model, to: coreConcept, exposedTo: scopeOfUse) else {
if let c = conformance(of: model, to: coreConcept, exposedTo: scopeOfUse) {
return isTrivial(c)
} else {
return model.isBuiltinOrRawTuple
}
}

/// Returns `true` iff `c` has no user-defined semantics.
public func isTrivial(_ c: Conformance) -> Bool {
// Non-synthethic conformances are not trivial.
if !c.isSynthethic {
return false
}

// Structural types are trivial if their parts are. Other types are trivial if they have their
// conformance to `coreConcept` was synthesized.
switch model.base {
switch c.model.base {
case let u as BufferType:
return isTrivialModel(u.element, of: coreConcept, in: scopeOfUse)
return isTrivialModel(u.element, of: c.concept, in: c.scope)
case let u as TupleType:
return u.elements.allSatisfy({ isTrivialModel($0.type, of: coreConcept, in: scopeOfUse) })
return u.elements.allSatisfy({ isTrivialModel($0.type, of: c.concept, in: c.scope) })
case let u as UnionType:
return u.elements.allSatisfy({ isTrivialModel($0, of: coreConcept, in: scopeOfUse) })
return u.elements.allSatisfy({ isTrivialModel($0, of: c.concept, in: c.scope) })
case is MetatypeType:
return true
case is ProductType:
Expand Down Expand Up @@ -416,9 +416,7 @@ public struct TypedProgram {
}

var checker = TypeChecker(asContextFor: self)
let bounds = checker.conformedTraits(
declaredByConstraintsOn: model,
exposedTo: scopeOfUse)
let bounds = checker.conformedTraits(declaredByConstraintsOn: model, exposedTo: scopeOfUse)
if !bounds.contains(concept) { return nil }

var implementations = Conformance.ImplementationMap()
Expand Down
6 changes: 4 additions & 2 deletions Sources/IR/CollectionWitness.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ struct CollectionWitness {
let positionAfter: Operand

/// The implementation of `Collection.[].let`.
let access: Function.ID
let access: FunctionReference

/// Creates the lowered witness of the conformance `c` for use in `module`.
init(_ c: Core.Conformance, in module: inout Module) {
let collection = module.program.ast.core.collection

self.position = module.program.associatedType(collection.position, for: c)
self.element = module.program.associatedType(collection.element, for: c)

self.startPosition = .constant(
module.reference(toImplementationOf: collection.startPosition, for: c))
self.endPosition = .constant(
module.reference(toImplementationOf: collection.endPosition, for: c))
self.positionAfter = .constant(
module.reference(toImplementationOf: collection.positionAfter, for: c))
self.access = module.demandImplementation(of: collection.access, for: c)

self.access = module.reference(toImplementationOf: collection.access, for: c)
}

}
31 changes: 14 additions & 17 deletions Sources/IR/Emitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1268,10 +1268,10 @@ struct Emitter {
insertionPoint = .end(of: enter)
let x6 = insert(module.makeAccess(.let, from: domain, at: introducer))!
let x7 = insert(module.makeAccess(.let, from: currentPosition, at: introducer))!

let t = RemoteType(.let, collectionWitness.element)
let x8 = insert(
module.makeProject(
.init(.let, collectionWitness.element), applying: collectionWitness.access,
specializedBy: collectionConformance.arguments, to: [x6, x7], at: introducer))!
module.makeProject(t, applying: collectionWitness.access, to: [x6, x7], at: introducer))!

if module.type(of: x8).ast != collectionWitness.element {
UNIMPLEMENTED("narrowing projections #1099")
Expand Down Expand Up @@ -2805,7 +2805,7 @@ struct Emitter {
}

// Use memcpy of `source` is trivially movable.
if program.isTriviallyMovable(model, in: insertionScope!) {
if program.isTrivial(movable) {
let x0 = insert(module.makeAccess(.sink, from: value, at: site))!
let x1 = insert(module.makeAccess(.set, from: storage, at: site))!
insert(module.makeMemoryCopy(x0, x1, at: site))
Expand Down Expand Up @@ -2911,22 +2911,19 @@ struct Emitter {
/// exposed to `self.insertionScope`.
mutating func emitDeinit(_ storage: Operand, at site: SourceRange) {
let model = module.type(of: storage).ast
let deinitializable = program.ast.core.deinitializable.type

// Use a no-op if the object is trivially deinitializable.
if program.isTriviallyDeinitializable(model, in: insertionScope!) {
if let c = program.conformance(of: model, to: deinitializable, exposedTo: insertionScope!) {
if program.isTrivial(c) {
insert(module.makeMarkState(storage, initialized: false, at: site))
} else {
emitDeinit(storage, withDeinitializableConformance: c, at: site)
}
} else if model.isBuiltinOrRawTuple {
insert(module.makeMarkState(storage, initialized: false, at: site))
return
}

// Use custom conformance to `Deinitializable` if possible.
let concept = program.ast.core.deinitializable.type
if let c = module.program.conformance(of: model, to: concept, exposedTo: insertionScope!) {
emitDeinit(storage, withDeinitializableConformance: c, at: site)
return
} else {
report(.error(module.type(of: storage).ast, doesNotConformTo: deinitializable, at: site))
}

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

/// Inserts the IR for deinitializing `storage`, using `deinitializable` to identify the locate
Expand Down
18 changes: 13 additions & 5 deletions Sources/IR/Operands/Instruction/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,23 @@ extension Project: CustomStringConvertible {

extension Module {

/// Creates a `project` anchored at `site` that projects a value of type `t` by applying
/// `callee`, which is parameterized by `specialization`, on `arguments`.
/// Creates a `project` anchored at `site` that projects a value of type `t` by applying `s`,
/// which is a reference to a lowered subscript, on `arguments`.
func makeProject(
_ t: RemoteType, applying callee: Function.ID, specializedBy specialization: GenericArguments,
to arguments: [Operand], at site: SourceRange
_ t: RemoteType, applying s: FunctionReference, to arguments: [Operand], at site: SourceRange
) -> Project {
.init(
projection: t, callee: callee, specialization: specialization,
projection: t, callee: s.function, specialization: s.specialization,
operands: arguments, site: site)
}

/// Creates a `project` anchored at `site` that projects a value of type `t` by applying `s`,
/// which is a lowered subscript, specialized by `z`, on `arguments`.
func makeProject(
_ t: RemoteType, applying s: Function.ID, specializedBy z: GenericArguments,
to arguments: [Operand], at site: SourceRange
) -> Project {
.init(projection: t, callee: s, specialization: z, operands: arguments, site: site)
}

}

0 comments on commit 2561a57

Please sign in to comment.