Type checker oddness compiling my variant standard library

Hi, I am building a variant, reduced and cut down standard library with a very slightly patched version of the compiler. Using c6b7b1f6b8668a517d753d2576282ab7d86ccf95 with [AVR] Fix IRGen function emission to respect LLVM DataLayout program address space. by carlos4242 · Pull Request #78810 · swiftlang/swift · GitHub added to fix AVR code emission.

I'm getting this slightly hard to decipher diagnostic, I wondered if anyone can help?...

UTF16.swift:91:23: error: type 'Encoding.Type' cannot conform to 'BinaryInteger' [#ProtocolTypeNonConformance]
 89 | 
 90 |     // Fast path for ASCII in a UTF8 buffer
 91 |     if sourceEncoding == Unicode.UTF8.self {
    |                       |- error: type 'Encoding.Type' cannot conform to 'BinaryInteger' [#ProtocolTypeNonConformance]
    |                       `- note: only concrete types such as structs, enums and classes can conform to protocols
 92 |       var peek: Encoding.CodeUnit = 0
 93 |       while let u = i.next() {

BinaryInteger.swift:269:1: note: required by referencing operator function '==' on 'BinaryInteger' where 'Self' = 'Encoding.Type'
267 | //===----------------------------------------------------------------------===//
268 | 
269 | extension BinaryInteger {
    | `- note: required by referencing operator function '==' on 'BinaryInteger' where 'Self' = 'Encoding.Type'
270 |   @_transparent
271 |   public static func == <

...the code in question is the same as the normal stdlib, rearranged into different files...

  @inlinable
  public static func transcodedLength<
    Input : IteratorProtocol,
    Encoding : Unicode.Encoding
  >(
    of input: Input,
    decodedAs sourceEncoding: Encoding.Type,
    repairingIllFormedSequences: Bool
  ) -> (count: Int, isASCII: Bool)?
    where Encoding.CodeUnit == Input.Element {

    var utf16Count: Int = 0
    var i = input
    var d = Encoding.ForwardParser()

    // Fast path for ASCII in a UTF8 buffer
    if sourceEncoding == Unicode.UTF8.self {
      var peek: Encoding.CodeUnit = 0
      while let u = i.next() {
        peek = u
        guard _fastPath(peek < 0x80) else { break }
        utf16Count = utf16Count + 1
      }
      if _fastPath(peek < 0x80) { return (utf16Count, true) }

      var d1 = UTF8.ForwardParser()
      d1._buffer.append(numericCast(peek))
      d = _identityCast(d1, to: Encoding.ForwardParser.self)
    }

This is being compiled in embedded mode. I've checked and _UnicodeEncoding seems normal and is included in the compile.

The compile command is...

"/Users/carl/Documents/Code/swift-project/build/Ninja-RelWithDebInfoAssert/swift-macosx-arm64/bin/swiftc" -parse-as-library -parse-stdlib -target avr-none-none-elf  -nostdimport -I uSwiftShims  -I "/Applications/S4A IDE Pro.app/Contents/XPCServices/BuildEngine.xpc/Contents/Resources/lib/avr-libgcc/include" -I "/Applications/S4A IDE Pro.app/Contents/XPCServices/BuildEngine.xpc/Contents/Resources/lib/avr-libc/include" -Xcc -DAVR_LIBC_DEFINED -Xcc -DLIBC_DEFINED -DAVR_LIBC_DEFINED_SWIFT  -DFORCE_MAIN_SWIFT_ARRAYS -enable-experimental-feature Embedded -Xfrontend -disable-reflection-metadata -Xfrontend -disable-stack-protector -Osize -whole-module-optimization -emit-module -emit-module-path bin/AVR-Embedded/Swift.swiftmodule -module-name Swift CoreOperators.swift CoreAliases.swift RawRepresentable.swift LiteralProtocols.swift TopLevelFunctions.swift CoreProtocols.swift CoreFloatingPoint.swift CoreBinaryFloatingPoint.swift Float.swift Float16.swift CoreFloatingPointFunctions.swift Optional.swift Bridging.swift CoreNumericProtocols.swift BinaryInteger.swift CoreIntegers.swift ErrorType.swift Bool.swift Integers.swift Ranges.swift Sequence.swift Stride.swift Slice.swift Collection.swift BidirectionalCollection.swift RandomAccessCollection.swift ClosedRange.swift MutableCollection.swift Hash.swift Pointer.swift UnsafeBufferPointer.swift UnsafeRawBufferPointer.swift UnsafeRawPointer.swift Indices.swift Existential.swift Algorithm.swift FixedWidth.swift IntegerMath.swift CTypes.swift UnsafePointer.swift ObjectIdentifier.swift CollectionAlgorithms.swift WriteBackMutableSlice.swift Random.swift RangeReplaceableCollection.swift MemoryLayout.swift Tuple.swift SequenceAlgorithms.swift LifetimeManager.swift Repeat.swift EmptyCollection.swift CollectionOfOne.swift StringLiterals.swift StaticString.swift StringInterpolation.swift Unicode.swift UnicodeScalar.swift UnicodeEncoding.swift UTF8.swift UTF16.swift ValidUTF8Buffer.swift UnicodeParser.swift UIntBuffer.swift UTFEncoding.swift UTF32.swift ArrayType.swift ArrayBufferProtocol.swift ArrayLiterals.swift ArrayShared.swift ContiguousArray.swift SliceBuffer.swift ArraySlice.swift Array.swift ArrayBody.swift ArrayCast.swift AnyHashable.swift ManagedBuffer.swift Reverse.swift Map.swift Zip.swift LazySequence.swift LazyCollection.swift Filter.swift FlatMap.swift Flatten.swift DropWhile.swift Volatile.swift uSwift.swift Identifiable.swift OptionSet.swift Sendable.swift SetAlgebra.swift Unmanaged.swift ContiguousArrayBuffer.swift Integer-16.swift IntegerMath-16.swift CTypes-16.swift Progmem.swift EmbeddedRuntime.swift version.swift

Can anyone suggest why the type checker is struggling here, when it works in the normal standard library?

Any help greatly appreciated!

Thanks,
Carl

@kubamracek thanks for your advice.

This seems like a bad idea.

It appears you’re missing the Any.Type overload of ==.

1 Like

I'm pretty sure people said that to me about the idea of Embedded Swift and Swift on Microcontrollers seven years ago, and yet here we are!... :smile:

Ahhhhhh! Excellent! Thank you so much! :smile: that sounds like exactly the sort of thing. I'll take a look through and see if I can find it.

Thanks again! You guys are great.

Possibly this is what's missing from mine?

/// Returns a Boolean value indicating whether two types are identical.
///
/// - Parameters:
///   - t0: A type to compare.
///   - t1: Another type to compare.
/// - Returns: `true` if both `t0` and `t1` are `nil` or if they represent the
///   same type; otherwise, `false`.
@_alwaysEmitIntoClient
@_transparent
public func == (
  t0: (any (~Copyable & ~Escapable).Type)?,
  t1: (any (~Copyable & ~Escapable).Type)?
) -> Bool {
  switch (t0, t1) {
  case (.none, .none):
    return true
  case let (.some(ty0), .some(ty1)):
#if compiler(>=5.3) && $GeneralizedIsSameMetaTypeBuiltin
    return Bool(Builtin.is_same_metatype(ty0, ty1))
#else
    // FIXME: Remove this branch once all supported compilers understand the
    // generalized is_same_metatype builtin
    let p1 = unsafeBitCast(ty0, to: UnsafeRawPointer.self)
    let p2 = unsafeBitCast(ty1, to: UnsafeRawPointer.self)
    return p1 == p2
#endif
  default:
    return false
  }
}

/// Returns a Boolean value indicating whether two types are not identical.
///
/// - Parameters:
///   - t0: A type to compare.
///   - t1: Another type to compare.
/// - Returns: `true` if one, but not both, of `t0` and `t1` are `nil`, or if
///   they represent different types; otherwise, `false`.
@_alwaysEmitIntoClient
@_transparent
public func != (
  t0: (any (~Copyable & ~Escapable).Type)?,
  t1: (any (~Copyable & ~Escapable).Type)?
) -> Bool {
  !(t0 == t1)
}

#if !$Embedded
// Embedded Swift is unhappy about conversions from `Any.Type` to
// `any (~Copyable & ~Escapable).Type` (rdar://145706221)
@usableFromInline
@_spi(SwiftStdlibLegacyABI) @available(swift, obsoleted: 1)
internal func == (t0: Any.Type?, t1: Any.Type?) -> Bool {
  switch (t0, t1) {
  case (.none, .none): return true
  case let (.some(ty0), .some(ty1)):
    return Bool(Builtin.is_same_metatype(ty0, ty1))
  default: return false
  }
}

@usableFromInline
@_spi(SwiftStdlibLegacyABI) @available(swift, obsoleted: 1)
internal func != (t0: Any.Type?, t1: Any.Type?) -> Bool {
  !(t0 == t1)
}
#endif

Looks like it. Also you probably don’t need the last two because they’re just for ABI compatibility on non-Embedded targets.

Sorry, don’t let me discourage you.

The standard library exercises almost every part of the frontend, and the compiler and stdlib often move in lockstep. There is also some brittleness in associated type inference so for example changing the order of declarations or source files can break the stdlib build.

So the thing to keep in mind is that maintaining an experimental fork will get rather difficult at times when upstream changes in ways you didn’t expect.

It that sounds like a fun challenge rather than potentially daunting, by all means go ahead ;-) We might all learn something new in the process.

1 Like

Adding the above code was all it needed. Compiling perfectly now!

I've experienced all of this over the last 5-6 years I've been doing it. You're 100% right! :slight_smile:

I have found it an absolutely fascinating exercise maintaining a sort of ... variant or experimental stdlib ... I wouldn't even call it a "fork", because I basically copied the files into a completely separate folder/project. They sit alongside my standard hardware library and it's all built using hand written Unix Makefiles (!), with its own Jenkins CI that runs unit tests using an AVR simulator.

(The compiler I use is almost standard but I need one patch just because the above PR is awaiting approval and the normal compiler crashes emitting IR for AVR when it's anything above a certain complexity.)

In our IDE it has worked well for the programs that people make on our platform... I've just ended up copying over changes and improvements quite often (like I am adding Span now). But it also gives me some certainty that I have all the control levers and I know everything that's going on (I try to eyeball everything I add to get some understanding of it). Plus I like to humbly think some of the issues I occasionally found with brittleness contribute a little to the common shared knowledge. :slight_smile:

This experiment probably has a limited lifespan ahead of it and will probably end in the not too distant future. Apart from anything else, Embedded Swift has just gotten SO GOOD that the benefit from this approach is fast evaporating, which is a very, very good problem to have!

(I haven't quite worked out a sensible approach to open sourcing my experiment because I'm nervous about upsetting someone or breaking some rule if it's "public"... when the day comes to hand all this over to someone else I'll encourage them to find a way to brain dump it somewhere!)

C